Compare commits

..

15 Commits

Author SHA1 Message Date
Ashwathi Shiva
6920b140c9 Merge branch 'pf_checks_unit_tests' into postflight_check_1 2020-06-04 12:11:47 -04:00
Ashwathi Shiva
0880255f8f MongodbUrl typo fixed 2020-06-04 12:01:35 -04:00
Ashwathi Shiva
a70c91bc95 unit tests for preflight checks 2020-06-04 12:00:02 -04:00
Ashwathi Shiva
ad11731f97 Merge branch 'master' into postflight_check_1 2020-06-03 13:12:18 -04:00
Ashwathi Shiva
cb76694623 fixed typo: changed kube-version to k8s-version 2020-06-03 11:49:36 -04:00
Ashwathi Shiva
d87a9e48bf added kubectl logs command after displaying error logs 2020-06-02 00:12:28 -04:00
Ashwathi Shiva
3d3941783d Merge branch 'master' into postflight_check_1 2020-06-01 00:42:26 -04:00
Ashwathi Shiva
3a08578532 Merge branch 'master' into postflight_check_1 2020-06-01 00:05:17 -04:00
Ashwathi Shiva
3f98680187 added some tests 2020-06-01 00:03:19 -04:00
Ashwathi Shiva
052dc71e7d updated doc 2020-05-31 21:47:02 -04:00
Ashwathi Shiva
b26b063eba Include pod name with error log 2020-05-31 20:49:23 -04:00
Ashwathi Shiva
8281fb962e filtered errors from logs 2020-05-31 20:19:11 -04:00
Ashwathi Shiva
b17de591f9 cleanup and adding tests 2020-05-31 00:08:53 -04:00
Ashwathi Shiva
48849b6489 initial commit: retrieve logs from failed init containers working, refactored code 2020-05-28 20:25:55 -04:00
Ashwathi Shiva
50fcd1826a fixed test for adding preflight-mongo image 2020-05-25 09:21:03 -04:00
59 changed files with 869 additions and 1329 deletions

View File

