Compare commits

..

66 Commits

Author SHA1 Message Date
Sanat Nayar
e6070a33c2 changed to bool 2020-04-15 10:04:53 -04:00
Sanat Nayar
22b9b902a9 modified tests 2020-04-15 09:32:36 -04:00
Sanat Nayar
5795988d01 added flags 2020-04-14 13:46:40 -04:00
Sanat Nayar
449642e6f4 changes 2020-04-13 17:32:57 -04:00
Sanat Nayar
14b6447154 changes 2020-04-13 17:09:10 -04:00
Sanat Nayar
7a8926773f changes 2020-04-13 16:50:33 -04:00
Sanat Nayar
0b868732a7 changes 2020-04-13 16:48:35 -04:00
Sanat Nayar
4f2581cde2 changes 2020-04-13 15:52:12 -04:00
Sanat Nayar
cb78b4da9f added confirmation for context-delete 2020-03-27 16:26:31 -04:00
Andriy Bulynko
f66a4bf245 Eula prompt integration (#248) 2020-03-27 11:59:16 -04:00
Sanat Nayar
72497d7255 consolidated docs for command references 2020-03-26 12:22:49 -04:00
Foysal Iqbal
b6235f20d4 fix set default context (#245)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-25 14:46:57 -04:00
Boris Kuschel
93af9b4386 Merge pull request #243 from qlik-oss/2nd-relative
change config and cr file path relative to ~/.qliksense
2020-03-25 13:51:18 -04:00
Foysal Iqbal
37fad3dbcf Merge branch 'master' into 2nd-relative 2020-03-25 10:01:56 -04:00
Foysal Iqbal
7a6a2b2d2b Merge branch 'master' of github.com:qlik-oss/sense-installer 2020-03-25 09:57:02 -04:00
Foysal Iqbal
184bc6f81a fix relative path manifestsroot
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-25 09:54:36 -04:00
Foysal Iqbal
140d9a6c33 fix relative path manifestsroot
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-25 09:22:28 -04:00
Foysal Iqbal
68ec172226 fix relative path
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-25 00:55:26 -04:00
Foysal Iqbal
e3c81fd717 fix relative path
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-25 00:07:14 -04:00
Foysal Iqbal
864d186f0b fix relative path
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-24 23:44:48 -04:00
Foysal Iqbal
a0f25848c7 fix relative path issue
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-24 22:02:08 -04:00
Ashwathi Shiva
9469bd8893 Preflight k8s version check (#240)
* qliksense preflight check-k8s-version working
* qliksense preflight all checks working
2020-03-24 18:43:26 -04:00
Foysal Iqbal
6ea5c3e1a8 fix relative path issue
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-24 17:03:19 -04:00
Foysal Iqbal
085e718ba8 merge conflict 2020-03-24 16:15:03 -04:00
Foysal Iqbal
29ebf2b499 Merge branch 'master' of github.com:qlik-oss/sense-installer 2020-03-24 16:12:42 -04:00
Foysal Iqbal
a4a7b3f0bd fix relative path issue
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-24 16:12:30 -04:00
Foysal Iqbal
f1871279d0 Install from file (#238) 2020-03-24 16:00:50 -04:00
Foysal Iqbal
e7b256dfd5 Merge branch 'master' of github.com:qlik-oss/sense-installer 2020-03-24 13:54:17 -04:00
Andriy Bulynko
775f438762 Enforcing eula acceptance for all context/CR based commands (#239) 2020-03-24 13:37:44 -04:00
Ashwathi Shiva
aa180b4af1 Port preflight (#237)
Demo comments incorporated
2020-03-23 09:22:33 -04:00
Ilir Bekteshi
af679c89bf Merge pull request #235 from qlik-oss/ibiqlik-patch-1
Remove space in zip path
2020-03-23 10:00:20 +01:00
Ilir Bekteshi
dcd3c0a99b Merge pull request #228 from qlik-oss/ibiqlik/mkdocswf
Fix trigger paths for mkdocs
2020-03-23 09:59:01 +01:00
Foysal Iqbal
ddcaba4fff Merge branch 'master' of github.com:qlik-oss/sense-installer 2020-03-20 22:58:17 -04:00
Sanat Nayar
19c4d37b42 fixed suggestion bug 2020-03-20 12:30:08 -04:00
Sanat Nayar
dcd90ed81a revert commit 2020-03-20 11:53:35 -04:00
Sanat Nayar
05e90c057c added getaccesstoken 2020-03-20 11:52:05 -04:00
Ashwathi Shiva
2ddfab9440 Port preflight into sense-installer (#234)
porting preflight
2020-03-20 10:00:44 -04:00
Ilir Bekteshi
2bc65f0bad Remove space in zip path 2020-03-20 11:39:27 +01:00
Foysal Iqbal
1eccc50e66 Merge branch 'master' of github.com:qlik-oss/sense-installer 2020-03-19 22:11:46 -04:00
Foysal Iqbal
1a2de669ba FIx gke issue (#233) 2020-03-19 21:39:44 -04:00
Ilir Bekteshi
b2a980de3a Generalizing gitops sample CR section 2020-03-19 15:41:26 +01:00
Ilir Bekteshi
bfba8198cf Update mkdocs.yml 2020-03-19 15:36:29 +01:00
Foysal Iqbal
3638994b91 Merge branch 'master' of github.com:qlik-oss/sense-installer 2020-03-19 09:38:53 -04:00
Foysal Iqbal
86e8805bc7 Fix doc for gitops (#227) 2020-03-19 09:31:37 -04:00
Foysal Iqbal
7e9dea4e5f Merge branch 'master' of github.com:qlik-oss/sense-installer 2020-03-19 09:07:23 -04:00
Foysal Iqbal
c2430c3817 fix doc for gitips (#226) 2020-03-19 09:04:57 -04:00
Foysal Iqbal
436162f173 fix doc for gitips
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-18 16:43:44 -04:00
Andriy Bulynko
2f039f2d2e get-versions command (#225) 2020-03-18 10:23:42 -04:00
Sanat Nayar
48ee673ddc refined auto-suggestion 2020-03-17 15:56:40 -04:00
Sanat Nayar
57a80a9533 refined auto-suggestion 2020-03-17 15:20:18 -04:00
Andriy Bulynko
4fe04d6142 Upgrading k-apis to v0.0.21 (#220) 2020-03-17 12:31:24 -04:00
Sanat Nayar
1fd3310e05 Merge pull request #219 from qlik-oss/support_config_set_gitops
changed k-api version
2020-03-17 11:33:54 -04:00
Sanat Nayar
b85269d908 changed k-api version 2020-03-17 11:29:39 -04:00
Sanat Nayar
cbdafadbaf changed k-api version 2020-03-17 11:24:27 -04:00
Sanat Nayar
c0e2128d5d Merge pull request #215 from qlik-oss/support_config_set_gitops
Support config set gitops
2020-03-17 08:59:21 -04:00
Sanat Nayar
c4f0ddcea3 added cron parser 2020-03-16 17:09:40 -04:00
Sanat Nayar
f57457029d added cron parser 2020-03-16 16:24:44 -04:00
Sanat Nayar
aa737b0594 changed gitops arch 2020-03-16 14:58:17 -04:00
Sanat Nayar
b7c0fd48b7 added basic test cases 2020-03-16 12:57:31 -04:00
Sanat Nayar
4530d1d9e2 fixed error 2020-03-16 12:50:40 -04:00
Sanat Nayar
ca20f8c992 go.sum 2020-03-13 17:44:11 -04:00
Sanat Nayar
b2c16a490b go.sum 2020-03-13 17:40:26 -04:00
Sanat Nayar
7f70cc661e go.sum 2020-03-13 17:06:30 -04:00
Sanat Nayar
2c054cd54e mod. go.sum 2020-03-13 17:04:02 -04:00
Sanat Nayar
30f00461ec added GitOps to spec 2020-03-13 09:30:48 -04:00
Sanat Nayar
613b918dde init 2020-03-11 11:20:53 -04:00
46 changed files with 2117 additions and 297 deletions

View File

@@ -4,8 +4,8 @@ on:
branches:
- master
paths:
- docs/
- mkdocs.yml
- 'docs/**'
- 'mkdocs.yml'
jobs:
build:

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ pkg/qliksense/packrd
pkg/qliksense/qliksense-packr.go
pkg/qliksense/docker-registry
/pkg/qliksense/tests
.DS_Store

View File

@@ -46,11 +46,11 @@ build: clean generate
.PHONY: test
test: clean generate
ifeq ($(shell ${WHICH} docker-registry 2>${DEVNUL}),)
$(eval TMP := $(shell mktemp -d))
git clone https://github.com/docker/distribution.git $(TMP)/docker-distribution
cd $(TMP)/docker-distribution; git checkout -b v2.7.1; make
cp $(TMP)/docker-distribution/bin/registry pkg/qliksense/docker-registry
-rm -rf $(TMP)/docker-distribution
$(eval TMP-docker-distribution := $(shell mktemp -d))
git clone https://github.com/docker/distribution.git $(TMP-docker-distribution)/docker-distribution
cd $(TMP-docker-distribution)/docker-distribution; git checkout -b v2.7.1; make
cp $(TMP-docker-distribution)/docker-distribution/bin/registry pkg/qliksense/docker-registry
-rm -rf $(TMP-docker-distribution)
endif
go test -short -count=1 -tags "$(BUILDTAGS)" -v ./...
$(MAKE) clean
@@ -70,7 +70,7 @@ $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT):
GOOS=$(CLIENT_PLATFORM) GOARCH=$(CLIENT_ARCH) $(XBUILD) -o $@ ./cmd/$(MIXIN)
ifeq ($(CLIENT_PLATFORM),windows)
zip $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).zip $(BINDIR)/$(VERSION)/ $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
zip $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).zip $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
else
tar -czvf $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).tar.gz -C $(BINDIR)/$(VERSION)/ $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
endif
@@ -91,12 +91,16 @@ clean-packr: packr2
cd pkg/qliksense && packr2 clean
get-crds:
$(eval TMP := $(shell mktemp -d))
git clone https://github.com/qlik-oss/qliksense-operator.git -b ms-3 $(TMP)/operator
ifeq ($(QLIKSENSE_OPERATOR_DIR),)
$(eval TMP-operator := $(shell mktemp -d))
git clone https://github.com/qlik-oss/qliksense-operator.git -b master $(TMP-operator)/operator
$(MAKE) QLIKSENSE_OPERATOR_DIR=$(TMP-operator)/operator get-crds
-rm -rf $(TMP-operator)
else
mkdir -p pkg/qliksense/crds/cr
mkdir -p pkg/qliksense/crds/crd
mkdir -p pkg/qliksense/crds/crd-deploy
cp $(TMP)/operator/deploy/*.yaml pkg/qliksense/crds/crd-deploy
cp $(TMP)/operator/deploy/crds/*_crd.yaml pkg/qliksense/crds/crd
cp $(TMP)/operator/deploy/crds/*_cr.yaml pkg/qliksense/crds/cr
-rm -rf $(TMP)/operator
cp $(QLIKSENSE_OPERATOR_DIR)/deploy/*.yaml pkg/qliksense/crds/crd-deploy
cp $(QLIKSENSE_OPERATOR_DIR)/deploy/crds/*_crd.yaml pkg/qliksense/crds/crd
cp $(QLIKSENSE_OPERATOR_DIR)/deploy/crds/*_cr.yaml pkg/qliksense/crds/cr
endif

View File

@@ -1,41 +0,0 @@
# qliksense about
About action will display inside information regarding [qliksense-k8](https://github.com/qlik-oss/qliksense-k8s) release.
it will support following flags
- `qliksense about 1.0.0` display default profile for tag `1.0.0`.
- `qliksense about 1.0.0 --profile=docker-desktop`
- `qliksense about`
- assuming current directory has `manifests/docker-desktop`
- or get version information from pull of `qliksense-k8s` `master`
using other supported commands user might have built the CR into the location `~/.qliksense/myqliksense.yaml`
```yaml
apiVersion: qlik.com/v1
kind: QlikSense
metadata:
name: myqliksense
spec:
profile: docker-desktop
manifestsRoot: /Usr/ddd/my-k8-repo/manifests
namespace: myqliksense
storageClassName: efs
configs:
qliksense:
- name: acceptEULA
value: "yes"
secrets:
qliksense:
- name: mongoDbUri
value: "mongo://mongo:3307"
- name: messagingPassword
valueFromKey: messagingPassword
```
In that case the command would be
- `qliksense about`
- display from `/Usr/ddd/my-k8-repo/manifests/docker-desktop` location
- pull from `master` if directory invalid/empty

View File

@@ -1,34 +0,0 @@
# qliksense config
Config action will perform operations on configurations and contexts regarding the [qliksense-k8](https://github.com/qlik-oss/qliksense-k8s) release.
it will support following commands:
- `qliksense config apply` - generate the patchs and apply manifests to k8s
- `qliksense config list-contexts` - retrieves the contexts and lists them
- `qliksense config set` - configure a key value pair into the current context
- `qliksense config set-configs` - set configurations into the qliksense context as key-value pairs
- `qliksense config set-context` - sets the context in which the Kubernetes cluster and resources live in
- `qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=false` - set secrets configurations into the qliksense context as key-value pairs and show encrypted value as part of CR
- `qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=true` - set secrets configurations into the qliksense context as key-value pairs and show a key reference to the created Kubernetes secret resource as part of the CR
- `qliksense config view` - view the qliksense operator CR
- `qliksense config delete-context` - deletes a specific context locally (not in-cluster). Deletes context in spec of `config.yaml` and locally deletes entire folder of specified context (does not delete in-cluster secrets)
the global file that abstracts all the contexts is `config.yaml`, located at: `~/.qliksense/config.yaml`:
```yaml
apiVersion: config.qlik.com/v1
kind: QliksenseConfig
metadata:
name: QliksenseConfigMetadata
spec:
contexts:
- name: qlik-default
crFile: /Users/fff/.qliksense/contexts/qlik-default/qlik-default.yaml
- name: myqliksense
crFile: /Users/fff/.qliksense/contexts/myqliksense/myqliksense.yaml
- name: hello
crFile: /Users/fff/.qliksense/contexts/hello/hello.yaml
currentContext: hello
```

37
cmd/qliksense/apply.go Normal file
View File

@@ -0,0 +1,37 @@
package main
import (
"io"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
func applyCmd(q *qliksense.Qliksense) *cobra.Command {
opts := &qliksense.InstallCommandOptions{}
filePath := ""
keepPatchFiles := false
c := &cobra.Command{
Use: "apply",
Short: "install qliksense based on provided cr file",
Long: `install qliksense based on provided cr file`,
Example: `qliksense apply -f file_name or cat cr_file | qliksense apply -f -`,
RunE: func(cmd *cobra.Command, args []string) error {
return runLoadOrApplyCommandE(cmd, func(reader io.Reader) error {
return q.ApplyCRFromReader(reader, opts, keepPatchFiles, true)
})
},
}
f := c.Flags()
f.StringVarP(&filePath, "file", "f", "", "Install from a CR file")
c.MarkFlagRequired("file")
f.StringVarP(&opts.StorageClass, "storageClass", "s", "", "Storage class for qliksense")
f.StringVarP(&opts.MongoDbUri, "mongoDbUri", "m", "", "mongoDbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
f.StringVarP(&opts.RotateKeys, "rotateKeys", "r", "", "Rotate JWT keys for qliksense (yes:rotate keys/ no:use exising keys from cluster/ None: use default EJSON_KEY from env")
f.BoolVar(&keepPatchFiles, keepPatchFilesFlagName, keepPatchFiles, keepPatchFilesFlagUsage)
eulaPreRunHooks.addValidator(c.Name(), loadOrApplyCommandEulaPreRunHook)
return c
}

View File

@@ -20,7 +20,7 @@ func configCmd(q *qliksense.Qliksense) *cobra.Command {
func configApplyCmd(q *qliksense.Qliksense) *cobra.Command {
c := &cobra.Command{
Use: "apply",
Short: "generate the patchs and apply manifests to k8s",
Short: "generate the patches and apply manifests to k8s",
Long: `generate patches based on CR and apply manifests to k8s`,
Example: `qliksense config apply`,
RunE: func(cmd *cobra.Command, args []string) error {

View File

@@ -115,15 +115,18 @@ func deleteContextConfigCmd(q *qliksense.Qliksense) *cobra.Command {
var (
cmd *cobra.Command
)
skipConfirmation := false
cmd = &cobra.Command{
Use: "delete-context",
Short: "deletes a specific context locally (not in-cluster)",
Example: `qliksense config delete-contexts <context_name>`,
RunE: func(cmd *cobra.Command, args []string) error {
return q.DeleteContextConfig(args)
return q.DeleteContextConfig(args, skipConfirmation)
},
}
f := cmd.Flags()
f.BoolVar(&skipConfirmation, "yes", skipConfirmation, "skips confirmation")
return cmd
}

106
cmd/qliksense/eula.go Normal file
View File

@@ -0,0 +1,106 @@
package main
import (
"fmt"
"os"
"strings"
"github.com/mattn/go-tty"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
type eulaPreRunHooksT struct {
validators map[string]func(cmd *cobra.Command, q *qliksense.Qliksense) (bool, error)
postValidationArtifacts map[string]interface{}
}
func (e *eulaPreRunHooksT) addValidator(command string, validator func(cmd *cobra.Command, q *qliksense.Qliksense) (bool, error)) {
e.validators[command] = validator
}
func (e *eulaPreRunHooksT) getValidator(command string) func(cmd *cobra.Command, q *qliksense.Qliksense) (bool, error) {
if validator, ok := e.validators[command]; ok {
return validator
}
return nil
}
func (e *eulaPreRunHooksT) addPostValidationArtifact(artifactName string, artifact interface{}) {
e.postValidationArtifacts[artifactName] = artifact
}
func (e *eulaPreRunHooksT) getPostValidationArtifact(artifactName string) interface{} {
if artifact, ok := e.postValidationArtifacts[artifactName]; ok {
return artifact
}
return nil
}
var eulaEnforced = os.Getenv("QLIKSENSE_EULA_ENFORCE") == "true"
var eulaText = "Please read the end user license agreement at: https://www.qlik.com/us/legal/license-terms"
var eulaPrompt = "Do you accept our EULA? (y/n): "
var eulaErrorInstruction = `You must enter "y" to continue`
var eulaPreRunHooks = eulaPreRunHooksT{
validators: make(map[string]func(cmd *cobra.Command, q *qliksense.Qliksense) (bool, error)),
postValidationArtifacts: make(map[string]interface{}),
}
func commandAlwaysRequiresEulaAcceptance(commandName string) bool {
return commandName == "install" || commandName == "upgrade" || commandName == "apply"
}
func globalEulaPreRun(cmd *cobra.Command, q *qliksense.Qliksense) {
if isEulaEnforced(cmd.Name()) {
if strings.TrimSpace(strings.ToLower(cmd.Flag("acceptEULA").Value.String())) != "yes" {
if eulaPreRunHook := eulaPreRunHooks.getValidator(cmd.Name()); eulaPreRunHook != nil {
if eulaAccepted, err := eulaPreRunHook(cmd, q); err != nil {
panic(err)
} else if !eulaAccepted {
doEnforceEula()
}
} else if qConfig, err := qapi.NewQConfigE(q.QliksenseHome); err != nil {
doEnforceEula()
} else if qcr, err := qConfig.GetCurrentCR(); err != nil || !qcr.IsEULA() {
doEnforceEula()
}
}
}
}
func globalEulaPostRun(cmd *cobra.Command, q *qliksense.Qliksense) {
if isEulaEnforced(cmd.Name()) {
if err := q.SetEulaAccepted(); err != nil {
panic(err)
}
}
}
func isEulaEnforced(commandName string) bool {
return eulaEnforced || commandAlwaysRequiresEulaAcceptance(commandName)
}
func doEnforceEula() {
fmt.Println(eulaText)
fmt.Print(eulaPrompt)
answer := readRuneFromTty()
fmt.Printf("%v\n", answer)
if strings.ToLower(answer) != "y" {
fmt.Println(eulaErrorInstruction)
os.Exit(1)
}
}
func readRuneFromTty() string {
t, err := tty.Open()
if err != nil {
panic(err)
}
defer t.Close()
answer, err := t.ReadRune()
if err != nil {
panic(err)
}
return string(answer)
}

View File

@@ -0,0 +1,29 @@
package main
import (
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
const defaultVersionsLimit = 10
func getInstallableVersionsCmd(q *qliksense.Qliksense) *cobra.Command {
opts := &qliksense.LsRemoteCmdOptions{
IncludeBranches: false,
Limit: defaultVersionsLimit,
}
c := &cobra.Command{
Use: "get-versions",
Short: "list remote/installable versions",
Long: `list remote/installable versions`,
Example: `qliksense get-versions`,
RunE: func(cmd *cobra.Command, args []string) error {
return q.GetInstallableVersions(opts)
},
}
f := c.Flags()
f.BoolVarP(&opts.IncludeBranches, "include-branches", "", opts.IncludeBranches, "Include branches")
f.IntVarP(&opts.Limit, "limit", "", opts.Limit, "Maximum versions to list (starting with the highest)")
return c
}

View File

@@ -14,18 +14,19 @@ func installCmd(q *qliksense.Qliksense) *cobra.Command {
Long: `install a qliksense release`,
Example: `qliksense install <version> #if no version provides, expect manifestsRoot is set somewhere in the file system`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return q.InstallQK8s("", opts, keepPatchFiles)
version := ""
if len(args) != 0 {
version = args[0]
}
return q.InstallQK8s(args[0], opts, keepPatchFiles)
return q.InstallQK8s(version, opts, keepPatchFiles)
},
}
f := c.Flags()
f.StringVarP(&opts.AcceptEULA, "acceptEULA", "a", "", "AcceptEULA for qliksense")
f.StringVarP(&opts.StorageClass, "storageClass", "s", "", "Storage class for qliksense")
f.StringVarP(&opts.MongoDbUri, "mongoDbUri", "m", "", "mongoDbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
f.StringVarP(&opts.RotateKeys, "rotateKeys", "r", "", "Rotate JWT keys for qliksense (yes:rotate keys/ no:use exising keys from cluster/ None: use default EJSON_KEY from env")
f.BoolVar(&keepPatchFiles, keepPatchFilesFlagName, keepPatchFiles, keepPatchFilesFlagUsage)
return c
}

84
cmd/qliksense/load.go Normal file
View File

@@ -0,0 +1,84 @@
package main
import (
"bytes"
"io"
"io/ioutil"
"os"
"github.com/pkg/errors"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
func loadCrFile(q *qliksense.Qliksense) *cobra.Command {
filePath := ""
overwriteExistingContext := false
c := &cobra.Command{
Use: "load",
Short: "load a CR a file and create necessary structure for future use",
Long: `load a CR a file and create necessary structure for future use`,
Example: `qliksense load -f file_name or cat cr_file | qliksense load -f -`,
RunE: func(cmd *cobra.Command, args []string) error {
return runLoadOrApplyCommandE(cmd, func(reader io.Reader) error {
return q.LoadCr(reader, overwriteExistingContext)
})
},
}
f := c.Flags()
f.StringVarP(&filePath, "file", "f", "", "File to load CR from")
c.MarkFlagRequired("file")
f.BoolVarP(&overwriteExistingContext, "overwrite", "o", overwriteExistingContext, "Overwrite any existing contexts with the same name")
eulaPreRunHooks.addValidator(c.Name(), loadOrApplyCommandEulaPreRunHook)
return c
}
func getCrFileFromFlag(cmd *cobra.Command, flagName string) (*os.File, error) {
filePath := cmd.Flag(flagName).Value.String()
if filePath == "-" {
if !isInputFromPipe() {
return nil, errors.New("No input pipe present")
}
return os.Stdin, nil
}
file, e := os.Open(filePath)
if e != nil {
return nil, errors.Wrapf(e,
"unable to read the file %s", filePath)
}
return file, nil
}
func isInputFromPipe() bool {
fileInfo, _ := os.Stdin.Stat()
return fileInfo.Mode()&os.ModeCharDevice == 0
}
func loadOrApplyCommandEulaPreRunHook(cmd *cobra.Command, q *qliksense.Qliksense) (bool, error) {
file, err := getCrFileFromFlag(cmd, "file")
if err != nil {
return false, err
}
defer file.Close()
if crBytes, err := ioutil.ReadAll(file); err != nil {
return false, err
} else {
eulaPreRunHooks.addPostValidationArtifact("CR", crBytes)
return q.IsEulaAcceptedInCrFile(bytes.NewBuffer(crBytes))
}
}
func runLoadOrApplyCommandE(cmd *cobra.Command, callBack func(io.Reader) error) error {
if crBytes := eulaPreRunHooks.getPostValidationArtifact("CR"); crBytes != nil {
return callBack(bytes.NewBuffer(crBytes.([]byte)))
} else {
file, err := getCrFileFromFlag(cmd, "file")
if err != nil {
return err
}
defer file.Close()
return callBack(file)
}
}

View File

@@ -0,0 +1,81 @@
package main
import (
"fmt"
"log"
"github.com/qlik-oss/sense-installer/pkg/preflight"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
func preflightCmd(q *qliksense.Qliksense) *cobra.Command {
var preflightCmd = &cobra.Command{
Use: "preflight",
Short: "perform preflight checks on the cluster",
Long: `perform preflight checks on the cluster`,
Example: `qliksense preflight <preflight_check_to_run>`,
}
return preflightCmd
}
func preflightCheckDnsCmd(q *qliksense.Qliksense) *cobra.Command {
var preflightDnsCmd = &cobra.Command{
Use: "dns",
Short: "perform preflight dns check",
Long: `perform preflight dns check to check DNS connectivity status in the cluster`,
Example: `qliksense preflight dns`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q}
err := qp.DownloadPreflight()
if err != nil {
err = fmt.Errorf("There has been an error downloading preflight: %+v", err)
log.Println(err)
return err
}
return qp.CheckDns()
},
}
return preflightDnsCmd
}
func preflightCheckK8sVersionCmd(q *qliksense.Qliksense) *cobra.Command {
var preflightCheckK8sVersionCmd = &cobra.Command{
Use: "k8s-version",
Short: "check k8s version",
Long: `check minimum valid k8s version on the cluster`,
Example: `qliksense preflight k8s-version`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q}
err := qp.DownloadPreflight()
if err != nil {
err = fmt.Errorf("There has been an error downloading preflight: %+v", err)
log.Println(err)
return err
}
return qp.CheckK8sVersion()
},
}
return preflightCheckK8sVersionCmd
}
func preflightAllChecksCmd(q *qliksense.Qliksense) *cobra.Command {
var preflightAllChecksCmd = &cobra.Command{
Use: "all",
Short: "perform all checks",
Long: `perform all preflight checks on the target cluster`,
Example: `qliksense preflight all`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q}
err := qp.DownloadPreflight()
if err != nil {
err = fmt.Errorf("There has been an error downloading preflight: %+v", err)
log.Println(err)
return err
}
return qp.RunAllPreflightChecks()
},
}
return preflightAllChecksCmd
}

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strings"
@@ -19,7 +18,7 @@ import (
"github.com/ttacon/chalk"
)
// To run this project in ddebug mode, run:
// To run this project in debug mode, run:
// export QLIKSENSE_DEBUG=true
// qliksense <command>
@@ -43,14 +42,13 @@ func initAndExecute() error {
api.LogDebugMessage("QliksenseHomeDir: %s", qlikSenseHome)
qliksenseClient := qliksense.New(qlikSenseHome)
qliksenseClient.SetUpQliksenseDefaultContext()
cmd := rootCmd(qliksenseClient)
//levenstein checks
if levenstein(cmd) == false {
if err := cmd.Execute(); err != nil {
return err
}
if err := cmd.Execute(); err != nil {
//levenstein checks (auto-suggestions)
levenstein(cmd)
return err
}
return nil
}
@@ -80,30 +78,60 @@ func setUpPaths() (string, error) {
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of qliksense cli",
Long: `All software has versions. This is Hugo's`,
Long: "Print the version number of qliksense cli",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("%s (%s, %s)\n", pkg.Version, pkg.Commit, pkg.CommitDate)
},
}
func rootCmd(p *qliksense.Qliksense) *cobra.Command {
var (
cmd *cobra.Command
)
func commandUsesContext(commandName string) bool {
return commandName != "" && commandName != "qliksense" && commandName != "help" && commandName != "version"
}
cmd = &cobra.Command{
func getRootCmd(p *qliksense.Qliksense) *cobra.Command {
cmd := &cobra.Command{
Use: "qliksense",
Short: "Qliksense cli tool",
Long: `qliksense cli tool provides functionality to perform operations on qliksense-k8s, qliksense operator, and kubernetes cluster`,
Args: cobra.ArbitraryArgs,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if commandUsesContext(cmd.Name()) {
globalEulaPreRun(cmd, p)
if err := p.SetUpQliksenseDefaultContext(); err != nil {
panic(err)
}
globalEulaPostRun(cmd, p)
}
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
if commandUsesContext(cmd.Name()) {
globalEulaPostRun(cmd, p)
}
},
}
origHelpFunc := cmd.HelpFunc()
cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
if !commandUsesContext(cmd.Name()) {
cmd.Flags().MarkHidden("acceptEULA")
}
origHelpFunc(cmd, args)
})
accept := ""
cmd.PersistentFlags().StringVarP(&accept, "acceptEULA", "a", "", "Accept EULA for qliksense")
cmd.Flags().SetInterspersed(false)
return cmd
}
func initConfig() {
viper.SetEnvPrefix("QLIKSENSE")
viper.AutomaticEnv()
}
func rootCmd(p *qliksense.Qliksense) *cobra.Command {
cmd := getRootCmd(p)
cobra.OnInitialize(initConfig)
// For qliksense overrides/commands
cmd.AddCommand(getInstallableVersionsCmd(p))
cmd.AddCommand(pullQliksenseImages(p))
cmd.AddCommand(pushQliksenseImages(p))
cmd.AddCommand(about(p))
@@ -164,40 +192,21 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
cmd.AddCommand(crdsCmd)
crdsCmd.AddCommand(crdsViewCmd(p))
crdsCmd.AddCommand(crdsInstallCmd(p))
// add preflight command
preflightCmd := preflightCmd(p)
preflightCmd.AddCommand(preflightCheckDnsCmd(p))
preflightCmd.AddCommand(preflightCheckK8sVersionCmd(p))
preflightCmd.AddCommand(preflightAllChecksCmd(p))
//preflightCmd.AddCommand(preflightCheckMongoCmd(p))
//preflightCmd.AddCommand(preflightCheckAllCmd(p))
cmd.AddCommand(preflightCmd)
cmd.AddCommand(loadCrFile(p))
cmd.AddCommand((applyCmd(p)))
return cmd
}
func initConfig() {
viper.SetEnvPrefix("QLIKSENSE")
viper.AutomaticEnv()
}
func downloadFile(url string, filepath string) error {
var (
out *os.File
err error
resp *http.Response
)
// Create the file
if out, err = os.Create(filepath); err != nil {
return err
}
defer out.Close()
// Get the data
if resp, err = http.Get(url); err != nil {
return err
}
defer resp.Body.Close()
// Write the body to file
if _, err = io.Copy(out, resp.Body); err != nil {
return err
}
return nil
}
func copy(src, dst string) (int64, error) {
var (
source, destination *os.File
@@ -226,28 +235,21 @@ func copy(src, dst string) (int64, error) {
return nBytes, err
}
func levenstein(cmd *cobra.Command) bool {
cmd.SuggestionsMinimumDistance = 4
func levenstein(cmd *cobra.Command) {
cmd.SuggestionsMinimumDistance = 2
if len(os.Args) > 1 {
args := os.Args[1]
for _, ctx := range cmd.Commands() {
val := *ctx
if args == val.Name() {
//found command
return false
}
}
suggest := cmd.SuggestionsFor(os.Args[1])
suggest := cmd.SuggestionsFor(args)
if len(suggest) > 0 {
arg := []string{}
for _, cm := range os.Args {
arg = append(arg, cm)
}
arg[1] = suggest[0]
out := ansi.NewColorableStdout()
fmt.Fprintln(out, chalk.Green.Color("Did you mean: "), chalk.Bold.TextStyle(strings.Join(arg, " ")), "?")
return true
if !strings.EqualFold(arg[1], suggest[0]) {
arg[1] = suggest[0]
out := ansi.NewColorableStdout()
fmt.Fprintln(out, chalk.Green.Color("Did you mean: "), chalk.Bold.TextStyle(strings.Join(arg, " ")), "?")
}
}
}
return false
}

View File

@@ -1,7 +1,6 @@
package main
import (
qapi "github.com/qlik-oss/sense-installer/pkg/api"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
@@ -9,7 +8,7 @@ import (
func uninstallCmd(q *qliksense.Qliksense) *cobra.Command {
c := &cobra.Command{
Use: "uninstall",
Short: "Uninstall the deployed qliksense with release name [ " + qapi.NewQConfig(q.QliksenseHome).Spec.CurrentContext + " ]",
Short: "Uninstall the deployed qliksense.",
Long: `Uninstall the deployed qliksense. By default uninstall the current context`,
Example: `qliksense uninstall <context-name>`,
RunE: func(cmd *cobra.Command, args []string) error {

119
docs/command_reference.md Normal file
View File

@@ -0,0 +1,119 @@
# qliksense command reference
## qliksense apply
`qliksense apply` command takes input a cr file or input from pipe
- `qliksense apply -f cr-file.yaml`
- `cat cr-file.yaml | qliksense apply -f -`
the content of `cr-file.yaml` should be something similar
```yaml
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: qlik-test
labels:
version: v0.0.2
spec:
configs:
qliksense:
- name: acceptEULA
value: "yes"
secrets:
qliksense:
- name: mongoDbUri
value: mongodb://qlik-test-mongodb:27017/qliksense?ssl=false
profile: docker-desktop
rotateKeys: "yes"
```
This will do everything `qliksense load` does and install the qliksense into the cluster.
## qliksense load
`qliksense load` command takes input a cr file or input from pipe.
- `qliksense load -f cr-file.yaml`
- `cat cr-file.yaml | qliksense load -f -`
This will load the CR into `${QLIKSENSE_HOME}` folder, create context structure and set the current context to that CR.
This will also encrypt the secrets from CR while writing the CR into the disk.
## qliksense about
About action will display inside information regarding [qliksense-k8](https://github.com/qlik-oss/qliksense-k8s) release.
it will support following flags
- `qliksense about 1.0.0` display default profile for tag `1.0.0`.
- `qliksense about 1.0.0 --profile=docker-desktop`
- `qliksense about`
- assuming current directory has `manifests/docker-desktop`
- or get version information from pull of `qliksense-k8s` `master`
using other supported commands user might have built the CR into the location `~/.qliksense/myqliksense.yaml`
```yaml
apiVersion: qlik.com/v1
kind: QlikSense
metadata:
name: myqliksense
spec:
profile: docker-desktop
manifestsRoot: /Usr/ddd/my-k8-repo/manifests
namespace: myqliksense
storageClassName: efs
configs:
qliksense:
- name: acceptEULA
value: "yes"
secrets:
qliksense:
- name: mongoDbUri
value: "mongo://mongo:3307"
- name: messagingPassword
valueFromKey: messagingPassword
```
In that case the command would be
- `qliksense about`
- display from `/Usr/ddd/my-k8-repo/manifests/docker-desktop` location
- pull from `master` if directory invalid/empty
## qliksense config
Config action will perform operations on configurations and contexts regarding the [qliksense-k8](https://github.com/qlik-oss/qliksense-k8s) release.
it will support following commands:
- `qliksense config apply` - generate the patchs and apply manifests to k8s
- `qliksense config list-contexts` - retrieves the contexts and lists them
- `qliksense config set` - configure a key value pair into the current context
- `qliksense config set-configs` - set configurations into the qliksense context as key-value pairs
- `qliksense config set-context` - sets the context in which the Kubernetes cluster and resources live in
- `qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=false` - set secrets configurations into the qliksense context as key-value pairs and show encrypted value as part of CR
- `qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=true` - set secrets configurations into the qliksense context as key-value pairs and show a key reference to the created Kubernetes secret resource as part of the CR
- `qliksense config view` - view the qliksense operator CR
- `qliksense config delete-context` - deletes a specific context locally (not in-cluster). Deletes context in spec of `config.yaml` and locally deletes entire folder of specified context (does not delete in-cluster secrets)
the global file that abstracts all the contexts is `config.yaml`, located at: `~/.qliksense/config.yaml`:
```yaml
apiVersion: config.qlik.com/v1
kind: QliksenseConfig
metadata:
name: QliksenseConfigMetadata
spec:
contexts:
- name: qlik-default
crFile: /Users/fff/.qliksense/contexts/qlik-default/qlik-default.yaml
- name: myqliksense
crFile: /Users/fff/.qliksense/contexts/myqliksense/myqliksense.yaml
- name: hello
crFile: /Users/fff/.qliksense/contexts/hello/hello.yaml
currentContext: hello
```

View File

@@ -75,3 +75,23 @@ When you perform `qliksense install` or `qliksene config apply`, qliksense opera
- Install kubernetes resources
- Push generated patches into a new branch in the provided git repo. _Gives you ability to merge patches into your master branch_
- Create a CronJob to monitor master branch. Any changes pushed to master branch will be applied into the cluster. _This is a light weight `git-ops` model_
## GitOps
To enable gitops, the following section should be in the CR
```yaml
....
spec:
git:
repository: https://github.com/<OWNER>/<REPO>
accessToken: "<git-token>"
userName: "<git-username>"
gitOps:
enabled: "yes"
schedule: "*/5 * * * *"
watchBranch: <myBranch>
image: qlik-docker-oss.bintray.io/qliksense-repo-watcher
....
```

100
docs/preflight_checks.md Normal file
View File

@@ -0,0 +1,100 @@
##Preflight checks
Preflight checks provide pre-installation cluster conformance testing and validation before we install qliksense on the cluster. We gather a suite of conformance tests that can be easily written and run on the target cluster to verify that cluster-specific requirements are met.
The suite consists of a set of `collectors` which run the specifications of every test and `analyzers` which analyze the results of every test run by the collector.
We support the following tests at the moment as part of preflight checks, and the range of the suite will be expanded in future.
Run the following command to view help about the commands supported by preflight at any moment:
```console
$ qliksense preflight
perform preflight checks on the cluster
Usage:
qliksense preflight [command]
Examples:
qliksense preflight <preflight_check_to_run>
Available Commands:
all perform all checks
dns perform preflight dns check
k8s-version check k8s version
Flags:
-h, --help help for preflight
```
### DNS check
Run the following command to perform preflight DNS check. We setup a kubernetes deployment and try to reach it as part of establishing DNS connectivity in this check.
The expected output should be similar to the one shown below.
```console
$ qliksense preflight dns
Creating resources to run preflight checks
deployment.apps/qnginx001 created
service/qnginx001 created
pod/qnginx001-6db5fc95c5-s9sl2 condition met
Running Preflight checks ⠇
--- PASS DNS check
--- DNS check passed
--- PASS cluster-preflight-checks
PASS
DNS check completed, cleaning up resources now
service "qnginx001" deleted
deployment.extensions "qnginx001" deleted
```
### Kubernetes version check
We check the version of the target kubernetes cluster and ensure that it falls in the valid range of kubernetes versions that are supported by qliksense.
The command to run this check and the expected similar output are as shown below:
```console
$ qliksense preflight k8s-version
Minimum Kubernetes version supported: 1.11.0
Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.3", GitCommit:"06ad960bfd03b39c8310aaf92d1e7c12ce618213", GitTreeState:"clean", BuildDate:"2020-02-13T18:08:14Z", GoVersion:"go1.13.8", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.5", GitCommit:"20c265fef0741dd71a66480e35bd69f18351daea", GitTreeState:"clean", BuildDate:"2019-10-15T19:07:57Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"linux/amd64"}
Running Preflight checks ⠇
--- PASS Required Kubernetes Version
--- Good to go.
--- PASS cluster-preflight-checks
PASS
Minimum kubernetes version check completed
```
### Running all checks
Run the command shown below to execute all preflight checks.
```console
$ qliksense preflight all
Running all preflight checks
Running DNS check...
Creating resources to run preflight checks
deployment.apps/qnginx001 created
service/qnginx001 created
pod/qnginx001-6db5fc95c5-grwv2 condition met
Running Preflight checks ⠇
--- PASS DNS check
--- DNS check passed
--- PASS cluster-preflight-checks
PASS
DNS check completed, cleaning up resources now
service "qnginx001" deleted
deployment.extensions "qnginx001" deleted
Running minimum kubernetes version check...
Minimum Kubernetes version supported: 1.11.0
Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.3", GitCommit:"06ad960bfd03b39c8310aaf92d1e7c12ce618213", GitTreeState:"clean", BuildDate:"2020-02-13T18:08:14Z", GoVersion:"go1.13.8", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.5", GitCommit:"20c265fef0741dd71a66480e35bd69f18351daea", GitTreeState:"clean", BuildDate:"2019-10-15T19:07:57Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"linux/amd64"}
Running Preflight checks ⠧
--- PASS Required Kubernetes Version
--- Good to go.
--- PASS cluster-preflight-checks
PASS
Minimum kubernetes version check completed
Completed running all preflight checks
```

10
go.mod
View File

@@ -37,24 +37,28 @@ require (
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/mattn/go-colorable v0.1.4
github.com/mattn/go-tty v0.0.3
github.com/mitchellh/go-homedir v1.1.0
github.com/morikuni/aec v1.0.0 // indirect
github.com/qlik-oss/k-apis v0.0.19
github.com/pkg/errors v0.8.1
github.com/qlik-oss/k-apis v0.0.22
github.com/robfig/cron/v3 v3.0.1
github.com/rogpeppe/go-internal v1.5.2 // indirect
github.com/spf13/cobra v0.0.6
github.com/spf13/viper v1.6.1
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 // indirect
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 // indirect
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a // indirect
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect
golang.org/x/tools v0.0.0-20200309202150-20ab64c0d93f // indirect
golang.org/x/tools v0.0.0-20200312194400-c312e98713c2 // indirect
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b // indirect
google.golang.org/grpc v1.27.0 // indirect
gopkg.in/yaml.v2 v2.2.8
gopkg.in/yaml.v3 v3.0.0-20190924164351-c8b7dadae555
k8s.io/api v0.17.0
k8s.io/apimachinery v0.17.0
k8s.io/client-go v11.0.0+incompatible
sigs.k8s.io/kustomize/api v0.3.2
sigs.k8s.io/yaml v1.1.0
)

23
go.sum
View File

@@ -696,9 +696,13 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k=
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-shellwords v1.0.5/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI=
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI=
github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -843,17 +847,16 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/qlik-oss/k-apis v0.0.16/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE=
github.com/qlik-oss/k-apis v0.0.17 h1:tOdrEe9gfb9CXq0+uowFnXIsI781qz/zgeN8xqupXYw=
github.com/qlik-oss/k-apis v0.0.17/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE=
github.com/qlik-oss/k-apis v0.0.19 h1:yrMgALQ08vMDi5hN6fnvIfyNsEaXA5fZjB1YhyIdTfg=
github.com/qlik-oss/k-apis v0.0.19/go.mod h1:DNiWYqCqPIN216l7+1rccArNIYPb1Le7kYDcPSyNp+Q=
github.com/qlik-oss/k-apis v0.0.22 h1:tntQEeRqDYkBi2Ku5+xt7ABGMeFPck7+DOKrHUnpzwI=
github.com/qlik-oss/k-apis v0.0.22/go.mod h1:DNiWYqCqPIN216l7+1rccArNIYPb1Le7kYDcPSyNp+Q=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9 h1:iqeqTS4zjp6rPEaxmFB7pemA2CMjOEN5dYSXZaQ82uw=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9/go.mod h1:OCt7FTrRVHj4kmR2xLJJUIqu00BTr6GeF09hSmM17Kw=
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -1043,10 +1046,8 @@ golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA=
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1208,8 +1209,8 @@ golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c h1:2EA2K0k9bcvvEDlqD8xdlOhCOqq+O/p9Voqi4x9W1YU=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200309202150-20ab64c0d93f h1:NbrfHxef+IfdI86qCgO/1Siq1BuMH2xG0NqgvCguRhQ=
golang.org/x/tools v0.0.0-20200309202150-20ab64c0d93f/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312194400-c312e98713c2 h1:6TB4+MaZlkcSsJDu+BS5yxSEuZIYhjWz+jhbSLEZylI=
golang.org/x/tools v0.0.0-20200312194400-c312e98713c2/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -17,6 +17,8 @@ markdown_extensions:
nav:
- Overview: index.md
- getting_started.md
- command_reference.md
- concepts.md
- preflight_checks.md
- air_gap.md
- Releases ⧉: https://github.com/qlik-oss/sense-installer/releases

BIN
pkg/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -27,30 +27,39 @@ const (
// NewQConfig create QliksenseConfig object from file ~/.qliksense/config.yaml
func NewQConfig(qsHome string) *QliksenseConfig {
qc, err := NewQConfigE(qsHome)
if err != nil {
fmt.Println("yaml unmarshalling error ", err)
os.Exit(1)
}
return qc
}
func NewQConfigE(qsHome string) (*QliksenseConfig, error) {
configFile := filepath.Join(qsHome, "config.yaml")
qc := &QliksenseConfig{}
err := ReadFromFile(qc, configFile)
if err != nil {
fmt.Println("yaml unmarshalling error ", err)
os.Exit(1)
return nil, err
}
qc.QliksenseHomePath = qsHome
return qc
return qc, nil
}
func NewQConfigEmpty(qsHome string) *QliksenseConfig {
return &QliksenseConfig{
QliksenseHomePath: qsHome,
}
}
// GetCR create a QliksenseCR object for a particular context
// from file ~/.qliksense/contexts/<contx-name>/<contx-name>.yaml
func (qc *QliksenseConfig) GetCR(contextName string) (*QliksenseCR, error) {
crFilePath := qc.getCRFilePath(contextName)
crFilePath := qc.GetCRFilePath(contextName)
if crFilePath == "" {
return nil, errors.New("context name " + contextName + " not found")
}
return getCRObject(crFilePath)
}
func getUnencryptedCR() {
return qc.GetAndTransformCrObject(crFilePath)
}
// GetCurrentCR create a QliksenseCR object for current context
@@ -59,14 +68,14 @@ func (qc *QliksenseConfig) GetCurrentCR() (*QliksenseCR, error) {
}
// SetCrLocation sets the CR location for a context. Helpful during test
func (qc *QliksenseConfig) SetCrLocation(contextName, filepath string) (*QliksenseConfig, error) {
func (qc *QliksenseConfig) SetCrLocation(contextName, filePath string) (*QliksenseConfig, error) {
tempQc := &QliksenseConfig{}
copier.Copy(tempQc, qc)
found := false
tempQc.Spec.Contexts = []Context{}
for _, c := range qc.Spec.Contexts {
if c.Name == contextName {
c.CrFile = filepath
c.CrFile = filePath
found = true
}
tempQc.Spec.Contexts = append(tempQc.Spec.Contexts, []Context{c}...)
@@ -77,7 +86,8 @@ func (qc *QliksenseConfig) SetCrLocation(contextName, filepath string) (*Qliksen
return nil, errors.New("cannot find the context")
}
func getCRObject(crfile string) (*QliksenseCR, error) {
// GetCRObject create a qliksense CR object from file
func GetCRObject(crfile string) (*QliksenseCR, error) {
cr := &QliksenseCR{}
err := ReadFromFile(cr, crfile)
if err != nil {
@@ -88,11 +98,36 @@ func getCRObject(crfile string) (*QliksenseCR, error) {
return cr, nil
}
func (qc *QliksenseConfig) getCRFilePath(contextName string) string {
func (qc *QliksenseConfig) GetAndTransformCrObject(crfile string) (*QliksenseCR, error) {
cr, err := GetCRObject(crfile)
if err != nil {
return nil, err
}
if cr.Spec.ManifestsRoot != "" && !filepath.IsAbs(cr.Spec.ManifestsRoot) {
cr.Spec.ManifestsRoot = filepath.Join(qc.QliksenseHomePath, cr.Spec.ManifestsRoot)
}
return cr, nil
}
//CreateCRObjectFromString create a QliksenseCR from string content
func CreateCRObjectFromString(crContent string) (*QliksenseCR, error) {
if crContent == "" {
return nil, errors.New("empty string cannot qliksensecr")
}
cr := &QliksenseCR{}
err := ReadFromStream(cr, strings.NewReader(crContent))
if err != nil {
fmt.Println("cannot unmarshal cr ", err)
return nil, err
}
return cr, nil
}
func (qc *QliksenseConfig) GetCRFilePath(contextName string) string {
crFilePath := ""
for _, ctx := range qc.Spec.Contexts {
if ctx.Name == contextName {
crFilePath = ctx.CrFile
crFilePath = filepath.Join(qc.QliksenseHomePath, ctx.CrFile)
break
}
}
@@ -117,7 +152,7 @@ func (qc *QliksenseConfig) BuildRepoPath(version string) string {
}
func (qc *QliksenseConfig) BuildRepoPathForContext(contextName, version string) string {
return filepath.Join(qc.QliksenseHomePath, qliksenseContextsDirName, contextName, "qlik-k8s", version)
return filepath.Join(qc.GetContextPath(contextName), "qlik-k8s", version)
}
func (qc *QliksenseConfig) BuildCurrentManifestsRoot(version string) string {
@@ -125,13 +160,59 @@ func (qc *QliksenseConfig) BuildCurrentManifestsRoot(version string) string {
}
func (qc *QliksenseConfig) WriteCR(cr *QliksenseCR, contextName string) error {
crf := qc.getCRFilePath(contextName)
crf := qc.GetCRFilePath(contextName)
if crf == "" {
return errors.New("context name " + contextName + " not found")
}
return WriteToFile(cr, crf)
return qc.TransformAndWriteCr(cr, crf)
}
//CreateOrWriteCrAndContext create necessary folder structure, update config.yaml and context yaml files
func (qc *QliksenseConfig) CreateOrWriteCrAndContext(cr *QliksenseCR) error {
if qc.QliksenseHomePath == "" {
return errors.New("qliksense home is not set")
}
crf := qc.GetCRFilePath(cr.GetName())
if crf == "" {
// create direcotry structure for context
cDir := filepath.Join(qc.QliksenseHomePath, "contexts", cr.GetName())
if err := os.MkdirAll(cDir, os.ModePerm); err != nil {
return err
}
crf = filepath.Join(cDir, cr.GetName()+".yaml")
ctx := Context{
Name: cr.GetName(),
CrFile: filepath.Join("contexts", cr.GetName(), cr.GetName()+".yaml"),
}
qc.AddToContexts(ctx)
if err := qc.Write(); err != nil {
return err
}
}
return qc.TransformAndWriteCr(cr, crf)
}
func (qc *QliksenseConfig) TransformAndWriteCr(cr *QliksenseCR, file string) error {
if strings.HasPrefix(cr.Spec.ManifestsRoot, qc.QliksenseHomePath) {
cr.Spec.ManifestsRoot = strings.Replace(cr.Spec.ManifestsRoot, qc.QliksenseHomePath+"/", "", 1)
}
if err := WriteToFile(cr, file); err != nil {
return err
}
if cr.Spec.ManifestsRoot != "" {
cr.Spec.ManifestsRoot = filepath.Join(qc.QliksenseHomePath, cr.Spec.ManifestsRoot)
}
return nil
}
func (qc *QliksenseConfig) AddToContexts(ctx Context) error {
//TODO: additional duplicate check may be added latter
qc.Spec.Contexts = append(qc.Spec.Contexts, ctx)
return nil
}
func (qc *QliksenseConfig) WriteCurrentContextCR(cr *QliksenseCR) error {
return qc.WriteCR(cr, qc.Spec.CurrentContext)
}
@@ -222,6 +303,15 @@ func (qc *QliksenseConfig) getDockerConfigJsonSecret(name string) (*DockerConfig
}
func (qc *QliksenseConfig) getCurrentContextEncryptionKeyPairLocation() (string, error) {
if qcr, err := qc.GetCurrentCR(); err != nil {
return "", err
} else {
return qc.getContextEncryptionKeyPairLocation(qcr.GetName())
}
}
func (qc *QliksenseConfig) getContextEncryptionKeyPairLocation(contextName string) (string, error) {
// Check env var: QLIKSENSE_KEY_LOCATION to determine location to store keypair
var secretKeyPairLocation string
if os.Getenv("QLIKSENSE_KEY_LOCATION") != "" {
@@ -230,13 +320,9 @@ func (qc *QliksenseConfig) getCurrentContextEncryptionKeyPairLocation() (string,
} else {
// QLIKSENSE_KEY_LOCATION has not been set, hence storing key pair in default location:
// /.qliksense/secrets/contexts/<current-context>/secrets/
if qcr, err := qc.GetCurrentCR(); err != nil {
return "", err
} else {
secretKeyPairLocation = filepath.Join(qc.QliksenseHomePath, qliksenseSecretsDirName, qliksenseContextsDirName, qcr.GetObjectMeta().GetName(), qliksenseSecretsDirName)
}
secretKeyPairLocation = filepath.Join(qc.QliksenseHomePath, qliksenseSecretsDirName, qliksenseContextsDirName, contextName, qliksenseSecretsDirName)
}
LogDebugMessage("SecretKeyLocation to store key pair: %s", secretKeyPairLocation)
return secretKeyPairLocation, nil
}
@@ -253,7 +339,15 @@ func (qc *QliksenseConfig) GetCurrentContextEjsonKeyDir() (string, error) {
}
func (qc *QliksenseConfig) GetCurrentContextEncryptionKeyPair() (*rsa.PublicKey, *rsa.PrivateKey, error) {
secretKeyPairLocation, err := qc.getCurrentContextEncryptionKeyPairLocation()
if qcr, err := qc.GetCurrentCR(); err != nil {
return nil, nil, err
} else {
return qc.GetContextEncryptionKeyPair(qcr.GetName())
}
}
func (qc *QliksenseConfig) GetContextEncryptionKeyPair(contextName string) (*rsa.PublicKey, *rsa.PrivateKey, error) {
secretKeyPairLocation, err := qc.getContextEncryptionKeyPairLocation(contextName)
if err != nil {
return nil, nil, err
}
@@ -342,6 +436,10 @@ func (cr *QliksenseCR) IsEULA() bool {
return false
}
func (cr *QliksenseCR) SetEULA(value string) {
cr.Spec.AddToConfigs("qliksense", "acceptEULA", value)
}
// GetDecryptedCr it decrypts all the encrypted value and return a new CR
func (qc *QliksenseConfig) GetDecryptedCr(cr *QliksenseCR) (*QliksenseCR, error) {
newCr := &QliksenseCR{}
@@ -374,3 +472,45 @@ func (qc *QliksenseConfig) GetDecryptedCr(cr *QliksenseCR) (*QliksenseCR, error)
newCr.Spec.Secrets = finalSecrets
return newCr, nil
}
//Validate validate CR
func (cr *QliksenseCR) Validate() bool {
return true
}
//CreateContextDirs create context dir structure ~/.qliksense/contexts/contextName
func (qc *QliksenseConfig) CreateContextDirs(contextName string) error {
return os.MkdirAll(qc.GetContextPath(contextName), os.ModePerm)
}
func (qc *QliksenseConfig) GetContextPath(contextName string) string {
return filepath.Join(qc.QliksenseHomePath, qliksenseContextsDirName, contextName)
}
//BuildCrFileAbsolutePath build absolute path for a cr ie. ~/.qliksense/contexts/qlik-defautl/qlik-default.yaml
func (qc *QliksenseConfig) BuildCrFileAbsolutePath(contextName string) string {
return filepath.Join(qc.GetContextPath(contextName), contextName+".yaml")
}
//BuildCrFilePath build cr file path i.e. contexts/qlik-default/qlik-default.yaml
func (qc *QliksenseConfig) BuildCrFilePath(contextName string) string {
return filepath.Join(qc.GetContextPath(contextName), contextName+".yaml")
}
//AddToContexts add the context into qc.Spec.Contexts
func (qc *QliksenseConfig) AddToContextsRaw(crName, crFile string) {
qc.Spec.Contexts = append(qc.Spec.Contexts, []Context{
{CrFile: crFile,
Name: crName},
}...)
}
//SetCurrentContextName set the qc.Spec.CurrentContext
func (qc *QliksenseConfig) SetCurrentContextName(name string) {
qc.Spec.CurrentContext = name
}
//Write write QliksenseConfig into config.yaml
func (qc *QliksenseConfig) Write() error {
return WriteToFile(qc, filepath.Join(qc.QliksenseHomePath, "config.yaml"))
}

View File

@@ -74,7 +74,7 @@ func TestGetCR(t *testing.T) {
// create CR
createCRFile(dir)
crFile := filepath.Join(dir, "contexts", "contx1", "contx1.yaml")
crFile := filepath.Join("contexts", "contx1", "contx1.yaml")
qct, e := qc.SetCrLocation("contx1", crFile)
if e != nil {
t.Fail()
@@ -100,7 +100,7 @@ func TestGetDecryptedCr(t *testing.T) {
// create CR
createCRFile(dir)
crFile := filepath.Join(dir, "contexts", "contx1", "contx1.yaml")
crFile := filepath.Join("contexts", "contx1", "contx1.yaml")
qct, e := qc.SetCrLocation("contx1", crFile)
if e != nil {
t.Fail()

View File

@@ -3,8 +3,10 @@ package api
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"github.com/qlik-oss/k-apis/pkg/config"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -103,14 +105,22 @@ func ReadFromFile(content interface{}, sourceFile string) error {
if content == nil || sourceFile == "" {
return nil
}
contents, err := ioutil.ReadFile(sourceFile)
file, e := os.Open(sourceFile)
if e != nil {
return e
}
return ReadFromStream(content, file)
}
// ReadFromStream reads from input stream and creat yaml struct of type content
func ReadFromStream(content interface{}, reader io.Reader) error {
contents, err := ioutil.ReadAll(reader)
if err != nil {
err = fmt.Errorf("There was an error reading from file: %s, %v", sourceFile, err)
err = fmt.Errorf("There was an error reading from reader: %v", err)
return err
}
// reading k8s style object
// https://stackoverflow.com/questions/44306554/how-to-deserialize-kubernetes-yaml-file
dec := machine_yaml.NewYAMLOrJSONDecoder(bytes.NewReader(contents), 10000)
dec.Decode(content)
return nil
return dec.Decode(content)
}

View File

@@ -97,3 +97,21 @@ func kubectlOperation(manifests string, oprName string, namespace string) error
os.Remove(tempYaml.Name())
return nil
}
func KubectlDirectOps(opr []string, namespace string) error {
arguments := []string{}
if namespace != "" {
arguments = append(arguments, "-n", namespace)
}
arguments = append(arguments, opr...)
cmd := exec.Command("kubectl", arguments...)
LogDebugMessage("Kubectl command: %s %v\n", "kubectl", arguments)
sterrBuffer := &bytes.Buffer{}
cmd.Stderr = sterrBuffer
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
return fmt.Errorf("kubectl %v failed with: %v, %v\n", opr, err, sterrBuffer.String())
}
return nil
}

View File

@@ -1,6 +1,8 @@
package api
import (
"fmt"
"strings"
"testing"
)
@@ -15,3 +17,16 @@ func TestGetKubectlNamespace(t *testing.T) {
}
SetKubectlNamespace(ns)
}
func TestKubectlDirectOps(t *testing.T) {
t.Skip()
SetKubectlNamespace("test")
ns := GetKubectlNamespace()
opr := fmt.Sprintf("version")
opr1 := strings.Fields(opr)
err := KubectlDirectOps(opr1, ns)
if err != nil {
t.Log(err)
t.Fail()
}
}

View File

@@ -1,13 +1,21 @@
package api
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/pkg/errors"
)
func checkExists(filename string) os.FileInfo {
@@ -109,3 +117,151 @@ func ExecuteTaskWithBlinkingStdoutFeedback(task func() (interface{}, error), fee
}
}
}
func DownloadFile(url, baseFolder, installerName string) error {
var (
out *os.File
err error
resp *http.Response
)
// Create the file
fileName := filepath.Join(baseFolder, installerName)
LogDebugMessage("Installer Filename: %s\n", fileName)
if out, err = os.Create(fileName); err != nil {
return err
}
defer out.Close()
// Get the data
if resp, err = http.Get(url); err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("unable to download the file from URL: %s, status: %s", url, resp.Status)
log.Println(err)
return err
}
// Write the body to file
if _, err = io.Copy(out, resp.Body); err != nil {
return err
}
err = os.Chmod(fileName, os.ModePerm)
if err != nil {
log.Println(err)
}
return nil
}
func ExplodePackage(destination, fileToUntar string) error {
LogDebugMessage("Destination: %s\n", destination)
LogDebugMessage("fileToUntar: %s\n", fileToUntar)
if strings.HasSuffix(fileToUntar, "zip") {
LogDebugMessage("This is a windows file : %s", fileToUntar)
err := UnZipFile(destination, fileToUntar)
if err != nil {
return nil
}
} else if strings.HasSuffix(fileToUntar, "tar.gz") {
LogDebugMessage("This is a mac/linux file: %s", fileToUntar)
err := UntarGzFile(destination, fileToUntar)
if err != nil {
return nil
}
}
return nil
}
func UntarGzFile(destination, fileToUntar string) error {
lFile, err := os.Open(fileToUntar)
if err != nil {
err = errors.Wrapf(err, "unable to read the local file %s", fileToUntar)
log.Fatal(err)
return err
}
gzReader, err := gzip.NewReader(lFile)
if err != nil {
err = errors.Wrap(err, "unable to load the file into a gz reader")
log.Fatal(err)
return err
}
defer gzReader.Close()
tarReader := tar.NewReader(gzReader)
for {
header, err := tarReader.Next()
switch {
case err == io.EOF:
return nil
case err != nil:
err = errors.Wrap(err, "error during untar")
log.Fatal(err)
return err
case header == nil:
continue
}
fileInLoop := filepath.Join(destination, header.Name)
switch header.Typeflag {
case tar.TypeDir:
if _, err := os.Stat(fileInLoop); err != nil {
if err := os.MkdirAll(fileInLoop, 0755); err != nil {
err = errors.Wrapf(err, "error creating directory %s", fileInLoop)
log.Fatal(err)
return err
}
}
case tar.TypeReg:
fileAtLoc, err := os.OpenFile(fileInLoop, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
err = errors.Wrapf(err, "error opening file %s", fileInLoop)
log.Fatal(err)
return err
}
if _, err := io.Copy(fileAtLoc, tarReader); err != nil {
err = errors.Wrapf(err, "error writing file %s", fileInLoop)
log.Fatal(err)
return err
}
fileAtLoc.Close()
fileAtLoc.Chmod(os.ModePerm)
}
}
}
func UnZipFile(destination, fileToUnzip string) error {
zipReader, _ := zip.OpenReader(fileToUnzip)
for _, file := range zipReader.Reader.File {
zippedFile, err := file.Open()
if err != nil {
log.Fatal(err)
}
defer zippedFile.Close()
extractedFilePath := filepath.Join(
destination,
file.Name,
)
outputFile, err := os.OpenFile(
extractedFilePath,
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
file.Mode(),
)
if err != nil {
log.Fatal(err)
}
defer outputFile.Close()
_, err = io.Copy(outputFile, zippedFile)
if err != nil {
log.Fatal(err)
}
LogDebugMessage("File extracted: %s, Extracted file path: %s\n", file.Name, extractedFilePath)
}
return nil
}

View File

@@ -0,0 +1,16 @@
package preflight
import (
"fmt"
)
func (qp *QliksensePreflight) RunAllPreflightChecks() error {
//run all preflight checks
fmt.Println("Running all preflight checks")
fmt.Printf("\nRunning DNS check...\n")
qp.CheckDns()
fmt.Printf("\nRunning minimum kubernetes version check...\n")
qp.CheckK8sVersion()
fmt.Println("Completed running all preflight checks")
return nil
}

128
pkg/preflight/dns_check.go Normal file
View File

@@ -0,0 +1,128 @@
package preflight
import (
"bytes"
"fmt"
"html/template"
"io/ioutil"
"path/filepath"
"github.com/qlik-oss/sense-installer/pkg/api"
)
const dnsCheckYAML = `
apiVersion: troubleshoot.replicated.com/v1beta1
kind: Preflight
metadata:
name: cluster-preflight-checks
namespace: {{ . }}
spec:
collectors:
- run:
collectorName: spin-up-pod
args: ["-z", "-v", "-w 1", "qnginx001", "80"]
command: ["nc"]
image: subfuzion/netcat:latest
imagePullPolicy: IfNotPresent
name: spin-up-pod-check-dns
namespace: {{ . }}
timeout: 30s
analyzers:
- textAnalyze:
checkName: DNS check
collectorName: spin-up-pod-check-dns
fileName: spin-up-pod.txt
regex: succeeded
outcomes:
- fail:
message: DNS check failed
- pass:
message: DNS check passed
`
func (qp *QliksensePreflight) CheckDns() error {
// retrieve namespace
namespace := api.GetKubectlNamespace()
api.LogDebugMessage("Namespace: %s\n", namespace)
tmpl, err := template.New("dnsCheckYAML").Parse(dnsCheckYAML)
if err != nil {
fmt.Printf("cannot parse template: %v", err)
return err
}
tempYaml, err := ioutil.TempFile("", "")
if err != nil {
fmt.Printf("cannot create file: %v", err)
return err
}
api.LogDebugMessage("Temp Yaml file: %s\n", tempYaml.Name())
b := bytes.Buffer{}
err = tmpl.Execute(&b, namespace)
if err != nil {
fmt.Println(err)
return err
}
tempYaml.WriteString(b.String())
// creating Kubectl resources
appName := "qnginx001"
const PreflightChecksDirName = "preflight_checks"
fmt.Println("Creating resources to run preflight checks")
// kubectl create deployment
opr := fmt.Sprintf("create deployment %s --image=nginx", appName)
err = initiateK8sOps(opr, namespace)
if err != nil {
fmt.Println(err)
return err
}
defer func() {
// Deleting deployment..
opr = fmt.Sprintf("delete deployment %s", appName)
// we want to delete the k8s resource here, we dont care a lot about an error here
_ = initiateK8sOps(opr, namespace)
api.LogDebugMessage("delete deployment executed")
}()
// create service
opr = fmt.Sprintf("create service clusterip %s --tcp=80:80", appName)
err = initiateK8sOps(opr, namespace)
if err != nil {
fmt.Println(err)
return err
}
defer func() {
// delete service
opr = fmt.Sprintf("delete service %s", appName)
// we want to delete the k8s resource here, we dont care a lot about an error here
_ = initiateK8sOps(opr, namespace)
api.LogDebugMessage("delete service executed")
}()
//kubectl -n $namespace wait --for=condition=ready pod -l app=$appName --timeout=120s
opr = fmt.Sprintf("wait --for=condition=ready pod -l app=%s --timeout=120s", appName)
err = initiateK8sOps(opr, namespace)
if err != nil {
fmt.Println(err)
return err
}
api.LogDebugMessage("kubectl wait executed")
// call preflight
preflightCommand := filepath.Join(qp.Q.QliksenseHome, PreflightChecksDirName, preflightFileName)
err = invokePreflight(preflightCommand, tempYaml)
if err != nil {
fmt.Println(err)
return err
}
fmt.Println("DNS check completed, cleaning up resources now")
return nil
}

View File

@@ -0,0 +1,187 @@
package preflight
import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/qlik-oss/sense-installer/pkg/api"
)
type QliksensePreflight struct {
Q *qliksense.Qliksense
}
const (
// preflight releases have the same version
preflightRelease = "v0.9.26"
preflightLinuxFile = "preflight_linux_amd64.tar.gz"
preflightMacFile = "preflight_darwin_amd64.tar.gz"
preflightWindowsFile = "preflight_windows_amd64.zip"
PreflightChecksDirName = "preflight_checks"
preflightFileName = "preflight"
)
var preflightBaseURL = fmt.Sprintf("https://github.com/replicatedhq/troubleshoot/releases/download/%s/", preflightRelease)
func (qp *QliksensePreflight) DownloadPreflight() error {
const preflightExecutable = "preflight"
preflightInstallDir := filepath.Join(qp.Q.QliksenseHome, PreflightChecksDirName)
platform := runtime.GOOS
exists, err := checkInstalled(preflightInstallDir, preflightExecutable)
if err != nil {
err = fmt.Errorf("There has been an error when trying to determine if preflight installer exists")
log.Println(err)
return err
}
if exists {
// preflight exist, no need to download again.
api.LogDebugMessage("Preflight already exists, proceeding to perform checks")
return nil
}
// Create the Preflight-check directory, download and install preflight
if !api.DirExists(preflightInstallDir) {
api.LogDebugMessage("%s does not exist, creating now\n", preflightInstallDir)
if err := os.Mkdir(preflightInstallDir, os.ModePerm); err != nil {
err = fmt.Errorf("Not able to create %s dir: %v", preflightInstallDir, err)
log.Println(err)
return nil
}
}
api.LogDebugMessage("Preflight-checks install Dir: %s exists", preflightInstallDir)
preflightUrl, preflightFile, err := determinePlatformSpecificUrls(platform)
if err != nil {
err = fmt.Errorf("There was an error when trying to determine platform specific paths")
return err
}
// Download Preflight
err = downloadAndExplode(preflightUrl, preflightInstallDir, preflightFile)
if err != nil {
return err
}
fmt.Println("Downloaded Preflight")
return nil
}
func checkInstalled(preflightInstallDir, preflightExecutable string) (bool, error) {
installerExists := true
preflightInstaller := filepath.Join(preflightInstallDir, preflightExecutable)
if api.DirExists(preflightInstallDir) {
if !api.FileExists(preflightInstaller) {
installerExists = false
api.LogDebugMessage("Preflight install directory exists, but preflight installer does not exist")
}
} else {
installerExists = false
}
return installerExists, nil
}
func downloadAndExplode(url, installDir, file string) error {
err := api.DownloadFile(url, installDir, file)
if err != nil {
return err
}
api.LogDebugMessage("Downloaded File: %s", file)
fileToUntar := filepath.Join(installDir, file)
api.LogDebugMessage("File to explode: %s", file)
err = api.ExplodePackage(installDir, fileToUntar)
if err != nil {
return err
}
return nil
}
func determinePlatformSpecificUrls(platform string) (string, string, error) {
var preflightUrl, preflightFile string
if runtime.GOARCH != `amd64` {
err := fmt.Errorf("%s architecture is not supported", runtime.GOARCH)
return "", "", err
}
switch platform {
case "windows":
preflightFile = preflightWindowsFile
case "darwin":
preflightFile = preflightMacFile
case "linux":
preflightFile = preflightLinuxFile
default:
err := fmt.Errorf("Unable to download the preflight executable for the underlying platform\n")
return "", "", err
}
preflightUrl = fmt.Sprintf("%s%s", preflightBaseURL, preflightFile)
return preflightUrl, preflightFile, nil
}
func initiateK8sOps(opr, namespace string) error {
opr1 := strings.Fields(opr)
err := api.KubectlDirectOps(opr1, namespace)
if err != nil {
fmt.Println(err)
return err
}
return nil
}
func invokePreflight(preflightCommand string, yamlFile *os.File) error {
arguments := []string{}
arguments = append(arguments, yamlFile.Name(), "--interactive=false")
cmd := exec.Command(preflightCommand, arguments...)
sterrBuffer := &bytes.Buffer{}
cmd.Stdout = sterrBuffer
cmd.Stderr = sterrBuffer
if err := cmd.Run(); err != nil {
return fmt.Errorf("Error when running preflight command: %v\n", err)
}
ind := strings.Index(sterrBuffer.String(), "---")
output := sterrBuffer.String()
if ind > -1 {
output = fmt.Sprintf("%s\n%s", output[:ind], output[ind:])
}
fmt.Printf("%v\n", output)
// Maybe good to retain this part in case we need to process the output in future.
// We are going to look for the first occurance of PASS or FAIL from the end
// there are also some space-like deceiving characters which are being hard to get by
//outputArr := strings.Fields(strings.TrimSpace(output))
//trackSuccess := false
//trackPrg := false
//for i := len(outputArr) - 1; i >= 0; i-- {
// if strings.TrimSpace(outputArr[i]) != "" {
// if outputArr[i] == "PASS" {
// trackSuccess = true
// trackPrg = true
// } else if outputArr[i] == "FAIL" {
// trackPrg = true
// }
// }
// if trackPrg {
// break
// }
//}
return nil
}

View File

@@ -0,0 +1,90 @@
package preflight
import (
"fmt"
"testing"
)
func Test_initiateK8sOps(t *testing.T) {
t.Skip()
type args struct {
opr string
namespace string
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "valid case",
args: args{
opr: fmt.Sprintf("version"),
namespace: "ash-ns",
},
wantErr: false,
},
{
name: "invalid case",
args: args{
opr: fmt.Sprintf("versions"),
namespace: "ash-ns",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := initiateK8sOps(tt.args.opr, tt.args.namespace); (err != nil) != tt.wantErr {
t.Errorf("initiateK8sOps() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_determinePlatformSpecificUrls(t *testing.T) {
type args struct {
platform string
}
tests := []struct {
name string
args args
want string
want1 string
wantErr bool
}{
{
name: "valid platform",
args: args{
platform: "windows",
},
want: fmt.Sprintf("%s%s", preflightBaseURL, preflightWindowsFile),
want1: preflightWindowsFile,
wantErr: false,
},
{
name: "invalid platform",
args: args{
platform: "unix",
},
want: "",
want1: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1, err := determinePlatformSpecificUrls(tt.args.platform)
if (err != nil) != tt.wantErr {
t.Errorf("determinePlatformSpecificUrls() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("determinePlatformSpecificUrls() got = %v, want %v", got, tt.want)
}
if got1 != tt.want1 {
t.Errorf("determinePlatformSpecificUrls() got1 = %v, want %v", got1, tt.want1)
}
})
}
}

View File

@@ -0,0 +1,84 @@
package preflight
import (
"bytes"
"fmt"
"io/ioutil"
"path/filepath"
"text/template"
"github.com/qlik-oss/sense-installer/pkg/api"
)
const minK8sVersion = "1.11.0"
const checkVersionYAML = `
apiVersion: troubleshoot.replicated.com/v1beta1
kind: Preflight
metadata:
name: cluster-preflight-checks
namespace: {{ .namespace }}
spec:
analyzers:
- clusterVersion:
outcomes:
- fail:
when: "< {{ .minK8sVersion }}"
message: The application requires at least Kubernetes {{ .minK8sVersion }} or later.
uri: https://www.kubernetes.io
- pass:
when: ">= {{ .minK8sVersion }}"
message: Good to go.
`
func (qp *QliksensePreflight) CheckK8sVersion() error {
// retrieve namespace
namespace := api.GetKubectlNamespace()
api.LogDebugMessage("Namespace: %s\n", namespace)
tmpl, err := template.New("checkVersionYAML").Parse(checkVersionYAML)
if err != nil {
fmt.Printf("cannot parse template: %v", err)
return err
}
tempYaml, err := ioutil.TempFile("", "")
if err != nil {
fmt.Printf("cannot create file: %v", err)
return err
}
api.LogDebugMessage("Temp Yaml file: %s\n", tempYaml.Name())
b := bytes.Buffer{}
err = tmpl.Execute(&b, map[string]string{
"namespace": namespace,
"minK8sVersion": minK8sVersion,
})
if err != nil {
fmt.Println(err)
return err
}
tempYaml.WriteString(b.String())
//api.LogDebugMessage("Temp yaml contents: %s", b.String())
fmt.Printf("Minimum Kubernetes version supported: %s\n", minK8sVersion)
// current kubectl version
opr := fmt.Sprintf("version")
err = initiateK8sOps(opr, namespace)
if err != nil {
fmt.Println(err)
return err
}
// call preflight
preflightCommand := filepath.Join(qp.Q.QliksenseHome, PreflightChecksDirName, preflightFileName)
err = invokePreflight(preflightCommand, tempYaml)
if err != nil {
fmt.Println(err)
return err
}
fmt.Println("Minimum kubernetes version check completed")
return nil
}

BIN
pkg/qliksense/.DS_Store vendored Normal file

Binary file not shown.

22
pkg/qliksense/apply.go Normal file
View File

@@ -0,0 +1,22 @@
package qliksense
import (
"io"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func (q *Qliksense) ApplyCRFromReader(r io.Reader, opts *InstallCommandOptions, keepPatchFiles, overwriteExistingContext bool) error {
if err := q.LoadCr(r, overwriteExistingContext); err != nil {
return err
}
qConfig := qapi.NewQConfig(q.QliksenseHome)
cr, err := qConfig.GetCurrentCR()
if err != nil {
return err
}
if err := q.InstallQK8s(cr.GetLabelFromCr("version"), opts, keepPatchFiles); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,24 @@
package qliksense
import (
"fmt"
"log"
"strings"
)
func AskForConfirmation(s string) bool {
for {
fmt.Printf("%s [y/n]: ", s)
var response string
_, err := fmt.Scanln(&response)
if err != nil {
log.Fatal(err)
}
if strings.EqualFold(response, "y") || strings.EqualFold(response, "yes") {
return true
} else if strings.EqualFold(response, "n") || strings.EqualFold(response, "no") {
return false
}
}
}

View File

@@ -10,9 +10,12 @@ import (
"strings"
"text/tabwriter"
"github.com/robfig/cron/v3"
"github.com/qlik-oss/k-apis/pkg/config"
b64 "encoding/base64"
ansi "github.com/mattn/go-colorable"
"github.com/qlik-oss/sense-installer/pkg/api"
"github.com/ttacon/chalk"
@@ -149,7 +152,6 @@ func (q *Qliksense) SetConfigs(args []string) error {
func retrieveCurrentContextInfo(q *Qliksense) (*api.QliksenseCR, string, error) {
var qliksenseConfig api.QliksenseConfig
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
if err := api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile); err != nil {
log.Println(err)
return nil, "", err
@@ -225,8 +227,42 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
}
qliksenseCR.Spec.RotateKeys = rotateKeys
api.LogDebugMessage("Current rotateKeys after modification: %s ", qliksenseCR.Spec.RotateKeys)
case "gitops.enabled":
if qliksenseCR.Spec.GitOps == nil {
qliksenseCR.Spec.GitOps = &config.GitOps{}
}
if strings.EqualFold(argsString[1], "yes") || strings.EqualFold(argsString[1], "no") {
qliksenseCR.Spec.GitOps.Enabled = argsString[1]
api.LogDebugMessage("Current gitOps enabled status : %s ", qliksenseCR.Spec.GitOps.Enabled)
} else {
err := fmt.Errorf("Please use yes or no")
log.Println(err)
return err
}
case "gitops.schedule":
if qliksenseCR.Spec.GitOps == nil {
qliksenseCR.Spec.GitOps = &config.GitOps{}
}
if _, err := cron.ParseStandard(argsString[1]); err != nil {
err := fmt.Errorf("Please enter string with standard cron scheduling syntax ")
return err
}
qliksenseCR.Spec.GitOps.Schedule = argsString[1]
api.LogDebugMessage("Current gitOps schedule is : %s ", qliksenseCR.Spec.GitOps.Schedule)
case "gitops.watchbranch":
if qliksenseCR.Spec.GitOps == nil {
qliksenseCR.Spec.GitOps = &config.GitOps{}
}
qliksenseCR.Spec.GitOps.WatchBranch = argsString[1]
api.LogDebugMessage("Current gitOps watchbranch is : %s ", qliksenseCR.Spec.GitOps.WatchBranch)
case "gitops.image":
if qliksenseCR.Spec.GitOps == nil {
qliksenseCR.Spec.GitOps = &config.GitOps{}
}
qliksenseCR.Spec.GitOps.Image = argsString[1]
api.LogDebugMessage("Current gitOps image is : %s ", qliksenseCR.Spec.GitOps.Image)
default:
err := fmt.Errorf("Please enter one of: profile, storageClassName,rotateKeys, manifestRoot or git.repository arguments to configure the current context")
err := fmt.Errorf("Please enter one of: profile, storageClassName,rotateKeys, manifestRoot, git.repository or gitops arguments to configure the current context")
log.Println(err)
return err
}
@@ -240,7 +276,7 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
// SetContextConfig - set the context for qliksense kubernetes resources to live in
func (q *Qliksense) SetContextConfig(args []string) error {
if len(args) == 1 {
err := q.SetUpQliksenseContext(args[0], false)
err := q.SetUpQliksenseContext(args[0])
if err != nil {
return err
}
@@ -265,7 +301,7 @@ func (q *Qliksense) ListContextConfigs() error {
w.Flush()
if len(qliksenseConfig.Spec.Contexts) > 0 {
for _, cont := range qliksenseConfig.Spec.Contexts {
fmt.Fprintln(w, cont.Name, "\t", cont.CrFile, "\t")
fmt.Fprintln(w, cont.Name, "\t", qliksenseConfig.GetCRFilePath(cont.Name), "\t")
}
w.Flush()
fmt.Fprintln(out, "")
@@ -276,7 +312,7 @@ func (q *Qliksense) ListContextConfigs() error {
return nil
}
func (q *Qliksense) DeleteContextConfig(args []string) error {
func (q *Qliksense) DeleteContextConfig(args []string, flag bool) error {
if len(args) == 1 {
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
var qliksenseConfig api.QliksenseConfig
@@ -318,9 +354,17 @@ func (q *Qliksense) DeleteContextConfig(args []string) error {
}
newLength := len(qliksenseConfig.Spec.Contexts)
if currentLength != newLength {
api.WriteToFile(&qliksenseConfig, qliksenseConfigFile)
fmt.Fprintln(out, chalk.Yellow.Color(chalk.Underline.TextStyle("Warning: Active resources may still be running in-cluster")))
fmt.Fprintln(out, chalk.Green.Color("Successfully deleted context: "), chalk.Bold.TextStyle(args[0]))
ans := flag
if ans == false {
ans = AskForConfirmation("Are You Sure? ")
}
if ans == true {
api.WriteToFile(&qliksenseConfig, qliksenseConfigFile)
fmt.Fprintln(out, chalk.Yellow.Color(chalk.Underline.TextStyle("Warning: Active resources may still be running in-cluster")))
fmt.Fprintln(out, chalk.Green.Color("Successfully deleted context: "), chalk.Bold.TextStyle(args[0]))
} else {
return nil
}
} else {
err := fmt.Errorf(chalk.Red.Color("Context not found"))
return err
@@ -338,11 +382,11 @@ func (q *Qliksense) DeleteContextConfig(args []string) error {
// SetUpQliksenseDefaultContext - to setup dir structure for default qliksense context
func (q *Qliksense) SetUpQliksenseDefaultContext() error {
return q.SetUpQliksenseContext(DefaultQliksenseContext, true)
return q.SetUpQliksenseContext(DefaultQliksenseContext)
}
// SetUpQliksenseContext - to setup qliksense context
func (q *Qliksense) SetUpQliksenseContext(contextName string, isDefaultContext bool) error {
func (q *Qliksense) SetUpQliksenseContext(contextName string) error {
if contextName == "" {
err := fmt.Errorf("Please enter a non-empty context-name")
log.Println(err)
@@ -356,83 +400,29 @@ func (q *Qliksense) SetUpQliksenseContext(contextName string, isDefaultContext b
}
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
var qliksenseConfig api.QliksenseConfig
configFileTrack := false
qliksenseConfig := api.NewQConfigEmpty(q.QliksenseHome)
if !api.FileExists(qliksenseConfigFile) {
qliksenseConfig.AddBaseQliksenseConfigs(contextName)
} else {
if err := api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile); err != nil {
log.Println(err)
return err
}
if isDefaultContext { // if config file exits but a default context is requested, we want to prevent writing to config file
configFileTrack = true
}
}
// creating a file in the name of the context if it does not exist/ opening it to append/modify content if it already exists
qliksenseContextsDir1 := filepath.Join(q.QliksenseHome, QliksenseContextsDir)
if !api.DirExists(qliksenseContextsDir1) {
if err := os.Mkdir(qliksenseContextsDir1, os.ModePerm); err != nil {
err = fmt.Errorf("Not able to create %s dir: %v", qliksenseContextsDir1, err)
if err := api.ReadFromFile(qliksenseConfig, qliksenseConfigFile); err != nil {
log.Println(err)
return err
}
}
api.LogDebugMessage("%s exists", qliksenseContextsDir1)
// creating contexts/qlik-default/qlik-default.yaml file
qliksenseContextFile := filepath.Join(qliksenseContextsDir1, contextName, contextName+".yaml")
//var qliksenseCR api.QliksenseCR
defaultContextsDir := filepath.Join(qliksenseContextsDir1, contextName)
if !api.DirExists(defaultContextsDir) {
if err := os.Mkdir(defaultContextsDir, os.ModePerm); err != nil {
err = fmt.Errorf("Not able to create %s: %v", defaultContextsDir, err)
log.Println(err)
return err
}
}
api.LogDebugMessage("%s exists", defaultContextsDir)
if !api.FileExists(qliksenseContextFile) {
qliksenseCR := &api.QliksenseCR{}
qliksenseCR.AddCommonConfig(contextName)
api.WriteToFile(&qliksenseCR, qliksenseContextFile)
api.LogDebugMessage("Added Context: %s", contextName)
}
// else {
// if err := api.ReadFromFile(&qliksenseCR, qliksenseContextFile); err != nil {
// log.Println(err)
// return err
// }
// }
//api.WriteToFile(&qliksenseCR, qliksenseContextFile)
ctxTrack := false
if len(qliksenseConfig.Spec.Contexts) > 0 {
for _, ctx := range qliksenseConfig.Spec.Contexts {
if ctx.Name == contextName {
ctx.CrFile = qliksenseContextFile
ctxTrack = true
break
}
}
}
if !ctxTrack {
qliksenseConfig.Spec.Contexts = append(qliksenseConfig.Spec.Contexts, api.Context{
Name: contextName,
CrFile: qliksenseContextFile,
})
if qliksenseConfig.IsContextExist(contextName) {
return nil
}
qliksenseCR := &api.QliksenseCR{}
qliksenseCR.AddCommonConfig(contextName)
qliksenseConfig.Spec.CurrentContext = contextName
if !configFileTrack {
api.WriteToFile(&qliksenseConfig, qliksenseConfigFile)
if err := qliksenseConfig.CreateOrWriteCrAndContext(qliksenseCR); err != nil {
return err
}
// set the encrypted default mongo
q.SetSecrets([]string{`qliksense.mongoDbUri="mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"`}, false)
return nil
// set the encrypted default mongo
return q.SetSecrets([]string{`qliksense.mongoDbUri="mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"`}, false)
}
func validateInput(input string) (string, error) {
@@ -543,3 +533,16 @@ func (q *Qliksense) SetImageRegistry(registry, pushUsername, pushPassword, pullU
qliksenseCR.Spec.AddToConfigs("qliksense", imageRegistryConfigKey, registry)
return api.WriteToFile(&qliksenseCR, qliksenseContextsFile)
}
func (q *Qliksense) SetEulaAccepted() error {
qConfig := api.NewQConfig(q.QliksenseHome)
qcr, err := qConfig.GetCurrentCR()
if err != nil {
return err
}
if !qcr.IsEULA() {
qcr.SetEULA("yes")
return qConfig.WriteCurrentContextCR(qcr)
}
return nil
}

View File

@@ -152,6 +152,7 @@ func removePrivateKey() {
func setup() func() {
// create tests dir
os.RemoveAll(testDir)
if err := os.Mkdir(testDir, 0777); err != nil {
log.Printf("\nError occurred: %v", err)
}
@@ -164,7 +165,7 @@ metadata:
spec:
contexts:
- name: qlik-default
crFile: /root/.qliksense/contexts/qlik-default.yaml
crFile: contexts/qlik-default/qlik-default.yaml
currentContext: qlik-default
`
configFile := filepath.Join(testDir, "config.yaml")
@@ -274,7 +275,7 @@ func TestSetUpQliksenseContext(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
q := New(tt.args.qlikSenseHome)
if err := q.SetUpQliksenseContext(tt.args.contextName, tt.args.isDefaultContext); (err != nil) != tt.wantErr {
if err := q.SetUpQliksenseContext(tt.args.contextName); (err != nil) != tt.wantErr {
t.Errorf("SetUpQliksenseContext() error = %v, wantErr %v", err, tt.wantErr)
}
})
@@ -326,7 +327,7 @@ func TestSetOtherConfigs(t *testing.T) {
q: &Qliksense{
QliksenseHome: testDir,
},
args: []string{"profile=minikube", "rotateKeys=yes", "storageClassName=efs"},
args: []string{"profile=minikube", "rotateKeys=yes", "storageClassName=efs", "gitops.enabled=yes", "gitops.schedule=30 * * * *"},
},
wantErr: false,
},
@@ -336,7 +337,7 @@ func TestSetOtherConfigs(t *testing.T) {
q: &Qliksense{
QliksenseHome: testDir,
},
args: []string{"someconfig=somevalue"},
args: []string{"someconfig=somevalue, gitops.schedule=bar", "gitops.enabled=bar"},
},
wantErr: true,
},
@@ -396,7 +397,7 @@ func TestSetConfigs(t *testing.T) {
func TestSetImageRegistry(t *testing.T) {
getQlikSense := func(tmpQlikSenseHome string) (*Qliksense, error) {
if err := ioutil.WriteFile(path.Join(tmpQlikSenseHome, "config.yaml"), []byte(fmt.Sprintf(`
if err := ioutil.WriteFile(path.Join(tmpQlikSenseHome, "config.yaml"), []byte(`
apiVersion: config.qlik.com/v1
kind: QliksenseConfig
metadata:
@@ -404,9 +405,9 @@ metadata:
spec:
contexts:
- name: qlik-default
crFile: %s/contexts/qlik-default/qlik-default.yaml
crFile: contexts/qlik-default/qlik-default.yaml
currentContext: qlik-default
`, tmpQlikSenseHome)), os.ModePerm); err != nil {
`), os.ModePerm); err != nil {
return nil, err
}
@@ -798,11 +799,11 @@ metadata:
spec:
contexts:
- name: qlik-default
crFile: /root/.qliksense/contexts/qlik-default.yaml
crFile: contexts/qlik-default.yaml
- name: qlik1
crFile: /root/.qliksense/contexts/qlik1.yaml
crFile: contexts/qlik1.yaml
- name: qlik2
crFile: /root/.qliksense/contexts/qlik2.yaml
crFile: contexts/qlik2.yaml
currentContext: qlik1
`
configFile := filepath.Join(testDir, "config.yaml")
@@ -927,9 +928,10 @@ func TestDeleteContexts(t *testing.T) {
q := New(tt.args.qlikSenseHome)
var arg []string
arg = append(arg, tt.args.contextName)
if err := q.DeleteContextConfig(arg); (err != nil) != tt.wantErr {
if err := q.DeleteContextConfig(arg, true); (err != nil) != tt.wantErr {
t.Errorf("DeleteContext() error = %v, wantErr %v", err, tt.wantErr)
}
})
}

View File

@@ -171,7 +171,7 @@ func Test_Pull_Push_ImagesForCurrentCR(t *testing.T) {
}
func setupQlikSenseHome(t *testing.T, tmpQlikSenseHome string, registry *testRegistryV2, clientAuth clientAuthType) error {
if err := ioutil.WriteFile(path.Join(tmpQlikSenseHome, "config.yaml"), []byte(fmt.Sprintf(`
if err := ioutil.WriteFile(path.Join(tmpQlikSenseHome, "config.yaml"), []byte(`
apiVersion: config.qlik.com/v1
kind: QliksenseConfig
metadata:
@@ -179,9 +179,9 @@ metadata:
spec:
contexts:
- name: qlik-default
crFile: %s/contexts/qlik-default/qlik-default.yaml
crFile: contexts/qlik-default/qlik-default.yaml
currentContext: qlik-default
`, tmpQlikSenseHome)), os.ModePerm); err != nil {
`), os.ModePerm); err != nil {
return err
}

View File

@@ -0,0 +1,35 @@
package qliksense
import (
"io/ioutil"
"path/filepath"
"testing"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func TestFetchAndUpdateCR(t *testing.T) {
tempHome, _ := ioutil.TempDir("", "")
q := &Qliksense{
QliksenseHome: tempHome,
}
q.SetUpQliksenseContext("test1")
qConfig := qapi.NewQConfig(tempHome)
if err := fetchAndUpdateCR(qConfig, "v0.0.2"); err != nil {
t.Log(err)
t.FailNow()
}
actualCrFile := filepath.Join(tempHome, "contexts", "test1", "test1.yaml")
cr := &qapi.QliksenseCR{}
if err := qapi.ReadFromFile(cr, actualCrFile); err != nil {
t.Log(err)
t.FailNow()
}
if cr.Spec.ManifestsRoot != "contexts/test1/qlik-k8s/v0.0.2" {
t.Log("actual path: " + cr.Spec.ManifestsRoot + ", expected path: contexts/test1/qlik-k8s/v0.0.2")
t.FailNow()
}
}

View File

@@ -0,0 +1,88 @@
package qliksense
import (
"errors"
"fmt"
"github.com/qlik-oss/k-apis/pkg/git"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
type LsRemoteCmdOptions struct {
IncludeBranches bool
Limit int
}
func (q *Qliksense) GetInstallableVersions(opts *LsRemoteCmdOptions) error {
qConfig := qapi.NewQConfig(q.QliksenseHome)
qcr, err := qConfig.GetCurrentCR()
if err != nil {
return err
}
var repoPath string
if qcr.Spec.GetManifestsRoot() != "" {
repoPath = qcr.Spec.GetManifestsRoot()
} else {
repoPath, err = downloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master")
if err != nil {
return err
}
}
r, err := git.OpenRepository(repoPath)
if err != nil {
return err
}
remoteRefsList, err := git.GetRemoteRefs(r, nil,
&git.RemoteRefConstraints{
Include: true,
Sort: true,
SortOrder: git.RefSortOrderDescending,
},
&git.RemoteRefConstraints{
Include: opts.IncludeBranches,
Sort: true,
SortOrder: git.RefSortOrderAscending,
})
if err != nil {
return err
}
if len(remoteRefsList) < 1 {
return errors.New("cannot find git remote information in the config repository")
}
var originRemoteRefs *git.RemoteRefs
for _, remoteRefs := range remoteRefsList {
if remoteRefs.Name == "origin" {
originRemoteRefs = remoteRefs
break
}
}
if originRemoteRefs == nil {
return errors.New(`cannot find git remote called "origin" in the config repository`)
}
tags := originRemoteRefs.Tags
if len(tags) > opts.Limit {
tags = tags[:opts.Limit]
}
fmt.Print("Versions:\n")
for _, tag := range tags {
fmt.Printf(" %s\n", tag)
}
if opts.IncludeBranches {
branches := originRemoteRefs.Branches
if len(branches) > opts.Limit {
branches = branches[:opts.Limit]
}
fmt.Print("Branches:\n")
for _, branch := range branches {
fmt.Printf(" %s\n", branch)
}
}
return nil
}

View File

@@ -12,7 +12,6 @@ import (
)
type InstallCommandOptions struct {
AcceptEULA string
StorageClass string
MongoDbUri string
RotateKeys string
@@ -41,9 +40,7 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, kee
return err
}
if opts.AcceptEULA != "" {
qcr.Spec.AddToConfigs("qliksense", "acceptEULA", opts.AcceptEULA)
}
qcr.SetEULA("yes")
if opts.MongoDbUri != "" {
qcr.Spec.AddToSecrets("qliksense", "mongoDbUri", opts.MongoDbUri, "")
}

View File

@@ -0,0 +1,65 @@
package qliksense
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func TestCreateK8sResoruceBeforePatch(t *testing.T) {
td := setup()
sampleCr := `
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: qlik-test3
labels:
version: v0.0.2
spec:
git:
repository: https://github.com/ffoysal/qliksense-k8s
accessToken: abababababababaab
userName: "blblbl"
gitOps:
enabled: "no"
schedule: "*/1 * * * *"
watchBranch: pr-branch-db1d26d6
image: qlik-docker-oss.bintray.io/qliksense-repo-watcher
configs:
qliksense:
- name: acceptEULA
value: "yes"
secrets:
qliksense:
- name: mongoDbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
profile: docker-desktop
rotateKeys: "yes"`
crFile := filepath.Join(testDir, "install_test.yaml")
ioutil.WriteFile(crFile, []byte(sampleCr), 0644)
q := New(testDir)
file, e := os.Open(crFile)
if e != nil {
t.Log(e)
t.FailNow()
}
if err := q.LoadCr(file, false); err != nil {
t.Log(err)
t.FailNow()
}
qConfig := qapi.NewQConfig(testDir)
cr, err := qConfig.GetCR("qlik-test3")
if err != nil {
t.Log(err)
t.FailNow()
}
if err = q.createK8sResoruceBeforePatch(cr); err != nil {
t.Log(err)
t.FailNow()
}
td()
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/qlik-oss/sense-installer/pkg/api"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // don't delete this line ref: https://github.com/kubernetes/client-go/issues/242
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/krusty"

81
pkg/qliksense/load_cr.go Normal file
View File

@@ -0,0 +1,81 @@
package qliksense
import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func (q *Qliksense) LoadCr(reader io.Reader, overwriteExistingContext bool) error {
if crBytes, err := ioutil.ReadAll(reader); err != nil {
return err
} else if crName, err := q.loadCrStringIntoFileSystem(string(crBytes), overwriteExistingContext); err != nil {
return err
} else {
fmt.Println("cr name: [ " + crName + " ] has been loaded")
}
return nil
}
func (q *Qliksense) IsEulaAcceptedInCrFile(reader io.Reader) (bool, error) {
if crBytes, err := ioutil.ReadAll(reader); err != nil {
return false, err
} else if cr, err := qapi.CreateCRObjectFromString(string(crBytes)); err != nil {
return false, err
} else {
return cr.IsEULA(), nil
}
}
func (q *Qliksense) loadCrStringIntoFileSystem(crstr string, overwriteExistingContext bool) (string, error) {
cr, err := qapi.CreateCRObjectFromString(crstr)
if err != nil {
return "", err
}
qConfig := qapi.NewQConfig(q.QliksenseHome)
if qConfig.IsContextExist(cr.GetName()) {
if !overwriteExistingContext {
return "", errors.New("Context with name: " + cr.GetName() + " already exists. " +
"Please delete the existing context first using the delete-context command or specify the --overwrite flag.")
} else if err := os.RemoveAll(qConfig.GetContextPath(cr.GetName())); err != nil {
return "", err
}
}
if err := qConfig.CreateContextDirs(cr.GetName()); err != nil {
return "", err
}
// encrypt the secrets and do base64 then update the CR
rsaPublicKey, _, err := qConfig.GetContextEncryptionKeyPair(cr.GetName())
if err != nil {
return "", err
}
for svc, nvs := range cr.Spec.Secrets {
for _, nv := range nvs {
if nv.ValueFrom == nil {
skv := &qapi.ServiceKeyValue{
Key: nv.Name,
Value: nv.Value,
SvcName: svc,
}
if err := q.processSecret(skv, rsaPublicKey, cr, false); err != nil {
return cr.GetName(), err
}
}
}
}
// write to disk
if err = qConfig.CreateOrWriteCrAndContext(cr); err != nil {
return "", err
}
qConfig.SetCurrentContextName(cr.GetName())
qConfig.Write()
return cr.GetName(), nil
}

View File

@@ -0,0 +1,140 @@
package qliksense
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func TestLoadCrFile(t *testing.T) {
td := setup()
setup()
sampleCr1 := `
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: qlik-test
labels:
version: v0.0.2
spec:
git:
repository: https://github.com/ffoysal/qliksense-k8s
accessToken: abababababababaab
userName: "blblbl"
gitOps:
enabled: "no"
schedule: "*/1 * * * *"
watchBranch: pr-branch-db1d26d6
image: qlik-docker-oss.bintray.io/qliksense-repo-watcher
configs:
qliksense:
- name: acceptEULA
value: "yes"
secrets:
qliksense:
- name: mongoDbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
profile: docker-desktop
rotateKeys: "yes"`
sampleCr2 := `
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: qlik-test3
labels:
version: v0.0.2
spec:
git:
repository: https://github.com/ffoysal/qliksense-k8s
accessToken: abababababababaab
userName: "blblbl"
gitOps:
enabled: "no"
schedule: "*/1 * * * *"
watchBranch: pr-branch-db1d26d6
image: qlik-docker-oss.bintray.io/qliksense-repo-watcher
configs:
qliksense:
- name: acceptEULA
value: "yes"
secrets:
qliksense:
- name: mongoDbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
profile: docker-desktop
rotateKeys: "yes"`
duplicateCr := `
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: qlik-default
labels:
version: v0.0.2
spec:
git:
repository: https://github.com/ffoysal/qliksense-k8s
accessToken: abababababababaab
userName: "blblbl"`
crFile1 := filepath.Join(testDir, "testcr1.yaml")
ioutil.WriteFile(crFile1, []byte(sampleCr1), 0644)
crFile2 := filepath.Join(testDir, "testcr2.yaml")
ioutil.WriteFile(crFile2, []byte(sampleCr2), 0644)
dupCrFile := filepath.Join(testDir, "dupcr.yaml")
ioutil.WriteFile(dupCrFile, []byte(duplicateCr), 0644)
q := New(testDir)
file1, e := os.Open(crFile1)
if e != nil {
t.Log(e)
t.FailNow()
}
if err := q.LoadCr(file1, false); err != nil {
t.Log(err)
t.FailNow()
}
file2, e := os.Open(crFile2)
if e != nil {
t.Log(e)
t.FailNow()
}
if err := q.LoadCr(file2, false); err != nil {
t.Log(err)
t.FailNow()
}
qConfig := qapi.NewQConfig(testDir)
cr, err := qConfig.GetCR("qlik-test")
if err != nil {
t.Log(err)
t.FailNow()
}
if cr.GetName() != "qlik-test" {
t.FailNow()
}
cr, err = qConfig.GetCR("qlik-test3")
if err != nil {
t.Log(err)
t.FailNow()
}
if cr.GetName() != "qlik-test3" {
t.FailNow()
}
if qConfig.Spec.CurrentContext != "qlik-test3" {
t.FailNow()
}
file, e := os.Open(dupCrFile)
if e != nil {
t.Log(e)
t.FailNow()
}
if err := q.LoadCr(file, false); err == nil {
t.FailNow()
}
td()
}