Compare commits

...

35 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
Ilir Bekteshi
2bc65f0bad Remove space in zip path 2020-03-20 11:39:27 +01: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
38 changed files with 1450 additions and 401 deletions

View File

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

View File

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

@@ -115,15 +115,18 @@ func deleteContextConfigCmd(q *qliksense.Qliksense) *cobra.Command {
var ( var (
cmd *cobra.Command cmd *cobra.Command
) )
skipConfirmation := false
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "delete-context", Use: "delete-context",
Short: "deletes a specific context locally (not in-cluster)", Short: "deletes a specific context locally (not in-cluster)",
Example: `qliksense config delete-contexts <context_name>`, Example: `qliksense config delete-contexts <context_name>`,
RunE: func(cmd *cobra.Command, args []string) error { 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 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

@@ -14,18 +14,19 @@ func installCmd(q *qliksense.Qliksense) *cobra.Command {
Long: `install a qliksense release`, Long: `install a qliksense release`,
Example: `qliksense install <version> #if no version provides, expect manifestsRoot is set somewhere in the file system`, 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 { RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 { version := ""
return q.InstallQK8s("", opts, keepPatchFiles) if len(args) != 0 {
version = args[0]
} }
return q.InstallQK8s(args[0], opts, keepPatchFiles) return q.InstallQK8s(version, opts, keepPatchFiles)
}, },
} }
f := c.Flags() 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.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.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.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) f.BoolVar(&keepPatchFiles, keepPatchFilesFlagName, keepPatchFiles, keepPatchFilesFlagUsage)
return c 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

@@ -4,21 +4,20 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/qlik-oss/sense-installer/pkg/preflight"
"github.com/qlik-oss/sense-installer/pkg/qliksense" "github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func preflightCmd(q *qliksense.Qliksense) *cobra.Command { func preflightCmd(q *qliksense.Qliksense) *cobra.Command {
var configCmd = &cobra.Command{ var preflightCmd = &cobra.Command{
Use: "preflight", Use: "preflight",
Short: "perform preflight checks on the cluster", Short: "perform preflight checks on the cluster",
Long: `perform preflight checks on the cluster`, Long: `perform preflight checks on the cluster`,
Example: `qliksense preflight <preflight_check_to_run> Example: `qliksense preflight <preflight_check_to_run>`,
Usage:
qliksense preflight dns
`,
} }
return configCmd return preflightCmd
} }
func preflightCheckDnsCmd(q *qliksense.Qliksense) *cobra.Command { func preflightCheckDnsCmd(q *qliksense.Qliksense) *cobra.Command {
@@ -28,14 +27,55 @@ func preflightCheckDnsCmd(q *qliksense.Qliksense) *cobra.Command {
Long: `perform preflight dns check to check DNS connectivity status in the cluster`, Long: `perform preflight dns check to check DNS connectivity status in the cluster`,
Example: `qliksense preflight dns`, Example: `qliksense preflight dns`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
err := q.DownloadPreflight() qp := &preflight.QliksensePreflight{Q: q}
err := qp.DownloadPreflight()
if err != nil { if err != nil {
err = fmt.Errorf("There has been an error downloading preflight: %+v", err) err = fmt.Errorf("There has been an error downloading preflight: %+v", err)
log.Println(err) log.Println(err)
return err return err
} }
return q.CheckDns() return qp.CheckDns()
}, },
} }
return preflightDnsCmd 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

