Compare commits

..

39 Commits

Author SHA1 Message Date
Andriy Bulynko
315af4d76e Fixing an issue with EULA prompt (#259) 2020-03-29 23:42:01 -04:00
Ashwathi Shiva
66236e1888 Preflight win error (#250)
* Windows yaml file path modified to address error- unsupported protocol schema
2020-03-28 11:21:22 -04:00
Foysal Iqbal
31fb9dd532 make path forward slash while saving (#249) 2020-03-27 23:01:31 -04:00
Sanat Nayar
a1be6d6b59 Merge pull request #230 from qlik-oss/cr_validation
Support setting any attribute in Spec (through CR Spec Validation)
2020-03-27 16:18:47 -04:00
Andriy Bulynko
f66a4bf245 Eula prompt integration (#248) 2020-03-27 11:59:16 -04:00
Sanat Nayar
f052ff7882 Merge branch 'master' into cr_validation 2020-03-27 11:15:22 -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
Sanat Nayar
aec352df32 added additional test cases 2020-03-19 13:43:43 -04:00
Sanat Nayar
c1bee27dff removed debugging printouts 2020-03-19 13:34:44 -04:00
Sanat Nayar
3c464e3316 Merge branch 'master' into cr_validation 2020-03-19 13:04:14 -04:00
Sanat Nayar
a71caf080e pkg/qliksense/context_configs.go 2020-03-19 12:54:03 -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
Sanat Nayar
5e9903ef3c init 2020-03-18 17:08:14 -04:00
Sanat Nayar
0adb31360a init 2020-03-18 16:35:27 -04:00
Sanat Nayar
590abfd5bf added crd spec 2020-03-17 14:58:29 -04:00
Sanat Nayar
fa5c854d3a init 2020-03-17 10:17:54 -04:00
36 changed files with 1469 additions and 717 deletions

View File

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

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 master $(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
}

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

@@ -0,0 +1,105 @@
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()
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.ReadString()
if err != nil {
panic(err)
}
return answer
}

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
}

View File

@@ -1,6 +1,9 @@
package main
import (
"bytes"
"io"
"io/ioutil"
"os"
"github.com/pkg/errors"
@@ -10,29 +13,72 @@ import (
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`,
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 {
if filePath == "-" {
return q.LoadCr(os.Stdin)
}
file, e := os.Open(filePath)
if e != nil {
return errors.Wrapf(e,
"unable to read the file %s", filePath)
}
return q.LoadCr(file)
return runLoadOrApplyCommandE(cmd, func(reader io.Reader) error {
return q.LoadCr(reader, overwriteExistingContext)
})
},
}
f := c.Flags()
f.StringVarP(&filePath, "file", "f", "", "File to laod CR from")
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"
"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 configCmd = &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>
Usage:
qliksense preflight dns
`,
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 configCmd
return preflightCmd
}
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`,
Example: `qliksense preflight dns`,
RunE: func(cmd *cobra.Command, args []string) error {
err := q.DownloadPreflight()
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 q.CheckDns()
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

@@ -42,7 +42,6 @@ func initAndExecute() error {
api.LogDebugMessage("QliksenseHomeDir: %s", qlikSenseHome)
qliksenseClient := qliksense.New(qlikSenseHome)
qliksenseClient.SetUpQliksenseDefaultContext()
cmd := rootCmd(qliksenseClient)
if err := cmd.Execute(); err != nil {
//levenstein checks (auto-suggestions)
@@ -79,30 +78,59 @@ 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))
@@ -168,19 +196,17 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
// 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 copy(src, dst string) (int64, error) {
var (
source, destination *os.File

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

@@ -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_
- 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
....
spec:
git:
repository: https://github.com/ffoysal/qliksense-k8s
accessToken: git-token
userName: git-username
repository: https://github.com/<OWNER>/<REPO>
accessToken: "<git-token>"
userName: "<git-username>"
gitOps:
enabled: "yes"
schedule: "*/5 * * * *"
watchBranch: pr-branch-24868a33
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
```

1
go.mod
View File

@@ -37,6 +37,7 @@ 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/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/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=

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

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}...)
@@ -89,6 +98,17 @@ func GetCRObject(crfile string) (*QliksenseCR, error) {
return cr, nil
}
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 == "" {
@@ -103,11 +123,11 @@ func CreateCRObjectFromString(crContent string) (*QliksenseCR, error) {
return cr, nil
}
func (qc *QliksenseConfig) getCRFilePath(contextName string) string {
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
}
}
@@ -132,23 +152,71 @@ 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 {
return qc.BuildRepoPath(version)
}
func (qc *QliksenseConfig) WriteCR(cr *QliksenseCR, contextName string) error {
crf := qc.getCRFilePath(contextName)
func (qc *QliksenseConfig) WriteCR(cr *QliksenseCR) error {
crf := qc.GetCRFilePath(cr.GetName())
if crf == "" {
return errors.New("context name " + contextName + " not found")
return errors.New("context name " + cr.GetName() + " 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: "contexts/" + cr.GetName() + "/" + cr.GetName() + ".yaml", //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)
cr.Spec.ManifestsRoot = strings.Replace(cr.Spec.ManifestsRoot, qc.QliksenseHomePath+"\\", "", 1)
cr.Spec.ManifestsRoot = strings.Replace(cr.Spec.ManifestsRoot, "\\", "/", -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)
return qc.WriteCR(cr)
}
func (qc *QliksenseConfig) IsContextExist(ctxName string) bool {
@@ -237,6 +305,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") != "" {
@@ -245,13 +322,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
}
@@ -268,7 +341,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
}
@@ -357,6 +438,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{}
@@ -396,17 +481,26 @@ func (cr *QliksenseCR) Validate() bool {
}
//CreateContextDirs create context dir structure ~/.qliksense/contexts/contextName
func (qc *QliksenseConfig) CreateContextDirs(contextName string) {
contexPath := filepath.Join(qc.QliksenseHomePath, qliksenseContextsDirName, contextName)
os.MkdirAll(contexPath, os.ModePerm)
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.QliksenseHomePath, qliksenseContextsDirName, contextName, contextName+".yaml")
return filepath.Join(qc.GetContextPath(contextName), contextName+".yaml")
}
//AddToContexts add the context into qc.Spec.Contexts
func (qc *QliksenseConfig) AddToContexts(crName, crFile string) {
func (qc *QliksenseConfig) AddToContextsRaw(crName, crFile string) {
qc.Spec.Contexts = append(qc.Spec.Contexts, []Context{
{CrFile: crFile,
Name: crName},

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

@@ -109,6 +109,7 @@ func KubectlDirectOps(opr []string, namespace string) error {
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())
}

View File

@@ -70,7 +70,7 @@ func ProcessConfigArgs(args []string) ([]*ServiceKeyValue, error) {
return nil, err
}
resultSvcKV := make([]*ServiceKeyValue, len(args))
re1 := regexp.MustCompile(`(\w{1,}).(\w{1,})=("*[\w\-?=_/:0-9]+"*)`)
re1 := regexp.MustCompile(`(\w{1,}).(\w{1,})=("*[\w\-?=_/:0-9\.]+"*)`)
for i, arg := range args {
LogDebugMessage("Arg received: %s", arg)
result := re1.FindStringSubmatch(arg)
@@ -232,7 +232,6 @@ func UntarGzFile(destination, fileToUntar string) error {
fileAtLoc.Chmod(os.ModePerm)
}
}
return nil
}
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
}

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

@@ -0,0 +1,130 @@
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: {{ .namespace }}
spec:
collectors:
- run:
collectorName: spin-up-pod
args: ["-z", "-v", "-w 1", "{{ .serviceName }}", "80"]
command: ["nc"]
image: subfuzion/netcat:latest
imagePullPolicy: IfNotPresent
name: spin-up-pod-check-dns
namespace: {{ .namespace }}
timeout: 30s
analyzers:
- textAnalyze:
checkName: DNS check
collectorName: spin-up-pod-check-dns
fileName: spin-up-pod.log
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())
appName := "qnginx001"
const PreflightChecksDirName = "preflight_checks"
b := bytes.Buffer{}
err = tmpl.Execute(&b, map[string]string{
"namespace": namespace,
"serviceName": appName,
})
if err != nil {
fmt.Println(err)
return err
}
tempYaml.WriteString(b.String())
// creating Kubectl resources
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,190 @@
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.28"
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 {
preflightExecutable := "preflight"
if runtime.GOOS == "windows" {
preflightExecutable += ".exe"
}
preflightInstallDir := filepath.Join(qp.Q.QliksenseHome, PreflightChecksDirName)
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(runtime.GOOS)
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 {
var 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

@@ -1,4 +1,4 @@
package qliksense
package preflight
import (
"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

@@ -3,16 +3,18 @@ package qliksense
import (
"crypto/rsa"
"fmt"
"github.com/qlik-oss/k-apis/pkg/config"
"github.com/robfig/cron/v3"
"io/ioutil"
"log"
"os"
"path/filepath"
"reflect"
"strings"
"text/tabwriter"
"github.com/qlik-oss/k-apis/pkg/config"
b64 "encoding/base64"
ansi "github.com/mattn/go-colorable"
@@ -38,7 +40,7 @@ const (
// SetSecrets - set-secrets <key>=<value> commands
func (q *Qliksense) SetSecrets(args []string, isSecretSet bool) error {
qConfig := api.NewQConfig(q.QliksenseHome)
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q)
qliksenseCR, err := qConfig.GetCurrentCR()
if err != nil {
return err
}
@@ -60,9 +62,7 @@ func (q *Qliksense) SetSecrets(args []string, isSecretSet bool) error {
}
}
// write modified content into context-yaml
api.WriteToFile(&qliksenseCR, qliksenseContextsFile)
return nil
return qConfig.WriteCR(qliksenseCR)
}
func (q *Qliksense) processSecret(ra *api.ServiceKeyValue, rsaPublicKey *rsa.PublicKey, qliksenseCR *api.QliksenseCR, isSecretSet bool) error {
@@ -130,7 +130,8 @@ func (q *Qliksense) processSecret(ra *api.ServiceKeyValue, rsaPublicKey *rsa.Pub
// SetConfigs - set-configs <key>=<value> commands
func (q *Qliksense) SetConfigs(args []string) error {
// retieve current context from config.yaml
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q)
qConfig := api.NewQConfig(q.QliksenseHome)
qliksenseCR, err := qConfig.GetCurrentCR()
if err != nil {
return err
}
@@ -143,54 +144,66 @@ func (q *Qliksense) SetConfigs(args []string) error {
qliksenseCR.Spec.AddToConfigs(ra.SvcName, ra.Key, ra.Value)
}
// write modified content into context.yaml
api.WriteToFile(&qliksenseCR, qliksenseContextsFile)
return nil
return qConfig.WriteCR(qliksenseCR)
}
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
}
currentContext := qliksenseConfig.Spec.CurrentContext
api.LogDebugMessage("Current-context from config.yaml: %s", currentContext)
if currentContext == "" {
// current-context is empty
err := fmt.Errorf(`Please run the "qliksense config set-context <context-name>" first before viewing the current context info`)
log.Println(err)
return nil, "", err
}
// read the context.yaml file
qliksenseCR := &api.QliksenseCR{}
if currentContext == "" {
// current-context is empty
err := fmt.Errorf(`Please run the "qliksense config set-context <context-name>" first before viewing the current context info`)
log.Println(err)
return nil, "", err
}
qliksenseContextsFile := filepath.Join(q.QliksenseHome, QliksenseContextsDir, currentContext, currentContext+".yaml")
if !api.FileExists(qliksenseContextsFile) {
err := fmt.Errorf("Context file does not exist.\nPlease try re-running `qliksense config set-context <context-name>` and then `qliksense config view` again")
log.Println(err)
return nil, "", err
}
if err := api.ReadFromFile(qliksenseCR, qliksenseContextsFile); err != nil {
log.Println(err)
return nil, "", err
}
func caseInsenstiveFieldByName(v reflect.Value, name string) reflect.Value {
name = strings.ToLower(name)
return v.FieldByNameFunc(func(n string) bool { return strings.ToLower(n) == name })
}
api.LogDebugMessage("Read context file: %s, Read QliksenseCR: %v", qliksenseContextsFile, qliksenseCR)
return qliksenseCR, qliksenseContextsFile, nil
func validateCR(key string, keySub string, value string, crSpec *api.QliksenseCR) (bool, *api.QliksenseCR) {
cr := reflect.ValueOf(crSpec.Spec)
keyValid := caseInsenstiveFieldByName(reflect.Indirect(cr), key)
if !keyValid.IsValid() {
//not in main spec
fmt.Println(key, "is an invalid key")
return false, crSpec
} else if keySub == "" {
if key == "rotatekeys" {
if _, err := validateInput(value); err != nil {
return false, crSpec
}
}
}
// checks if it is git or gitops
if keySub != "" {
if !keyValid.IsNil() {
if !caseInsenstiveFieldByName(reflect.Indirect(keyValid), keySub).IsValid() {
fmt.Println(keySub, "is an invalid key")
return false, crSpec
} else {
// verify gitops enabled and gitops schedule
switch keySub {
case "schedule":
if _, err := cron.ParseStandard(value); err != nil {
fmt.Println("Please enter string with standard cron scheduling syntax ")
return false, crSpec
}
case "enabled":
if !strings.EqualFold(value, "yes") && !strings.EqualFold(value, "no") {
fmt.Println("Please use yes or no for key enabled")
return false, crSpec
}
}
}
} else {
switch key {
case "gitops":
crSpec.Spec.GitOps = &config.GitOps{}
case "git":
crSpec.Spec.Git = &config.Repo{}
}
}
}
return true, crSpec
}
// SetOtherConfigs - set profile/storageclassname/git.repository/manifestRoot commands
func (q *Qliksense) SetOtherConfigs(args []string) error {
// retieve current context from config.yaml
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q)
qConfig := api.NewQConfig(q.QliksenseHome)
qliksenseCR, err := qConfig.GetCurrentCR()
if err != nil {
return err
}
@@ -204,78 +217,46 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
for _, arg := range args {
argsString := strings.Split(arg, "=")
switch argsString[0] {
case "profile":
qliksenseCR.Spec.Profile = argsString[1]
api.LogDebugMessage("Current profile after modification: %s ", qliksenseCR.Spec.Profile)
case "git.repository":
if qliksenseCR.Spec.Git == nil {
qliksenseCR.Spec.Git = &config.Repo{}
}
qliksenseCR.Spec.Git.Repository = argsString[1]
api.LogDebugMessage("Current git repository after modification: %s ", qliksenseCR.Spec.Git.Repository)
case "storageClassName":
qliksenseCR.Spec.StorageClassName = argsString[1]
api.LogDebugMessage("Current StorageClassName after modification: %s ", qliksenseCR.Spec.StorageClassName)
case "manifestsRoot":
qliksenseCR.Spec.ManifestsRoot = argsString[1]
case "rotateKeys":
rotateKeys, err := validateInput(argsString[1])
if err != nil {
return err
}
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, git.repository or gitops arguments to configure the current context")
log.Println(err)
return err
key := strings.ToLower(argsString[0])
value := argsString[1]
// check if key is for git or gitops (sub objects)
keySplit := strings.Split(key, ".")
key = keySplit[0]
keySub := ""
if len(keySplit) == 2 {
keySub = strings.ToLower(keySplit[1])
}
valid := true
valid, qliksenseCR = validateCR(key, keySub, value, qliksenseCR)
field := caseInsenstiveFieldByName(reflect.Indirect(reflect.ValueOf(qliksenseCR.Spec)), key)
if !valid {
err := fmt.Errorf("Please enter one of: profile, storageClassName,rotateKeys, manifestRoot, git.repository or gitops arguments to configure the current context")
return err
} else if strings.EqualFold("", keySub) {
// set spec for everything excluding git and gitops
if field.CanSet() {
field.SetString(value)
}
} else {
// set spec for git or gitops
subField := caseInsenstiveFieldByName(reflect.Indirect(field), keySub)
if subField.CanSet() {
subField.SetString(value)
}
}
fmt.Println(chalk.Green.Color("Successfully added to Custom Resource Spec"))
}
// write modified content into context.yaml
api.WriteToFile(&qliksenseCR, qliksenseContextsFile)
return nil
return qConfig.WriteCR(qliksenseCR)
}
// 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
}
@@ -300,7 +281,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, "")
@@ -373,11 +354,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)
@@ -391,83 +372,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) {
@@ -549,7 +476,7 @@ func readTargetfile(targetFile string) ([]byte, error) {
func (q *Qliksense) SetImageRegistry(registry, pushUsername, pushPassword, pullUsername, pullPassword string) error {
qConfig := api.NewQConfig(q.QliksenseHome)
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q)
qliksenseCR, err := qConfig.GetCurrentCR()
if err != nil {
return err
}
@@ -576,5 +503,18 @@ func (q *Qliksense) SetImageRegistry(registry, pushUsername, pushPassword, pullU
}
qliksenseCR.Spec.AddToConfigs("qliksense", imageRegistryConfigKey, registry)
return api.WriteToFile(&qliksenseCR, qliksenseContextsFile)
return qConfig.WriteCR(qliksenseCR)
}
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

@@ -165,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")
@@ -224,7 +224,8 @@ func Test_retrieveCurrentContextInfo(t *testing.T) {
q := &Qliksense{
QliksenseHome: testDir,
}
_, _, err := retrieveCurrentContextInfo(q)
qConfig := api.NewQConfig(q.QliksenseHome)
_, err := qConfig.GetCurrentCR()
if err != nil {
t.FailNow()
}
@@ -275,7 +276,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)
}
})
@@ -327,7 +328,7 @@ func TestSetOtherConfigs(t *testing.T) {
q: &Qliksense{
QliksenseHome: testDir,
},
args: []string{"profile=minikube", "rotateKeys=yes", "storageClassName=efs", "gitops.enabled=yes", "gitops.schedule=30 * * * *"},
args: []string{"profile=minikube", "rotateKeys=yes", "storageClassName=efs", "gitops.enabled=yes", "gitops.schedule=30 * * * *", "git.repository=master", "git.username=foo", "git.accesstoken=1234"},
},
wantErr: false,
},
@@ -337,7 +338,7 @@ func TestSetOtherConfigs(t *testing.T) {
q: &Qliksense{
QliksenseHome: testDir,
},
args: []string{"someconfig=somevalue, gitops.schedule=bar", "gitops.enabled=bar"},
args: []string{"someconfig=somevalue, gitops.schedule=bar", "gitops.enabled=bar", "git.foo=bar", "rotatekeys=bar"},
},
wantErr: true,
},
@@ -397,7 +398,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:
@@ -405,9 +406,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
}
@@ -799,11 +800,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")

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