@@ -16,6 +16,6 @@ jobs:
uses: actions/checkout@v1
- name: Deploy docs
uses: mhausenblas/mkdocs-deploy-gh-pages@1.12
uses: mhausenblas/mkdocs-deploy-gh-pages@1.11
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -19,7 +19,7 @@ func about(q *qliksense.Qliksense) *cobra.Command {
c := &cobra.Command{
Use: "about ref",
Short: "Displays information pertaining to qliksense on Kubernetes",
Short: "Displays information pertaining to Qliksense on Kubernetes",
Long: "Gives the version of QLik Sense on Kubernetes and versions of images.",
Example: `
qliksense about 1.0.0

View File

@@ -1,44 +1,74 @@
package main
import (
"bytes"
"errors"
"fmt"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
func applyCmd(q *qliksense.Qliksense) *cobra.Command {
opts := &qliksense.InstallCommandOptions{
CleanPatchFiles: true,
}
opts := &qliksense.InstallCommandOptions{}
filePath := ""
keepPatchFiles, pull, push := false, false, 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 apply(q, cmd, opts)
return runLoadOrApplyCommandE(cmd, func(crBytes []byte) error {
if cr, crBytesWithEula, err := getCrWithEulaInserted(crBytes); err != nil {
return err
} else if err := validatePullPushFlagsOnApply(cr, pull, push); err != nil {
return err
} else {
return q.ApplyCRFromReader(bytes.NewReader(crBytesWithEula), opts, keepPatchFiles, true, pull, push)
}
})
},
}
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.BoolVar(&opts.CleanPatchFiles, cleanPatchFilesFlagName, opts.CleanPatchFiles, cleanPatchFilesFlagUsage)
f.BoolVarP(&opts.Pull, pullFlagName, pullFlagShorthand, opts.Pull, pullFlagUsage)
f.BoolVarP(&opts.Push, pushFlagName, pushFlagShorthand, opts.Push, pushFlagUsage)
f.StringVarP(&opts.AcceptEULA, "acceptEULA", "a", opts.AcceptEULA, "Accept EULA for qliksense")
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.BoolVarP(&pull, pullFlagName, pullFlagShorthand, pull, pullFlagUsage)
f.BoolVarP(&push, pushFlagName, pushFlagShorthand, push, pushFlagUsage)
eulaPreRunHooks.addValidator(fmt.Sprintf("%v %v", rootCommandName, c.Name()), loadOrApplyCommandEulaPreRunHook)
if err := c.MarkFlagRequired("file"); err != nil {
panic(err)
}
return c
}
func apply(q *qliksense.Qliksense, cmd *cobra.Command, opts *qliksense.InstallCommandOptions) error {
if crBytes, err := getCrBytesFromFileFlag(cmd); err != nil {
return err
func validatePullPushFlagsOnApply(cr *qapi.QliksenseCR, pull, push bool) error {
if pull && !push {
fmt.Printf("WARNING: pulling images without pushing them")
}
if push {
if registry := cr.Spec.GetImageRegistry(); registry == "" {
return errors.New("no image registry set in the CR; to set it use: qliksense config set-image-registry")
}
}
return nil
}
func getCrWithEulaInserted(crBytes []byte) (*qapi.QliksenseCR, []byte, error) {
if cr, err := qapi.CreateCRObjectFromString(string(crBytes)); err != nil {
return nil, nil, err
} else {
return q.ApplyCRFromBytes(crBytes, opts, true)
cr.SetEULA("yes")
if crBytesWithEula, err := qapi.K8sToYaml(cr); err != nil {
return nil, nil, err
} else {
return cr, crBytesWithEula, nil
}
}
}

View File

@@ -34,8 +34,8 @@ func crdsInstallCmd(q *qliksense.Qliksense) *cobra.Command {
}
c := &cobra.Command{
Use: "install",
Short: "Install CRDs for qliksense application. Use install --all=false to exclude the operator CRD",
Long: "Install CRDs for qliksense application. Use install --all=false to exclude the operator CRD",
Short: "Install CRDs for Qliksense application. Use install --all=false to exclude the operator CRD",
Long: "Install CRDs for Qliksense application. Use install --all=false to exclude the operator CRD",
RunE: func(cmd *cobra.Command, args []string) error {
return q.InstallCrds(opts)
},

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

@@ -0,0 +1,109 @@
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 or execute the command with the acceptEULA flag set to "yes"`
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 == fmt.Sprintf("%v install", rootCommandName) ||
commandName == fmt.Sprintf("%v apply", rootCommandName)
}
func globalEulaPreRun(cmd *cobra.Command, q *qliksense.Qliksense) {
if isEulaEnforced(cmd.CommandPath()) {
eulaFlagValue := strings.TrimSpace(strings.ToLower(cmd.Flag("acceptEULA").Value.String()))
if eulaFlagValue != "" && eulaFlagValue != "yes" {
doEnforceEula()
} else if eulaFlagValue == "" {
if eulaPreRunHook := eulaPreRunHooks.getValidator(cmd.CommandPath()); 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.CommandPath()) {
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

@@ -1,15 +1,19 @@
package main
import (
"bytes"
"fmt"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
func installCmd(q *qliksense.Qliksense) *cobra.Command {
opts := &qliksense.InstallCommandOptions{
CleanPatchFiles: true,
}
opts := &qliksense.InstallCommandOptions{}
filePath := ""
keepPatchFiles, pull, push := false, false, false
c := &cobra.Command{
Use: "install",
Short: "install a qliksense release",
@@ -18,35 +22,76 @@ func installCmd(q *qliksense.Qliksense) *cobra.Command {
# qliksense install -f file_name or cat cr_file | qliksense install -f -
`,
RunE: func(cmd *cobra.Command, args []string) error {
version := ""
if len(args) != 0 {
version = args[0]
}
if filePath != "" {
if err := apply(q, cmd, opts); err != nil {
return err
}
return runLoadOrApplyCommandE(cmd, func(crBytes []byte) error {
if cr, crBytesWithEula, err := getCrWithEulaInserted(crBytes); err != nil {
return err
} else if err := validatePullPushFlagsOnApply(cr, pull, push); err != nil {
return err
} else {
return q.ApplyCRFromReader(bytes.NewReader(crBytesWithEula), opts, keepPatchFiles, true, pull, push)
}
})
} else {
if err := q.InstallQK8s(version, opts); err != nil {
version := ""
if len(args) != 0 {
version = args[0]
}
if err := validatePullPushFlagsOnInstall(q, pull, push); err != nil {
return err
}
if pull {
fmt.Println("Pulling images...")
if err := q.PullImages(version, ""); err != nil {
return err
}
}
if push {
fmt.Println("Pushing images...")
if err := q.PushImagesForCurrentCR(); err != nil {
return err
}
}
return q.InstallQK8s(version, opts, keepPatchFiles)
}
postflightChecksCmd := AllPostflightChecks(q)
postflightChecksCmd.DisableFlagParsing = true
return postflightChecksCmd.Execute()
},
}
eulaPreRunHooks.addValidator(fmt.Sprintf("%v %v", rootCommandName, c.Name()), func(cmd *cobra.Command, q *qliksense.Qliksense) (b bool, err error) {
if filePath != "" {
return loadOrApplyCommandEulaPreRunHook(cmd, q)
} else if qConfig, err := qapi.NewQConfigE(q.QliksenseHome); err != nil {
return false, nil
} else if qcr, err := qConfig.GetCurrentCR(); err != nil {
return false, nil
} else {
return qcr.IsEULA(), nil
}
})
f := c.Flags()
f.StringVarP(&filePath, "file", "f", "", "Install from a CR 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.BoolVar(&opts.CleanPatchFiles, cleanPatchFilesFlagName, opts.CleanPatchFiles, cleanPatchFilesFlagUsage)
f.BoolVarP(&opts.Pull, pullFlagName, pullFlagShorthand, opts.Pull, pullFlagUsage)
f.BoolVarP(&opts.Push, pushFlagName, pushFlagShorthand, opts.Push, pushFlagUsage)
f.StringVarP(&opts.AcceptEULA, "acceptEULA", "a", opts.AcceptEULA, "Accept EULA for qliksense")
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.StringVarP(&filePath, "file", "f", "", "Install from a CR file")
f.BoolVarP(&opts.DryRun, "dry-run", "", false, "Dry run will generate the patches without rotating keys")
f.BoolVarP(&pull, pullFlagName, pullFlagShorthand, pull, pullFlagUsage)
f.BoolVarP(&push, pushFlagName, pushFlagShorthand, push, pushFlagUsage)
return c
}
func validatePullPushFlagsOnInstall(q *qliksense.Qliksense, pull, push bool) error {
if pull && !push {
fmt.Printf("WARNING: pulling images without pushing them")
}
if push {
if err := ensureImageRegistrySetInCR(q); err != nil {
return err
}
}
return nil
}

View File

@@ -1,31 +0,0 @@
package main
import (
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
var keysCmd = &cobra.Command{
Use: "keys",
Short: "keys for qliksense",
}
func keysRotateCmd(q *qliksense.Qliksense) *cobra.Command {
c := &cobra.Command{
Use: "rotate",
Short: "Rotate qliksense application keys",
RunE: func(cmd *cobra.Command, args []string) error {
if err := q.InstallQK8s("", &qliksense.InstallCommandOptions{
CleanPatchFiles: true,
RotateKeys: true,
}); err != nil {
return err
} else {
postFlightChecksCmd := AllPostflightChecks(q)
postFlightChecksCmd.DisableFlagParsing = true
return postFlightChecksCmd.Execute()
}
},
}
return c
}

View File

@@ -1,6 +1,8 @@
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
@@ -18,48 +20,69 @@ func loadCrFile(q *qliksense.Qliksense) *cobra.Command {
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 crBytes, err := getCrBytesFromFileFlag(cmd); err != nil {
return err
} else {
return q.LoadCr(crBytes, overwriteExistingContext)
}
return runLoadOrApplyCommandE(cmd, func(buffer []byte) error {
return q.LoadCr(bytes.NewReader(buffer), 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")
if err := c.MarkFlagRequired("file"); err != nil {
panic(err)
}
eulaPreRunHooks.addValidator(fmt.Sprintf("%v %v", rootCommandName, c.Name()), loadOrApplyCommandEulaPreRunHook)
return c
}
func isInputFromPipe() bool {
fileInfo, _ := os.Stdin.Stat()
return fileInfo.Mode()&os.ModeCharDevice == 0
}
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")
} else {
return os.Stdin, nil
}
} else if file, err := os.Open(filePath); err != nil {
return nil, errors.Wrapf(err, "unable to read the file %s", filePath)
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 {
return file, nil
eulaPreRunHooks.addPostValidationArtifact("CR", crBytes)
return q.IsEulaAcceptedInCrFile(bytes.NewBuffer(crBytes))
}
}
func getCrBytesFromFileFlag(cmd *cobra.Command) ([]byte, error) {
if file, err := getCrFileFromFlag(cmd, "file"); err != nil {
return nil, err
func runLoadOrApplyCommandE(cmd *cobra.Command, callBack func(buffer []byte) error) error {
if crBytes := eulaPreRunHooks.getPostValidationArtifact("CR"); crBytes != nil {
return callBack(crBytes.([]byte))
} else {
file, err := getCrFileFromFlag(cmd, "file")
if err != nil {
return err
}
defer file.Close()
return ioutil.ReadAll(file)
if crBytes, err := ioutil.ReadAll(file); err != nil {
return err
} else {
return callBack(crBytes)
}
}
}

View File

@@ -24,7 +24,7 @@ func postflightCmd(q *qliksense.Qliksense) *cobra.Command {
return postflightCmd
}
func postflightMigrationCheck(q *qliksense.Qliksense) *cobra.Command {
func pfMigrationCheck(q *qliksense.Qliksense) *cobra.Command {
out := ansi.NewColorableStdout()
postflightOpts := &postflight.PostflightOptions{}
var postflightMigrationCmd = &cobra.Command{
@@ -58,39 +58,3 @@ func postflightMigrationCheck(q *qliksense.Qliksense) *cobra.Command {
f.BoolVarP(&postflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return postflightMigrationCmd
}
func AllPostflightChecks(q *qliksense.Qliksense) *cobra.Command {
out := ansi.NewColorableStdout()
postflightOpts := &postflight.PostflightOptions{}
var postflightAllChecksCmd = &cobra.Command{
Use: "all",
Short: "perform all checks",
Long: `perform all postflight checks`,
Example: `qliksense postflight all`,
RunE: func(cmd *cobra.Command, args []string) error {
pf := &postflight.QliksensePostflight{Q: q, P: postflightOpts, CG: &api.ClientGoUtils{Verbose: postflightOpts.Verbose}}
// run all postflight checks
fmt.Printf("Running all postflight checks...\n\n")
namespace, kubeConfigContents, err := pf.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Fprintf(out, "%s\n", Red("Unable to run all postflight checks"))
fmt.Printf("Error: %v\n", err)
return nil
}
if namespace == "" {
namespace = "default"
}
if err = pf.RunAllPostflightChecks(namespace, kubeConfigContents, postflightOpts); err != nil {
fmt.Fprintf(out, "%s\n", Red("1 or more preflight checks have FAILED"))
fmt.Printf("Completed running all postflight checks")
return nil
}
fmt.Fprintf(out, "%s\n", Green("All postflight checks have PASSED"))
return nil
},
}
f := postflightAllChecksCmd.Flags()
f.BoolVarP(&postflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return postflightAllChecksCmd
}

View File

@@ -43,7 +43,7 @@ func pfDnsCheckCmd(q *qliksense.Qliksense) *cobra.Command {
// Preflight DNS check
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight DNS check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
@@ -51,11 +51,11 @@ func pfDnsCheckCmd(q *qliksense.Qliksense) *cobra.Command {
namespace = "default"
}
if err = qp.CheckDns(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight DNS check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
fmt.Fprintf(out, "%s\n", Green("Preflight DNS check PASSED"))
return nil
},
}
@@ -81,16 +81,16 @@ func pfK8sVersionCheckCmd(q *qliksense.Qliksense) *cobra.Command {
// Preflight Kubernetes minimum version check
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight kubernetes minimum version check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if err = qp.CheckK8sVersion(namespace, kubeConfigContents); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight kubernetes minimum version check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
fmt.Fprintf(out, "%s\n", Green("Preflight kubernetes minimum version check PASSED"))
return nil
},
}
@@ -157,7 +157,7 @@ func pfDeploymentCheckCmd(q *qliksense.Qliksense) *cobra.Command {
// Preflight deployments check
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight deployment check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
@@ -165,11 +165,11 @@ func pfDeploymentCheckCmd(q *qliksense.Qliksense) *cobra.Command {
namespace = "default"
}
if err = qp.CheckDeployment(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight deployment check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
fmt.Fprintf(out, "%s\n", Green("Preflight deployment check PASSED"))
return nil
},
}
@@ -195,7 +195,7 @@ func pfServiceCheckCmd(q *qliksense.Qliksense) *cobra.Command {
// Preflight service check
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight service check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
@@ -204,11 +204,11 @@ func pfServiceCheckCmd(q *qliksense.Qliksense) *cobra.Command {
namespace = "default"
}
if err = qp.CheckService(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight service check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
fmt.Fprintf(out, "%s\n", Green("Preflight service check PASSED"))
return nil
},
}
@@ -234,7 +234,7 @@ func pfPodCheckCmd(q *qliksense.Qliksense) *cobra.Command {
// Preflight pod check
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight pod check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
@@ -242,11 +242,11 @@ func pfPodCheckCmd(q *qliksense.Qliksense) *cobra.Command {
namespace = "default"
}
if err = qp.CheckPod(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight pod check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
fmt.Fprintf(out, "%s\n", Green("Preflight pod check PASSED"))
return nil
},
}
@@ -272,16 +272,16 @@ func pfCreateRoleCheckCmd(q *qliksense.Qliksense) *cobra.Command {
// Preflight role check
namespace, _, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight role check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if err = qp.CheckCreateRole(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight role check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
fmt.Fprintf(out, "%s\n", Green("Preflight role check PASSED"))
return nil
},
}
@@ -307,16 +307,16 @@ func pfCreateRoleBindingCheckCmd(q *qliksense.Qliksense) *cobra.Command {
// Preflight createRoleBinding check
namespace, _, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight rolebinding check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if err = qp.CheckCreateRoleBinding(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight rolebinding check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
fmt.Fprintf(out, "%s\n", Green("Preflight rolebinding check PASSED"))
return nil
},
}
@@ -342,16 +342,16 @@ func pfCreateServiceAccountCheckCmd(q *qliksense.Qliksense) *cobra.Command {
// Preflight createServiceAccount check
namespace, _, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight ServiceAccount check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if err = qp.CheckCreateServiceAccount(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight ServiceAccount check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
fmt.Fprintf(out, "%s\n", Green("Preflight ServiceAccount check PASSED"))
return nil
},
}
@@ -376,16 +376,16 @@ func pfCreateAuthCheckCmd(q *qliksense.Qliksense) *cobra.Command {
// Preflight authcheck
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight authcheck FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if err = qp.CheckCreateRB(namespace, kubeConfigContents); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight authcheck FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
fmt.Fprintf(out, "%s\n", Green("Preflight authcheck PASSED"))
return nil
},
}
@@ -411,7 +411,7 @@ func pfMongoCheckCmd(q *qliksense.Qliksense) *cobra.Command {
// Preflight mongo check
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight mongo check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
@@ -419,11 +419,11 @@ func pfMongoCheckCmd(q *qliksense.Qliksense) *cobra.Command {
namespace = "default"
}
if err = qp.CheckMongo(kubeConfigContents, namespace, preflightOpts, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight mongo check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
fmt.Fprintf(out, "%s\n", Green("Preflight mongo check PASSED"))
return nil
},
}
@@ -472,42 +472,3 @@ func pfCleanupCmd(q *qliksense.Qliksense) *cobra.Command {
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return pfCleanCmd
}
func pfVerifyCAChainCmd(q *qliksense.Qliksense) *cobra.Command {
out := ansi.NewColorableStdout()
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var pfVerifyCAChainCmd = &cobra.Command{
Use: "verify-ca-chain",
Short: "verify-ca-chain using openssl verify",
Long: `verify the CA chain using openssl verify to ensure that mongodb certificate is valid`,
Example: `qliksense preflight verify-ca-chain`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
// Preflight service check
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if namespace == "" {
namespace = "default"
}
if err = qp.VerifyCAChain(kubeConfigContents, namespace, preflightOpts, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
return nil
},
}
f := pfVerifyCAChainCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return pfVerifyCAChainCmd
}

View File

@@ -1,6 +1,9 @@
package main
import (
"errors"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
@@ -31,8 +34,22 @@ func pushQliksenseImages(q *qliksense.Qliksense) *cobra.Command {
Short: "Push docker images for offline install",
Example: `qliksense push`,
RunE: func(cmd *cobra.Command, args []string) error {
return q.PushImagesForCurrentCR()
if err := ensureImageRegistrySetInCR(q); err != nil {
return err
} else {
return q.PushImagesForCurrentCR()
}
},
}
return cmd
}
func ensureImageRegistrySetInCR(q *qliksense.Qliksense) error {
qConfig := qapi.NewQConfig(q.QliksenseHome)
if qcr, err := qConfig.GetCurrentCR(); err != nil {
return err
} else if registry := qcr.Spec.GetImageRegistry(); registry == "" {
return errors.New("no image registry set in the CR; to set it use: qliksense config set-image-registry")
}
return nil
}

View File

@@ -2,6 +2,7 @@ package main
import (
"fmt"
"io"
"log"
"os"
"path/filepath"
@@ -22,17 +23,17 @@ import (
// qliksense <command>
const (
qlikSenseHomeVar = "QLIKSENSE_HOME"
qlikSenseDirVar = ".qliksense"
cleanPatchFilesFlagName = "clean"
cleanPatchFilesFlagUsage = "Set --clean=false to keep any prior config repo file changes on install (for debugging)"
pullFlagName = "pull"
pullFlagShorthand = "d"
pullFlagUsage = "If using private docker registry, pull (download) all required qliksense images before install"
pushFlagName = "push"
pushFlagShorthand = "u"
pushFlagUsage = "If using private docker registry, push (upload) all downloaded qliksense images to that registry before install"
rootCommandName = "qliksense"
qlikSenseHomeVar = "QLIKSENSE_HOME"
qlikSenseDirVar = ".qliksense"
keepPatchFilesFlagName = "keep-config-repo-patches"
keepPatchFilesFlagUsage = "Keep config repo patch files (for debugging)"
pullFlagName = "pull"
pullFlagShorthand = "d"
pullFlagUsage = "If using private docker registry, pull (download) all required Qliksense images before install"
pushFlagName = "push"
pushFlagShorthand = "u"
pushFlagUsage = "If using private docker registry, push (upload) all downloaded Qliksense images to that registry before install"
rootCommandName = "qliksense"
)
func initAndExecute() error {
@@ -100,11 +101,12 @@ func commandUsesContext(commandName string) bool {
func getRootCmd(p *qliksense.Qliksense) *cobra.Command {
cmd := &cobra.Command{
Use: rootCommandName,
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`,
Args: cobra.ArbitraryArgs,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if commandUsesContext(cmd.CommandPath()) {
globalEulaPreRun(cmd, p)
if err := p.SetUpQliksenseDefaultContext(); err != nil {
panic(err)
}
@@ -112,10 +114,25 @@ func getRootCmd(p *qliksense.Qliksense) *cobra.Command {
if err := pf.Initialize(); err != nil {
panic(err)
}
globalEulaPostRun(cmd, p)
}
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
if commandUsesContext(cmd.CommandPath()) {
globalEulaPostRun(cmd, p)
}
},
SilenceUsage: true,
}
origHelpFunc := cmd.HelpFunc()
cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
if !commandUsesContext(cmd.CommandPath()) {
cmd.Flags().MarkHidden("acceptEULA")
}
origHelpFunc(cmd, args)
})
accept := ""
cmd.PersistentFlags().StringVarP(&accept, "acceptEULA", "a", "", "Accept EULA for qliksense")
cmd.Flags().SetInterspersed(false)
return cmd
}
@@ -151,9 +168,7 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
// add config command
configCmd := configCmd(p)
cmd.AddCommand(configCmd)
/** disabling for now
configCmd.AddCommand(configApplyCmd(p))
**/
configCmd.AddCommand(configViewCmd(p))
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
@@ -209,7 +224,6 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
preflightCmd.AddCommand(pfCreateRoleBindingCheckCmd(p))
preflightCmd.AddCommand(pfCreateServiceAccountCheckCmd(p))
preflightCmd.AddCommand(pfCreateAuthCheckCmd(p))
preflightCmd.AddCommand(pfVerifyCAChainCmd(p))
preflightCmd.AddCommand(pfCleanupCmd(p))
cmd.AddCommand(preflightCmd)
@@ -218,17 +232,40 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
// add postflight command
postflightCmd := postflightCmd(p)
postflightCmd.AddCommand(postflightMigrationCheck(p))
postflightCmd.AddCommand(AllPostflightChecks(p))
postflightCmd.AddCommand(pfMigrationCheck(p))
cmd.AddCommand(postflightCmd)
// add keys command
cmd.AddCommand(keysCmd)
keysCmd.AddCommand(keysRotateCmd(p))
return cmd
}
func copy(src, dst string) (int64, error) {
var (
source, destination *os.File
sourceFileStat os.FileInfo
err error
nBytes int64
)
if sourceFileStat, err = os.Stat(src); err != nil {
return 0, err
}
if !sourceFileStat.Mode().IsRegular() {
return 0, fmt.Errorf("%s is not a regular file", src)
}
if source, err = os.Open(src); err != nil {
return 0, err
}
defer source.Close()
if destination, err = os.Create(dst); err != nil {
return 0, err
}
defer destination.Close()
nBytes, err = io.Copy(destination, source)
return nBytes, err
}
func levenstein(cmd *cobra.Command) {
cmd.SuggestionsMinimumDistance = 2
if len(os.Args) > 1 {

View File

@@ -74,6 +74,7 @@ spec:
- name: mongodbUri
value: mongodb://qlik-test-mongodb:27017/qliksense?ssl=false
profile: docker-desktop
rotateKeys: "yes"
```
`qliksense apply` does everything `qliksense load` does but will install Qlik Sense into the cluster as well
@@ -129,6 +130,7 @@ In this case, the result of `qliksense about` command would display information
It supports the following flags:
- `qliksense config apply` - generate the patches and apply manifests to K8s
- `qliksense config list-contexts` - get and list contexts
- `qliksense config set` - configure a key-value pair into the current context
- `qliksense config set-configs` - set configurations into qliksense context as key-value pairs

View File

@@ -1,6 +1,6 @@
# How CLI works
At the initialization, `qliksense` cli creates few files in the director `~/.qliksense` and it contains following files:
At the initialization, `qliksense` cli creates few files in the director `~/.qliksene` and it contains following files:
```console
.qliksense
@@ -25,6 +25,7 @@ spec:
qliksense:
- name: mongodbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
rotateKeys: "yes"
releaseName: qlik-default
```
@@ -49,7 +50,7 @@ In this mode `qliksense` CLI downloads the specified version from [qliksense-k8s
The qliksense cli creates a CR for the QlikSense operator and all config operations are performed to edit the CR.
`qliksense install` will generate patches in local file system (i.e `~/.qliksense/contexts/<context-name>/qlik-k8s`) and
`qliksense install` or `qliksense config apply` will generate patches in local file system (i.e `~/.qliksense/contexts/<context-name>/qlik-k8s`) and
- Install those manifests into the cluster
- Create a custom resource (CR) for the `qliksene operator`.
@@ -67,7 +68,7 @@ qliksense config set git.repository="https://github.com/my-org/qliksense-k8s"
qliksense config set git.accessToken="<mySecretToken>"
```
When you perform `qliksense install`, qliksense operator performs these tasks:
When you perform `qliksense install` or `qliksene config apply`, qliksense operator performs these tasks:
- Download corresponding version of manifests from the your git repo
- Generate kustomize patches

View File

@@ -1,162 +1,53 @@
# Getting started
To get familiar with the Qlik Sense on Kubernetes Operator Command Line Interface (CLI), we will install Qlik Sense on Kubernetes on docker desktop. In subsequent sections we will enhance this configuration to include an Identity Provider (keycloak) and demonstrate air gapped capabilities as well.
## Requirements
- Kubernetes cluster (Docker Desktop with enabled Kubernetes)
- `kubectl` installed, configured and able to communicate with kubernetes cluster. _`qliksense` CLI uses `kubectl` to perform some operations on cluster_
- `kubectl` installed, configured and able to communicate with kubernetes cluster. _`qliksense` CLI uses `kubectl` under the hood to perform operations on cluster_
## Installing `qliksense` CLI
Download the executable for your platform from [releases page](https://github.com/qlik-oss/sense-installer/releases) and rename it to `qliksense`
=== "Linux"
??? tldr "Linux"
``` bash
# bash
curl -LOJ https://storage.googleapis.com/kubernetes-release/release/v1.16.8/bin/linux/amd64/kubectl
curl -LOJ https://github.com/qlik-oss/sense-installer/releases/latest/download/qliksense-linux-amd64
sudo mv qliksense-linux-amd64 kubectl /usr/local/bin
sudo chmod ugo+x /usr/local/bin/qliksense-linux-amd64 /usr/local/bin/kubectl
sudo ln -s /usr/local/bin/qliksense-linux-amd64 /usr/local/bin/qliksense
sudo ln -s /usr/local/bin/qliksense-linux-amd64 /usr/local/bin/kubectl-qliksense
curl -Lo qliksense https://github.com/qlik-oss/sense-installer/releases/download/v0.7.0/qliksense-linux-amd64
chmod +x qliksense
sudo mv qliksense /usr/local/bin
```
=== "MacOS"
??? tldr "MacOS"
``` bash
# bash
curl -LOJ https://storage.googleapis.com/kubernetes-release/release/v1.16.8/bin/darwin/amd64/kubectl
curl -LOJ https://github.com/qlik-oss/sense-installer/releases/latest/download/qliksense-darwin-amd64
sudo mv qliksense-darwin-amd64 kubectl /usr/local/bin
sudo chmod ugo+x /usr/local/bin/qliksense-darwin-amd64 /usr/local/bin/kubectl
sudo ln -s /usr/local/bin/qliksense-darwin-amd64 /usr/local/bin/qliksense
sudo ln -s /usr/local/bin/qliksense-darwin-amd64 /usr/local/bin/kubectl-qliksense
curl -Lo qliksense https://github.com/qlik-oss/sense-installer/releases/download/v0.7.0/qliksense-darwin-amd64
chmod +x qliksense
sudo mv qliksense /usr/local/bin
```
=== "Windows"
??? tldr "Windows"
Download Windows executable and add it in your `PATH` as `qliksense.exe`
``` powershell
# powershell
[https://github.com/qlik-oss/sense-installer/releases/download/v0.7.0/qliksense-windows-amd64.exe](https://github.com/qlik-oss/sense-installer/releases/download/v0.7.0/qliksense-windows-amd64.exe)
Invoke-WebRequest https://storage.googleapis.com/kubernetes-release/release/v1.16.8/bin/windows/amd64/kubectl.exe -O C:\bin\kubectl.exe
Invoke-WebRequest https://github.com/qlik-oss/sense-installer/releases/latest/download/qliksense-windows-amd64.exe -O C:\bin\qliksense.exe
Copy-Item C:\bin\qliksense.exe C:\bin\kubectl-qliksense.exe
# Add C:\bin to current Path
$Env:Path += ";C:\bin"
# Save Path to User environment scope
[Environment]::SetEnvironmentVariable("Path",[Environment]::GetEnvironmentVariable("Path", [EnvironmentVariableTarget]::User) + ";C:\bin",[EnvironmentVariableTarget]::User)
```
## Quick start
### Setting the contexts
By default a `qlik-default` configuration context is provided and can be used, as is. In effect, this is the name of the Qlik Sense instance in the target cluster. All resources installed into the target namespace will be prefixed with `qlik-default`. The name of the Qlik Sense application will correspondingly be `qliksense`.
Ex.: To change this to `qliksense-dev`:
- To download the version `v0.0.2` from qliksense-k8s [releases](https://github.com/qlik-oss/qliksense-k8s/releases).
```shell
qliksense config set-context qliksense-dev
qliksense fetch v0.0.2
```
!!! info ""
For the purposes of the Quick Start we will be using `qlik-default`
The target namespace is determined by the kubectl connection context.
ex. Ensure a connection to cluster to change the configuration context's target namespace with kubectl to `qliksense`
- To install CRDs for QSEoK and qliksense operator into the kubernetes cluster.
```shell
kubectl config set-context --current --namespace=qliksense
qliksense crds install --all
```
!!! info ""
For the purposes of the Quick Start we will be using the default namespace. (`default`)
### Downloading a version of Qlik Sense on Kubernetes
To download the latest version of Qlik Sense on Kubernetes from qliksense-k8s
- To install QSEoK into a namespace in the kubernetes cluster where `kubectl` is pointing to.
```shell
qliksense fetch
```
#### More Options
- To download a specific version `v1.59.20` from qliksense-k8s [releases](https://github.com/qlik-oss/qliksense-k8s/releases)
```shell
qliksense fetch v1.58.20
```
- To download from a GitHub repository fork of the `qliksense-k8s` repository (master branch)
```shell
qliksense fetch --url https://github.com/bkuschel/qliksense-k8s.git master
```
### Deployment Profiles
Deployment profiles are a sets components that require sets of key/value pairs to satisfy the requirements for the generation of a Qlik Sense on Kubernetes manifest. Along with the profile name, sets of key/value pairs are provided through the Qlik Sense custom application resources (see here).
Profiles can be developed and added to the qliksense-k8s repo but is considered an advanced topic (see here) not covered here.
#### Default Profile: Docker Desktop
By default, the `docker-desktop` profile is associated with the configuration context when initially created. This profile is guaranteed to work on Docker Desktop but can generally be used on other types of Kubernetes clusters, provided that the required configuration tweaks are provided specific to the hosting requirements (Ex. storage class).
The docker-desktop profile does not have any scaling characteristics and is generally set up to have the ability to work on a reasonably powerful computer (16GB, 4 cores minimum, greater is better). It also includes a self-contained mongodb instance for non-production purposes.
Generally it doesn't require any extra configuration to work except an acceptance of the Qlik User License Agreement (QULA), which is prompted on install but can also be set in advance (having read the QULA)
```shell
qliksense config set-configs qliksense.acceptEULA="yes"
```
More information on the possible configuration parameters for docker-desktop here (see here).
!!! Info
To access an installation of the docker desktop profile in docker desktop, the host `elastic.example` needs to be added to the system host file as an alias to `127.0.0.1`
```
127.0.0.1 elastic.example
```
File location:
- Linux - `/etc/hosts`
- MacOS - `/etc/hosts`
- Windows - `C:\Windows\System32\drivers\etc\hosts`
### Installing Qlik Sense on Kubernetes
#### Custom Resource Definitions (CRDs)
Besides the CLI, a Kubernetes operator (read here) is a core component of the Qlik Sense Operator. Additionally, there are other Kubernetes operators in Qlik Sense on Kubernetes that provide other types functionality (ex. scaling). Depending on the profile chosen [(see Deployment profiles)](#deployment-profiles), additional CRDs can also be installed for third-party components (see gke-demo).
Kubernetes operators require Custom resource definitions (CRD) (read here), which are YAML schemas for custom resources (CR). The Qlik Sense application instance, corresponding to the name of the configuration context, corresponds to a CR (ex. `qlik-default`).
CRDs require cluster scope permissions and are shared cluster-wide across namespaces. These need to be installed first (if not done previously).
To install CRDs for Qlik Sense on Kubernetes into the Kubernetes cluster.
```shell
qliksense crds install
```
#### Preflight Checks
To check that your environment fullfills Qlik Sense requirements
```shell
qliksense preflight all
```
#### Qlik Sense
To install Qlik Sense into a namespace in the Kubernetes cluster where `kubectl` is pointing to
```shell
qliksense install
qliksense install --acceptEULA="yes"
```

View File

@@ -1,22 +1,15 @@
# Overview
The Qlik Sense on Kubernetes Operator CLI (`qliksense`) facilitates:
The Qlik Sense on Kubernetes CLI (`qliksense`) provides an imperative interface to many of the configurations that need to be applied against the declarative structure described in [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s).
The CLI facilitates:
- Installation of QSEoK
- Installation of Qliksense operator to manage the QSEoK installation
- Installation of qliksense operator to manage QSEoK
- Air gapped installation of QSEoK
- Cluster configuration management
- Pre-flight and Post-flight environment and configuration checks
The Qlik Sense on Kubernetes Operator CLI provides an imperative interface to many of the configurations that need to be applied against the declarative structure described in the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository
To get start quickly go to the [Getting Started page](getting_started.md).
To learn more about the internal workings of the Qlik Sense on Kubernetes Operator, go to [How CLI works](concepts.md).
!!! info ""
This is a technology preview that uses Qlik modified [kustomize](https://github.com/qlik-oss/kustomize) for Kubernetes manifests on [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository
!!! info ""
See QlikSense [edge releases on qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s/releases) repository

View File

@@ -20,22 +20,6 @@ Flags:
-v, --verbose verbose mode
```
### Run all postflight checks
This command runs all the postflight checks available.
```shell
$ qliksense postflight all
Running all postflight checks...
Postflight db migration check...
Logs from pod: qliksense-users-6977cb7788-qlgmv
{"caller":"main.go:39","environment":"qseok","error":"error parsing uri: scheme must be \"mongodb\" or \"mongodb+srv\"","level":"error","message":"failed to connect to ","timestamp":"2020-06-17T04:10:11.7891913Z","version":""}
To view more logs in this context, please run the command: kubectl logs -n test_ns qliksense-users-6977cb7788-qlgmv migration
PASSED
All postflight checks have PASSED
```
### DB migration check
This command checks init containers for successful database migrarion completions, and reports failure, if any to the user.
@@ -45,7 +29,5 @@ An example run of this check produces an output as shown below:
$ qliksense postflight db-migration-check
Logs from pod: qliksense-users-6977cb7788-cxxwh
{"caller":"main.go:39","environment":"qseok","error":"error parsing uri: scheme must be \"mongodb\" or \"mongodb+srv\"","level":"error","message":"failed to connect to ","timestamp":"2020-06-01T01:07:18.4170507Z","version":""}
To view more logs in this context, please run the command: kubectl logs -n test_ns qliksense-users-6977cb7788-qlgmv migration
PASSED
Postflight db_migration_check completed
```

View File

@@ -305,21 +305,3 @@ Removing mongo check components...
Preflight cleanup complete
```
### Verify-ca-chain check
We use the command below to verify the ca certificate chain and server certificate. We run this check over mongodbUrl and discoveryUrl we inferred from idpconfigs in the CR.
```shell
$ qliksense preflight preflight verify-ca-chain -v
Preflight verify-ca-chain check...
-----------------------------------
Openssl verify mongodbUrl:
Mongodb url inferred form CR: <mongodbUrl_from_CR>
Host: <host extracted from mongodbUrl>
Openssl verify discoveryUrl:
Discovery url: <discoveryUrl_from_CR>
Host: <host extracted from discoveryUrl>
Completed preflight verify-CA-chain check
PASSED
```

7
go.mod
View File

@@ -10,7 +10,7 @@ replace (
k8s.io/client-go => k8s.io/client-go v0.17.0
k8s.io/kubectl => k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd
sigs.k8s.io/kustomize/api => github.com/qlik-oss/kustomize/api v0.3.3-0.20200612023448-4c1f2f38ea9b
sigs.k8s.io/kustomize/api => github.com/qlik-oss/kustomize/api v0.3.3-0.20200604075616-c46582ece468
)
require (
@@ -22,7 +22,7 @@ require (
github.com/bugsnag/bugsnag-go v1.5.3 // indirect
github.com/containers/image/v5 v5.1.0
github.com/docker/go-metrics v0.0.1 // indirect
github.com/go-git/go-git/v5 v5.4.2
github.com/go-git/go-git/v5 v5.0.0
github.com/gobuffalo/envy v1.9.0 // indirect
github.com/gobuffalo/logger v1.0.3 // indirect
github.com/gobuffalo/packd v1.0.0 // indirect
@@ -40,7 +40,7 @@ require (
github.com/mitchellh/go-homedir v1.1.0
github.com/otiai10/copy v1.1.1
github.com/pkg/errors v0.9.1
github.com/qlik-oss/k-apis v0.1.16
github.com/qlik-oss/k-apis v0.1.5
github.com/robfig/cron/v3 v3.0.1
github.com/rogpeppe/go-internal v1.5.2 // indirect
github.com/spf13/cobra v0.0.6
@@ -55,7 +55,6 @@ require (
k8s.io/apiextensions-apiserver v0.17.2
k8s.io/apimachinery v0.17.2
k8s.io/client-go v11.0.0+incompatible
k8s.io/kubectl v0.17.2
sigs.k8s.io/kustomize/api v0.3.2
sigs.k8s.io/yaml v1.1.0
)

18
go.sum
View File

@@ -309,8 +309,6 @@ github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp
github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
github.com/go-git/go-git/v5 v5.0.0 h1:k5RWPm4iJwYtfWoxIJy4wJX9ON7ihPeZZYC1fLYDnpg=
github.com/go-git/go-git/v5 v5.0.0/go.mod h1:oYD8y9kWsGINPFJoLdaScGCN6dlKg23blmClfZwtUVA=
github.com/go-git/go-git/v5 v5.1.0 h1:HxJn9g/E7eYvKW3Fm7Jt4ee8LXfPOm/H1cdDu8vEssk=
github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
@@ -473,6 +471,10 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-replayers/grpcreplay v0.1.0 h1:eNb1y9rZFmY4ax45uEEECSa8fsxGRU+8Bil52ASAwic=
github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
github.com/google/go-replayers/httpreplay v0.1.0 h1:AX7FUb4BjrrzNvblr/OlgwrmFiep6soj5K2QSDW7BGk=
@@ -622,8 +624,6 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@@ -885,10 +885,10 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/qlik-oss/k-apis v0.1.16 h1:R3gCZs4A3EHPNx4B7p1idWD+OhyaU/bAlGYBWc0ZNz4=
github.com/qlik-oss/k-apis v0.1.16/go.mod h1:AkNa/kaZHpGVs9l+pHe6nvz99Sp9WO1f9ylBES95o+I=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200612023448-4c1f2f38ea9b h1:RDh3OZJOriy/ap1NUHVKsPG07N4DALaCzaqXFFK57T0=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200612023448-4c1f2f38ea9b/go.mod h1:zh3yFgE5zFk1kreqzVyyj1eXyIxQJT53l4zSg8Wt4SA=
github.com/qlik-oss/k-apis v0.1.5 h1:IeqHuF1IIQCsuSmsUhL7GjdfkOFsNgh3z2UyX59GTsk=
github.com/qlik-oss/k-apis v0.1.5/go.mod h1:yoYGgPJ/H0t9H3NSq64dWfyQY6QWi2L9c+hCJoVO03U=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200604075616-c46582ece468 h1:fAo9wsK2zNiKkOAo0OPCrrrtt/X1Y5W5YEx0t/Y4jwg=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200604075616-c46582ece468/go.mod h1:zh3yFgE5zFk1kreqzVyyj1eXyIxQJT53l4zSg8Wt4SA=
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
@@ -1166,6 +1166,8 @@ golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8ou
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=

View File

@@ -1,13 +1,11 @@
site_name: Qlik Sense on Kubernetes CLI
repo_url: 'https://github.com/qlik-oss/sense-installer'
strict: true
theme:
name: "material"
palette:
primary: 'green'
accent: 'indigo'
markdown_extensions:
- toc:
permalink: true
@@ -16,8 +14,6 @@ markdown_extensions:
- pymdownx.inlinehilite
- pymdownx.superfences
- pymdownx.details
- pymdownx.tabbed
nav:
- Overview: index.md
- getting_started.md

View File

@@ -144,17 +144,17 @@ func (cr *QliksenseCR) IsRepoExist() bool {
}
func (cr *QliksenseCR) GetFetchUrl() string {
if cr.Spec.Git == nil || cr.Spec.Git.Repository == "" {
if cr.Spec.FetchSource == nil || cr.Spec.FetchSource.Repository == "" {
return QLIK_GIT_REPO
}
return cr.Spec.Git.Repository
return cr.Spec.FetchSource.Repository
}
func (cr *QliksenseCR) GetFetchAccessToken(encryptionKey string) string {
if cr.Spec.Git == nil {
if cr.Spec.FetchSource == nil {
return ""
}
if tok, err := cr.Spec.Git.GetAccessToken(); err != nil {
if tok, err := cr.Spec.FetchSource.GetAccessToken(); err != nil {
fmt.Println(err)
return ""
} else if tok == "" {
@@ -171,29 +171,29 @@ func (cr *QliksenseCR) GetFetchAccessToken(encryptionKey string) string {
}
func (cr *QliksenseCR) SetFetchUrl(url string) {
if cr.Spec.Git == nil {
cr.Spec.Git = &config.Repo{}
if cr.Spec.FetchSource == nil {
cr.Spec.FetchSource = &config.Repo{}
}
cr.Spec.Git.Repository = url
cr.Spec.FetchSource.Repository = url
}
func (cr *QliksenseCR) SetFetchAccessToken(token, encryptionKey string) error {
if cr.Spec.Git == nil {
cr.Spec.Git = &config.Repo{}
if cr.Spec.FetchSource == nil {
cr.Spec.FetchSource = &config.Repo{}
}
res, err := EncryptData([]byte(token), encryptionKey)
if err != nil {
return err
}
cr.Spec.Git.AccessToken = b64.StdEncoding.EncodeToString(res)
cr.Spec.FetchSource.AccessToken = b64.StdEncoding.EncodeToString(res)
return nil
}
func (cr *QliksenseCR) SetFetchAccessSecretName(sec string) {
if cr.Spec.Git == nil {
cr.Spec.Git = &config.Repo{}
if cr.Spec.FetchSource == nil {
cr.Spec.FetchSource = &config.Repo{}
}
cr.Spec.Git.SecretName = sec
cr.Spec.FetchSource.SecretName = sec
}
//DeleteRepo delete the manifest repo and unset manifestsRoot
@@ -524,9 +524,9 @@ func (qc *QliksenseConfig) GetDecryptedCr(cr *QliksenseCR) (*QliksenseCR, error)
}
newCr.Spec.Secrets = finalSecrets
if newCr.Spec.Git != nil && newCr.Spec.Git.AccessToken != "" {
if newCr.Spec.FetchSource != nil && newCr.Spec.FetchSource.AccessToken != "" {
decData := cr.GetFetchAccessToken(encryptionKey)
newCr.Spec.Git.AccessToken = decData
newCr.Spec.FetchSource.AccessToken = decData
}
return newCr, nil
}

View File

@@ -127,7 +127,7 @@ func TestGetDecryptedCr(t *testing.T) {
if decryptedValue == orignalValue {
t.Fail()
}
if newCr.Spec.Git.AccessToken != "mytoken" {
if newCr.Spec.FetchSource.AccessToken != "mytoken" {
t.Fail()
}
td()

View File

@@ -371,6 +371,7 @@ func (p *ClientGoUtils) GetPodContainerLogs(clientset kubernetes.Interface, pod
return "", err
}
defer podLogs.Close()
// time.Sleep(15 * time.Second)
buf := new(bytes.Buffer)
_, err = io.Copy(buf, podLogs)
if err != nil {

View File

@@ -19,7 +19,6 @@ import (
)
func TestClientGoUtils_getDeployment(t *testing.T) {
t.Parallel()
type fields struct {
Verbose bool
}
@@ -86,7 +85,6 @@ func TestClientGoUtils_getDeployment(t *testing.T) {
}
func TestClientGoUtils_DeleteDeployment(t *testing.T) {
t.Parallel()
type fields struct {
Verbose bool
}
@@ -140,7 +138,6 @@ func TestClientGoUtils_DeleteDeployment(t *testing.T) {
}
func TestClientGoUtils_GetService(t *testing.T) {
t.Parallel()
type fields struct {
Verbose bool
}
@@ -207,7 +204,6 @@ func TestClientGoUtils_GetService(t *testing.T) {
}
func TestClientGoUtils_CreatePreflightTestDeployment(t *testing.T) {
t.Parallel()
fk := fake.NewSimpleClientset()
fk.Fake.PrependReactor("create", "deployments", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, &appsv1.Deployment{}, errors.New("Error creating deployment")
@@ -307,7 +303,6 @@ func TestClientGoUtils_CreatePreflightTestDeployment(t *testing.T) {
}
func TestClientGoUtils_DeleteService(t *testing.T) {
t.Parallel()
type fields struct {
Verbose bool
}
@@ -365,7 +360,6 @@ func TestClientGoUtils_DeleteService(t *testing.T) {
}
func TestClientGoUtils_DeletePod(t *testing.T) {
t.Parallel()
type fields struct {
Verbose bool
}
@@ -423,7 +417,6 @@ func TestClientGoUtils_DeletePod(t *testing.T) {
}
func TestClientGoUtils_CreatePreflightTestPod(t *testing.T) {
t.Parallel()
fk := fake.NewSimpleClientset()
fk.Fake.PrependReactor("create", "pods", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, &apiv1.Pod{}, errors.New("Error creating pod")
@@ -508,7 +501,7 @@ func TestClientGoUtils_CreatePreflightTestPod(t *testing.T) {
ImagePullPolicy: apiv1.PullIfNotPresent,
Command: []string{"echo"},
VolumeMounts: []apiv1.VolumeMount{
{
apiv1.VolumeMount{
Name: "secret1",
MountPath: filepath.Dir("/etc/secret1"),
ReadOnly: true,
@@ -517,7 +510,7 @@ func TestClientGoUtils_CreatePreflightTestPod(t *testing.T) {
},
},
Volumes: []apiv1.Volume{
{
apiv1.Volume{
Name: "secret1",
VolumeSource: apiv1.VolumeSource{
Secret: &apiv1.SecretVolumeSource{
@@ -567,7 +560,6 @@ func TestClientGoUtils_CreatePreflightTestPod(t *testing.T) {
}
func TestClientGoUtils_getPod(t *testing.T) {
t.Parallel()
type fields struct {
Verbose bool
}
@@ -640,7 +632,6 @@ func TestClientGoUtils_getPod(t *testing.T) {
// There is an issue with mocking logs: https://github.com/kubernetes/kubernetes/issues/84203
// We are waiting for this PR: https://github.com/kubernetes/kubernetes/pull/91485/files to be merged to be able to test this feature
func TestClientGoUtils_GetPodContainerLogs(t *testing.T) {
t.SkipNow()
type fields struct {
Verbose bool
}
@@ -654,7 +645,6 @@ func TestClientGoUtils_GetPodContainerLogs(t *testing.T) {
fields fields
args args
want string
mockLog string
wantErr bool
}{
{
@@ -676,7 +666,6 @@ func TestClientGoUtils_GetPodContainerLogs(t *testing.T) {
container: "",
},
want: "blah",
mockLog: "blah",
wantErr: false,
},
}
@@ -698,8 +687,7 @@ func TestClientGoUtils_GetPodContainerLogs(t *testing.T) {
}
func TestClientGoUtils_WaitForDeployment(t *testing.T) {
t.Parallel()
waitTimeout = 10 * time.Second
waitTimeout = 30 * time.Second
dep := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "test-dep",
@@ -737,7 +725,7 @@ func TestClientGoUtils_WaitForDeployment(t *testing.T) {
pfDeployment: dep,
},
wantErr: false,
timeoutForChangingReplicaCount: 6 * time.Second,
timeoutForChangingReplicaCount: 10 * time.Second,
mockErr: false,
},
{
@@ -835,8 +823,7 @@ func TestClientGoUtils_WaitForDeployment(t *testing.T) {
}
func TestClientGoUtils_WaitForPod(t *testing.T) {
t.Parallel()
waitTimeout = 10 * time.Second
waitTimeout = 30 * time.Second
po := &apiv1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-dep",
@@ -844,7 +831,7 @@ func TestClientGoUtils_WaitForPod(t *testing.T) {
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{},
apiv1.Container{},
},
},
Status: apiv1.PodStatus{
@@ -879,7 +866,7 @@ func TestClientGoUtils_WaitForPod(t *testing.T) {
pod: po,
},
wantErr: false,
timeoutForChangingReplicaCount: 6 * time.Second,
timeoutForChangingReplicaCount: 10 * time.Second,
mockErr: false,
},
{
@@ -895,7 +882,7 @@ func TestClientGoUtils_WaitForPod(t *testing.T) {
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{},
apiv1.Container{},
},
},
Status: apiv1.PodStatus{
@@ -942,7 +929,7 @@ func TestClientGoUtils_WaitForPod(t *testing.T) {
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{},
apiv1.Container{},
},
},
Status: apiv1.PodStatus{
@@ -1050,8 +1037,7 @@ func TestClientGoUtils_WaitForPod(t *testing.T) {
}
func TestClientGoUtils_WaitForPodToDie(t *testing.T) {
t.Parallel()
waitTimeout = 10 * time.Second
waitTimeout = 30 * time.Second
po := &apiv1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-dep",
@@ -1059,7 +1045,7 @@ func TestClientGoUtils_WaitForPodToDie(t *testing.T) {
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{},
apiv1.Container{},
},
},
Status: apiv1.PodStatus{
@@ -1094,7 +1080,7 @@ func TestClientGoUtils_WaitForPodToDie(t *testing.T) {
pod: po,
},
wantErr: false,
timeoutForChangingReplicaCount: 6 * time.Second,
timeoutForChangingReplicaCount: 10 * time.Second,
mockErr: false,
},
{
@@ -1131,7 +1117,7 @@ func TestClientGoUtils_WaitForPodToDie(t *testing.T) {
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{},
apiv1.Container{},
},
},
Status: apiv1.PodStatus{
@@ -1205,8 +1191,7 @@ func TestClientGoUtils_WaitForPodToDie(t *testing.T) {
}
func TestClientGoUtils_waitForPodToDelete(t *testing.T) {
t.Parallel()
waitTimeout = 10 * time.Second
waitTimeout = 30 * time.Second
po := &apiv1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
@@ -1240,7 +1225,7 @@ func TestClientGoUtils_waitForPodToDelete(t *testing.T) {
pod: "test-pod",
},
wantErr: false,
timeoutForChangingReplicaCount: 6 * time.Second,
timeoutForChangingReplicaCount: 10 * time.Second,
},
{
name: "valid case instant",
@@ -1288,8 +1273,7 @@ func TestClientGoUtils_waitForPodToDelete(t *testing.T) {
}
func TestClientGoUtils_WaitForDeploymentToDelete(t *testing.T) {
t.Parallel()
waitTimeout = 10 * time.Second
waitTimeout = 30 * time.Second
dep := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "test-dep",
@@ -1325,7 +1309,7 @@ func TestClientGoUtils_WaitForDeploymentToDelete(t *testing.T) {
deploymentName: dep.Name,
},
wantErr: false,
timeoutForChangingReplicaCount: 6 * time.Second,
timeoutForChangingReplicaCount: 10 * time.Second,
},
{
name: "valid case instant",
@@ -1373,7 +1357,6 @@ func TestClientGoUtils_WaitForDeploymentToDelete(t *testing.T) {
}
func TestClientGoUtils_CreatePreflightTestSecret(t *testing.T) {
t.Parallel()
fk := fake.NewSimpleClientset()
fk.Fake.PrependReactor("create", "secrets", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, &apiv1.Secret{}, errors.New("Error creating deployment")
@@ -1448,7 +1431,6 @@ func TestClientGoUtils_CreatePreflightTestSecret(t *testing.T) {
}
func TestClientGoUtils_DeleteK8sSecret(t *testing.T) {
t.Parallel()
type fields struct {
Verbose bool
}
@@ -1502,7 +1484,6 @@ func TestClientGoUtils_DeleteK8sSecret(t *testing.T) {
}
func TestClientGoUtils_CreateStatefulSet(t *testing.T) {
t.Parallel()
fk := fake.NewSimpleClientset()
fk.Fake.PrependReactor("create", "statefulsets", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, &appsv1.StatefulSet{}, errors.New("Error")
@@ -1605,346 +1586,3 @@ func TestClientGoUtils_CreateStatefulSet(t *testing.T) {
})
}
}
func TestClientGoUtils_waitForStatefulSet(t *testing.T) {
t.Parallel()
waitTimeout = 10 * time.Second
ss := &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ss",
Namespace: "test-ns",
},
Status: appsv1.StatefulSetStatus{
ReadyReplicas: 0,
},
}
type fields struct {
Verbose bool
}
type args struct {
clientset kubernetes.Interface
namespace string
pfStatefulset *appsv1.StatefulSet
}
tests := []struct {
name string
fields fields
args args
wantErr bool
timeoutForChangingReplicaCount time.Duration
mockErr bool
}{
{
name: "valid case",
fields: fields{
Verbose: true,
},
args: args{
clientset: fake.NewSimpleClientset(ss),
namespace: "test-ns",
pfStatefulset: ss,
},
wantErr: false,
timeoutForChangingReplicaCount: 6 * time.Second,
mockErr: false,
},
{
name: "valid case instantly ready",
fields: fields{
Verbose: true,
},
args: args{
clientset: fake.NewSimpleClientset(&appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ss",
Namespace: "test-ns",
},
Status: appsv1.StatefulSetStatus{
ReadyReplicas: 1,
},
}),
namespace: "test-ns",
pfStatefulset: ss,
},
wantErr: false,
mockErr: false,
},
{
name: "k8s returning error case",
fields: fields{
Verbose: true,
},
args: args{
clientset: fake.NewSimpleClientset(ss),
namespace: "test-ns",
pfStatefulset: ss,
},
wantErr: true,
mockErr: true,
},
{
name: "timeout",
fields: fields{
Verbose: true,
},
args: args{
clientset: fake.NewSimpleClientset(ss),
namespace: "test-ns",
pfStatefulset: ss,
},
wantErr: true,
mockErr: false,
},
{
name: "statefulset goes missing",
fields: fields{
Verbose: true,
},
args: args{
clientset: fake.NewSimpleClientset(),
namespace: "test-ns",
pfStatefulset: ss,
},
wantErr: true,
mockErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.timeoutForChangingReplicaCount.Seconds() > 0 {
go func() {
time.Sleep(tt.timeoutForChangingReplicaCount)
tt.args.clientset.(*fake.Clientset).Fake.PrependReactor("get", "statefulsets", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ss",
Namespace: "test-ns",
},
Status: appsv1.StatefulSetStatus{
ReadyReplicas: 1,
},
}, nil
})
}()
}
if tt.mockErr {
tt.args.clientset.(*fake.Clientset).Fake.PrependReactor("get", "statefulsets", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, &appsv1.StatefulSet{}, fmt.Errorf("error")
})
}
p := &ClientGoUtils{
Verbose: tt.fields.Verbose,
}
if err := p.waitForStatefulSet(tt.args.clientset, tt.args.namespace, tt.args.pfStatefulset); (err != nil) != tt.wantErr {
t.Errorf("ClientGoUtils.waitForStatefulSet() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestClientGoUtils_getStatefulset(t *testing.T) {
t.Parallel()
type fields struct {
Verbose bool
}
type args struct {
clientset kubernetes.Interface
namespace string
statefulsetName string
}
tests := []struct {
name string
fields fields
args args
want *appsv1.StatefulSet
wantErr bool
}{
{
name: "valid case",
fields: fields{Verbose: true},
args: args{
clientset: fake.NewSimpleClientset(&appsv1.StatefulSet{
ObjectMeta: v1.ObjectMeta{
Name: "test-ss",
Namespace: "test-ns",
},
}),
namespace: "test-ns",
statefulsetName: "test-ss",
},
want: &appsv1.StatefulSet{
ObjectMeta: v1.ObjectMeta{
Name: "test-ss",
Namespace: "test-ns",
},
},
wantErr: false,
},
{
name: "retrieve non-existent ss",
fields: fields{Verbose: true},
args: args{
clientset: fake.NewSimpleClientset(),
namespace: "test-ns",
statefulsetName: "test-ss",
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &ClientGoUtils{
Verbose: tt.fields.Verbose,
}
got, err := p.getStatefulset(tt.args.clientset, tt.args.namespace, tt.args.statefulsetName)
if (err != nil) != tt.wantErr {
t.Errorf("ClientGoUtils.getStatefulset() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ClientGoUtils.getStatefulset() = %v, want %v", got, tt.want)
}
})
}
}
func TestClientGoUtils_deleteStatefulSet(t *testing.T) {
t.Parallel()
type fields struct {
Verbose bool
}
type args struct {
clientset kubernetes.Interface
namespace string
name string
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "valid case",
fields: fields{Verbose: true},
args: args{
clientset: fake.NewSimpleClientset(&appsv1.StatefulSet{
ObjectMeta: v1.ObjectMeta{
Name: "test-ss",
Namespace: "test-ns",
},
}),
name: "test-ss",
namespace: "test-ns",
},
wantErr: false,
},
{
name: "delete non-existent ss case",
fields: fields{Verbose: true},
args: args{
clientset: fake.NewSimpleClientset(),
name: "test-ss",
namespace: "test-ns",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &ClientGoUtils{
Verbose: tt.fields.Verbose,
}
if err := p.deleteStatefulSet(tt.args.clientset, tt.args.namespace, tt.args.name); (err != nil) != tt.wantErr {
t.Errorf("ClientGoUtils.deleteStatefulSet() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestClientGoUtils_waitForStatefulsetToDelete(t *testing.T) {
t.Parallel()
waitTimeout = 10 * time.Second
ss := &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ss",
Namespace: "test-ns",
},
Status: appsv1.StatefulSetStatus{
ReadyReplicas: 0,
},
}
type fields struct {
Verbose bool
}
type args struct {
clientset kubernetes.Interface
namespace string
statefulsetName string
}
tests := []struct {
name string
fields fields
args args
wantErr bool
timeoutForChangingReplicaCount time.Duration
}{
{
name: "valid case",
fields: fields{
Verbose: true,
},
args: args{
clientset: fake.NewSimpleClientset(ss),
namespace: "test-ns",
statefulsetName: ss.Name,
},
wantErr: false,
timeoutForChangingReplicaCount: 6 * time.Second,
},
{
name: "valid case instant",
fields: fields{
Verbose: true,
},
args: args{
clientset: fake.NewSimpleClientset(),
namespace: "test-ns",
statefulsetName: ss.Name,
},
wantErr: false,
},
{
name: "timeout",
fields: fields{
Verbose: true,
},
args: args{
clientset: fake.NewSimpleClientset(ss),
namespace: "test-ns",
statefulsetName: ss.Name,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.timeoutForChangingReplicaCount.Seconds() > 0 {
go func() {
time.Sleep(tt.timeoutForChangingReplicaCount)
tt.args.clientset.(*fake.Clientset).Fake.PrependReactor("get", "statefulsets", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, &appsv1.StatefulSet{}, fmt.Errorf("error")
})
}()
}
p := &ClientGoUtils{
Verbose: tt.fields.Verbose,
}
if err := p.waitForStatefulsetToDelete(tt.args.clientset, tt.args.namespace, tt.args.statefulsetName); (err != nil) != tt.wantErr {
t.Errorf("ClientGoUtils.waitForStatefulsetToDelete() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@@ -7,7 +7,6 @@ import (
"io/ioutil"
"log"
"os"
"strings"
"github.com/qlik-oss/k-apis/pkg/config"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -23,6 +22,7 @@ const (
QliksenseKind = "Qliksense"
QliksenseGroup = "qlik.com"
QliksenseDefaultProfile = "docker-desktop"
DefaultRotateKeys = "yes"
QliksenseMetadataName = "QliksenseConfigMetadata"
DefaultMongodbUri = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
DefaultMongodbUriKey = "mongodbUri"
@@ -37,9 +37,10 @@ func (qliksenseCR *QliksenseCR) AddCommonConfig(contextName string) {
})
qliksenseCR.SetName(contextName)
qliksenseCR.Spec = &config.CRSpec{
Profile: QliksenseDefaultProfile,
Profile: QliksenseDefaultProfile,
RotateKeys: DefaultRotateKeys,
}
qliksenseCR.Spec.AddToSecrets("qliksense", DefaultMongodbUriKey, strings.Replace(DefaultMongodbUri, "qlik-default", contextName, 1), "")
qliksenseCR.Spec.AddToSecrets("qliksense", DefaultMongodbUriKey, DefaultMongodbUri, "")
}
// AddBaseQliksenseConfigs adds configs into config.yaml

View File

@@ -2,7 +2,6 @@ package api
import (
"reflect"
"strings"
"testing"
"github.com/qlik-oss/k-apis/pkg/config"
@@ -23,11 +22,12 @@ func TestAddCommonConfig(t *testing.T) {
q.SetName("myqliksense")
q.SetGroupVersionKind(gvk)
q.Spec = &config.CRSpec{
Profile: QliksenseDefaultProfile,
Profile: QliksenseDefaultProfile,
RotateKeys: DefaultRotateKeys,
Secrets: map[string]config.NameValues{
"qliksense": []config.NameValue{{
Name: DefaultMongodbUriKey,
Value: strings.Replace(DefaultMongodbUri, "qlik-default", "myqliksense", 1),
Value: DefaultMongodbUri,
},
},
},

View File

@@ -5,7 +5,6 @@ import (
"os"
"path"
"path/filepath"
"regexp"
"testing"
kapis_git "github.com/qlik-oss/k-apis/pkg/git"
@@ -61,7 +60,7 @@ func TestCopyDirectory_withGit_withKuz(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
if err := kapis_git.Checkout(repo2, "v0.0.8", "", nil); err != nil {
if err := kapis_git.Checkout(repo2, "v0.0.2", "", nil); err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -70,7 +69,7 @@ func TestCopyDirectory_withGit_withKuz(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
if err := kapis_git.Checkout(repo1, "v0.0.8", "", nil); err != nil {
if err := kapis_git.Checkout(repo1, "v0.0.2", "", nil); err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -79,15 +78,9 @@ func TestCopyDirectory_withGit_withKuz(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
re, err := regexp.Compile(`name: qliksense-ca-certificates-[a-z]{5}`)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
repo1ManifestTweaked := re.ReplaceAllString(string(repo1Manifest), "name: qliksense-ca-certificates")
repo2ManifestTweaked := re.ReplaceAllString(string(repo2Manifest), "name: qliksense-ca-certificates")
if repo2ManifestTweaked != repo1ManifestTweaked {
t.Logf("manifest generated on the original config:\n%v", repo1ManifestTweaked)
t.Logf("manifest generated on the copied config:\n%v", repo2ManifestTweaked)
if string(repo2Manifest) != string(repo1Manifest) {
t.Logf("manifest generated on the original config:\n%v", string(repo1Manifest))
t.Logf("manifest generated on the copied config:\n%v", string(repo2Manifest))
t.Fatal("expected manifests to be equal, but they were not")
}
}

View File

@@ -131,8 +131,8 @@ func (p *PreflightConfig) Initialize() error {
}
p.AddMinK8sV("1.15")
p.AddMinMongoV("3.6")
p.AddImage("nginx", "nginx:1.19.0-alpine")
p.AddImage("netcat", "qlik-docker-oss.bintray.io/preflight-netcat:v1.0.0")
p.AddImage("preflight-mongo", "qlik-docker-oss.bintray.io/preflight-mongo:v1.0.0")
p.AddImage("nginx", "nginx")
p.AddImage("netcat", "subfuzion/netcat")
p.AddImage("preflight-mongo", "qlik-docker-oss.bintray.io/preflight-mongo")
return p.Write()
}

View File

@@ -1,31 +0,0 @@
package postflight
import (
"fmt"
. "github.com/logrusorgru/aurora"
ansi "github.com/mattn/go-colorable"
"github.com/pkg/errors"
)
func (qp *QliksensePostflight) RunAllPostflightChecks(namespace string, kubeConfigContents []byte, preflightOpts *PostflightOptions) error {
checkCount := 0
totalCount := 0
out := ansi.NewColorableStdout()
// Postflight db migration check
if err := qp.DbMigrationCheck(namespace, kubeConfigContents); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++
}
totalCount++
if checkCount == totalCount {
// All postflight checks were successful
return nil
}
return errors.New("1 or more postflight checks have FAILED")
}

View File

@@ -11,8 +11,7 @@ import (
const initContainerNameToCheck = "migration"
func (p *QliksensePostflight) DbMigrationCheck(namespace string, kubeConfigContents []byte) error {
fmt.Printf("Postflight db migration check... \n")
p.CG.LogVerboseMessage("\n----------------------------------- \n")
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
if err != nil {
err = fmt.Errorf("unable to create a kubernetes client: %v", err)

View File

@@ -15,100 +15,90 @@ func (qp *QliksensePreflight) RunAllPreflightChecks(kubeConfigContents []byte, n
out := ansi.NewColorableStdout()
// Preflight minimum kuberenetes version check
if err := qp.CheckK8sVersion(namespace, kubeConfigContents); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight kubernetes minimum version check FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
fmt.Fprintf(out, "%s\n\n", Green("Preflight kubernetes minimum version check PASSED"))
checkCount++
}
totalCount++
// Preflight deployment check
if err := qp.CheckDeployment(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight deployment check FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
fmt.Fprintf(out, "%s\n\n", Green("Preflight deployment check PASSED"))
checkCount++
}
totalCount++
// Preflight service check
if err := qp.CheckService(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight service check FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
fmt.Fprintf(out, "%s\n\n", Green("Preflight service check PASSED"))
checkCount++
}
totalCount++
// Preflight pod check
if err := qp.CheckPod(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight pod check FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
fmt.Fprintf(out, "%s\n\n", Green("Preflight pod check PASSED"))
checkCount++
}
totalCount++
// Preflight role check
if err := qp.CheckCreateRole(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red("Preflight role check FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
fmt.Fprintf(out, "%s\n\n", Green("Preflight role check PASSED"))
checkCount++
}
totalCount++
// Preflight rolebinding check
if err := qp.CheckCreateRoleBinding(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red(" Preflight rolebinding check FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
fmt.Fprintf(out, "%s\n\n", Green("Preflight rolebinding check PASSED"))
checkCount++
}
totalCount++
// Preflight serviceaccount check
if err := qp.CheckCreateServiceAccount(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red(" Preflight serviceaccount check FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
fmt.Fprintf(out, "%s\n\n", Green("Preflight serviceaccount check PASSED"))
checkCount++
}
totalCount++
// Preflight mongo check
if err := qp.CheckMongo(kubeConfigContents, namespace, preflightOpts, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red(" Preflight mongo check FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
fmt.Fprintf(out, "%s\n\n", Green("Preflight mongo check PASSED"))
checkCount++
}
totalCount++
// Preflight DNS check
if err := qp.CheckDns(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Fprintf(out, "%s\n", Red(" Preflight DNS check FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++
}
totalCount++
// Preflight verify ca chain check
if err := qp.VerifyCAChain(kubeConfigContents, namespace, preflightOpts, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
fmt.Fprintf(out, "%s\n\n", Green("Preflight DNS check PASSED"))
checkCount++
}
totalCount++

View File

@@ -15,8 +15,8 @@ func (p *QliksensePreflight) CheckDeployment(namespace string, kubeConfigContent
// Deployment check
if !cleanup {
fmt.Print("Preflight deployment check... ")
p.CG.LogVerboseMessage("\n--------------------------- \n")
p.CG.LogVerboseMessage("Preflight deployment check: \n")
p.CG.LogVerboseMessage("--------------------------- \n")
}
err = p.checkPfDeployment(clientset, namespace, cleanup)
if err != nil {
@@ -38,8 +38,8 @@ func (p *QliksensePreflight) CheckService(namespace string, kubeConfigContents [
}
// Service check
if !cleanup {
fmt.Print("Preflight service check... ")
p.CG.LogVerboseMessage("\n------------------------ \n")
p.CG.LogVerboseMessage("Preflight service check: \n")
p.CG.LogVerboseMessage("------------------------ \n")
}
err = p.checkPfService(clientset, namespace, cleanup)
if err != nil {
@@ -61,8 +61,8 @@ func (p *QliksensePreflight) CheckPod(namespace string, kubeConfigContents []byt
}
// Pod check
if !cleanup {
fmt.Print("Preflight pod check... ")
p.CG.LogVerboseMessage("\n-------------------- \n")
p.CG.LogVerboseMessage("Preflight pod check: \n")
p.CG.LogVerboseMessage("-------------------- \n")
}
err = p.checkPfPod(clientset, namespace, cleanup)
if err != nil {

View File

@@ -18,8 +18,8 @@ func (p *QliksensePreflight) CheckDns(namespace string, kubeConfigContents []byt
podName := "pf-pod-1"
if !cleanup {
fmt.Print("Preflight DNS check... ")
p.CG.LogVerboseMessage("\n------------------- \n")
p.CG.LogVerboseMessage("Preflight DNS check: \n")
p.CG.LogVerboseMessage("------------------- \n")
}
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
if err != nil {

View File

@@ -22,8 +22,8 @@ const (
func (qp *QliksensePreflight) CheckMongo(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions, cleanup bool) error {
if !cleanup {
fmt.Print("Preflight mongodb check... ")
qp.CG.LogVerboseMessage("\n------------------------ \n")
qp.CG.LogVerboseMessage("Preflight mongodb check: \n")
qp.CG.LogVerboseMessage("------------------------ \n")
}
var currentCR *qapi.QliksenseCR
var err error

View File

@@ -10,6 +10,13 @@ type PreflightOptions struct {
MongoOptions *MongoOptions
}
// // LogVerboseMessage logs a verbose message
// func (p *PreflightOptions) LogVerboseMessage(strMessage string, args ...interface{}) {
// if p.Verbose || os.Getenv("QLIKSENSE_DEBUG") == "true" {
// fmt.Printf(strMessage, args...)
// }
// }
type MongoOptions struct {
MongodbUrl string
CaCertFile string

View File

@@ -14,8 +14,8 @@ import (
func (qp *QliksensePreflight) CheckCreateRole(namespace string, cleanup bool) error {
// create a Role
if !cleanup {
fmt.Print("Preflight role check... ")
qp.CG.LogVerboseMessage("\n--------------------- \n")
qp.CG.LogVerboseMessage("Preflight role check: \n")
qp.CG.LogVerboseMessage("--------------------- \n")
}
err := qp.checkCreateEntity(namespace, "Role", cleanup)
if err != nil {
@@ -30,8 +30,8 @@ func (qp *QliksensePreflight) CheckCreateRole(namespace string, cleanup bool) er
func (qp *QliksensePreflight) CheckCreateRoleBinding(namespace string, cleanup bool) error {
// create a RoleBinding
if !cleanup {
fmt.Print("Preflight rolebinding check... ")
qp.CG.LogVerboseMessage("\n---------------------------- \n")
qp.CG.LogVerboseMessage("Preflight rolebinding check: \n")
qp.CG.LogVerboseMessage("---------------------------- \n")
}
err := qp.checkCreateEntity(namespace, "RoleBinding", cleanup)
if err != nil {
@@ -46,8 +46,8 @@ func (qp *QliksensePreflight) CheckCreateRoleBinding(namespace string, cleanup b
func (qp *QliksensePreflight) CheckCreateServiceAccount(namespace string, cleanup bool) error {
// create a service account
if !cleanup {
fmt.Print("Preflight serviceaccount check... ")
qp.CG.LogVerboseMessage("\n------------------------------- \n")
qp.CG.LogVerboseMessage("Preflight serviceaccount check: \n")
qp.CG.LogVerboseMessage("------------------------------- \n")
}
err := qp.checkCreateEntity(namespace, "ServiceAccount", cleanup)
if err != nil {
@@ -95,8 +95,7 @@ func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string,
if sa != "" {
sa = strings.Replace(sa, "name: qliksense", "name: preflight", -1)
} else {
err = fmt.Errorf(`We were unable to retrieve valid %ss from running "kustomize" in your %s directory.
Please check the value in the "Profile" field of your CR. `, strings.ToLower(entityToTest), kusDir)
err := fmt.Errorf("Unable to retrieve yamls to apply on cluster from dir: %s, error: %v", kusDir, err)
return err
}
namespace = "" // namespace is handled when generating the manifests

View File

@@ -1,123 +0,0 @@
package preflight
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"net/url"
"strings"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func (qp *QliksensePreflight) VerifyCAChain(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions, cleanup bool) error {
var currentCR *qapi.QliksenseCR
var err error
qConfig := qapi.NewQConfig(qp.Q.QliksenseHome)
qConfig.SetNamespace(namespace)
fmt.Print("Preflight verify-ca-chain check... ")
qp.CG.LogVerboseMessage("\n----------------------------------- \n")
currentCR, err = qConfig.GetCurrentCR()
if err != nil {
qp.CG.LogVerboseMessage("Unable to retrieve current CR: %v\n", err)
return err
}
decryptedCR, err := qConfig.GetDecryptedCr(currentCR)
if err != nil {
qp.CG.LogVerboseMessage("An error occurred while retrieving mongodbUrl from current CR: %v\n", err)
return err
}
// infer ca certs form CR
caCertificates := strings.TrimSpace(decryptedCR.Spec.GetFromSecrets("qliksense", "caCertificates"))
fmt.Println("Openssl verify mongodbUrl:")
// infer mongodb url from CR
mongodbUrl := strings.TrimSpace(decryptedCR.Spec.GetFromSecrets("qliksense", "mongodbUri"))
qp.CG.LogVerboseMessage("Mongodb url inferred form CR: %s\n", mongodbUrl)
// parse out server and port from mongodb url and execute openssl verify
if err := qp.extractCertAndVerify(mongodbUrl, caCertificates); err != nil {
return err
}
fmt.Printf("\nOpenssl verify discoveryUrl:\n")
// infer idpConfigs form CR
idpConfigs := strings.TrimSpace(decryptedCR.Spec.GetFromSecrets("identity-providers", "idpConfigs"))
data := []map[string]interface{}{}
if err := json.Unmarshal([]byte(idpConfigs), &data); err != nil {
panic(err)
}
var discoveryUrl string
for _, idpData := range data {
discoveryUrl = idpData["discoveryUrl"].(string)
qp.CG.LogVerboseMessage("Discovery url: %s\n", discoveryUrl)
}
if err := qp.extractCertAndVerify(discoveryUrl, caCertificates); err != nil {
return err
}
qp.CG.LogVerboseMessage("Completed preflight verify-ca-chain check\n")
return nil
}
func (qp *QliksensePreflight) extractCertAndVerify(server string, caCertificates string) error {
u, err := url.Parse(server)
if err != nil {
return fmt.Errorf("unable to parse url: %v", err)
}
switch strings.ToLower(u.Scheme) {
case "http":
return fmt.Errorf("http url is not supported for this operation")
case "https":
if u.Port() == "" {
u.Host += ":443"
}
}
qp.CG.LogVerboseMessage("Host: %s, port: %s\n", u.Host, u.Port())
conn, err := tls.Dial("tcp", u.Host, &tls.Config{})
qp.CG.LogVerboseMessage("Host: %s\n", u.Host)
if err != nil {
return fmt.Errorf("failed to connect: " + err.Error())
}
defer conn.Close()
// Get the ConnectionState struct as that's the one which gives us x509.Certificate struct
x509Certificates := conn.ConnectionState().PeerCertificates
var serverCert *x509.Certificate
if len(x509Certificates) == 0 {
return fmt.Errorf("no server certificates retrieved from the server")
}
// we retrieve and verify the server certificate, we ignore intermediate certificates at this point.
for _, x509Cert := range x509Certificates {
if !x509Cert.IsCA {
serverCert = x509Cert
break
}
}
if serverCert == nil {
return fmt.Errorf("no valid server certificates retrieved from the server")
}
roots := x509.NewCertPool()
if ok := roots.AppendCertsFromPEM([]byte(caCertificates)); !ok {
return fmt.Errorf("failed to parse root certificate.")
}
opts := x509.VerifyOptions{
Roots: roots,
DNSName: u.Hostname(),
}
if _, err := serverCert.Verify(opts); err != nil {
return fmt.Errorf("failed to verify certificate: " + err.Error())
}
return nil
}

View File

@@ -9,8 +9,8 @@ import (
)
func (p *QliksensePreflight) CheckK8sVersion(namespace string, kubeConfigContents []byte) error {
fmt.Print("Preflight kubernetes version check... ")
p.CG.LogVerboseMessage("\n----------------------------------- \n")
p.CG.LogVerboseMessage("Preflight kubernetes version check: \n")
p.CG.LogVerboseMessage("----------------------------------- \n")
var currentVersion *semver.Version
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")

View File

@@ -23,12 +23,13 @@ type patch struct {
Patch string `yaml:"patch"`
}
type annotationTransformer struct {
type selectivePatch struct {
APIVersion string `yaml:"apiVersion"`
Metadata struct {
Name string `yaml:"name"`
} `yaml:"metadata"`
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
Enabled bool `yaml:"enabled"`
Patches []patch `yaml:"patches"`
}
type helmChart struct {
@@ -72,7 +73,7 @@ func (q *Qliksense) About(gitRef, profile string) (*VersionOutput, error) {
}
func (q *Qliksense) AboutDir(configDirectory, profile string) (*VersionOutput, error) {
if chartVersion, err := getChartVersion(filepath.Join(configDirectory, "manifests", "base", "transformers", "release", "annotations.yaml"), "app.kubernetes.io/version"); err != nil {
if chartVersion, err := getChartVersion(filepath.Join(configDirectory, "transformers", "qseokversion.yaml"), "qliksense"); err != nil {
return nil, err
} else if kuzManifest, err := executeKustomizeBuildWithStdoutProgress(filepath.Join(configDirectory, "manifests", profile)); err != nil {
return nil, err
@@ -222,16 +223,22 @@ func traverseYamlDecodedMapRecursively(val reflect.Value, path []string, visitor
}
}
func getChartVersion(versionFile, versionAnnotation string) (string, error) {
var annTransformer annotationTransformer
func getChartVersion(versionFile, chartName string) (string, error) {
var patchInst patch
var selPatch selectivePatch
var chart helmChart
if bytes, err := ioutil.ReadFile(versionFile); err != nil {
return "", err
} else if err = yaml.Unmarshal(bytes, &annTransformer); err != nil {
} else if err = yaml.Unmarshal(bytes, &selPatch); err != nil {
return "", err
}
if version, ok := annTransformer.Annotations[versionAnnotation]; ok {
return version, nil
for _, patchInst = range selPatch.Patches {
if err := yaml.Unmarshal([]byte(patchInst.Patch), &chart); err == nil {
if chart.ChartName == chartName {
return chart.ChartVersion, nil
}
}
}
return "", nil
}

View File

@@ -1,8 +1,61 @@
package qliksense
func (q *Qliksense) ApplyCRFromBytes(crBytes []byte, opts *InstallCommandOptions, overwriteExistingContext bool) error {
if err := q.LoadCr(crBytes, overwriteExistingContext); err != nil {
import (
"fmt"
"io"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func (q *Qliksense) ApplyCRFromReader(r io.Reader, opts *InstallCommandOptions, keepPatchFiles, overwriteExistingContext, pull, push bool) error {
if err := q.LoadCr(r, overwriteExistingContext); err != nil {
return err
}
return q.InstallQK8s("", opts)
qConfig := qapi.NewQConfig(q.QliksenseHome)
cr, err := qConfig.GetCurrentCR()
if err != nil {
return err
}
version := cr.GetLabelFromCr("version")
if pull {
fmt.Println("Pulling images...")
if err := q.PullImages(version, ""); err != nil {
return err
}
}
if push {
fmt.Println("Pushing images...")
if err := q.PushImagesForCurrentCR(); err != nil {
return err
}
}
if IsQliksenseInstalled(cr.GetName()) {
// it is needed in case want to upgrade from one version to another
if cr.Spec.ManifestsRoot == "" && cr.Spec.Git == nil {
if !qConfig.IsRepoExistForCurrent(version) {
if err := q.FetchQK8s(version); err != nil {
return err
}
}
}
return q.UpgradeQK8s(keepPatchFiles)
}
return q.InstallQK8s(version, opts, keepPatchFiles)
}
func IsQliksenseInstalled(crName string) bool {
args := []string{
"get",
"qliksense",
crName,
"-ogo-template",
`--template='{{ .metadata.name}}'`,
}
_, err := qapi.KubectlDirectOps(args, "")
if err != nil {
return false
}
return true
}

View File

@@ -5,19 +5,16 @@ import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"github.com/qlik-oss/k-apis/pkg/config"
"github.com/mitchellh/go-homedir"
"gopkg.in/yaml.v2"
"github.com/qlik-oss/k-apis/pkg/cr"
"github.com/qlik-oss/sense-installer/pkg/api"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
"k8s.io/kubectl/pkg/cmd/util/editor"
)
const (
@@ -76,21 +73,20 @@ func (q *Qliksense) configEjson() error {
}
func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error {
if err := q.configEjson(); err != nil {
return err
if qcr.Spec.RotateKeys != "None" {
if err := q.configEjson(); err != nil {
return err
}
}
userHomeDir, err := homedir.Dir()
if err != nil {
fmt.Printf(`error fetching user's home directory: %v\n`, err)
return err
}
fmt.Println("Manifests root: " + qcr.Spec.GetManifestsRoot())
qcr.SetNamespace(qapi.GetKubectlNamespace())
b, _ := yaml.Marshal(qcr.KApiCr)
fmt.Printf("%v", string(b))
// os.Exit(0)
// generate patches
cr.GeneratePatches(&qcr.KApiCr, config.KeysActionRestoreOrRotate, path.Join(userHomeDir, ".kube", "config"))
cr.GeneratePatches(&qcr.KApiCr, path.Join(userHomeDir, ".kube", "config"))
// apply generated manifests
profilePath := filepath.Join(qcr.Spec.GetManifestsRoot(), qcr.Spec.GetProfileDir())
fmt.Printf("Generating manifests for profile: %v\n", profilePath)
@@ -196,12 +192,13 @@ func (q *Qliksense) EditCR(contextName string) error {
if err := ioutil.WriteFile(tempFile.Name(), crContent, os.ModePerm); err != nil {
return nil
}
currentEditor := editor.NewDefaultEditor([]string{"KUBE_EDITOR", "EDITOR"})
if err = currentEditor.Launch(tempFile.Name()); err != nil {
cmd := exec.Command(getKubeEditorTool(), tempFile.Name())
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
err = cmd.Run()
if err != nil {
return err
}
newCr, err := qapi.GetCRObject(tempFile.Name())
if err != nil {
return errors.New("cannot save the cr. Someting wrong in the file format. It is not saved\n" + err.Error())
@@ -216,3 +213,11 @@ func (q *Qliksense) EditCR(contextName string) error {
}
return nil
}
func getKubeEditorTool() string {
editor := os.Getenv("KUBE_EDITOR")
if editor == "" {
editor = "vim"
}
return editor
}

View File

@@ -176,6 +176,53 @@ func caseInsenstiveFieldByName(v reflect.Value, name string) reflect.Value {
return v.FieldByNameFunc(func(n string) bool { return strings.ToLower(n) == name })
}
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 "opsrunner":
crSpec.Spec.OpsRunner = &config.OpsRunner{}
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
@@ -193,7 +240,11 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
}
for _, arg := range args {
if strings.HasPrefix(arg, "git.") {
if strings.HasPrefix(arg, "fetchSource.") {
if err := q.processSetFetchSource(arg, qliksenseCR); err != nil {
return err
}
} else if strings.HasPrefix(arg, "git.") {
if err := q.processSetGit(arg, qliksenseCR); err != nil {
return err
}
@@ -222,34 +273,64 @@ func processSetSingleArg(arg string, cr *api.QliksenseCR) error {
cr.Spec.Profile = nv[1]
case "storageClassName":
cr.Spec.StorageClassName = nv[1]
case "rotateKeys":
valid := false
for _, v := range []string{"yes", "no", "None"} {
if nv[1] == v {
valid = true
}
}
if !valid {
return errors.New("please povide rotateKeys=yes|no|None")
}
cr.Spec.RotateKeys = nv[1]
default:
return errors.New("Please enter one of: profile, storageClassName, manifestRoot, git to configure the current context")
return errors.New("Please enter one of: profile, storageClassName,rotateKeys, manifestRoot to configure the current context")
}
return nil
}
func (q *Qliksense) processSetGit(arg string, cr *api.QliksenseCR) error {
s := strings.Split(arg, "=")
tArg0 := strings.TrimSpace(s[0])
tArg1 := strings.TrimSpace(s[1])
subs := strings.Split(tArg0, ".")
if cr.Spec.Git == nil {
cr.Spec.Git = &config.Repo{}
func (q *Qliksense) processSetFetchSource(arg string, cr *api.QliksenseCR) error {
args := strings.Split(arg, "=")
subs := strings.Split(args[0], ".")
if cr.Spec.FetchSource == nil {
cr.Spec.FetchSource = &config.Repo{}
}
switch subs[1] {
case "repository":
cr.Spec.Git.Repository = tArg1
cr.Spec.FetchSource.Repository = args[1]
case "accessToken":
qConfig := api.NewQConfig(q.QliksenseHome)
key, err := qConfig.GetEncryptionKeyFor(cr.GetName())
if err != nil {
return err
}
return cr.SetFetchAccessToken(tArg1, key)
return cr.SetFetchAccessToken(args[1], key)
case "secretName":
cr.Spec.Git.SecretName = tArg1
cr.Spec.FetchSource.SecretName = args[1]
case "userName":
cr.Spec.Git.UserName = tArg1
cr.Spec.FetchSource.UserName = args[1]
default:
return errors.New(arg + " does not match any cr spec")
}
return nil
}
func (q *Qliksense) processSetGit(arg string, cr *api.QliksenseCR) error {
args := strings.Split(arg, "=")
subs := strings.Split(args[0], ".")
if cr.Spec.Git == nil {
cr.Spec.Git = &config.Repo{}
}
switch subs[1] {
case "repository":
cr.Spec.Git.Repository = args[1]
case "accessToken":
cr.Spec.Git.AccessToken = args[1]
case "secretName":
cr.Spec.Git.SecretName = args[1]
case "userName":
cr.Spec.Git.UserName = args[1]
default:
return errors.New(arg + " does not match any cr spec")
}
@@ -438,8 +519,8 @@ func (q *Qliksense) SetUpQliksenseContext(contextName string) error {
return err
}
// set the encrypted default mongo for the context in current CR
return q.SetSecrets([]string{fmt.Sprintf("qliksense.mongodbUri=mongodb://%s-mongodb:27017/qliksense?ssl=false", contextName)}, false, false)
// set the encrypted default mongo
return q.SetSecrets([]string{`qliksense.mongodbUri="mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"`}, false, false)
}
func validateInput(input string) (string, error) {

View File

@@ -96,6 +96,7 @@ metadata:
name: qlik-default
spec:
profile: docker-desktop
rotateKeys: "yes"
releaseName: qlik-default
`
qlikDefaultContext := "qlik-default"
@@ -243,7 +244,7 @@ func TestSetOtherConfigs(t *testing.T) {
q: &Qliksense{
QliksenseHome: testDir,
},
args: []string{"profile=minikube", "storageClassName=efs", "opsRunner.enabled=yes", "opsRunner.schedule=30 * * * *", "git.repository=master", "git.userName=foo", "git.accessToken=1234"},
args: []string{"profile=minikube", "rotateKeys=yes", "storageClassName=efs", "opsRunner.enabled=yes", "opsRunner.schedule=30 * * * *", "git.repository=master", "git.userName=foo", "git.accessToken=1234"},
},
wantErr: false,
},
@@ -253,7 +254,7 @@ func TestSetOtherConfigs(t *testing.T) {
q: &Qliksense{
QliksenseHome: testDir,
},
args: []string{"someconfig=somevalue, opsRunner.schedule=bar", "opsRunner.enabled=bar", "git.foo=bar"},
args: []string{"someconfig=somevalue, opsRunner.schedule=bar", "opsRunner.enabled=bar", "git.foo=bar", "rotateKeys=bar"},
},
wantErr: true,
},
@@ -743,6 +744,7 @@ metadata:
name: qlik-default
spec:
profile: docker-desktop
rotateKeys: "yes"
releaseName: qlik-default
`
qlikDefaultContext := "qlik-default"
@@ -761,6 +763,7 @@ metadata:
name: qlik1
spec:
profile: docker-desktop
rotateKeys: "yes"
releaseName: qlik1`
contextYaml2 :=
@@ -771,6 +774,7 @@ metadata:
name: qlik2
spec:
profile: docker-desktop
rotateKeys: "yes"
releaseName: qlik2`
contextsDir := filepath.Join(testDir, contexts, "qlik1")

View File

@@ -38,11 +38,7 @@ func unsetAll(qHome string, args []string) error {
}
// delete key inside configs if present
// delete key inside secrets if present
if isRemoved = unsetServiceKey(arg, qcr); isRemoved {
//return qConfig.WriteCR(qcr)
continue
}
if isRemoved = unsetTopAttrKey(arg, qcr); !isRemoved {
if isRemoved = unsetServiceKey(arg, qcr); !isRemoved {
return fmt.Errorf("%s not found in the context", arg)
}
}
@@ -53,7 +49,7 @@ func unsetOnlyKey(key string, qcr *api.QliksenseCR) bool {
v := reflect.ValueOf(qcr.Spec).Elem().FieldByName(strings.Title(key))
if v.IsValid() && v.CanSet() {
v.Set(reflect.Zero(v.Type()))
v.SetString("")
return true
}
return false
@@ -103,21 +99,6 @@ func unsetServiceKey(svcKey string, qcr *api.QliksenseCR) bool {
return false
}
func unsetTopAttrKey(attKey string, qcr *api.QliksenseCR) bool {
sk := strings.Split(attKey, ".")
attStruct := sk[0]
key := sk[1]
attV := reflect.ValueOf(qcr.Spec).Elem().FieldByName(strings.Title(attStruct))
if !attV.IsValid() || attV.IsZero() || attV.IsNil() {
return false
}
v := attV.Elem().FieldByName(strings.Title(key))
if v.IsValid() && v.CanSet() {
v.Set(reflect.Zero(v.Type()))
return true
}
return false
}
func findIndex(elem string, nvs kconfig.NameValues) int {
for i, nv := range nvs {
if nv.Name == elem {

View File

@@ -16,9 +16,7 @@ func TestUnsetAll(t *testing.T) {
testPepareDir(qHome)
defer os.RemoveAll(qHome)
//fmt.Print(qHome)
args := []string{"qliksense", "qliksense2.acceptEula3", "serviceA.acceptEula", "opsRunner.watchBranch"}
//args := []string{"opsRunner"}
//args := []string{"opsRunner.watchBranch"}
args := []string{"rotateKeys", "qliksense", "qliksense2.acceptEula3", "serviceA.acceptEula"}
if err := unsetAll(qHome, args); err != nil {
t.Log("error during unset", err)
t.FailNow()
@@ -29,6 +27,10 @@ func TestUnsetAll(t *testing.T) {
t.Log("error while getting current cr", err)
t.FailNow()
}
if qcr.Spec.RotateKeys != "" {
t.Log("Expected empty rotateKeys but got: " + qcr.Spec.RotateKeys)
t.Fail()
}
if qcr.Spec.Configs["qliksense"] != nil {
t.Log("qliksense in configs not deleted")
@@ -42,14 +44,6 @@ func TestUnsetAll(t *testing.T) {
t.Log("serviceA not deleted")
t.Fail()
}
if qcr.Spec.OpsRunner == nil {
t.Log("opsRunner not deleted")
t.Fail()
}
if qcr.Spec.OpsRunner.WatchBranch != "" {
t.Log("opsRunner.watchBranch not deleted")
t.Fail()
}
}
func testPepareDir(qHome string) {
@@ -78,9 +72,7 @@ metadata:
name: qlik-default
spec:
profile: docker-desktop
opsRunner:
enabled: "yes"
watchBranch: something
rotateKeys: "yes"
configs:
qliksense:
- name: acceptEula

View File

@@ -93,15 +93,11 @@ func getQliksenseInitCrds(qcr *qapi.QliksenseCR) (string, error) {
}
}
qInitMsPath := filepath.Join(repoPath, "manifests", qcr.Spec.Profile, "crds")
qInitMsPath := filepath.Join(repoPath, Q_INIT_CRD_PATH)
if _, err := os.Lstat(qInitMsPath); err != nil {
qInitMsPath = filepath.Join(repoPath, Q_INIT_CRD_PATH)
if _, err := os.Lstat(qInitMsPath); err != nil {
// older version of qliksense-init used
qInitMsPath = filepath.Join(repoPath, "manifests/base/manifests/qliksense-init")
}
// older version of qliksense-init used
qInitMsPath = filepath.Join(repoPath, "manifests/base/manifests/qliksense-init")
}
qInitByte, err := ExecuteKustomizeBuild(qInitMsPath)
if err != nil {
fmt.Println("cannot generate crds for qliksense-init", err)

View File

@@ -30,14 +30,19 @@ const (
func (q *Qliksense) PullImages(version, profile string) error {
qConfig := qapi.NewQConfig(q.QliksenseHome)
if version != "" {
if !qConfig.IsRepoExistForCurrent(version) {
if err := q.FetchQK8s(version); err != nil {
return err
}
}
}
qcr, err := qConfig.GetCurrentCR()
if err != nil {
return err
}
if !qcr.IsRepoExist() {
if err := fetchAndUpdateCR(qConfig, version); err != nil {
return err
}
return errors.New("ManifestsRoot not found")
}
if profile != "" {
qcr.Spec.Profile = profile
@@ -155,10 +160,7 @@ func (q *Qliksense) PushImagesForCurrentCR() error {
qcr, err := qConfig.GetCurrentCR()
if err != nil {
return err
} else if err := ensureImageRegistrySetInCR(qcr); err != nil {
return err
}
version := qcr.GetLabelFromCr("version")
profile := qcr.Spec.Profile
repoDir := qcr.Spec.ManifestsRoot
@@ -342,20 +344,3 @@ func (q *Qliksense) writeVersionOutput(versionOut *VersionOutput, imagesDir, ver
}
return nil
}
func validatePullPushFlagsOnInstall(cr *qapi.QliksenseCR, pull, push bool) error {
if pull && !push {
fmt.Printf("WARNING: pulling images without pushing them\n")
}
if push {
return ensureImageRegistrySetInCR(cr)
}
return nil
}
func ensureImageRegistrySetInCR(cr *qapi.QliksenseCR) error {
if registry := cr.Spec.GetImageRegistry(); registry == "" {
return errors.New("no image registry set in the CR; to set it use: qliksense config set-image-registry")
}
return nil
}

View File

@@ -235,17 +235,17 @@ spec:
t.Fatal("expected to find the GitOps image in the list, but it wasn't there")
}
if !haveMatchingImage(func(image string) bool {
return strings.Contains(image, "nginx")
return image == "nginx"
}) {
t.Fatal("expected to find the nginx Preflight image in the list, but it wasn't there")
}
if !haveMatchingImage(func(image string) bool {
return strings.Contains(image, "preflight-netcat")
return image == "subfuzion/netcat"
}) {
t.Fatal("expected to find the netcat Preflight image in the list, but it wasn't there")
}
if !haveMatchingImage(func(image string) bool {
return strings.Contains(image, "qlik-docker-oss.bintray.io/preflight-mongo")
return image == "qlik-docker-oss.bintray.io/preflight-mongo"
}) {
t.Fatal("expected to find the preflight-mongo image in the list, but it wasn't there")
}
@@ -268,6 +268,7 @@ spec:
- name: imageRegistry
value: %s
manifestsRoot: %s
rotateKeys: "yes"
releaseName: qlik-default
`, version, registry.url, manifestsRootDir)
setupQliksenseTestDefaultContext(t, tmpQlikSenseHome, cr)
@@ -314,23 +315,23 @@ spec:
return err
}
transformersDir := path.Join(manifestsRootDir, "manifests", "base", "transformers", "release")
transformersDir := path.Join(manifestsRootDir, "transformers")
if err := os.MkdirAll(transformersDir, os.ModePerm); err != nil {
return err
}
if err := ioutil.WriteFile(path.Join(transformersDir, "annotations.yaml"), []byte(`
apiVersion: builtin
kind: AnnotationsTransformer
if err := ioutil.WriteFile(path.Join(transformersDir, "qseokversion.yaml"), []byte(`
apiVersion: qlik.com/v1
kind: SelectivePatch
metadata:
name: common-annotations
annotations:
app.kubernetes.io/name: qliksense
app.kubernetes.io/instance: $(PREFIX)
app.kubernetes.io/version: 1.21.23
app.kubernetes.io/managed-by: qliksense-operator
fieldSpecs:
- path: metadata/annotations
create: true
name: qseokversion
enabled: true
patches:
- target:
kind: HelmChart
labelSelector: name!=qliksense-init
patch: |-
chartName: qliksense
chartVersion: 1.21.23
`), os.ModePerm); err != nil {
return err
}

View File

@@ -94,6 +94,7 @@ func fetchAndUpdateCR(qConfig *qapi.QliksenseConfig, version string) error {
if err != nil {
return err
}
destDir := qConfig.BuildRepoPath(version)
fmt.Printf("fetching version [%s] from %s\n", version, qcr.GetFetchUrl())
if err := qapi.CopyDirectory(tempDest, destDir); err != nil {
@@ -137,7 +138,7 @@ func getVersion(opts *FetchCommandOptions, qcr *qapi.QliksenseCR) string {
func getVerionsOverwriteConfirmation(version string) string {
reader := bufio.NewReader(os.Stdin)
fmt.Println("The version [" + version + "] already exists")
fmt.Println("The version [" + version + "] already exist")
cfm := "n"
for {
fmt.Print("Do you want to delete and fetch again [y/N]: ")

View File

@@ -16,7 +16,7 @@ func TestFetchAndUpdateCR(t *testing.T) {
}
q.SetUpQliksenseContext("test1")
qConfig := qapi.NewQConfig(tempHome)
if err := fetchAndUpdateCR(qConfig, "v0.0.8"); err != nil {
if err := fetchAndUpdateCR(qConfig, "v0.0.2"); err != nil {
t.Log(err)
t.FailNow()
}
@@ -28,8 +28,8 @@ func TestFetchAndUpdateCR(t *testing.T) {
t.FailNow()
}
if cr.Spec.ManifestsRoot != "contexts/test1/qlik-k8s/v0.0.8" {
t.Log("actual path: " + cr.Spec.ManifestsRoot + ", expected path: contexts/test1/qlik-k8s/v0.0.8")
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()
}
//testing latest tag is fetched
@@ -43,7 +43,7 @@ func TestFetchAndUpdateCR(t *testing.T) {
cr = &qapi.QliksenseCR{}
qapi.ReadFromFile(cr, actualCrFile)
v := cr.GetLabelFromCr("version")
if v == "" || v == "v0.0.8" {
if v == "" || v == "v0.0.2" {
t.Log("should get latest but got version: " + v)
t.Fail()
}

View File

@@ -5,8 +5,6 @@ import (
"fmt"
"github.com/Masterminds/semver/v3"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/qlik-oss/k-apis/pkg/git"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
@@ -24,20 +22,8 @@ func (q *Qliksense) GetInstallableVersions(opts *LsRemoteCmdOptions) error {
}
var repoPath string
var auth transport.AuthMethod
if qcr.Spec.GetManifestsRoot() != "" {
repoPath = qcr.Spec.GetManifestsRoot()
encKey, err := qConfig.GetEncryptionKeyFor(qcr.GetName())
if err != nil {
return err
}
accessToken := qcr.GetFetchAccessToken(encKey)
if accessToken != "" {
auth = &http.BasicAuth{
Username: "something",
Password: accessToken,
}
}
} else {
repoPath, err = DownloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master")
if err != nil {
@@ -50,7 +36,7 @@ func (q *Qliksense) GetInstallableVersions(opts *LsRemoteCmdOptions) error {
return err
}
remoteRefsList, err := git.GetRemoteRefs(r, auth,
remoteRefsList, err := git.GetRemoteRefs(r, nil,
&git.RemoteRefConstraints{
Include: true,
Sort: true,
@@ -110,18 +96,13 @@ func getLatestTag(repoUrl, accessToken string) (string, error) {
if err != nil {
return "", err
}
r, err := git.OpenRepository(repoPath)
if err != nil {
return "", err
}
var auth transport.AuthMethod
if accessToken != "" {
auth = &http.BasicAuth{
Username: "something",
Password: accessToken,
}
}
remoteRefsList, err := git.GetRemoteRefs(r, auth,
remoteRefsList, err := git.GetRemoteRefs(r, nil,
&git.RemoteRefConstraints{
Include: true,
Sort: true,

View File

@@ -7,11 +7,6 @@ import (
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/mattn/go-tty"
"github.com/mitchellh/go-homedir"
"github.com/qlik-oss/k-apis/pkg/config"
@@ -22,71 +17,53 @@ import (
)
type InstallCommandOptions struct {
StorageClass string
MongodbUri string
AcceptEULA string
DryRun bool
Pull bool
Push bool
CleanPatchFiles bool
RotateKeys bool
StorageClass string
MongodbUri string
RotateKeys string
DryRun bool
}
const (
eulaText = "Please read the end user license agreement at: https://www.qlik.com/us/legal/license-terms"
eulaPrompt = "Do you accept our EULA? (y/n): "
eulaErrorInstruction = `You must enter "y" to continue or execute the command with the acceptEULA flag set to "yes"`
)
func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, keepPatchFiles bool) error {
func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) error {
// step1: fetch 1.0.0 # pull down qliksense-k8s@1.0.0
// step2: operator view | kubectl apply -f # operator manifest (CRD)
// step3: config apply | kubectl apply -f # generates patches (if required) in configuration directory, applies manifest
// step4: config view | kubectl apply -f # generates Custom Resource manifest (CR)
// fetch the version
qConfig := qapi.NewQConfig(q.QliksenseHome)
if !keepPatchFiles {
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
fmt.Printf("error removing temporary changes to the config: %v\n", err)
}
}
qcr, err := qConfig.GetCurrentCR()
if err != nil {
fmt.Println("cannot get the current-context cr", err)
return err
}
if !qcr.IsRepoExist() {
if err := fetchAndUpdateCR(qConfig, version); err != nil {
return err
} else if qcr, err = qConfig.GetCurrentCR(); err != nil {
return err
}
}
if opts.AcceptEULA != "" && opts.AcceptEULA != "yes" {
enforceEula()
} else if opts.AcceptEULA == "" && !qcr.IsEULA() {
enforceEula()
}
qcr.SetEULA("yes")
if opts.MongodbUri != "" {
qcr.Spec.AddToSecrets("qliksense", "mongodbUri", opts.MongodbUri, "")
}
if opts.StorageClass != "" {
qcr.Spec.StorageClassName = opts.StorageClass
}
if err := qConfig.WriteCurrentContextCR(qcr); err != nil {
return err
if opts.RotateKeys != "" {
qcr.Spec.RotateKeys = opts.RotateKeys
}
if opts.CleanPatchFiles {
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
fmt.Printf("error removing temporary changes to the config: %v\n", err)
}
}
// for debugging purpose
if opts.DryRun {
// generate patches
qcr.Spec.RotateKeys = "None"
userHomeDir, _ := homedir.Dir()
fmt.Println("Generating patches only")
cr.GeneratePatches(&qcr.KApiCr, config.KeysActionDoNothing, path.Join(userHomeDir, ".kube", "config"))
cr.GeneratePatches(&qcr.KApiCr, path.Join(userHomeDir, ".kube", "config"))
return nil
}
qConfig.WriteCurrentContextCR(qcr)
if installed, err := q.CheckAllCrdsInstalled(); err != nil {
fmt.Println("error verifying whether CRDs are installed", err)
@@ -95,22 +72,6 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) err
return errors.New(`please install CRDs by executing: $ qliksense crds install`)
}
if err := validatePullPushFlagsOnInstall(qcr, opts.Pull, opts.Push); err != nil {
return err
}
if opts.Pull {
fmt.Println("Pulling images...")
if err := q.PullImages(version, ""); err != nil {
return err
}
}
if opts.Push {
fmt.Println("Pushing images...")
if err := q.PushImagesForCurrentCR(); err != nil {
return err
}
}
if err := applyImagePullSecret(qConfig); err != nil {
return err
}
@@ -132,18 +93,6 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) err
return err
}
if opts.RotateKeys {
fmt.Println("Deleting stored application keys")
if err := q.DeleteKeysClusterBackup(); err != nil {
return err
} else {
qcr.AddLabelToCr("keys-rotated", strconv.FormatInt(time.Now().Unix(), 10))
if err := qConfig.WriteCurrentContextCR(qcr); err != nil {
return err
}
}
}
if qcr.Spec.OpsRunner != nil {
// fetching and applying manifest will be in the operator controller
// get decrypted cr
@@ -153,16 +102,35 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) err
return q.applyCR(dcr)
}
}
if !qcr.IsRepoExist() {
if err := fetchAndUpdateCR(qConfig, version); err != nil {
return err
}
}
qcr, err = qConfig.GetCurrentCR()
if err != nil {
fmt.Println("cannot get the current-context cr", err)
return err
} else if qcr.Spec.GetManifestsRoot() == "" {
return errors.New("cannot get the manifest root. Use qliksense fetch <version> or qliksense set manifestsRoot")
}
// install generated manifests into cluster
fmt.Println("Installing generated manifests into the cluster")
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
return err
} else if err := q.applyConfigToK8s(dcr); err != nil {
fmt.Println("cannot do kubectl apply on manifests")
return err
} else {
return q.applyCR(dcr)
if IsQliksenseInstalled(dcr.GetName()) {
return q.UpgradeQK8s(keepPatchFiles)
}
if err := q.applyConfigToK8s(dcr); err != nil {
fmt.Println("cannot do kubectl apply on manifests")
return err
} else {
return q.applyCR(dcr)
}
}
}
@@ -258,26 +226,3 @@ func (q *Qliksense) createK8sResourceBeforePatch(qcr *qapi.QliksenseCR) error {
func isK8sSecretNeedToCreate(nv config.NameValue) bool {
return nv.ValueFrom != nil
}
func enforceEula() {
fmt.Println(eulaText)
fmt.Print(eulaPrompt)
answer := readAnswerFromTty()
if strings.ToLower(answer) != "y" {
fmt.Println(eulaErrorInstruction)
os.Exit(1)
}
}
func readAnswerFromTty() 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

@@ -5,15 +5,17 @@ import (
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"testing"
"github.com/gobuffalo/packr/v2"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/api/resid"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
"github.com/gobuffalo/packr/v2"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func TestCreateK8sResourceBeforePatch(t *testing.T) {
@@ -43,10 +45,18 @@ spec:
qliksense:
- name: mongodbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
profile: docker-desktop`
profile: docker-desktop
rotateKeys: "yes"`
crFile := filepath.Join(testDir, "install_test.yaml")
ioutil.WriteFile(crFile, []byte(sampleCr), 0644)
q := New(testDir)
if err := q.LoadCr([]byte(sampleCr), false); err != nil {
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()
}

View File

@@ -1,21 +0,0 @@
package qliksense
import (
"path"
"github.com/mitchellh/go-homedir"
"github.com/qlik-oss/k-apis/pkg/cr"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func (q *Qliksense) DeleteKeysClusterBackup() error {
qConfig := qapi.NewQConfig(q.QliksenseHome)
if qcr, err := qConfig.GetCurrentCR(); err != nil {
return err
} else if userHomeDir, err := homedir.Dir(); err != nil {
return err
} else if err := cr.DeleteKeysClusterBackup(&qcr.KApiCr, path.Join(userHomeDir, ".kube", "config")); err != nil {
return err
}
return nil
}

View File

@@ -276,11 +276,9 @@ func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
configPath := filepath.Join(tmpDir, "config")
if repo, err := kapis_git.CloneRepository(configPath, defaultConfigRepoGitUrl, nil); err != nil {
t.Fatalf("unexpected error: %v\n", err)
} else if err := kapis_git.Checkout(repo, "e38df644e759abf0b5941c1511d1a2cd5e3c42fa", "commit-e38df644e759abf0b5941c1511d1a2cd5e3c42fa", nil); err != nil {
} else if err := kapis_git.Checkout(repo, "e38df644e759abf0b5941c1511d1a2cd5e3c42fa", "", nil); err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
//tmpDir := "/var/folders/mf/5hs1qkq508q_scjbhxhmf9qwjrp346/T/679268230"
//configPath := "/var/folders/mf/5hs1qkq508q_scjbhxhmf9qwjrp346/T/679268230/config"
cr := &config.CRSpec{
ManifestsRoot: configPath,
@@ -312,8 +310,8 @@ func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
}
break
}
if resource["kind"].(string) == "Secret" && strings.Contains(resource["metadata"].(map[interface{}]interface{})["name"].(string), "users-secrets-") {
keyIdBase64 = resource["data"].(map[interface{}]interface{})["tokenAuthPrivateKeyId"].(string)
if resource["kind"].(string) == "Secret" && strings.Contains(resource["metadata"].(map[interface {}]interface {})["name"].(string), "users-secrets-") {
keyIdBase64 = resource["data"].(map[interface {}]interface {})["tokenAuthPrivateKeyId"].(string)
break
}
}

View File

@@ -3,13 +3,17 @@ package qliksense
import (
"errors"
"fmt"
"io"
"io/ioutil"
"strings"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func (q *Qliksense) LoadCr(crBytes []byte, overwriteExistingContext bool) error {
if crName, err := q.loadCrStringIntoFileSystem(string(crBytes), overwriteExistingContext); err != nil {
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")
@@ -17,6 +21,16 @@ func (q *Qliksense) LoadCr(crBytes []byte, overwriteExistingContext bool) error
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 {
@@ -55,8 +69,8 @@ func (q *Qliksense) loadCrStringIntoFileSystem(crstr string, overwriteExistingCo
}
}
}
if cr.Spec.Git != nil && cr.Spec.Git.AccessToken != "" {
if err := cr.SetFetchAccessToken(cr.Spec.Git.AccessToken, encryptionKey); err != nil {
if cr.Spec.FetchSource != nil && cr.Spec.FetchSource.AccessToken != "" {
if err := cr.SetFetchAccessToken(cr.Spec.FetchSource.AccessToken, encryptionKey); err != nil {
return "", err
}
}

View File

@@ -1,6 +1,9 @@
package qliksense
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
@@ -34,7 +37,8 @@ spec:
qliksense:
- name: mongodbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
profile: docker-desktop`
profile: docker-desktop
rotateKeys: "yes"`
sampleCr2 := `
apiVersion: qlik.com/v1
kind: Qliksense
@@ -60,7 +64,8 @@ spec:
qliksense:
- name: mongodbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
profile: docker-desktop`
profile: docker-desktop
rotateKeys: "yes"`
duplicateCr := `
apiVersion: qlik.com/v1
@@ -74,13 +79,30 @@ spec:
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)
if err := q.LoadCr([]byte(sampleCr1), false); err != nil {
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()
}
if err := q.LoadCr([]byte(sampleCr2), false); err != nil {
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()
}
@@ -106,7 +128,12 @@ spec:
if qConfig.Spec.CurrentContext != "qlik-test3" {
t.FailNow()
}
if err := q.LoadCr([]byte(duplicateCr), false); err == nil {
file, e := os.Open(dupCrFile)
if e != nil {
t.Log(e)
t.FailNow()
}
if err := q.LoadCr(file, false); err == nil {
t.FailNow()
}
td()

View File

@@ -6,7 +6,7 @@ import (
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func (q *Qliksense) UpgradeQK8s(cleanPatchFiles bool) error {
func (q *Qliksense) UpgradeQK8s(keepPatchFiles bool) error {
// step1: get CR
// step2: run kustomize
@@ -14,10 +14,12 @@ func (q *Qliksense) UpgradeQK8s(cleanPatchFiles bool) error {
// fetch the version
qConfig := qapi.NewQConfig(q.QliksenseHome)
if cleanPatchFiles {
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
fmt.Printf("error removing temporary changes to the config: %v\n", err)
}
if !keepPatchFiles {
defer func() {
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
fmt.Printf("error removing temporary changes to the config: %v\n", err)
}
}()
}
qcr, err := qConfig.GetCurrentCR()
@@ -25,6 +27,7 @@ func (q *Qliksense) UpgradeQK8s(cleanPatchFiles bool) error {
fmt.Println("cannot get the current-context cr", err)
return err
}
qcr.Spec.RotateKeys = "no"
dcr, err := qConfig.GetDecryptedCr(qcr)
if err != nil {