@@ -42,7 +42,6 @@ func initAndExecute() error {
api.LogDebugMessage("QliksenseHomeDir: %s", qlikSenseHome) api.LogDebugMessage("QliksenseHomeDir: %s", qlikSenseHome)
qliksenseClient := qliksense.New(qlikSenseHome) qliksenseClient := qliksense.New(qlikSenseHome)
qliksenseClient.SetUpQliksenseDefaultContext()
cmd := rootCmd(qliksenseClient) cmd := rootCmd(qliksenseClient)
if err := cmd.Execute(); err != nil { if err := cmd.Execute(); err != nil {
//levenstein checks (auto-suggestions) //levenstein checks (auto-suggestions)
@@ -50,7 +49,7 @@ func initAndExecute() error {
return err return err
} }
return nil return nil
} }
func setUpPaths() (string, error) { func setUpPaths() (string, error) {
@@ -79,30 +78,59 @@ func setUpPaths() (string, error) {
var versionCmd = &cobra.Command{ var versionCmd = &cobra.Command{
Use: "version", Use: "version",
Short: "Print the version number of qliksense cli", 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) { Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("%s (%s, %s)\n", pkg.Version, pkg.Commit, pkg.CommitDate) fmt.Printf("%s (%s, %s)\n", pkg.Version, pkg.Commit, pkg.CommitDate)
}, },
} }
func rootCmd(p *qliksense.Qliksense) *cobra.Command { func commandUsesContext(commandName string) bool {
var ( return commandName != "" && commandName != "qliksense" && commandName != "help" && commandName != "version"
cmd *cobra.Command }
)
cmd = &cobra.Command{ func getRootCmd(p *qliksense.Qliksense) *cobra.Command {
cmd := &cobra.Command{
Use: "qliksense", Use: "qliksense",
Short: "Qliksense cli tool", Short: "Qliksense cli tool",
Long: `qliksense cli tool provides functionality to perform operations on qliksense-k8s, qliksense operator, and kubernetes cluster`, Long: `qliksense cli tool provides functionality to perform operations on qliksense-k8s, qliksense operator, and kubernetes cluster`,
Args: cobra.ArbitraryArgs, 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) 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) cobra.OnInitialize(initConfig)
// For qliksense overrides/commands
cmd.AddCommand(getInstallableVersionsCmd(p)) cmd.AddCommand(getInstallableVersionsCmd(p))
cmd.AddCommand(pullQliksenseImages(p)) cmd.AddCommand(pullQliksenseImages(p))
cmd.AddCommand(pushQliksenseImages(p)) cmd.AddCommand(pushQliksenseImages(p))
@@ -168,19 +196,17 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
// add preflight command // add preflight command
preflightCmd := preflightCmd(p) preflightCmd := preflightCmd(p)
preflightCmd.AddCommand(preflightCheckDnsCmd(p)) preflightCmd.AddCommand(preflightCheckDnsCmd(p))
preflightCmd.AddCommand(preflightCheckK8sVersionCmd(p))
preflightCmd.AddCommand(preflightAllChecksCmd(p))
//preflightCmd.AddCommand(preflightCheckMongoCmd(p)) //preflightCmd.AddCommand(preflightCheckMongoCmd(p))
//preflightCmd.AddCommand(preflightCheckAllCmd(p)) //preflightCmd.AddCommand(preflightCheckAllCmd(p))
cmd.AddCommand(preflightCmd) cmd.AddCommand(preflightCmd)
cmd.AddCommand(loadCrFile(p))
cmd.AddCommand((applyCmd(p)))
return cmd return cmd
} }
func initConfig() {
viper.SetEnvPrefix("QLIKSENSE")
viper.AutomaticEnv()
}
func copy(src, dst string) (int64, error) { func copy(src, dst string) (int64, error) {
var ( var (
source, destination *os.File source, destination *os.File

View File

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

@@ -76,21 +76,22 @@ When you perform `qliksense install` or `qliksene config apply`, qliksense opera
- Push generated patches into a new branch in the provided git repo. _Gives you ability to merge patches into your master branch_ - 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_ - 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_
## Enable GitOps ## GitOps
to enable gitops the following section should be in the CR To enable gitops, the following section should be in the CR
```yaml ```yaml
.... ....
spec: spec:
git: git:
repository: https://github.com/ffoysal/qliksense-k8s repository: https://github.com/<OWNER>/<REPO>
accessToken: git-token accessToken: "<git-token>"
userName: git-username userName: "<git-username>"
gitOps: gitOps:
enabled: "yes" enabled: "yes"
schedule: "*/5 * * * *" schedule: "*/5 * * * *"
watchBranch: pr-branch-24868a33 watchBranch: <myBranch>
image: qlik-docker-oss.bintray.io/qliksense-repo-watcher 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
```

1
go.mod
View File

@@ -37,6 +37,7 @@ require (
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/mattn/go-colorable v0.1.4 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/mitchellh/go-homedir v1.1.0
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/pkg/errors v0.8.1 github.com/pkg/errors v0.8.1

4
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 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.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.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 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI=
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= 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/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 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=

View File

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

View File

@@ -27,30 +27,39 @@ const (
// NewQConfig create QliksenseConfig object from file ~/.qliksense/config.yaml // NewQConfig create QliksenseConfig object from file ~/.qliksense/config.yaml
func NewQConfig(qsHome string) *QliksenseConfig { 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") configFile := filepath.Join(qsHome, "config.yaml")
qc := &QliksenseConfig{} qc := &QliksenseConfig{}
err := ReadFromFile(qc, configFile) err := ReadFromFile(qc, configFile)
if err != nil { if err != nil {
fmt.Println("yaml unmarshalling error ", err) return nil, err
os.Exit(1)
} }
qc.QliksenseHomePath = qsHome 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 // GetCR create a QliksenseCR object for a particular context
// from file ~/.qliksense/contexts/<contx-name>/<contx-name>.yaml // from file ~/.qliksense/contexts/<contx-name>/<contx-name>.yaml
func (qc *QliksenseConfig) GetCR(contextName string) (*QliksenseCR, error) { func (qc *QliksenseConfig) GetCR(contextName string) (*QliksenseCR, error) {
crFilePath := qc.getCRFilePath(contextName) crFilePath := qc.GetCRFilePath(contextName)
if crFilePath == "" { if crFilePath == "" {
return nil, errors.New("context name " + contextName + " not found") return nil, errors.New("context name " + contextName + " not found")
} }
return getCRObject(crFilePath) return qc.GetAndTransformCrObject(crFilePath)
}
func getUnencryptedCR() {
} }
// GetCurrentCR create a QliksenseCR object for current context // 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 // 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{} tempQc := &QliksenseConfig{}
copier.Copy(tempQc, qc) copier.Copy(tempQc, qc)
found := false found := false
tempQc.Spec.Contexts = []Context{} tempQc.Spec.Contexts = []Context{}
for _, c := range qc.Spec.Contexts { for _, c := range qc.Spec.Contexts {
if c.Name == contextName { if c.Name == contextName {
c.CrFile = filepath c.CrFile = filePath
found = true found = true
} }
tempQc.Spec.Contexts = append(tempQc.Spec.Contexts, []Context{c}...) 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") 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{} cr := &QliksenseCR{}
err := ReadFromFile(cr, crfile) err := ReadFromFile(cr, crfile)
if err != nil { if err != nil {
@@ -88,11 +98,36 @@ func getCRObject(crfile string) (*QliksenseCR, error) {
return cr, nil 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 := "" crFilePath := ""
for _, ctx := range qc.Spec.Contexts { for _, ctx := range qc.Spec.Contexts {
if ctx.Name == contextName { if ctx.Name == contextName {
crFilePath = ctx.CrFile crFilePath = filepath.Join(qc.QliksenseHomePath, ctx.CrFile)
break break
} }
} }
@@ -117,7 +152,7 @@ func (qc *QliksenseConfig) BuildRepoPath(version string) string {
} }
func (qc *QliksenseConfig) BuildRepoPathForContext(contextName, 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 { 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 { func (qc *QliksenseConfig) WriteCR(cr *QliksenseCR, contextName string) error {
crf := qc.getCRFilePath(contextName) crf := qc.GetCRFilePath(contextName)
if crf == "" { if crf == "" {
return errors.New("context name " + contextName + " not found") 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 { func (qc *QliksenseConfig) WriteCurrentContextCR(cr *QliksenseCR) error {
return qc.WriteCR(cr, qc.Spec.CurrentContext) return qc.WriteCR(cr, qc.Spec.CurrentContext)
} }
@@ -222,6 +303,15 @@ func (qc *QliksenseConfig) getDockerConfigJsonSecret(name string) (*DockerConfig
} }
func (qc *QliksenseConfig) getCurrentContextEncryptionKeyPairLocation() (string, error) { 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 // Check env var: QLIKSENSE_KEY_LOCATION to determine location to store keypair
var secretKeyPairLocation string var secretKeyPairLocation string
if os.Getenv("QLIKSENSE_KEY_LOCATION") != "" { if os.Getenv("QLIKSENSE_KEY_LOCATION") != "" {
@@ -230,13 +320,9 @@ func (qc *QliksenseConfig) getCurrentContextEncryptionKeyPairLocation() (string,
} else { } else {
// QLIKSENSE_KEY_LOCATION has not been set, hence storing key pair in default location: // QLIKSENSE_KEY_LOCATION has not been set, hence storing key pair in default location:
// /.qliksense/secrets/contexts/<current-context>/secrets/ // /.qliksense/secrets/contexts/<current-context>/secrets/
if qcr, err := qc.GetCurrentCR(); err != nil { secretKeyPairLocation = filepath.Join(qc.QliksenseHomePath, qliksenseSecretsDirName, qliksenseContextsDirName, contextName, qliksenseSecretsDirName)
return "", err
} else {
secretKeyPairLocation = filepath.Join(qc.QliksenseHomePath, qliksenseSecretsDirName, qliksenseContextsDirName, qcr.GetObjectMeta().GetName(), qliksenseSecretsDirName)
}
} }
LogDebugMessage("SecretKeyLocation to store key pair: %s", secretKeyPairLocation)
return secretKeyPairLocation, nil return secretKeyPairLocation, nil
} }
@@ -253,7 +339,15 @@ func (qc *QliksenseConfig) GetCurrentContextEjsonKeyDir() (string, error) {
} }
func (qc *QliksenseConfig) GetCurrentContextEncryptionKeyPair() (*rsa.PublicKey, *rsa.PrivateKey, 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -342,6 +436,10 @@ func (cr *QliksenseCR) IsEULA() bool {
return false 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 // GetDecryptedCr it decrypts all the encrypted value and return a new CR
func (qc *QliksenseConfig) GetDecryptedCr(cr *QliksenseCR) (*QliksenseCR, error) { func (qc *QliksenseConfig) GetDecryptedCr(cr *QliksenseCR) (*QliksenseCR, error) {
newCr := &QliksenseCR{} newCr := &QliksenseCR{}
@@ -374,3 +472,45 @@ func (qc *QliksenseConfig) GetDecryptedCr(cr *QliksenseCR) (*QliksenseCR, error)
newCr.Spec.Secrets = finalSecrets newCr.Spec.Secrets = finalSecrets
return newCr, nil 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 // create CR
createCRFile(dir) createCRFile(dir)
crFile := filepath.Join(dir, "contexts", "contx1", "contx1.yaml") crFile := filepath.Join("contexts", "contx1", "contx1.yaml")
qct, e := qc.SetCrLocation("contx1", crFile) qct, e := qc.SetCrLocation("contx1", crFile)
if e != nil { if e != nil {
t.Fail() t.Fail()
@@ -100,7 +100,7 @@ func TestGetDecryptedCr(t *testing.T) {
// create CR // create CR
createCRFile(dir) createCRFile(dir)
crFile := filepath.Join(dir, "contexts", "contx1", "contx1.yaml") crFile := filepath.Join("contexts", "contx1", "contx1.yaml")
qct, e := qc.SetCrLocation("contx1", crFile) qct, e := qc.SetCrLocation("contx1", crFile)
if e != nil { if e != nil {
t.Fail() t.Fail()

View File

@@ -3,8 +3,10 @@ package api
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"log" "log"
"os"
"github.com/qlik-oss/k-apis/pkg/config" "github.com/qlik-oss/k-apis/pkg/config"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
@@ -103,14 +105,22 @@ func ReadFromFile(content interface{}, sourceFile string) error {
if content == nil || sourceFile == "" { if content == nil || sourceFile == "" {
return nil 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 { 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 return err
} }
// reading k8s style object // reading k8s style object
// https://stackoverflow.com/questions/44306554/how-to-deserialize-kubernetes-yaml-file // https://stackoverflow.com/questions/44306554/how-to-deserialize-kubernetes-yaml-file
dec := machine_yaml.NewYAMLOrJSONDecoder(bytes.NewReader(contents), 10000) dec := machine_yaml.NewYAMLOrJSONDecoder(bytes.NewReader(contents), 10000)
dec.Decode(content) return dec.Decode(content)
return nil
} }

View File

@@ -109,6 +109,7 @@ func KubectlDirectOps(opr []string, namespace string) error {
LogDebugMessage("Kubectl command: %s %v\n", "kubectl", arguments) LogDebugMessage("Kubectl command: %s %v\n", "kubectl", arguments)
sterrBuffer := &bytes.Buffer{} sterrBuffer := &bytes.Buffer{}
cmd.Stderr = sterrBuffer cmd.Stderr = sterrBuffer
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return fmt.Errorf("kubectl %v failed with: %v, %v\n", opr, err, sterrBuffer.String()) return fmt.Errorf("kubectl %v failed with: %v, %v\n", opr, err, sterrBuffer.String())
} }

View File

@@ -232,7 +232,6 @@ func UntarGzFile(destination, fileToUntar string) error {
fileAtLoc.Chmod(os.ModePerm) fileAtLoc.Chmod(os.ModePerm)
} }
} }
return nil
} }
func UnZipFile(destination, fileToUnzip string) error { func UnZipFile(destination, fileToUnzip string) error {

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

@@ -1,9 +1,8 @@
package qliksense package preflight
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"os" "os"
"os/exec" "os/exec"
@@ -11,11 +10,15 @@ import (
"runtime" "runtime"
"strings" "strings"
"text/template" "github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/qlik-oss/sense-installer/pkg/api" "github.com/qlik-oss/sense-installer/pkg/api"
) )
type QliksensePreflight struct {
Q *qliksense.Qliksense
}
const ( const (
// preflight releases have the same version // preflight releases have the same version
preflightRelease = "v0.9.26" preflightRelease = "v0.9.26"
@@ -23,45 +26,15 @@ const (
preflightMacFile = "preflight_darwin_amd64.tar.gz" preflightMacFile = "preflight_darwin_amd64.tar.gz"
preflightWindowsFile = "preflight_windows_amd64.zip" preflightWindowsFile = "preflight_windows_amd64.zip"
PreflightChecksDirName = "preflight_checks" PreflightChecksDirName = "preflight_checks"
preflightFileName = "preflight"
) )
var preflightBaseURL = fmt.Sprintf("https://github.com/replicatedhq/troubleshoot/releases/download/%s/", preflightRelease) var preflightBaseURL = fmt.Sprintf("https://github.com/replicatedhq/troubleshoot/releases/download/%s/", preflightRelease)
const dnsCheckYAML = ` func (qp *QliksensePreflight) DownloadPreflight() error {
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 (q *Qliksense) DownloadPreflight() error {
const preflightExecutable = "preflight" const preflightExecutable = "preflight"
preflightInstallDir := filepath.Join(q.QliksenseHome, PreflightChecksDirName) preflightInstallDir := filepath.Join(qp.Q.QliksenseHome, PreflightChecksDirName)
platform := runtime.GOOS platform := runtime.GOOS
exists, err := checkInstalled(preflightInstallDir, preflightExecutable) exists, err := checkInstalled(preflightInstallDir, preflightExecutable)
@@ -72,7 +45,7 @@ func (q *Qliksense) DownloadPreflight() error {
} }
if exists { if exists {
// preflight exist, no need to download again. // preflight exist, no need to download again.
api.LogDebugMessage("Preflight already exist, proceeding to perform checks") api.LogDebugMessage("Preflight already exists, proceeding to perform checks")
return nil return nil
} }
@@ -160,97 +133,6 @@ func determinePlatformSpecificUrls(platform string) (string, string, error) {
return preflightUrl, preflightFile, nil return preflightUrl, preflightFile, nil
} }
func (q *Qliksense) CheckDns() error {
// retrieve namespace
namespace := api.GetKubectlNamespace()
api.LogDebugMessage("Namespace here: %s", namespace)
tmpl, err := template.New("test").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"
const preflightFileName = "preflight"
// kubectl create deployment
opr := fmt.Sprintf("create deployment %s --image=nginx", appName)
err = initiateK8sOps(opr, namespace)
if err != nil {
fmt.Println(err)
return err
}
api.LogDebugMessage("create deployment executed")
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
}
api.LogDebugMessage("create service executed")
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(q.QliksenseHome, PreflightChecksDirName, preflightFileName)
trackSuccess, err := invokePreflight(preflightCommand, tempYaml)
if err != nil {
fmt.Println(err)
return err
}
if trackSuccess {
fmt.Println("PREFLIGHT DNS CHECK PASSED")
} else {
fmt.Println("PREFLIGHT DNS CHECK FAILED")
}
return nil
}
func initiateK8sOps(opr, namespace string) error { func initiateK8sOps(opr, namespace string) error {
opr1 := strings.Fields(opr) opr1 := strings.Fields(opr)
err := api.KubectlDirectOps(opr1, namespace) err := api.KubectlDirectOps(opr1, namespace)
@@ -261,7 +143,7 @@ func initiateK8sOps(opr, namespace string) error {
return nil return nil
} }
func invokePreflight(preflightCommand string, yamlFile *os.File) (bool, error) { func invokePreflight(preflightCommand string, yamlFile *os.File) error {
arguments := []string{} arguments := []string{}
arguments = append(arguments, yamlFile.Name(), "--interactive=false") arguments = append(arguments, yamlFile.Name(), "--interactive=false")
cmd := exec.Command(preflightCommand, arguments...) cmd := exec.Command(preflightCommand, arguments...)
@@ -270,7 +152,7 @@ func invokePreflight(preflightCommand string, yamlFile *os.File) (bool, error) {
cmd.Stdout = sterrBuffer cmd.Stdout = sterrBuffer
cmd.Stderr = sterrBuffer cmd.Stderr = sterrBuffer
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return false, fmt.Errorf("Error when running preflight command: %v\n", err) return fmt.Errorf("Error when running preflight command: %v\n", err)
} }
ind := strings.Index(sterrBuffer.String(), "---") ind := strings.Index(sterrBuffer.String(), "---")
output := sterrBuffer.String() output := sterrBuffer.String()
@@ -278,25 +160,28 @@ func invokePreflight(preflightCommand string, yamlFile *os.File) (bool, error) {
output = fmt.Sprintf("%s\n%s", output[:ind], output[ind:]) output = fmt.Sprintf("%s\n%s", output[:ind], output[ind:])
} }
fmt.Printf("%v\n", output) fmt.Printf("%v\n", output)
outputArr := strings.Fields(strings.TrimSpace(output))
trackSuccess := false
trackPrg := false
// We are only checking the overall "PASS" or "FAIL" // 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 // We are going to look for the first occurance of PASS or FAIL from the end
// there are also some space-like deceiving characters // there are also some space-like deceiving characters which are being hard to get by
for i := len(outputArr) - 1; i >= 0; i-- {
if strings.TrimSpace(outputArr[i]) != "" { //outputArr := strings.Fields(strings.TrimSpace(output))
if outputArr[i] == "PASS" { //trackSuccess := false
trackSuccess = true //trackPrg := false
trackPrg = true
} else if outputArr[i] == "FAIL" { //for i := len(outputArr) - 1; i >= 0; i-- {
trackPrg = true // if strings.TrimSpace(outputArr[i]) != "" {
} // if outputArr[i] == "PASS" {
} // trackSuccess = true
if trackPrg { // trackPrg = true
break // } else if outputArr[i] == "FAIL" {
} // trackPrg = true
} // }
return trackSuccess, nil // }
// if trackPrg {
// break
// }
//}
return nil
} }

View File

@@ -1,4 +1,4 @@
package qliksense package preflight
import ( import (
"fmt" "fmt"

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
}

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

@@ -3,7 +3,6 @@ package qliksense
import ( import (
"crypto/rsa" "crypto/rsa"
"fmt" "fmt"
"github.com/robfig/cron/v3"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
@@ -11,6 +10,8 @@ import (
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"github.com/robfig/cron/v3"
"github.com/qlik-oss/k-apis/pkg/config" "github.com/qlik-oss/k-apis/pkg/config"
b64 "encoding/base64" b64 "encoding/base64"
@@ -275,7 +276,7 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
// SetContextConfig - set the context for qliksense kubernetes resources to live in // SetContextConfig - set the context for qliksense kubernetes resources to live in
func (q *Qliksense) SetContextConfig(args []string) error { func (q *Qliksense) SetContextConfig(args []string) error {
if len(args) == 1 { if len(args) == 1 {
err := q.SetUpQliksenseContext(args[0], false) err := q.SetUpQliksenseContext(args[0])
if err != nil { if err != nil {
return err return err
} }
@@ -300,7 +301,7 @@ func (q *Qliksense) ListContextConfigs() error {
w.Flush() w.Flush()
if len(qliksenseConfig.Spec.Contexts) > 0 { if len(qliksenseConfig.Spec.Contexts) > 0 {
for _, cont := range qliksenseConfig.Spec.Contexts { 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() w.Flush()
fmt.Fprintln(out, "") fmt.Fprintln(out, "")
@@ -311,7 +312,7 @@ func (q *Qliksense) ListContextConfigs() error {
return nil return nil
} }
func (q *Qliksense) DeleteContextConfig(args []string) error { func (q *Qliksense) DeleteContextConfig(args []string, flag bool) error {
if len(args) == 1 { if len(args) == 1 {
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile) qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
var qliksenseConfig api.QliksenseConfig var qliksenseConfig api.QliksenseConfig
@@ -353,9 +354,17 @@ func (q *Qliksense) DeleteContextConfig(args []string) error {
} }
newLength := len(qliksenseConfig.Spec.Contexts) newLength := len(qliksenseConfig.Spec.Contexts)
if currentLength != newLength { if currentLength != newLength {
api.WriteToFile(&qliksenseConfig, qliksenseConfigFile) ans := flag
fmt.Fprintln(out, chalk.Yellow.Color(chalk.Underline.TextStyle("Warning: Active resources may still be running in-cluster"))) if ans == false {
fmt.Fprintln(out, chalk.Green.Color("Successfully deleted context: "), chalk.Bold.TextStyle(args[0])) 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 { } else {
err := fmt.Errorf(chalk.Red.Color("Context not found")) err := fmt.Errorf(chalk.Red.Color("Context not found"))
return err return err
@@ -373,11 +382,11 @@ func (q *Qliksense) DeleteContextConfig(args []string) error {
// SetUpQliksenseDefaultContext - to setup dir structure for default qliksense context // SetUpQliksenseDefaultContext - to setup dir structure for default qliksense context
func (q *Qliksense) SetUpQliksenseDefaultContext() error { func (q *Qliksense) SetUpQliksenseDefaultContext() error {
return q.SetUpQliksenseContext(DefaultQliksenseContext, true) return q.SetUpQliksenseContext(DefaultQliksenseContext)
} }
// SetUpQliksenseContext - to setup qliksense context // SetUpQliksenseContext - to setup qliksense context
func (q *Qliksense) SetUpQliksenseContext(contextName string, isDefaultContext bool) error { func (q *Qliksense) SetUpQliksenseContext(contextName string) error {
if contextName == "" { if contextName == "" {
err := fmt.Errorf("Please enter a non-empty context-name") err := fmt.Errorf("Please enter a non-empty context-name")
log.Println(err) log.Println(err)
@@ -391,83 +400,29 @@ func (q *Qliksense) SetUpQliksenseContext(contextName string, isDefaultContext b
} }
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile) qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
var qliksenseConfig api.QliksenseConfig qliksenseConfig := api.NewQConfigEmpty(q.QliksenseHome)
configFileTrack := false
if !api.FileExists(qliksenseConfigFile) { if !api.FileExists(qliksenseConfigFile) {
qliksenseConfig.AddBaseQliksenseConfigs(contextName) qliksenseConfig.AddBaseQliksenseConfigs(contextName)
} else { } else {
if err := api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile); err != nil { 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)
log.Println(err) log.Println(err)
return err return err
} }
} }
api.LogDebugMessage("%s exists", qliksenseContextsDir1)
// creating contexts/qlik-default/qlik-default.yaml file if qliksenseConfig.IsContextExist(contextName) {
qliksenseContextFile := filepath.Join(qliksenseContextsDir1, contextName, contextName+".yaml") return nil
//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,
})
} }
qliksenseCR := &api.QliksenseCR{}
qliksenseCR.AddCommonConfig(contextName)
qliksenseConfig.Spec.CurrentContext = contextName qliksenseConfig.Spec.CurrentContext = contextName
if !configFileTrack { if err := qliksenseConfig.CreateOrWriteCrAndContext(qliksenseCR); err != nil {
api.WriteToFile(&qliksenseConfig, qliksenseConfigFile) 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) { func validateInput(input string) (string, error) {
@@ -578,3 +533,16 @@ func (q *Qliksense) SetImageRegistry(registry, pushUsername, pushPassword, pullU
qliksenseCR.Spec.AddToConfigs("qliksense", imageRegistryConfigKey, registry) qliksenseCR.Spec.AddToConfigs("qliksense", imageRegistryConfigKey, registry)
return api.WriteToFile(&qliksenseCR, qliksenseContextsFile) 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() { func setup() func() {
// create tests dir // create tests dir
os.RemoveAll(testDir)
if err := os.Mkdir(testDir, 0777); err != nil { if err := os.Mkdir(testDir, 0777); err != nil {
log.Printf("\nError occurred: %v", err) log.Printf("\nError occurred: %v", err)
} }
@@ -164,7 +165,7 @@ metadata:
spec: spec:
contexts: contexts:
- name: qlik-default - name: qlik-default
crFile: /root/.qliksense/contexts/qlik-default.yaml crFile: contexts/qlik-default/qlik-default.yaml
currentContext: qlik-default currentContext: qlik-default
` `
configFile := filepath.Join(testDir, "config.yaml") configFile := filepath.Join(testDir, "config.yaml")
@@ -274,7 +275,7 @@ func TestSetUpQliksenseContext(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
q := New(tt.args.qlikSenseHome) 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) t.Errorf("SetUpQliksenseContext() error = %v, wantErr %v", err, tt.wantErr)
} }
}) })
@@ -396,7 +397,7 @@ func TestSetConfigs(t *testing.T) {
func TestSetImageRegistry(t *testing.T) { func TestSetImageRegistry(t *testing.T) {
getQlikSense := func(tmpQlikSenseHome string) (*Qliksense, error) { 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 apiVersion: config.qlik.com/v1
kind: QliksenseConfig kind: QliksenseConfig
metadata: metadata:
@@ -404,9 +405,9 @@ metadata:
spec: spec:
contexts: contexts:
- name: qlik-default - name: qlik-default
crFile: %s/contexts/qlik-default/qlik-default.yaml crFile: contexts/qlik-default/qlik-default.yaml
currentContext: qlik-default currentContext: qlik-default
`, tmpQlikSenseHome)), os.ModePerm); err != nil { `), os.ModePerm); err != nil {
return nil, err return nil, err
} }
@@ -798,11 +799,11 @@ metadata:
spec: spec:
contexts: contexts:
- name: qlik-default - name: qlik-default
crFile: /root/.qliksense/contexts/qlik-default.yaml crFile: contexts/qlik-default.yaml
- name: qlik1 - name: qlik1
crFile: /root/.qliksense/contexts/qlik1.yaml crFile: contexts/qlik1.yaml
- name: qlik2 - name: qlik2
crFile: /root/.qliksense/contexts/qlik2.yaml crFile: contexts/qlik2.yaml
currentContext: qlik1 currentContext: qlik1
` `
configFile := filepath.Join(testDir, "config.yaml") configFile := filepath.Join(testDir, "config.yaml")
@@ -927,9 +928,10 @@ func TestDeleteContexts(t *testing.T) {
q := New(tt.args.qlikSenseHome) q := New(tt.args.qlikSenseHome)
var arg []string var arg []string
arg = append(arg, tt.args.contextName) 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) 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 { 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 apiVersion: config.qlik.com/v1
kind: QliksenseConfig kind: QliksenseConfig
metadata: metadata:
@@ -179,9 +179,9 @@ metadata:
spec: spec:
contexts: contexts:
- name: qlik-default - name: qlik-default
crFile: %s/contexts/qlik-default/qlik-default.yaml crFile: contexts/qlik-default/qlik-default.yaml
currentContext: qlik-default currentContext: qlik-default
`, tmpQlikSenseHome)), os.ModePerm); err != nil { `), os.ModePerm); err != nil {
return err 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

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

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()
}