@@ -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

@@ -1,60 +1,81 @@
package qliksense
import (
"bufio"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
//
func (q *Qliksense) LoadCr(reader io.Reader) error {
for _, doc := range readMultipleYamlFromReader(reader) {
if crName, err := q.loadCrStringIntoFileSystem(doc); err != nil {
return err
} else {
fmt.Println("cr name: [ " + crName + " ] has been loaded")
}
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) loadCrStringIntoFileSystem(crstr string) (string, error) {
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()) {
return "", errors.New("Context Name: " + cr.GetName() + " already exist. please delete the existing context first using delete-context command")
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
}
}
qConfig.CreateContextDirs(cr.GetName())
if err = qapi.WriteToFile(cr, qConfig.BuildCrFilePath(cr.GetName())); err != nil {
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.AddToContexts(cr.GetName(), qConfig.BuildCrFilePath(cr.GetName()))
qConfig.SetCurrentContextName(cr.GetName())
qConfig.Write()
return cr.GetName(), nil
}
func readMultipleYamlFromReader(reader io.Reader) []string {
docs := make([]string, 0)
scanner := bufio.NewScanner(bufio.NewReader(reader))
adoc := ""
for scanner.Scan() {
s := scanner.Text()
if s == "---" {
docs = append(docs, adoc)
adoc = ""
}
adoc = adoc + "\n" + s
}
if adoc != "" {
docs = append(docs, adoc)
}
return docs
}

View File

@@ -11,7 +11,8 @@ import (
func TestLoadCrFile(t *testing.T) {
td := setup()
sampleCr := `
setup()
sampleCr1 := `
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
@@ -38,6 +39,33 @@ spec:
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
@@ -51,19 +79,30 @@ spec:
repository: https://github.com/ffoysal/qliksense-k8s
accessToken: abababababababaab
userName: "blblbl"`
crFile := filepath.Join(testDir, "testcr.yaml")
ioutil.WriteFile(crFile, []byte(sampleCr), 0644)
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)
file, e := os.Open(crFile)
file1, e := os.Open(crFile1)
if e != nil {
t.Log(e)
t.FailNow()
}
if err := q.LoadCr(file); err != nil {
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()
}
@@ -76,15 +115,25 @@ spec:
if cr.GetName() != "qlik-test" {
t.FailNow()
}
if qConfig.Spec.CurrentContext != "qlik-test" {
cr, err = qConfig.GetCR("qlik-test3")
if err != nil {
t.Log(err)
t.FailNow()
}
file, e = os.Open(dupCrFile)
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); err == nil {
if err := q.LoadCr(file, false); err == nil {
t.FailNow()
}
td()

View File

@@ -1,302 +0,0 @@
package qliksense
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"text/template"
"github.com/qlik-oss/sense-installer/pkg/api"
)
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"
)
var preflightBaseURL = fmt.Sprintf("https://github.com/replicatedhq/troubleshoot/releases/download/%s/", preflightRelease)
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 (q *Qliksense) DownloadPreflight() error {
const preflightExecutable = "preflight"
preflightInstallDir := filepath.Join(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 exist, 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 (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 {
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) (bool, 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 false, 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)
outputArr := strings.Fields(strings.TrimSpace(output))
trackSuccess := false
trackPrg := false
// We are only checking the overall "PASS" or "FAIL"
// We are going to look for the first occurance of PASS or FAIL from the end
// there are also some space-like deceiving characters
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 trackSuccess, nil
}