Compare commits

..

5 Commits

Author SHA1 Message Date
Sanat Nayar
02b8692d7f init 2020-04-27 10:32:32 -04:00
Sanat Nayar
02f7b2006a init 2020-04-27 09:58:42 -04:00
Sanat Nayar
ae676258ac init 2020-04-27 09:56:08 -04:00
Sanat Nayar
dbe5639fd0 init 2020-04-27 09:52:33 -04:00
Sanat Nayar
65040f9fb6 added flags 2020-04-23 17:15:36 -04:00
57 changed files with 1383 additions and 4139 deletions

View File

@@ -14,7 +14,7 @@ import (
func applyCmd(q *qliksense.Qliksense) *cobra.Command {
opts := &qliksense.InstallCommandOptions{}
filePath := ""
cleanPatchFiles, pull, push := true, false, false
keepPatchFiles, pull, push := false, false, false
c := &cobra.Command{
Use: "apply",
Short: "install qliksense based on provided cr file",
@@ -27,7 +27,7 @@ func applyCmd(q *qliksense.Qliksense) *cobra.Command {
} else if err := validatePullPushFlagsOnApply(cr, pull, push); err != nil {
return err
} else {
return q.ApplyCRFromReader(bytes.NewReader(crBytesWithEula), opts, cleanPatchFiles, true, pull, push)
return q.ApplyCRFromReader(bytes.NewReader(crBytesWithEula), opts, keepPatchFiles, true, pull, push)
}
})
},
@@ -37,9 +37,9 @@ func applyCmd(q *qliksense.Qliksense) *cobra.Command {
f.StringVarP(&filePath, "file", "f", "", "Install from a CR file")
c.MarkFlagRequired("file")
f.StringVarP(&opts.StorageClass, "storageClass", "s", "", "Storage class for qliksense")
f.StringVarP(&opts.MongodbUri, "mongodbUri", "m", "", "mongodbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
f.StringVarP(&opts.MongoDbUri, "mongoDbUri", "m", "", "mongoDbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
f.StringVarP(&opts.RotateKeys, "rotateKeys", "r", "", "Rotate JWT keys for qliksense (yes:rotate keys/ no:use exising keys from cluster/ None: use default EJSON_KEY from env")
f.BoolVar(&cleanPatchFiles, cleanPatchFilesFlagName, cleanPatchFiles, cleanPatchFilesFlagUsage)
f.BoolVar(&keepPatchFiles, keepPatchFilesFlagName, keepPatchFiles, keepPatchFilesFlagUsage)
f.BoolVarP(&pull, pullFlagName, pullFlagShorthand, pull, pullFlagUsage)
f.BoolVarP(&push, pushFlagName, pushFlagShorthand, push, pushFlagUsage)

View File

@@ -220,27 +220,3 @@ func cleanConfigRepoPatchesCmd(q *qliksense.Qliksense) *cobra.Command {
},
}
}
func unsetCmd(q *qliksense.Qliksense) *cobra.Command {
cmd := &cobra.Command{
Use: "unset",
Short: "remove a key from a context or a secrets or a configs from the context",
Example: `
# remove the key from CR
qliksense config unset <key>
# remove the key from service inside configs/secrets of CR
qliksense config unset <service>.<key>
# remove the service from inside configs/secrets of CR
qliksense config usnet <servcie>
all of the above supports space separated multiple arguments
`,
RunE: func(cmd *cobra.Command, args []string) error {
return q.UnsetCmd(args)
},
Args: cobra.MinimumNArgs(1),
}
return cmd
}

View File

@@ -12,35 +12,31 @@ var crdsCmd = &cobra.Command{
}
func crdsViewCmd(q *qliksense.Qliksense) *cobra.Command {
opts := &qliksense.CrdCommandOptions{
All: true,
}
opts := &qliksense.CrdCommandOptions{}
c := &cobra.Command{
Use: "view",
Short: "View CRDs for qliksense application. Use view --all=false to exclude the operator CRD",
Long: "View CRDs for qliksense application. Use view --all=false to exclude the operator CRD",
Short: "View CRDs for qliksense application. use view --all to see opearator crd as well ",
Long: `View CRDs for qliksense application. use view --all to see opearator crd as well`,
RunE: func(cmd *cobra.Command, args []string) error {
return q.ViewCrds(opts)
},
}
f := c.Flags()
f.BoolVarP(&opts.All, "all", "", opts.All, "If set to false, then the operator CRD is excluded")
f.BoolVarP(&opts.All, "all", "", false, "Include All CRDs")
return c
}
func crdsInstallCmd(q *qliksense.Qliksense) *cobra.Command {
opts := &qliksense.CrdCommandOptions{
All: true,
}
opts := &qliksense.CrdCommandOptions{}
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 fro Qliksense application. Use install --all to include operator crd",
Long: `Install CRDs fro Qliksense application. Use install --all to include operator crd`,
RunE: func(cmd *cobra.Command, args []string) error {
return q.InstallCrds(opts)
},
}
f := c.Flags()
f.BoolVarP(&opts.All, "all", "", opts.All, "If set to false, then the operator CRD is excluded")
f.BoolVarP(&opts.All, "all", "", false, "Include All CRDs")
return c
}

View File

@@ -41,7 +41,7 @@ func (e *eulaPreRunHooksT) getPostValidationArtifact(artifactName string) interf
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 eulaErrorInstruction = `You must enter "y" to continue`
var eulaPreRunHooks = eulaPreRunHooksT{
validators: make(map[string]func(cmd *cobra.Command, q *qliksense.Qliksense) (bool, error)),
postValidationArtifacts: make(map[string]interface{}),
@@ -54,10 +54,7 @@ func commandAlwaysRequiresEulaAcceptance(commandName string) bool {
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 strings.TrimSpace(strings.ToLower(cmd.Flag("acceptEULA").Value.String())) != "yes" {
if eulaPreRunHook := eulaPreRunHooks.getValidator(cmd.CommandPath()); eulaPreRunHook != nil {
if eulaAccepted, err := eulaPreRunHook(cmd, q); err != nil {
panic(err)

27
cmd/qliksense/export.go Normal file
View File

@@ -0,0 +1,27 @@
package main
import (
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
func exportCmd(q *qliksense.Qliksense) *cobra.Command {
filePath := q.QliksenseHome
c := &cobra.Command{
Use: "export",
Short: "export files for corresponding context",
Long: `exports all context files in zip format`,
Example: `qliksense export <context_name>`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
context := args[0]
return q.ExportContext(context, filePath)
}
return nil
},
}
f := c.Flags()
f.StringVarP(&filePath, "output", "o", "", "Output Directory")
return c
}

View File

@@ -13,7 +13,7 @@ import (
func installCmd(q *qliksense.Qliksense) *cobra.Command {
opts := &qliksense.InstallCommandOptions{}
filePath := ""
cleanPatchFiles, pull, push := true, false, false
keepPatchFiles, pull, push := false, false, false
c := &cobra.Command{
Use: "install",
Short: "install a qliksense release",
@@ -29,7 +29,7 @@ func installCmd(q *qliksense.Qliksense) *cobra.Command {
} else if err := validatePullPushFlagsOnApply(cr, pull, push); err != nil {
return err
} else {
return q.ApplyCRFromReader(bytes.NewReader(crBytesWithEula), opts, cleanPatchFiles, true, pull, push)
return q.ApplyCRFromReader(bytes.NewReader(crBytesWithEula), opts, keepPatchFiles, true, pull, push)
}
})
} else {
@@ -52,7 +52,7 @@ func installCmd(q *qliksense.Qliksense) *cobra.Command {
return err
}
}
return q.InstallQK8s(version, opts, cleanPatchFiles)
return q.InstallQK8s(version, opts, keepPatchFiles)
}
},
}
@@ -61,9 +61,9 @@ func installCmd(q *qliksense.Qliksense) *cobra.Command {
if filePath != "" {
return loadOrApplyCommandEulaPreRunHook(cmd, q)
} else if qConfig, err := qapi.NewQConfigE(q.QliksenseHome); err != nil {
return false, nil
return false, err
} else if qcr, err := qConfig.GetCurrentCR(); err != nil {
return false, nil
return false, err
} else {
return qcr.IsEULA(), nil
}
@@ -71,13 +71,11 @@ func installCmd(q *qliksense.Qliksense) *cobra.Command {
f := c.Flags()
f.StringVarP(&opts.StorageClass, "storageClass", "s", "", "Storage class for qliksense")
f.StringVarP(&opts.MongodbUri, "mongodbUri", "m", "", "mongodbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
f.StringVarP(&opts.MongoDbUri, "mongoDbUri", "m", "", "mongoDbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
f.StringVarP(&opts.RotateKeys, "rotateKeys", "r", "", "Rotate JWT keys for qliksense (yes:rotate keys/ no:use exising keys from cluster/ None: use default EJSON_KEY from env")
f.BoolVar(&cleanPatchFiles, cleanPatchFilesFlagName, cleanPatchFiles, cleanPatchFilesFlagUsage)
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)

View File

@@ -1,60 +0,0 @@
package main
import (
"fmt"
. "github.com/logrusorgru/aurora"
ansi "github.com/mattn/go-colorable"
"github.com/qlik-oss/sense-installer/pkg/api"
postflight "github.com/qlik-oss/sense-installer/pkg/postflight"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
func postflightCmd(q *qliksense.Qliksense) *cobra.Command {
postflightOpts := &postflight.PostflightOptions{}
var postflightCmd = &cobra.Command{
Use: "postflight",
Short: "perform postflight checks on the cluster",
Long: `perform postflight checks on the cluster`,
Example: `qliksense postflight <postflight_check_to_run>`,
}
f := postflightCmd.Flags()
f.BoolVarP(&postflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return postflightCmd
}
func pfMigrationCheck(q *qliksense.Qliksense) *cobra.Command {
out := ansi.NewColorableStdout()
postflightOpts := &postflight.PostflightOptions{}
var postflightMigrationCmd = &cobra.Command{
Use: "db-migration-check",
Short: "check mongodb migration status on the cluster",
Long: `check mongodb migration status on the cluster`,
Example: `qliksense postflight db-migration-check`,
RunE: func(cmd *cobra.Command, args []string) error {
pf := &postflight.QliksensePostflight{Q: q, P: postflightOpts, CG: &api.ClientGoUtils{Verbose: postflightOpts.Verbose}}
// Postflight db_migration_check
namespace, kubeConfigContents, err := pf.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Fprintf(out, "%s\n", Red("Postflight db_migration_check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if namespace == "" {
namespace = "default"
}
if err = pf.DbMigrationCheck(namespace, kubeConfigContents); err != nil {
fmt.Fprintf(out, "%s\n", Red("Postflight db_migration_check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("Postflight db_migration_check completed"))
return nil
},
}
f := postflightMigrationCmd.Flags()
f.BoolVarP(&postflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return postflightMigrationCmd
}

View File

@@ -3,27 +3,21 @@ package main
import (
"fmt"
. "github.com/logrusorgru/aurora"
ansi "github.com/mattn/go-colorable"
"github.com/qlik-oss/sense-installer/pkg/api"
"github.com/qlik-oss/sense-installer/pkg/preflight"
. "github.com/logrusorgru/aurora"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
func preflightCmd(q *qliksense.Qliksense) *cobra.Command {
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var preflightCmd = &cobra.Command{
Use: "preflight",
Short: "perform preflight checks on the cluster",
Long: `perform preflight checks on the cluster`,
Example: `qliksense preflight <preflight_check_to_run>`,
}
f := preflightCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return preflightCmd
}
@@ -38,24 +32,24 @@ func pfDnsCheckCmd(q *qliksense.Qliksense) *cobra.Command {
Long: `perform preflight dns check to check DNS connectivity status in the cluster`,
Example: `qliksense preflight dns`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts}
// Preflight DNS check
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
namespace, kubeConfigContents, err := preflight.InitPreflight()
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
}
if namespace == "" {
namespace = "default"
}
if err = qp.CheckDns(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
if err = qp.CheckDns(namespace, kubeConfigContents); err != nil {
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
},
}
@@ -71,26 +65,26 @@ func pfK8sVersionCheckCmd(q *qliksense.Qliksense) *cobra.Command {
}
var preflightCheckK8sVersionCmd = &cobra.Command{
Use: "k8s-version",
Use: "kube-version",
Short: "check kubernetes version",
Long: `check minimum valid kubernetes version on the cluster`,
Example: `qliksense preflight k8s-version`,
Example: `qliksense preflight kube-version`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts}
// Preflight Kubernetes minimum version check
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
namespace, kubeConfigContents, err := preflight.InitPreflight()
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
},
}
@@ -112,11 +106,11 @@ func pfAllChecksCmd(q *qliksense.Qliksense) *cobra.Command {
Long: `perform all preflight checks on the target cluster`,
Example: `qliksense preflight all`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts}
// Preflight run all checks
fmt.Printf("Running all preflight checks...\n\n")
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
namespace, kubeConfigContents, err := preflight.InitPreflight()
if err != nil {
fmt.Fprintf(out, "%s\n", Red("Unable to run the preflight checks suite"))
fmt.Printf("Error: %v\n", err)
@@ -137,7 +131,12 @@ func pfAllChecksCmd(q *qliksense.Qliksense) *cobra.Command {
f := preflightAllChecksCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
f.StringVarP(&preflightOpts.MongoOptions.MongodbUrl, "mongodb-url", "", "", "mongodbUrl to try connecting to")
f.StringVarP(&preflightOpts.MongoOptions.Username, "mongodb-username", "", "", "username to connect to mongodb")
f.StringVarP(&preflightOpts.MongoOptions.Password, "mongodb-password", "", "", "password to connect to mongodb")
f.StringVarP(&preflightOpts.MongoOptions.CaCertFile, "mongodb-ca-cert", "", "", "certificate to use for mongodb check")
f.StringVarP(&preflightOpts.MongoOptions.ClientCertFile, "mongodb-client-cert", "", "", "client-certificate to use for mongodb check")
f.BoolVar(&preflightOpts.MongoOptions.Tls, "mongodb-tls", false, "enable tls?")
return preflightAllChecksCmd
}
@@ -148,28 +147,28 @@ func pfDeploymentCheckCmd(q *qliksense.Qliksense) *cobra.Command {
}
var pfDeploymentCheckCmd = &cobra.Command{
Use: "deployment",
Short: "perform preflight deployment check",
Short: "perform preflight deploymwnt check",
Long: `perform preflight deployment check to ensure that we can create deployments in the cluster`,
Example: `qliksense preflight deployment`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts}
// Preflight deployments check
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
namespace, kubeConfigContents, err := preflight.InitPreflight()
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
}
if namespace == "" {
namespace = "default"
}
if err = qp.CheckDeployment(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
if err = qp.CheckDeployment(namespace, kubeConfigContents); err != nil {
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
},
}
@@ -190,12 +189,12 @@ func pfServiceCheckCmd(q *qliksense.Qliksense) *cobra.Command {
Long: `perform preflight service check to ensure that we are able to create services in the cluster`,
Example: `qliksense preflight service`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts}
// Preflight service check
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
namespace, kubeConfigContents, err := preflight.InitPreflight()
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
}
@@ -203,12 +202,12 @@ func pfServiceCheckCmd(q *qliksense.Qliksense) *cobra.Command {
if namespace == "" {
namespace = "default"
}
if err = qp.CheckService(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
if err = qp.CheckService(namespace, kubeConfigContents); err != nil {
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
},
}
@@ -229,24 +228,24 @@ func pfPodCheckCmd(q *qliksense.Qliksense) *cobra.Command {
Long: `perform preflight pod check to ensure we can create pods in the cluster`,
Example: `qliksense preflight pod`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts}
// Preflight pod check
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
namespace, kubeConfigContents, err := preflight.InitPreflight()
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
}
if namespace == "" {
namespace = "default"
}
if err = qp.CheckPod(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
if err = qp.CheckPod(namespace, kubeConfigContents); err != nil {
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
},
}
@@ -267,21 +266,21 @@ func pfCreateRoleCheckCmd(q *qliksense.Qliksense) *cobra.Command {
Long: `perform preflight role check to ensure we are able to create a role in the cluster`,
Example: `qliksense preflight createRole`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts}
// Preflight role check
namespace, _, err := qp.CG.LoadKubeConfigAndNamespace()
namespace, _, err := preflight.InitPreflight()
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"))
if err = qp.CheckCreateRole(namespace); err != nil {
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
},
}
@@ -302,21 +301,21 @@ func pfCreateRoleBindingCheckCmd(q *qliksense.Qliksense) *cobra.Command {
Long: `perform preflight rolebinding check to ensure we are able to create a rolebinding in the cluster`,
Example: `qliksense preflight rolebinding`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts}
// Preflight createRoleBinding check
namespace, _, err := qp.CG.LoadKubeConfigAndNamespace()
namespace, _, err := preflight.InitPreflight()
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"))
if err = qp.CheckCreateRoleBinding(namespace); err != nil {
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
},
}
@@ -333,25 +332,25 @@ func pfCreateServiceAccountCheckCmd(q *qliksense.Qliksense) *cobra.Command {
var preflightServiceAccountCmd = &cobra.Command{
Use: "serviceaccount",
Short: "preflight create serviceaccount check",
Short: "preflight create ServiceAccount check",
Long: `perform preflight serviceaccount check to ensure we are able to create a service account in the cluster`,
Example: `qliksense preflight serviceaccount`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts}
// Preflight createServiceAccount check
namespace, _, err := qp.CG.LoadKubeConfigAndNamespace()
namespace, _, err := preflight.InitPreflight()
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"))
if err = qp.CheckCreateServiceAccount(namespace); err != nil {
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
},
}
@@ -371,21 +370,21 @@ func pfCreateAuthCheckCmd(q *qliksense.Qliksense) *cobra.Command {
Long: `perform preflight authcheck that combines the role, rolebinding and serviceaccount checks`,
Example: `qliksense preflight authcheck`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts}
// Preflight authcheck
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
namespace, kubeConfigContents, err := preflight.InitPreflight()
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
},
}
@@ -406,69 +405,34 @@ func pfMongoCheckCmd(q *qliksense.Qliksense) *cobra.Command {
Long: `perform preflight mongo check to ensure we are able to connect to a mongodb instance in the cluster`,
Example: `qliksense preflight mongo OR preflight mongo --url=<url>`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts}
// Preflight mongo check
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
namespace, kubeConfigContents, err := preflight.InitPreflight()
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
}
if namespace == "" {
namespace = "default"
}
if err = qp.CheckMongo(kubeConfigContents, namespace, preflightOpts, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
if err = qp.CheckMongo(kubeConfigContents, namespace, preflightOpts); err != nil {
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
},
}
f := preflightMongoCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
f.StringVarP(&preflightOpts.MongoOptions.MongodbUrl, "url", "", "", "mongodbUrl to try connecting to")
f.StringVarP(&preflightOpts.MongoOptions.Username, "username", "", "", "username to connect to mongodb")
f.StringVarP(&preflightOpts.MongoOptions.Password, "password", "", "", "password to connect to mongodb")
f.StringVarP(&preflightOpts.MongoOptions.CaCertFile, "ca-cert", "", "", "ca certificate to use for mongodb check")
f.StringVarP(&preflightOpts.MongoOptions.ClientCertFile, "client-cert", "", "", "client-certificate to use for mongodb check")
f.BoolVar(&preflightOpts.MongoOptions.Tls, "tls", false, "enable tls?")
return preflightMongoCmd
}
func pfCleanupCmd(q *qliksense.Qliksense) *cobra.Command {
out := ansi.NewColorableStdout()
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var pfCleanCmd = &cobra.Command{
Use: "clean",
Short: "perform preflight clean",
Long: `perform preflight clean to ensure that all resources are cleared up in the cluster`,
Example: `qliksense preflight clean`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
// Preflight clean
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight cleanup FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if namespace == "" {
namespace = "default"
}
if err = qp.Cleanup(namespace, kubeConfigContents); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight cleanup FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("Preflight cleanup complete"))
return nil
},
}
f := pfCleanCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return pfCleanCmd
}

View File

@@ -8,7 +8,6 @@ import (
"path/filepath"
"strings"
. "github.com/logrusorgru/aurora"
ansi "github.com/mattn/go-colorable"
"github.com/mitchellh/go-homedir"
"github.com/qlik-oss/sense-installer/pkg"
@@ -16,6 +15,7 @@ import (
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
"github.com/spf13/viper"
. "github.com/logrusorgru/aurora"
)
// To run this project in debug mode, run:
@@ -23,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 {
@@ -46,7 +46,7 @@ func initAndExecute() error {
log.Fatal(err)
}
// create dirs and appropriate files for setting up contexts
api.LogDebugMessage("QliksenseHomeDir: %s\n", qlikSenseHome)
api.LogDebugMessage("QliksenseHomeDir: %s", qlikSenseHome)
qliksenseClient := qliksense.New(qlikSenseHome)
cmd := rootCmd(qliksenseClient)
@@ -122,7 +122,6 @@ func getRootCmd(p *qliksense.Qliksense) *cobra.Command {
globalEulaPostRun(cmd, p)
}
},
SilenceUsage: true,
}
origHelpFunc := cmd.HelpFunc()
cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
@@ -168,9 +167,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("-", "_"))
@@ -198,22 +195,22 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
// add clean-config-repo-patches command as a sub-command to the app config sub-command
configCmd.AddCommand(cleanConfigRepoPatchesCmd(p))
// open editor for config
configCmd.AddCommand(configEditCmd(p))
// add unset for config
configCmd.AddCommand((unsetCmd(p)))
// add uninstall command
cmd.AddCommand(uninstallCmd(p))
// add export command
cmd.AddCommand(exportCmd(p))
// add crds
cmd.AddCommand(crdsCmd)
crdsCmd.AddCommand(crdsViewCmd(p))
crdsCmd.AddCommand(crdsInstallCmd(p))
// add preflight commands
// add preflight command
preflightCmd := preflightCmd(p)
preflightCmd.AddCommand(pfDnsCheckCmd(p))
preflightCmd.AddCommand(pfK8sVersionCheckCmd(p))
@@ -226,17 +223,10 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
preflightCmd.AddCommand(pfCreateRoleBindingCheckCmd(p))
preflightCmd.AddCommand(pfCreateServiceAccountCheckCmd(p))
preflightCmd.AddCommand(pfCreateAuthCheckCmd(p))
preflightCmd.AddCommand(pfCleanupCmd(p))
cmd.AddCommand(preflightCmd)
cmd.AddCommand(loadCrFile(p))
cmd.AddCommand((applyCmd(p)))
// add postflight command
postflightCmd := postflightCmd(p)
postflightCmd.AddCommand(pfMigrationCheck(p))
cmd.AddCommand(postflightCmd)
return cmd
}

View File

@@ -4,7 +4,7 @@
Preflight checks provide pre-installation cluster conformance testing and validation before we install qliksense on the cluster. We gather a suite of conformance tests that can be easily written and run on the target cluster to verify that cluster-specific requirements are met.
We support a couple of tests at the moment as part of preflight checks, and the range of the suite will be expanded in future.
We support the following tests at the moment as part of preflight checks, and the range of the suite will be expanded in future.
Run the following command to view help about the commands supported by preflight at any moment:
```
@@ -23,20 +23,6 @@ Run the following command to execute a specific check
qliksense preflight dns
```
#### Running cleanup
Run the following command to cleanup entities created for preflight checks that were left behind on the cluster.
```
qliksense preflight clean
```
### qliksense postflight
Postflight checks are performed after qliksense is installed on the cluster and during normal operating mode of the product. Such checks can range from validating certain conditions to checking the status of certain operations or entities.
Run the following command to view help about the commands supported by postflight at any moment:
```
qliksense postflight
```
### qliksense load
`qliksense load` command takes input from a file or from pipe
@@ -71,7 +57,7 @@ spec:
value: "yes"
secrets:
qliksense:
- name: mongodbUri
- name: mongoDbUri
value: mongodb://qlik-test-mongodb:27017/qliksense?ssl=false
profile: docker-desktop
rotateKeys: "yes"
@@ -112,7 +98,7 @@ spec:
value: "yes"
secrets:
qliksense:
- name: mongodbUri
- name: mongoDbUri
value: "mongo://mongo:3307"
- name: messagingPassword
valueFromKey: messagingPassword
@@ -130,6 +116,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

@@ -23,7 +23,7 @@ spec:
profile: docker-desktop
secrets:
qliksense:
- name: mongodbUri
- name: mongoDbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
rotateKeys: "yes"
releaseName: qlik-default
@@ -50,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`.
@@ -68,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,33 +0,0 @@
# Postflight checks
Postflight checks are performed after qliksense is installed on the cluster and during normal operating mode of the product. Such checks can range from validating certain conditions to checking the status of certain operations or entities on the kubernetes cluster.
Run the following command to view help about the commands supported by postflight at any moment:
```
$ qliksense postflight
perform postflight checks on the cluster
Usage:
qliksense postflight [command]
Examples:
qliksense postflight <postflight_check_to_run>
Available Commands:
db-migration-check check mongodb migration status on the cluster
Flags:
-h, --help help for postflight
-v, --verbose verbose mode
```
### DB migration check
This command checks init containers for successful database migrarion completions, and reports failure, if any to the user.
An example run of this check produces an output as shown below:
```shell
$ 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":""}
Postflight db_migration_check completed
```

View File

@@ -2,7 +2,7 @@
Preflight checks provide pre-installation cluster conformance testing and validation before we install qliksense on the cluster. We gather a suite of conformance tests that can be easily written and run on the target cluster to verify that cluster-specific requirements are met.
We support a couple of tests at the moment as part of preflight checks, and the range of the suite will be expanded in future.
We support the following tests at the moment as part of preflight checks, and the range of the suite will be expanded in future.
Run the following command to view help about the commands supported by preflight at any moment:
```shell
@@ -16,29 +16,19 @@ Examples:
qliksense preflight <preflight_check_to_run>
Available Commands:
all perform all checks
authcheck preflight authcheck
clean perform preflight clean
deployment perform preflight deployment check
dns perform preflight dns check
k8s-version check kubernetes version
mongo preflight mongo OR preflight mongo --url=<url>
pod perform preflight pod check
role preflight create role check
rolebinding preflight create rolebinding check
service perform preflight service check
serviceaccount preflight create ServiceAccount check
all perform all checks
dns perform preflight dns check
k8s-version check k8s version
Flags:
-h, --help help for preflight
-v, --verbose verbose mode
```
### DNS check
Run the following command to perform preflight DNS check. We setup a kubernetes deployment and try to reach it as part of establishing DNS connectivity in this check.
The expected output should be similar to the one shown below.
```shell
$ qliksense preflight dns -v
$ qliksense preflight dns
Preflight DNS check
---------------------
@@ -61,7 +51,7 @@ Deleted deployment: dep-dns-preflight-check
We check the version of the target kubernetes cluster and ensure that it falls in the valid range of kubernetes versions that are supported by qliksense.
The command to run this check and the expected similar output are as shown below:
```shell
$ qliksense preflight k8s-version -v
$ qliksense preflight k8s-version
Preflight kubernetes minimum version check
------------------------------------------
@@ -76,7 +66,7 @@ Completed Preflight kubernetes minimum version check
### Service check
We use the commmand below to test if we are able to create a service in the cluster.
```shell
$ qliksense preflight service -v
$ qliksense preflight service
Preflight service check
-----------------------
@@ -92,7 +82,7 @@ Completed preflight service check
### Deployment check
We use the commmand below to test if we are able to create a deployment in the cluster. After the test exexutes, we wait until the created deployment terminates before we exit the command.
```shell
$ qliksense preflight deployment -v
$ qliksense preflight deployment
Preflight deployment check
-----------------------
@@ -107,7 +97,7 @@ Completed preflight deployment check
### Pod check
We use the commmand below to test if we are able to create a pod in the cluster.
```shell
$ qliksense preflight pod -v
$ qliksense preflight pod
Preflight pod check
--------------------
@@ -120,61 +110,61 @@ Deleted pod: pod-pf-check
Completed preflight pod check
```
### Role check
### Create-Role check
We use the command below to test if we are able to create a role in the cluster
```shell
$ qliksense preflight role -v
Preflight role check
$ qliksense preflight create-role
Preflight create-role check
---------------------------
Preflight role check:
Preflight create-role check:
Created role: role-preflight-check
Preflight role check: PASSED
Preflight create-role check: PASSED
Cleaning up resources...
Deleted role: role-preflight-check
Completed preflight role check
Completed preflight create-role check
```
### RoleBinding check
### Create-RoleBinding check
We use the command below to test if we are able to create a role binding in the cluster
```shell
$ qliksense preflight rolebinding -v
$ qliksense preflight createRoleBinding
Preflight rolebinding check
Preflight create roleBinding check
---------------------------
Preflight rolebinding check:
Preflight createRoleBinding check:
Created RoleBinding: role-binding-preflight-check
Preflight rolebinding check: PASSED
Preflight createRoleBinding check: PASSED
Cleaning up resources...
Deleting RoleBinding: role-binding-preflight-check
Deleted RoleBinding: role-binding-preflight-check
Completed preflight rolebinding check
Completed preflight createRoleBinding check
```
### Create-ServiceAccount check
We use the command below to test if we are able to create a service account in the cluster
```shell
$ qliksense preflight serviceaccount -v
$ qliksense preflight createServiceAccount
Preflight ServiceAccount check
Preflight create ServiceAccount check
-------------------------------------
Preflight serviceaccount check:
Preflight createServiceAccount check:
Created Service Account: preflight-check-test-serviceaccount
Preflight serviceaccount check: PASSED
Preflight createServiceAccount check: PASSED
Cleaning up resources...
Deleting ServiceAccount: preflight-check-test-serviceaccount
Deleted ServiceAccount: preflight-check-test-serviceaccount
Completed preflight serviceaccount check
Completed preflight createServiceAccount check
```
### Auth check
### CreateRB check
We use the command below to combine creation of role, role binding, and service account tests
```shell
$ qliksense preflight authcheck -v
$ qliksense preflight createRB
Preflight auth check
Preflight createRB check
-------------------------------------
Preflight create-role check:
Created role: role-preflight-check
@@ -199,18 +189,18 @@ Cleaning up resources...
Deleted ServiceAccount: preflight-check-test-serviceaccount
Completed preflight createServiceAccount check
Completed preflight auth check
Completed preflight CreateRB check
```
### Mongodb check
We can check if we are able to connect to an instance of mongodb on the cluster by either supplying the mongodbUri as part of the command or infer it from the current context.
```shell
qliksense preflight mongo --url=<url> -v OR
qliksense preflight mongo -v
qliksense preflight mongo --url=<mongo-server url> --ca-cert=<path to ca-cert file> -v
```
```shell
qliksense preflight mongo --url=<url> OR
qliksense preflight mongo
qliksense preflight mongo --url=<mongo-server url> --ca-cert=<path to ca-cert file>
Preflight mongo check
---------------------
Preflight mongodb check:
@@ -226,35 +216,13 @@ Deleted pod: pf-mongo-pod
Completed preflight mongodb check
```
#### Mongodb check with mutual tls
In order to perform mutual tls with mongo we need to:
- append client certificate to the beginning/end of CA certificate. Make sure to include the beginning and end tags on each certificate.
The CA certificate file should look like this in the end:
```shell
<existing contents of CA cert>
...
-----BEGIN RSA PRIVATE KEY-----
<private key>
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
<public key>
-----END CERTIFICATE-----
```
- Run the command below to set the ca certificate into the CR
```shell
cat <path_to_ca.crt> | base64 | qliksense config set-secrets qliksense.caCertificates --base64
```
Next, run:
```shell
qliksense preflight mongo -v
```
### Running all checks
Run the command shown below to execute all preflight checks.
```shell
$ qliksense preflight all --mongodb-url=<url> -v OR
$ qliksense preflight all --mongodb-url=<mongo-server url> --mongodb-ca-cert=<path to ca-cert file> -v
$ qliksense preflight all --mongodb-url=<url> OR
$ qliksense preflight all --mongodb-url=<mongo-server url> --mongodb-ca-cert=<path to ca-cert file>
Running all preflight checks
@@ -285,23 +253,4 @@ Completed Preflight kubernetes minimum version check
All preflight checks have PASSED
Completed running all preflight checks
```
### Clean
Run the command below to cleanup entities that were created for the purpose of running preflight checks and left behind in the cluster.
```shell
$ qliksense preflight clean -v
Preflight clean
----------------
Removing deployment...
Removing service...
Removing pod...
Removing role...
Removing rolebinding...
Removing serviceaccount...
Removing DNS check components...
Removing mongo check components...
Preflight cleanup complete
```
```

12
go.mod
View File

@@ -10,13 +10,13 @@ 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.20200604192606-17370c1af57b
sigs.k8s.io/kustomize/api => github.com/qlik-oss/kustomize/api v0.3.3-0.20200424070349-b0312eb71568
)
require (
cloud.google.com/go v0.52.0 // indirect
cloud.google.com/go/storage v1.5.0 // indirect
github.com/Masterminds/semver/v3 v3.1.0
github.com/Masterminds/semver/v3 v3.0.3
github.com/Shopify/ejson v1.2.1
github.com/aws/aws-sdk-go v1.28.9 // indirect
github.com/bugsnag/bugsnag-go v1.5.3 // indirect
@@ -34,25 +34,27 @@ require (
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381
github.com/mattn/go-colorable v0.1.4
github.com/mattn/go-tty v0.0.3
github.com/mitchellh/go-homedir v1.1.0
github.com/otiai10/copy v1.1.1
github.com/pkg/errors v0.9.1
github.com/qlik-oss/k-apis v0.1.7
github.com/qlik-oss/k-apis v0.1.1
github.com/robfig/cron/v3 v3.0.1
github.com/rogpeppe/go-internal v1.5.2 // indirect
github.com/spf13/cobra v0.0.6
github.com/spf13/viper v1.6.1
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 // indirect
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a // indirect
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
golang.org/x/tools v0.0.0-20200312194400-c312e98713c2 // indirect
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b // indirect
gopkg.in/yaml.v2 v2.2.8
gopkg.in/yaml.v3 v3.0.0-20190924164351-c8b7dadae555
k8s.io/api v0.17.2
k8s.io/apiextensions-apiserver v0.17.2
k8s.io/apimachinery v0.17.2
k8s.io/client-go v11.0.0+incompatible
sigs.k8s.io/kustomize/api v0.3.2

19
go.sum
View File

@@ -72,8 +72,6 @@ github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RP
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.0.3 h1:znjIyLfpXEDQjOIEWh+ehwpTU14UzUPub3c3sm36u14=
github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/sprig/v3 v3.0.2 h1:wz22D0CiSctrliXiI9ZO3HoNApweeRGftyDN+BQa3B8=
github.com/Masterminds/sprig/v3 v3.0.2/go.mod h1:oesJ8kPONMONaZgtiHNzUShJbksypC5kWczhZAf6+aU=
github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
@@ -298,7 +296,6 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
@@ -369,7 +366,6 @@ github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2K
github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
@@ -885,14 +881,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.5 h1:IeqHuF1IIQCsuSmsUhL7GjdfkOFsNgh3z2UyX59GTsk=
github.com/qlik-oss/k-apis v0.1.5/go.mod h1:yoYGgPJ/H0t9H3NSq64dWfyQY6QWi2L9c+hCJoVO03U=
github.com/qlik-oss/k-apis v0.1.6 h1:4rTRwgIN1GtkmLa0VBjvpW2oyVC3h1fOcFHEg3NieNA=
github.com/qlik-oss/k-apis v0.1.6/go.mod h1:r5hXo1mrHOzIdI0Ri9TI4SKjEXft1TZnAyJzOSm9pi0=
github.com/qlik-oss/k-apis v0.7.1 h1:7XHTcIVTSgBM8s9LglWbOeWlnKm84cgHIm7Slrjxb9Y=
github.com/qlik-oss/k-apis v0.7.1/go.mod h1:r5hXo1mrHOzIdI0Ri9TI4SKjEXft1TZnAyJzOSm9pi0=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200604192606-17370c1af57b h1:UqyMR9WA/MbUxCOaaDFk8D2VwM3fG98foDU5nG2HPoM=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200604192606-17370c1af57b/go.mod h1:zh3yFgE5zFk1kreqzVyyj1eXyIxQJT53l4zSg8Wt4SA=
github.com/qlik-oss/k-apis v0.1.1 h1:aZ4eTMB3mSn03Kuj7+RI0eFLkjK9+0qxADBioRb3qVA=
github.com/qlik-oss/k-apis v0.1.1/go.mod h1:yoYGgPJ/H0t9H3NSq64dWfyQY6QWi2L9c+hCJoVO03U=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200424070349-b0312eb71568 h1:wHOUCGfnmgYqW3aCjuP3fXmB2T/uZXMvltO+F3us83E=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200424070349-b0312eb71568/go.mod h1:Yg8bqX8Mq/eSgXfcenxCxhZuSXg+NCsKq6NBdch/oUc=
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=
@@ -1055,7 +1047,6 @@ go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.2 h1:jxcFYjlkl8xaERsgLo+RNquI0epW6zuy/ZRQs6jnrFA=
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -1176,8 +1167,6 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZ
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=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b h1:IYiJPiJfzktmDAO1HQiwjMjwjlYKHAL7KzeD544RJPs=
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=

View File

@@ -157,8 +157,6 @@ func (cr *QliksenseCR) GetFetchAccessToken(encryptionKey string) string {
if tok, err := cr.Spec.FetchSource.GetAccessToken(); err != nil {
fmt.Println(err)
return ""
} else if tok == "" {
return tok
} else {
by, _ := b64.StdEncoding.DecodeString(tok)
res, err := DecryptData(by, encryptionKey)

View File

@@ -107,7 +107,7 @@ func TestGetDecryptedCr(t *testing.T) {
key, _ := setupGenerateKey(dir)
ecn, _ := EncryptData([]byte("mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"), key)
b := b64.StdEncoding.EncodeToString(ecn)
qcr.Spec.AddToSecrets("qliksense", "mongodbUri", b, "")
qcr.Spec.AddToSecrets("qliksense", "mongoDbUri", b, "")
qcr.SetFetchAccessToken("mytoken", key)
@@ -117,8 +117,8 @@ func TestGetDecryptedCr(t *testing.T) {
t.Log(err)
}
decryptedValue := newCr.Spec.GetFromSecrets("qliksense", "mongodbUri")
orignalValue := qcr.Spec.GetFromSecrets("qliksense", "mongodbUri")
decryptedValue := newCr.Spec.GetFromSecrets("qliksense", "mongoDbUri")
orignalValue := qcr.Spec.GetFromSecrets("qliksense", "mongoDbUri")
if decryptedValue != "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false" {
t.Fail()
b, _ := K8sToYaml(newCr)

View File

@@ -1,727 +0,0 @@
package api
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/mitchellh/go-homedir"
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/retry"
)
var gracePeriod int64 = 0
var waitTimeout = 2 * time.Minute
type ClientGoUtils struct {
Verbose bool
}
func (p *ClientGoUtils) LogVerboseMessage(strMessage string, args ...interface{}) {
if p.Verbose || os.Getenv("QLIKSENSE_DEBUG") == "true" {
fmt.Printf(strMessage, args...)
}
}
func int32Ptr(i int32) *int32 { return &i }
func (p *ClientGoUtils) LoadKubeConfigAndNamespace() (string, []byte, error) {
LogDebugMessage("Reading .kube/config file...")
homeDir, err := homedir.Dir()
if err != nil {
err = fmt.Errorf("Unable to deduce home dir")
return "", nil, err
}
LogDebugMessage("Kube config location: %s\n\n", filepath.Join(homeDir, ".kube", "config"))
kubeConfig := filepath.Join(homeDir, ".kube", "config")
kubeConfigContents, err := ioutil.ReadFile(kubeConfig)
if err != nil {
err = fmt.Errorf("Unable to deduce home dir")
return "", nil, err
}
// retrieve namespace
namespace := GetKubectlNamespace()
// if namespace comes back empty, we will run checks in the default namespace
if namespace == "" {
namespace = "default"
}
return namespace, kubeConfigContents, nil
}
func (p *ClientGoUtils) RetryOnError(mf func() error) error {
return retry.OnError(wait.Backoff{
Duration: 1 * time.Second,
Factor: 1,
Jitter: 0.1,
Steps: 5,
}, func(err error) bool {
return k8serrors.IsConflict(err) || k8serrors.IsGone(err) || k8serrors.IsServerTimeout(err) ||
k8serrors.IsServiceUnavailable(err) || k8serrors.IsTimeout(err) || k8serrors.IsTooManyRequests(err)
}, mf)
}
func (p *ClientGoUtils) GetK8SClientSet(kubeconfig []byte, contextName string) (*kubernetes.Clientset, *rest.Config, error) {
var clientConfig *rest.Config
var err error
if len(kubeconfig) == 0 {
clientConfig, err = rest.InClusterConfig()
if err != nil {
err = fmt.Errorf("Unable to load in-cluster kubeconfig: %w", err)
return nil, nil, err
}
} else {
config, err := clientcmd.Load(kubeconfig)
if err != nil {
err = fmt.Errorf("Unable to load kubeconfig: %w", err)
return nil, nil, err
}
if contextName != "" {
config.CurrentContext = contextName
}
clientConfig, err = clientcmd.NewDefaultClientConfig(*config, &clientcmd.ConfigOverrides{}).ClientConfig()
if err != nil {
err = fmt.Errorf("Unable to create client config from config: %w", err)
return nil, nil, err
}
}
clientset, err := kubernetes.NewForConfig(clientConfig)
if err != nil {
err = fmt.Errorf("Unable to create clientset: %w", err)
return nil, nil, err
}
return clientset, clientConfig, nil
}
func (p *ClientGoUtils) CreatePreflightTestDeployment(clientset kubernetes.Interface, namespace string, depName string, imageName string) (*appsv1.Deployment, error) {
deploymentsClient := clientset.AppsV1().Deployments(namespace)
deployment := &appsv1.Deployment{
ObjectMeta: v1.ObjectMeta{
Name: depName,
},
Spec: appsv1.DeploymentSpec{
Replicas: int32Ptr(1),
Selector: &v1.LabelSelector{
MatchLabels: map[string]string{
"app": "preflight-check",
},
},
Template: apiv1.PodTemplateSpec{
ObjectMeta: v1.ObjectMeta{
Labels: map[string]string{
"app": "preflight-check",
"label": "preflight-check-label",
},
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{
Name: "dep",
Image: imageName,
Ports: []apiv1.ContainerPort{
{
Name: "http",
Protocol: apiv1.ProtocolTCP,
ContainerPort: 80,
},
},
},
},
},
},
},
}
// Create Deployment
var result *appsv1.Deployment
if err := p.RetryOnError(func() (err error) {
result, err = deploymentsClient.Create(deployment)
return err
}); err != nil {
err = fmt.Errorf("unable to create deployments in the %s namespace: %w", namespace, err)
return nil, err
}
p.LogVerboseMessage("Created deployment %q\n", result.GetObjectMeta().GetName())
return deployment, nil
}
func (p *ClientGoUtils) getDeployment(clientset kubernetes.Interface, namespace, depName string) (*appsv1.Deployment, error) {
deploymentsClient := clientset.AppsV1().Deployments(namespace)
var deployment *appsv1.Deployment
if err := p.RetryOnError(func() (err error) {
deployment, err = deploymentsClient.Get(depName, v1.GetOptions{})
return err
}); err != nil {
err = fmt.Errorf("unable to get deployments in the %s namespace: %w", namespace, err)
LogDebugMessage("%v\n", err)
return nil, err
}
return deployment, nil
}
func (p *ClientGoUtils) DeleteDeployment(clientset kubernetes.Interface, namespace, name string) error {
deploymentsClient := clientset.AppsV1().Deployments(namespace)
// Create Deployment
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
GracePeriodSeconds: &gracePeriod,
}
if err := p.RetryOnError(func() (err error) {
return deploymentsClient.Delete(name, &deleteOptions)
}); err != nil {
return err
}
if err := p.WaitForDeploymentToDelete(clientset, namespace, name); err != nil {
return err
}
p.LogVerboseMessage("Deleted deployment: %s\n", name)
return nil
}
func (p *ClientGoUtils) CreatePreflightTestService(clientset kubernetes.Interface, namespace string, svcName string) (*apiv1.Service, error) {
iptr := int32Ptr(80)
servicesClient := clientset.CoreV1().Services(namespace)
service := &apiv1.Service{
ObjectMeta: v1.ObjectMeta{
Name: svcName,
Namespace: namespace,
Labels: map[string]string{
"app": "preflight-check",
},
},
Spec: apiv1.ServiceSpec{
Ports: []apiv1.ServicePort{
{Name: "port1",
Port: *iptr,
},
},
Selector: map[string]string{
"app": "preflight-check",
},
ClusterIP: "",
},
}
var result *apiv1.Service
if err := p.RetryOnError(func() (err error) {
result, err = servicesClient.Create(service)
return err
}); err != nil {
return nil, err
}
p.LogVerboseMessage("Created service %q\n", result.GetObjectMeta().GetName())
return service, nil
}
func (p *ClientGoUtils) GetService(clientset kubernetes.Interface, namespace, svcName string) (*apiv1.Service, error) {
servicesClient := clientset.CoreV1().Services(namespace)
var svc *apiv1.Service
if err := p.RetryOnError(func() (err error) {
svc, err = servicesClient.Get(svcName, v1.GetOptions{})
return err
}); err != nil {
err = fmt.Errorf("unable to get services in the %s namespace: %w", namespace, err)
return nil, err
}
return svc, nil
}
func (p *ClientGoUtils) DeleteService(clientset kubernetes.Interface, namespace, name string) error {
servicesClient := clientset.CoreV1().Services(namespace)
// Create Deployment
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}
if err := p.RetryOnError(func() (err error) {
return servicesClient.Delete(name, &deleteOptions)
}); err != nil {
return err
}
p.LogVerboseMessage("Deleted service: %s\n", name)
return nil
}
func (p *ClientGoUtils) DeletePod(clientset kubernetes.Interface, namespace, name string) error {
podsClient := clientset.CoreV1().Pods(namespace)
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
GracePeriodSeconds: &gracePeriod,
}
if err := p.RetryOnError(func() (err error) {
return podsClient.Delete(name, &deleteOptions)
}); err != nil {
return err
}
if err := p.waitForPodToDelete(clientset, namespace, name); err != nil {
return err
}
p.LogVerboseMessage("Deleted pod: %s\n", name)
return nil
}
func (p *ClientGoUtils) CreatePreflightTestPod(clientset kubernetes.Interface, namespace, podName, imageName string, secretNames map[string]string, commandToRun []string) (*apiv1.Pod, error) {
// build the pod definition we want to deploy
pod := &apiv1.Pod{
ObjectMeta: v1.ObjectMeta{
Name: podName,
Namespace: namespace,
Labels: map[string]string{
"app": "preflight",
},
},
Spec: apiv1.PodSpec{
RestartPolicy: apiv1.RestartPolicyNever,
Containers: []apiv1.Container{
{
Name: "cnt",
Image: imageName,
ImagePullPolicy: apiv1.PullIfNotPresent,
Command: commandToRun,
},
},
},
}
if len(secretNames) > 0 {
for secretName, mountPath := range secretNames {
pod.Spec.Volumes = append(pod.Spec.Volumes, apiv1.Volume{
Name: secretName,
VolumeSource: apiv1.VolumeSource{
Secret: &apiv1.SecretVolumeSource{
SecretName: secretName,
Items: []apiv1.KeyToPath{
{
Key: secretName,
Path: filepath.Base(mountPath),
},
},
},
},
})
if len(pod.Spec.Containers) > 0 {
pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, apiv1.VolumeMount{
Name: secretName,
MountPath: filepath.Dir(mountPath),
ReadOnly: true,
})
}
}
}
// now create the pod in kubernetes cluster using the clientset
if err := p.RetryOnError(func() (err error) {
pod, err = clientset.CoreV1().Pods(namespace).Create(pod)
return err
}); err != nil {
return nil, err
}
p.LogVerboseMessage("Created pod: %s\n", pod.Name)
return pod, nil
}
func (p *ClientGoUtils) getPod(clientset kubernetes.Interface, namespace, podName string) (*apiv1.Pod, error) {
LogDebugMessage("Fetching pod: %s\n", podName)
var pod *apiv1.Pod
if err := p.RetryOnError(func() (err error) {
pod, err = clientset.CoreV1().Pods(namespace).Get(podName, v1.GetOptions{})
return err
}); err != nil {
LogDebugMessage("%v\n", err)
return nil, err
}
return pod, nil
}
func (p *ClientGoUtils) GetPodLogs(clientset kubernetes.Interface, pod *apiv1.Pod) (string, error) {
return p.GetPodContainerLogs(clientset, pod, "")
}
func (p *ClientGoUtils) GetPodContainerLogs(clientset kubernetes.Interface, pod *apiv1.Pod, container string) (string, error) {
podLogOpts := apiv1.PodLogOptions{}
if container != "" {
podLogOpts.Container = container
}
LogDebugMessage("Retrieving logs for pod: %s namespace: %s\n", pod.GetName(), pod.Namespace)
req := clientset.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &podLogOpts)
podLogs, err := req.Stream()
if err != nil {
return "", err
}
defer podLogs.Close()
buf := new(bytes.Buffer)
_, err = io.Copy(buf, podLogs)
if err != nil {
return "", err
}
LogDebugMessage("Log from pod: %s\n", buf.String())
return buf.String(), nil
}
func (p *ClientGoUtils) waitForResource(checkFunc func() (interface{}, error), validateFunc func(interface{}) bool) error {
timeout := time.NewTicker(waitTimeout)
defer timeout.Stop()
OUT:
for {
r, err := checkFunc()
if err != nil {
return err
}
select {
case <-timeout.C:
break OUT
default:
if validateFunc(r) {
break OUT
}
}
time.Sleep(5 * time.Second)
}
return nil
}
func (p *ClientGoUtils) WaitForDeployment(clientset kubernetes.Interface, namespace string, pfDeployment *appsv1.Deployment) error {
var err error
depName := pfDeployment.GetName()
checkFunc := func() (interface{}, error) {
pfDeployment, err = p.getDeployment(clientset, namespace, depName)
if err != nil {
err = fmt.Errorf("unable to retrieve deployment: %s\n", depName)
return nil, err
}
return pfDeployment, nil
}
validateFunc := func(data interface{}) bool {
d := data.(*appsv1.Deployment)
return int(d.Status.ReadyReplicas) > 0
}
if err := p.waitForResource(checkFunc, validateFunc); err != nil {
return err
}
if int(pfDeployment.Status.ReadyReplicas) == 0 {
err = fmt.Errorf("deployment took longer than expected to spin up pods")
return err
}
return nil
}
func (p *ClientGoUtils) WaitForPod(clientset kubernetes.Interface, namespace string, pod *apiv1.Pod) error {
var err error
if len(pod.Spec.Containers) == 0 {
err = fmt.Errorf("there are no containers in the pod")
return err
}
podName := pod.Name
checkFunc := func() (interface{}, error) {
pod, err = p.getPod(clientset, namespace, podName)
if err != nil {
err = fmt.Errorf("unable to retrieve %s pod by name", podName)
return nil, err
}
return pod, nil
}
validateFunc := func(data interface{}) bool {
po := data.(*apiv1.Pod)
return po.Status.Phase == apiv1.PodRunning || po.Status.Phase == apiv1.PodSucceeded || po.Status.Phase == apiv1.PodFailed
}
if err := p.waitForResource(checkFunc, validateFunc); err != nil {
return err
}
if pod.Status.Phase != apiv1.PodRunning && pod.Status.Phase != apiv1.PodSucceeded && pod.Status.Phase != apiv1.PodFailed {
err = fmt.Errorf("container is taking much longer than expected")
return err
}
return nil
}
func (p *ClientGoUtils) WaitForPodToDie(clientset kubernetes.Interface, namespace string, pod *apiv1.Pod) error {
podName := pod.Name
checkFunc := func() (interface{}, error) {
po, err := p.getPod(clientset, namespace, podName)
if err != nil {
err = fmt.Errorf("unable to retrieve %s pod by name", podName)
return nil, err
}
return po, nil
}
validateFunc := func(r interface{}) bool {
po := r.(*apiv1.Pod)
return po.Status.Phase == apiv1.PodFailed || po.Status.Phase == apiv1.PodSucceeded
}
if err := p.waitForResource(checkFunc, validateFunc); err != nil {
return err
}
return nil
}
func (p *ClientGoUtils) waitForPodToDelete(clientset kubernetes.Interface, namespace, podName string) error {
checkFunc := func() (interface{}, error) {
po, err := p.getPod(clientset, namespace, podName)
if err != nil {
return nil, err
}
return po, nil
}
validateFunc := func(po interface{}) bool {
return false
}
if err := p.waitForResource(checkFunc, validateFunc); err != nil {
return nil
}
err := fmt.Errorf("delete pod is taking unusually long")
return err
}
func (p *ClientGoUtils) WaitForDeploymentToDelete(clientset kubernetes.Interface, namespace, deploymentName string) error {
checkFunc := func() (interface{}, error) {
dep, err := p.getDeployment(clientset, namespace, deploymentName)
if err != nil {
return nil, err
}
return dep, nil
}
validateFunc := func(po interface{}) bool {
return false
}
if err := p.waitForResource(checkFunc, validateFunc); err != nil {
return nil
}
err := fmt.Errorf("delete deployment is taking unusually long")
return err
}
func (p *ClientGoUtils) CreatePreflightTestSecret(clientset kubernetes.Interface, namespace, secretName string, secretData []byte) (*apiv1.Secret, error) {
var secret *apiv1.Secret
var err error
// build the secret defination we want to create
secretSpec := &apiv1.Secret{
ObjectMeta: v1.ObjectMeta{
Name: secretName,
Namespace: namespace,
Labels: map[string]string{
"app": "preflight",
},
},
Data: map[string][]byte{
secretName: secretData,
},
}
// now create the secret in kubernetes cluster using the clientset
if err = p.RetryOnError(func() (err error) {
secret, err = clientset.CoreV1().Secrets(namespace).Create(secretSpec)
return err
}); err != nil {
return nil, err
}
p.LogVerboseMessage("Created Secret: %s\n", secret.Name)
return secret, nil
}
func (p *ClientGoUtils) DeleteK8sSecret(clientset kubernetes.Interface, namespace string, secretName string) error {
secretClient := clientset.CoreV1().Secrets(namespace)
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}
err := secretClient.Delete(secretName, &deleteOptions)
if err != nil {
return err
}
p.LogVerboseMessage("Deleted Secret: %s\n", secretName)
return nil
}
func (p *ClientGoUtils) CreateStatefulSet(clientset kubernetes.Interface, namespace string, statefulSetName string, imageName string) (*appsv1.StatefulSet, error) {
statefulSetsClient := clientset.AppsV1().StatefulSets(namespace)
statefulset := &appsv1.StatefulSet{
ObjectMeta: v1.ObjectMeta{
Name: statefulSetName,
},
Spec: appsv1.StatefulSetSpec{
Replicas: int32Ptr(1),
Selector: &v1.LabelSelector{
MatchLabels: map[string]string{
"app": "postflight-check",
},
},
Template: apiv1.PodTemplateSpec{
ObjectMeta: v1.ObjectMeta{
Labels: map[string]string{
"app": "postflight-check",
"label": "postflight-check-label",
},
},
Spec: apiv1.PodSpec{
InitContainers: []apiv1.Container{
{
Name: "migration",
Image: "ubuntu",
ImagePullPolicy: apiv1.PullIfNotPresent,
// Command: []string{"bash", "-c", "for i in {1..10}; do echo \"from init container...\"; sleep 1; done"},
Command: []string{"bash", "-c", "for i in {1..10}; do echo \"from init container...\"; sleep 1; exit 1; done"},
},
},
Containers: []apiv1.Container{
{
Name: "statefulset",
Image: imageName,
Ports: []apiv1.ContainerPort{
{
Name: "http",
Protocol: apiv1.ProtocolTCP,
ContainerPort: 80,
},
},
},
},
},
},
},
}
// Create Statefulset
var result *appsv1.StatefulSet
if err := p.RetryOnError(func() (err error) {
result, err = statefulSetsClient.Create(statefulset)
return err
}); err != nil {
err = fmt.Errorf("unable to create statefulsets in the %s namespace: %w", namespace, err)
return nil, err
}
LogDebugMessage("Created statefulset %q\n", result.GetObjectMeta().GetName())
return statefulset, nil
}
func (p *ClientGoUtils) waitForStatefulSet(clientset kubernetes.Interface, namespace string, pfStatefulset *appsv1.StatefulSet) error {
var err error
statefulsetName := pfStatefulset.GetName()
checkFunc := func() (interface{}, error) {
pfStatefulset, err = p.getStatefulset(clientset, namespace, statefulsetName)
if err != nil {
err = fmt.Errorf("unable to retrieve stateful set: %s\n", statefulsetName)
return nil, err
}
return pfStatefulset, nil
}
validateFunc := func(data interface{}) bool {
s := data.(*appsv1.StatefulSet)
return int(s.Status.ReadyReplicas) > 0
}
if err := p.waitForResource(checkFunc, validateFunc); err != nil {
return err
}
if int(pfStatefulset.Status.ReadyReplicas) == 0 {
err = fmt.Errorf("deployment took longer than expected to spin up pods")
return err
}
return nil
}
func (p *ClientGoUtils) getStatefulset(clientset kubernetes.Interface, namespace, statefulsetName string) (*appsv1.StatefulSet, error) {
statefulsetsClient := clientset.AppsV1().StatefulSets(namespace)
var statefulset *appsv1.StatefulSet
if err := p.RetryOnError(func() (err error) {
statefulset, err = statefulsetsClient.Get(statefulsetName, v1.GetOptions{})
return err
}); err != nil {
err = fmt.Errorf("unable to get statefulsets in the %s namespace: %w", namespace, err)
fmt.Printf("%v\n", err)
return nil, err
}
return statefulset, nil
}
func (p *ClientGoUtils) deleteStatefulSet(clientset kubernetes.Interface, namespace, name string) error {
statefulsetClient := clientset.AppsV1().StatefulSets(namespace)
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
GracePeriodSeconds: &gracePeriod,
}
if err := p.RetryOnError(func() (err error) {
return statefulsetClient.Delete(name, &deleteOptions)
}); err != nil {
return err
}
if err := p.waitForStatefulsetToDelete(clientset, namespace, name); err != nil {
return err
}
LogDebugMessage("Deleted statefulset: %s\n", name)
return nil
}
func (p *ClientGoUtils) waitForStatefulsetToDelete(clientset kubernetes.Interface, namespace, statefulsetName string) error {
checkFunc := func() (interface{}, error) {
statefulset, err := p.getStatefulset(clientset, namespace, statefulsetName)
if err != nil {
return nil, err
}
return statefulset, nil
}
validateFunc := func(po interface{}) bool {
return false
}
if err := p.waitForResource(checkFunc, validateFunc); err != nil {
return nil
}
err := fmt.Errorf("delete statefulset is taking unusually long")
return err
}
func (p *ClientGoUtils) GetPodsAndPodLogsFromFailedInitContainer(clientset kubernetes.Interface, lbls map[string]string, namespace, containerName string) (map[string]string, error) {
set := labels.Set(lbls)
listOptions := v1.ListOptions{LabelSelector: set.AsSelector().String()}
podList, err := clientset.CoreV1().Pods(namespace).List(listOptions)
if err != nil {
err = fmt.Errorf("unable to get podlist: %v", err)
fmt.Printf("%s\n", err)
}
LogDebugMessage("%d Pods retrieved\n ", len(podList.Items))
// var logs map[string]string
logs := map[string]string{}
for _, pod := range podList.Items {
LogDebugMessage("pod: %v\n", pod.GetName())
LogDebugMessage("%d init containers retrieved\n", len(pod.Spec.InitContainers))
for _, cs := range pod.Status.InitContainerStatuses {
if cs.Name == containerName && ((cs.State.Terminated != nil && (cs.State.Terminated.Reason != "Completed" || cs.State.Terminated.ExitCode > 0)) ||
(cs.LastTerminationState.Terminated != nil && (cs.LastTerminationState.Terminated.Reason != "Completed" || cs.LastTerminationState.Terminated.ExitCode > 0))) {
logs[pod.GetName()], err = p.GetPodContainerLogs(clientset, &pod, cs.Name)
if err != nil {
err = fmt.Errorf("unable to get pod logs: %v", err)
fmt.Printf("%s\n", err)
return nil, err
}
}
}
}
return logs, nil
}

File diff suppressed because it is too large Load Diff

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"
@@ -25,8 +24,8 @@ const (
QliksenseDefaultProfile = "docker-desktop"
DefaultRotateKeys = "yes"
QliksenseMetadataName = "QliksenseConfigMetadata"
DefaultMongodbUri = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
DefaultMongodbUriKey = "mongodbUri"
DefaultMongoDbUri = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
DefaultMongoDbUriKey = "mongoDbUri"
)
// AddCommonConfig adds common configs into CRs
@@ -41,7 +40,7 @@ func (qliksenseCR *QliksenseCR) AddCommonConfig(contextName string) {
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
@@ -97,7 +96,7 @@ func WriteToFile(content interface{}, targetFile string) error {
log.Println(err)
return err
}
LogDebugMessage("Wrote content into %s\n", targetFile)
LogDebugMessage("Wrote content into %s", targetFile)
return nil
}

View File

@@ -2,7 +2,6 @@ package api
import (
"reflect"
"strings"
"testing"
"github.com/qlik-oss/k-apis/pkg/config"
@@ -27,8 +26,8 @@ func TestAddCommonConfig(t *testing.T) {
RotateKeys: DefaultRotateKeys,
Secrets: map[string]config.NameValues{
"qliksense": []config.NameValue{{
Name: DefaultMongodbUriKey,
Value: strings.Replace(DefaultMongodbUri, "qlik-default", "myqliksense", 1),
Name: DefaultMongoDbUriKey,
Value: DefaultMongoDbUri,
},
},
},

View File

@@ -7,7 +7,7 @@ import (
"reflect"
"testing"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
func TestDockerConfigJsonSecret(t *testing.T) {
@@ -34,10 +34,10 @@ func TestDockerConfigJsonSecret(t *testing.T) {
t.Fatalf("error unmarshalling yaml string: %v, error: %v", string(dockerConfigJsonSecretYamlBytes), err)
} else if validYamlMap["apiVersion"] != "v1" ||
validYamlMap["kind"] != "Secret" ||
validYamlMap["metadata"].(map[interface {}]interface {})["name"] != dockerConfigJsonSecret.Name ||
validYamlMap["metadata"].(map[string]interface{})["name"] != dockerConfigJsonSecret.Name ||
validYamlMap["type"] != "kubernetes.io/dockerconfigjson" {
t.Fatalf("error verifying validity of secret yaml: %v", string(dockerConfigJsonSecretYamlBytes))
} else if dockerConfigJsonBytesBase64, ok := validYamlMap["data"].(map[interface {}]interface {})[".dockerconfigjson"]; !ok {
} else if dockerConfigJsonBytesBase64, ok := validYamlMap["data"].(map[string]interface{})[".dockerconfigjson"]; !ok {
t.Fatalf("no .dockerconfigjson data key in the secret yaml: %v", string(dockerConfigJsonSecretYamlBytes))
} else if dockerConfigJsonEncryptedBytes, err := base64.StdEncoding.DecodeString(dockerConfigJsonBytesBase64.(string)); err != nil {
t.Fatalf("error decoding dockerConfigJsonBytes from base64: %v", err)
@@ -45,14 +45,14 @@ func TestDockerConfigJsonSecret(t *testing.T) {
t.Fatalf("error decrypting dockerConfigJsonBytes: %v", err)
} else if err := json.Unmarshal(dockerConfigJsonBytes, &dockerConfigJsonMap); err != nil {
t.Fatalf("error unmarshalling dockerConfigJson from json: %v", err)
} else if dockerConfigJson, ok := dockerConfigJsonMap["auths"].(map[string]interface {})[dockerConfigJsonSecret.Uri]; !ok {
} else if dockerConfigJson, ok := dockerConfigJsonMap["auths"].(map[string]interface{})[dockerConfigJsonSecret.Uri]; !ok {
t.Fatalf("dockerConfigJson map does not contain data for the registry: %v", dockerConfigJsonSecret.Uri)
} else if dockerConfigJson.(map[string]interface {})["username"] != dockerConfigJsonSecret.Username ||
dockerConfigJson.(map[string]interface {})["password"] != dockerConfigJsonSecret.Password ||
dockerConfigJson.(map[string]interface {})["email"] != dockerConfigJsonSecret.Email {
} else if dockerConfigJson.(map[string]interface{})["username"] != dockerConfigJsonSecret.Username ||
dockerConfigJson.(map[string]interface{})["password"] != dockerConfigJsonSecret.Password ||
dockerConfigJson.(map[string]interface{})["email"] != dockerConfigJsonSecret.Email {
t.Fatal("dockerConfigJson map does not contain expected values")
} else {
authBase64 := dockerConfigJson.(map[string]interface {})["auth"]
authBase64 := dockerConfigJson.(map[string]interface{})["auth"]
if auth, err := base64.StdEncoding.DecodeString(authBase64.(string)); err != nil {
t.Fatal("error base64 decoding auth value")
} else if string(auth) != fmt.Sprintf("%s:%s", dockerConfigJsonSecret.Username, dockerConfigJsonSecret.Password) {

View File

@@ -9,7 +9,7 @@ import (
"strings"
)
// KubectlApply create resources in the provided namespace,
// KubectlApply create resoruces in the provided namespace,
// if namespace="" then use whatever the kubectl default is
func KubectlApply(manifests, namespace string) error {
return kubectlOperation(manifests, "apply", namespace)
@@ -19,7 +19,7 @@ func KubectlApplyVerbose(manifests, namespace string, verbose bool) error {
return kubectlOperationVerbose(manifests, "apply", namespace, verbose)
}
// KubectlDelete delete resources in the provided namespace,
// KubectlDelete delete resoruces in the provided namespace,
// if namespace="" then use whatever the kubectl default is
func KubectlDelete(manifests, namespace string) error {
return kubectlOperation(manifests, "delete", namespace)

View File

@@ -17,9 +17,8 @@ type PreflightConfig struct {
}
type PreflightSpec struct {
MinK8sVersion string `json:"minK8sVersion,omitempty" yaml:"minK8sVersion,omitempty"`
MinMongoVersion string `json:"minMongoVersion,omitempty" yaml:"minMongoVersion,omitempty"`
Images map[string]string `json:"images,omitempty" yaml:"images,omitempty"`
MinK8sVersion string `json:"minK8sVersion,omitempty" yaml:"minK8sVersion,omitempty"`
Images map[string]string `json:"images,omitempty" yaml:"images,omitempty"`
}
//NewPreflightConfigEmpty create empty PreflightConfig object
@@ -75,13 +74,6 @@ func (p *PreflightConfig) AddMinK8sV(version string) {
p.Spec.MinK8sVersion = version
}
func (p *PreflightConfig) AddMinMongoV(version string) {
if p.Spec == nil {
p.Spec = &PreflightSpec{}
}
p.Spec.MinMongoVersion = version
}
func (p *PreflightConfig) AddImage(imageFor, imageName string) {
if p.Spec.Images == nil {
p.Spec.Images = make(map[string]string)
@@ -109,11 +101,6 @@ func (p *PreflightConfig) GetImageName(imageFor string, accountForImageRegistry
func (p *PreflightConfig) GetMinK8sVersion() string {
return p.Spec.MinK8sVersion
}
func (p *PreflightConfig) GetMinMongoVersion() string {
return p.Spec.MinMongoVersion
}
func (p *PreflightConfig) IsExistOnDisk() bool {
if _, err := os.Lstat(p.GetConfigFilePath()); err != nil {
return false
@@ -130,9 +117,8 @@ func (p *PreflightConfig) Initialize() error {
return nil
}
p.AddMinK8sV("1.15")
p.AddMinMongoV("3.6")
p.AddImage("nginx", "nginx")
p.AddImage("netcat", "subfuzion/netcat")
p.AddImage("preflight-mongo", "qlik-docker-oss.bintray.io/preflight-mongo")
p.AddImage("mongo", "mongo")
return p.Write()
}

View File

@@ -12,7 +12,6 @@ import (
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"time"
@@ -24,7 +23,7 @@ func checkExists(filename string) os.FileInfo {
if os.IsNotExist(err) {
return nil
}
LogDebugMessage("File exists\n")
LogDebugMessage("File exists")
return info
}
@@ -74,14 +73,13 @@ func ProcessConfigArgs(args []string, base64Encoded bool) ([]*ServiceKeyValue, e
resultSvcKV := make([]*ServiceKeyValue, len(args))
// qliksense.mongodb=somethig
for i, arg := range args {
LogDebugMessage("Arg received: %s\n", arg)
LogDebugMessage("Arg received: %s", arg)
first := strings.SplitN(arg, "=", 2)
if len(first) != 2 {
return nil, notValidErr
}
svcKey := getSvcAndKey(first[0])
if len(svcKey) != 2 {
second := strings.SplitN(first[0], ".", 2)
if len(second) != 2 {
return nil, notValidErr
}
resultValue := strings.Trim(first[1], "\"")
@@ -93,33 +91,14 @@ func ProcessConfigArgs(args []string, base64Encoded bool) ([]*ServiceKeyValue, e
}
}
resultSvcKV[i] = &ServiceKeyValue{
SvcName: svcKey[0],
Key: svcKey[1],
SvcName: second[0],
Key: second[1],
Value: resultValue,
}
}
return resultSvcKV, nil
}
// input should be svc[key]
func getSvcAndKey(arg string) []string {
// for key
re := regexp.MustCompile(`\[(.*)\]`)
// for service
re2 := regexp.MustCompile(`(.*)\[`)
keys := re.FindStringSubmatch(arg)
svcs := re2.FindStringSubmatch(arg)
if len(svcs) != 2 || len(keys) != 2 {
return strings.SplitN(arg, ".", 2)
}
if svcs[1] == "" || keys[1] == "" {
return []string{}
}
return []string{svcs[1], keys[1]}
}
func ExecuteTaskWithBlinkingStdoutFeedback(task func() (interface{}, error), feedback string) (result interface{}, err error) {
taskDone := make(chan bool)
go func() {

View File

@@ -45,24 +45,3 @@ func contains(arr []string, str string) bool {
}
return false
}
func TestGetSvcAndKey(t *testing.T) {
s1 := "qliksense[tls.cert]"
sa := getSvcAndKey(s1)
if sa[0] != "qliksense" || sa[1] != "tls.cert" {
t.Fail()
t.Logf("expected service: qliksense but got %s", sa[0])
t.Logf("expected key: tls.cert but got %s", sa[1])
}
s1 = "qliksense-idps.tls"
sa = getSvcAndKey(s1)
for _, s := range sa {
t.Logf("|%s|", s)
}
if sa[0] != "qliksense-idps" || sa[1] != "tls" {
t.Fail()
t.Logf("expected service: qliksense-idps but got %s", sa[0])
t.Logf("expected key: tls but got %s", sa[1])
}
}

View File

@@ -1,73 +0,0 @@
package postflight
import (
"fmt"
"strings"
"github.com/qlik-oss/sense-installer/pkg/api"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const initContainerNameToCheck = "migration"
func (p *QliksensePostflight) DbMigrationCheck(namespace string, kubeConfigContents []byte) error {
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
if err != nil {
err = fmt.Errorf("unable to create a kubernetes client: %v", err)
fmt.Printf("%s\n", err)
return err
}
var logsMap map[string]string
// Retrieve all deployments
p.CG.LogVerboseMessage("Retrieving logs from deployments\n")
deploymentsClient := clientset.AppsV1().Deployments(namespace)
deployments, err := deploymentsClient.List(v1.ListOptions{})
api.LogDebugMessage("Number of deployments found: %d\n", deployments.Size())
for _, deployment := range deployments.Items {
api.LogDebugMessage("Deployment name: %s\n", deployment.GetName())
if logsMap, err = p.CG.GetPodsAndPodLogsFromFailedInitContainer(clientset, deployment.Spec.Template.Labels, namespace, initContainerNameToCheck); err != nil {
fmt.Printf("%s\n", err)
return err
}
p.filterLogsForErrors(logsMap, namespace)
}
// retrieve all statefulsets
p.CG.LogVerboseMessage("Retrieving logs from statefulsets\n")
statefulsetsClient := clientset.AppsV1().StatefulSets(namespace)
statefulsets, err := statefulsetsClient.List(v1.ListOptions{})
api.LogDebugMessage("Number of statefulsets found: %d\n", statefulsets.Size())
for _, statefulset := range statefulsets.Items {
api.LogDebugMessage("Statefulset name: %s\n", statefulset.GetName())
if logsMap, err = p.CG.GetPodsAndPodLogsFromFailedInitContainer(clientset, statefulset.Spec.Template.Labels, namespace, initContainerNameToCheck); err != nil {
fmt.Printf("%s\n", err)
return err
}
p.filterLogsForErrors(logsMap, namespace)
}
return nil
}
func (p *QliksensePostflight) filterLogsForErrors(logsMap map[string]string, namespace string) {
errorLogsPresent := false
for podName, podLog := range logsMap {
containerLogs := strings.Split(podLog, "\n")
if len(containerLogs) > 0 {
for _, logLine := range containerLogs {
if strings.Contains(strings.ToLower(logLine), "error") {
errorLogsPresent = true
fmt.Printf("Logs from pod: %s\n%s\n", podName, logLine)
}
}
if errorLogsPresent {
fmt.Printf("To view more logs in this context, please run the command: kubectl logs -n %s %s %s\n", namespace, podName, initContainerNameToCheck)
}
} else {
fmt.Printf("no logs obtained\n\n")
}
}
}

View File

@@ -1,16 +0,0 @@
package postflight
import (
"github.com/qlik-oss/sense-installer/pkg/api"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
)
type PostflightOptions struct {
Verbose bool
}
type QliksensePostflight struct {
Q *qliksense.Qliksense
P *PostflightOptions
CG *api.ClientGoUtils
}

View File

@@ -3,9 +3,9 @@ package preflight
import (
"fmt"
. "github.com/logrusorgru/aurora"
ansi "github.com/mattn/go-colorable"
"github.com/pkg/errors"
. "github.com/logrusorgru/aurora"
)
func (qp *QliksensePreflight) RunAllPreflightChecks(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions) error {
@@ -15,90 +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"))
if err := qp.CheckDeployment(namespace, kubeConfigContents); err != nil {
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"))
if err := qp.CheckService(namespace, kubeConfigContents); err != nil {
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"))
if err := qp.CheckPod(namespace, kubeConfigContents); err != nil {
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"))
if err := qp.CheckCreateRole(namespace); err != nil {
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"))
if err := qp.CheckCreateRoleBinding(namespace); err != nil {
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"))
if err := qp.CheckCreateServiceAccount(namespace); err != nil {
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"))
if err := qp.CheckMongo(kubeConfigContents, namespace, preflightOpts); err != nil {
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"))
if err := qp.CheckDns(namespace, kubeConfigContents); err != nil {
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"))
fmt.Fprintf(out, "%s\n\n", Green("Preflight DNS check PASSED"))
checkCount++
}
totalCount++

View File

@@ -6,151 +6,122 @@ import (
"k8s.io/client-go/kubernetes"
)
func (p *QliksensePreflight) CheckDeployment(namespace string, kubeConfigContents []byte, cleanup bool) error {
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
func (qp *QliksensePreflight) CheckDeployment(namespace string, kubeConfigContents []byte) error {
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
if err != nil {
err = fmt.Errorf("Kube config error: %v\n", err)
return err
}
// Deployment check
if !cleanup {
fmt.Print("Preflight deployment check... ")
p.CG.LogVerboseMessage("\n--------------------------- \n")
}
err = p.checkPfDeployment(clientset, namespace, cleanup)
qp.P.LogVerboseMessage("Preflight deployment check: \n")
qp.P.LogVerboseMessage("--------------------------- \n")
err = qp.checkPfDeployment(clientset, namespace, "deployment-preflight-check")
if err != nil {
p.CG.LogVerboseMessage("Preflight Deployment check: FAILED\n")
qp.P.LogVerboseMessage("Preflight Deployment check: FAILED\n")
return err
}
if !cleanup {
p.CG.LogVerboseMessage("Completed preflight deployment check\n")
}
qp.P.LogVerboseMessage("Completed preflight deployment check\n")
return nil
}
func (p *QliksensePreflight) CheckService(namespace string, kubeConfigContents []byte, cleanup bool) error {
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
func (qp *QliksensePreflight) CheckService(namespace string, kubeConfigContents []byte) error {
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
if err != nil {
err = fmt.Errorf("unable to create a kubernetes client: %v\n", err)
return err
}
// Service check
if !cleanup {
fmt.Print("Preflight service check... ")
p.CG.LogVerboseMessage("\n------------------------ \n")
}
err = p.checkPfService(clientset, namespace, cleanup)
qp.P.LogVerboseMessage("Preflight service check: \n")
qp.P.LogVerboseMessage("------------------------ \n")
err = qp.checkPfService(clientset, namespace)
if err != nil {
p.CG.LogVerboseMessage("Preflight Service check: FAILED\n")
qp.P.LogVerboseMessage("Preflight Service check: FAILED\n")
return err
}
if !cleanup {
p.CG.LogVerboseMessage("Completed preflight service check\n")
}
qp.P.LogVerboseMessage("Completed preflight service check\n")
return nil
}
func (p *QliksensePreflight) CheckPod(namespace string, kubeConfigContents []byte, cleanup bool) error {
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
func (qp *QliksensePreflight) CheckPod(namespace string, kubeConfigContents []byte) error {
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
if err != nil {
err = fmt.Errorf("error: unable to create a kubernetes client: %v\n", err)
return err
}
// Pod check
if !cleanup {
fmt.Print("Preflight pod check... ")
p.CG.LogVerboseMessage("\n-------------------- \n")
}
err = p.checkPfPod(clientset, namespace, cleanup)
qp.P.LogVerboseMessage("Preflight pod check: \n")
qp.P.LogVerboseMessage("-------------------- \n")
err = qp.checkPfPod(clientset, namespace)
if err != nil {
p.CG.LogVerboseMessage("Preflight Pod check: FAILED\n")
qp.P.LogVerboseMessage("Preflight Pod check: FAILED\n")
return err
}
if !cleanup {
p.CG.LogVerboseMessage("Completed preflight pod check\n")
}
qp.P.LogVerboseMessage("Completed preflight pod check\n")
return nil
}
func (p *QliksensePreflight) checkPfPod(clientset kubernetes.Interface, namespace string, cleanup bool) error {
// delete the pod we are going to create, if it already exists in the cluster
func (qp *QliksensePreflight) checkPfPod(clientset *kubernetes.Clientset, namespace string) error {
// create a pod
podName := "pod-pf-check"
p.CG.DeletePod(clientset, namespace, podName)
if cleanup {
return nil
}
commandToRun := []string{}
imageName, err := p.GetPreflightConfigObj().GetImageName(nginx, true)
imageName, err := qp.GetPreflightConfigObj().GetImageName(nginx, true)
if err != nil {
return err
}
// create a pod
pod, err := p.CG.CreatePreflightTestPod(clientset, namespace, podName, imageName, nil, commandToRun)
pod, err := qp.createPreflightTestPod(clientset, namespace, podName, imageName, nil, commandToRun)
if err != nil {
err = fmt.Errorf("unable to create pod - %v\n", err)
return err
}
defer p.CG.DeletePod(clientset, namespace, podName)
defer qp.deletePod(clientset, namespace, podName)
if err := p.CG.WaitForPod(clientset, namespace, pod); err != nil {
if err := waitForPod(clientset, namespace, pod); err != nil {
return err
}
p.CG.LogVerboseMessage("Preflight pod creation check: PASSED\n")
p.CG.LogVerboseMessage("Cleaning up resources...\n")
qp.P.LogVerboseMessage("Preflight pod creation check: PASSED\n")
qp.P.LogVerboseMessage("Cleaning up resources...\n")
return nil
}
func (p *QliksensePreflight) checkPfService(clientset kubernetes.Interface, namespace string, cleanup bool) error {
// delete the service we are going to create, if it already exists in the cluster
serviceName := "svc-pf-check"
p.CG.DeleteService(clientset, namespace, serviceName)
if cleanup {
return nil
}
func (qp *QliksensePreflight) checkPfService(clientset *kubernetes.Clientset, namespace string) error {
// creating service
pfService, err := p.CG.CreatePreflightTestService(clientset, namespace, serviceName)
serviceName := "svc-pf-check"
pfService, err := qp.createPreflightTestService(clientset, namespace, serviceName)
if err != nil {
err = fmt.Errorf("unable to create service - %v\n", err)
return err
}
defer p.CG.DeleteService(clientset, namespace, serviceName)
_, err = p.CG.GetService(clientset, namespace, pfService.GetName())
defer qp.deleteService(clientset, namespace, serviceName)
_, err = getService(clientset, namespace, pfService.GetName())
if err != nil {
err = fmt.Errorf("unable to retrieve service - %v\n", err)
return err
}
p.CG.LogVerboseMessage("Preflight service creation check: PASSED\n")
p.CG.LogVerboseMessage("Cleaning up resources...\n")
qp.P.LogVerboseMessage("Preflight service creation check: PASSED\n")
qp.P.LogVerboseMessage("Cleaning up resources...\n")
return nil
}
func (p *QliksensePreflight) checkPfDeployment(clientset kubernetes.Interface, namespace string, cleanup bool) error {
// delete the deployment we are going to create, if it already exists in the cluster
depName := "deployment-preflight-check"
p.CG.DeleteDeployment(clientset, namespace, depName)
if cleanup {
return nil
}
func (qp *QliksensePreflight) checkPfDeployment(clientset *kubernetes.Clientset, namespace, depName string) error {
// check if we are able to create a deployment
imageName, err := p.GetPreflightConfigObj().GetImageName(nginx, true)
imageName, err := qp.GetPreflightConfigObj().GetImageName(nginx, true)
if err != nil {
return err
}
pfDeployment, err := p.CG.CreatePreflightTestDeployment(clientset, namespace, depName, imageName)
pfDeployment, err := qp.createPreflightTestDeployment(clientset, namespace, depName, imageName)
if err != nil {
err = fmt.Errorf("unable to create deployment - %v\n", err)
return err
}
defer p.CG.DeleteDeployment(clientset, namespace, depName)
if err := p.CG.WaitForDeployment(clientset, namespace, pfDeployment); err != nil {
defer qp.deleteDeployment(clientset, namespace, depName)
if err := waitForDeployment(clientset, namespace, pfDeployment); err != nil {
return err
}
p.CG.LogVerboseMessage("Preflight Deployment check: PASSED\n")
p.CG.LogVerboseMessage("Cleaning up resources...\n")
qp.P.LogVerboseMessage("Preflight Deployment check: PASSED\n")
qp.P.LogVerboseMessage("Cleaning up resources...\n")
return nil
}

View File

@@ -3,8 +3,6 @@ package preflight
import (
"fmt"
"strings"
"k8s.io/client-go/kubernetes"
)
const (
@@ -12,68 +10,58 @@ const (
netcat = "netcat"
)
func (p *QliksensePreflight) CheckDns(namespace string, kubeConfigContents []byte, cleanup bool) error {
depName := "dep-dns-preflight-check"
serviceName := "svc-dns-pf-check"
podName := "pf-pod-1"
if !cleanup {
fmt.Print("Preflight DNS check... ")
p.CG.LogVerboseMessage("\n------------------- \n")
}
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
func (qp *QliksensePreflight) CheckDns(namespace string, kubeConfigContents []byte) error {
qp.P.LogVerboseMessage("Preflight DNS check: \n")
qp.P.LogVerboseMessage("------------------- \n")
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
if err != nil {
err = fmt.Errorf("unable to create a kubernetes client: %v\n", err)
return err
}
// delete the deployment we are going to create, if it already exists in the cluster
p.runDNSCleanup(clientset, namespace, podName, serviceName, depName)
if cleanup {
return nil
}
// creating deployment
nginxImageName, err := p.GetPreflightConfigObj().GetImageName(nginx, true)
depName := "dep-dns-preflight-check"
nginxImageName, err := qp.GetPreflightConfigObj().GetImageName(nginx, true)
if err != nil {
return err
}
dnsDeployment, err := p.CG.CreatePreflightTestDeployment(clientset, namespace, depName, nginxImageName)
dnsDeployment, err := qp.createPreflightTestDeployment(clientset, namespace, depName, nginxImageName)
if err != nil {
err = fmt.Errorf("unable to create deployment: %v\n", err)
return err
}
defer p.CG.DeleteDeployment(clientset, namespace, depName)
defer qp.deleteDeployment(clientset, namespace, depName)
if err := p.CG.WaitForDeployment(clientset, namespace, dnsDeployment); err != nil {
if err := waitForDeployment(clientset, namespace, dnsDeployment); err != nil {
return err
}
// creating service
dnsService, err := p.CG.CreatePreflightTestService(clientset, namespace, serviceName)
serviceName := "svc-dns-pf-check"
dnsService, err := qp.createPreflightTestService(clientset, namespace, serviceName)
if err != nil {
err = fmt.Errorf("unable to create service : %s, %s\n", serviceName, err)
return err
}
defer p.CG.DeleteService(clientset, namespace, serviceName)
defer qp.deleteService(clientset, namespace, serviceName)
// create a pod
podName := "pf-pod-1"
commandToRun := []string{"sh", "-c", "sleep 10; nc -z -v -w 1 " + dnsService.Name + " 80"}
netcatImageName, err := p.GetPreflightConfigObj().GetImageName(netcat, true)
netcatImageName, err := qp.GetPreflightConfigObj().GetImageName(netcat, true)
if err != nil {
err = fmt.Errorf("unable to retrieve image : %v\n", err)
return err
}
dnsPod, err := p.CG.CreatePreflightTestPod(clientset, namespace, podName, netcatImageName, nil, commandToRun)
dnsPod, err := qp.createPreflightTestPod(clientset, namespace, podName, netcatImageName, nil, commandToRun)
if err != nil {
err = fmt.Errorf("unable to create pod : %s, %s\n", podName, err)
return err
}
defer p.CG.DeletePod(clientset, namespace, podName)
defer qp.deletePod(clientset, namespace, podName)
if err := p.CG.WaitForPod(clientset, namespace, dnsPod); err != nil {
if err := waitForPod(clientset, namespace, dnsPod); err != nil {
return err
}
if len(dnsPod.Spec.Containers) == 0 {
@@ -81,30 +69,23 @@ func (p *QliksensePreflight) CheckDns(namespace string, kubeConfigContents []byt
return err
}
p.CG.WaitForPodToDie(clientset, namespace, dnsPod)
waitForPodToDie(clientset, namespace, dnsPod)
logStr, err := p.CG.GetPodLogs(clientset, dnsPod)
logStr, err := getPodLogs(clientset, dnsPod)
if err != nil {
err = fmt.Errorf("unable to execute dns check in the cluster: %v", err)
return err
}
if strings.HasSuffix(strings.TrimSpace(logStr), "succeeded!") {
p.CG.LogVerboseMessage("Preflight DNS check: PASSED\n")
qp.P.LogVerboseMessage("Preflight DNS check: PASSED\n")
} else {
err = fmt.Errorf("Expected response not found\n")
return err
}
if !cleanup {
p.CG.LogVerboseMessage("Completed preflight DNS check\n")
p.CG.LogVerboseMessage("Cleaning up resources...\n")
}
qp.P.LogVerboseMessage("Completed preflight DNS check\n")
qp.P.LogVerboseMessage("Cleaning up resources...\n")
return nil
}
func (p *QliksensePreflight) runDNSCleanup(clientset kubernetes.Interface, namespace, podName, serviceName, depName string) {
p.CG.DeleteDeployment(clientset, namespace, depName)
p.CG.DeletePod(clientset, namespace, podName)
p.CG.DeleteService(clientset, namespace, serviceName)
}

View File

@@ -3,12 +3,8 @@ package preflight
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/Masterminds/semver/v3"
"github.com/pkg/errors"
"github.com/qlik-oss/sense-installer/pkg/api"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
apiv1 "k8s.io/api/core/v1"
@@ -16,133 +12,131 @@ import (
)
const (
preflight_mongo = "preflight-mongo"
caCertMountPath = "/etc/ssl/certs/ca-certificates.crt"
mongo = "mongo"
)
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")
}
var currentCR *qapi.QliksenseCR
var err error
qConfig := qapi.NewQConfig(qp.Q.QliksenseHome)
qConfig.SetNamespace(namespace)
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
}
if preflightOpts.MongoOptions.MongodbUrl == "" && !cleanup {
func (qp *QliksensePreflight) CheckMongo(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions) error {
qp.P.LogVerboseMessage("Preflight mongodb check: \n")
qp.P.LogVerboseMessage("------------------------ \n")
if preflightOpts.MongoOptions.MongodbUrl == "" {
// infer mongoDbUrl from currentCR
qp.CG.LogVerboseMessage("mongodbUri is empty, infer from CR\n")
preflightOpts.MongoOptions.MongodbUrl = strings.TrimSpace(decryptedCR.Spec.GetFromSecrets("qliksense", "mongodbUri"))
}
qp.P.LogVerboseMessage("MongoDbUri is empty, infer from CR\n")
qConfig := qapi.NewQConfig(qp.Q.QliksenseHome)
var currentCR *qapi.QliksenseCR
if preflightOpts.MongoOptions.CaCertFile == "" && !cleanup {
caCertStr := decryptedCR.Spec.GetFromSecrets("qliksense", "caCertificates")
tmpDir := os.TempDir()
caCrtFile := filepath.Join(tmpDir, "rootCA.crt")
api.LogDebugMessage("received ca crt: %s\n", caCertStr)
if err := ioutil.WriteFile(caCrtFile, []byte(caCertStr), 0644); err != nil {
return fmt.Errorf("unable to write CA crt to file: %v", err)
var err error
qConfig.SetNamespace(namespace)
currentCR, err = qConfig.GetCurrentCR()
if err != nil {
qp.P.LogVerboseMessage("Unable to retrieve current CR: %v\n", err)
return err
}
preflightOpts.MongoOptions.CaCertFile = caCrtFile
}
if !cleanup {
qp.CG.LogVerboseMessage("MongodbUrl: %s\n", preflightOpts.MongoOptions.MongodbUrl)
// if mongodbUrl is empty, abort check
if preflightOpts.MongoOptions.MongodbUrl == "" {
qp.CG.LogVerboseMessage("Mongodb Url is empty, hence aborting preflight check\n")
return errors.New("MongodbUrl is empty")
decryptedCR, err := qConfig.GetDecryptedCr(currentCR)
if err != nil {
qp.P.LogVerboseMessage("An error occurred while retrieving mongodbUrl from current CR: %v\n", err)
return err
}
preflightOpts.MongoOptions.MongodbUrl = decryptedCR.Spec.GetFromSecrets("qliksense", "mongoDbUri")
}
if err := qp.mongoConnCheck(kubeConfigContents, namespace, preflightOpts, cleanup); err != nil {
qp.P.LogVerboseMessage("MongodbUrl: %s\n", preflightOpts.MongoOptions.MongodbUrl)
if err := qp.mongoConnCheck(kubeConfigContents, namespace, preflightOpts); err != nil {
return err
}
if !cleanup {
qp.CG.LogVerboseMessage("Completed preflight mongodb check\n")
}
qp.P.LogVerboseMessage("Completed preflight mongodb check\n")
return nil
}
func (p *QliksensePreflight) mongoConnCheck(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions, cleanup bool) error {
caCertSecretName := "ca-certificates-crt"
mongoPodName := "pf-mongo-pod"
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
func (qp *QliksensePreflight) mongoConnCheck(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions) error {
var caCertSecretName, clientCertSecretName string
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
if err != nil {
err = fmt.Errorf("unable to create a kubernetes client: %v\n", err)
return err
}
// cleanup before starting check
p.runMongoCleanup(clientset, namespace, mongoPodName, caCertSecretName)
if cleanup {
return nil
}
secrets := map[string]string{}
var secrets []string
if preflightOpts.MongoOptions.CaCertFile != "" {
caCertSecret, err := p.createSecret(clientset, namespace, preflightOpts.MongoOptions.CaCertFile, caCertSecretName)
caCertSecretName = "preflight-mongo-test-cacert"
caCertSecret, err := qp.createSecret(clientset, namespace, preflightOpts.MongoOptions.CaCertFile, caCertSecretName)
if err != nil {
err = fmt.Errorf("unable to create a ca cert kubernetes secret: %v\n", err)
return err
}
defer p.CG.DeleteK8sSecret(clientset, namespace, caCertSecret.Name)
secrets[caCertSecretName] = caCertMountPath
defer qp.deleteK8sSecret(clientset, namespace, caCertSecret)
secrets = append(secrets, caCertSecretName)
}
if preflightOpts.MongoOptions.ClientCertFile != "" {
clientCertSecretName = "preflight-mongo-test-clientcert"
clientCertSecret, err := qp.createSecret(clientset, namespace, preflightOpts.MongoOptions.ClientCertFile, clientCertSecretName)
if err != nil {
err = fmt.Errorf("unable to create a client cert kubernetes secret: %v\n", err)
return err
}
defer qp.deleteK8sSecret(clientset, namespace, clientCertSecret)
secrets = append(secrets, clientCertSecretName)
}
commandToRun := []string{"./preflight-mongo", fmt.Sprintf(`-url="%s"`, preflightOpts.MongoOptions.MongodbUrl)}
mongoCommand := strings.Builder{}
mongoCommand.WriteString(fmt.Sprintf("sleep 10;mongo %s", preflightOpts.MongoOptions.MongodbUrl))
if preflightOpts.MongoOptions.Username != "" {
mongoCommand.WriteString(fmt.Sprintf(" --username %s", preflightOpts.MongoOptions.Username))
api.LogDebugMessage("Adding username: Mongo command: %s\n", mongoCommand.String())
}
if preflightOpts.MongoOptions.Password != "" {
mongoCommand.WriteString(fmt.Sprintf(" --password %s", preflightOpts.MongoOptions.Password))
api.LogDebugMessage("Adding username and password\n")
}
if preflightOpts.MongoOptions.Tls || preflightOpts.MongoOptions.CaCertFile != "" || preflightOpts.MongoOptions.ClientCertFile != "" {
mongoCommand.WriteString(" --tls")
api.LogDebugMessage("Adding --tls: Mongo command: %s\n", mongoCommand.String())
}
if preflightOpts.MongoOptions.CaCertFile != "" {
mongoCommand.WriteString(fmt.Sprintf(" --tlsCAFile=/etc/ssl/%s/%[1]s", caCertSecretName))
api.LogDebugMessage("Adding caCertFile: Mongo command: %s\n", mongoCommand.String())
}
if preflightOpts.MongoOptions.ClientCertFile != "" {
mongoCommand.WriteString(fmt.Sprintf(" --tlsCertificateKeyFile=/etc/ssl/%s/%[1]s", clientCertSecretName))
api.LogDebugMessage("Adding clientCertFile: Mongo command: %s\n", mongoCommand.String())
}
mongoCommand.WriteString(` --eval "print(\"connected to mongo\")"`)
commandToRun := []string{"sh", "-c", mongoCommand.String()}
api.LogDebugMessage("Mongo command: %s\n", strings.Join(commandToRun, " "))
// create a pod
imageName, err := p.GetPreflightConfigObj().GetImageName(preflight_mongo, true)
podName := "pf-mongo-pod"
imageName, err := qp.GetPreflightConfigObj().GetImageName(mongo, true)
if err != nil {
err = fmt.Errorf("unable to retrieve image : %v\n", err)
return err
}
api.LogDebugMessage("image name to be used: %s\n", imageName)
mongoPod, err := p.CG.CreatePreflightTestPod(clientset, namespace, mongoPodName, imageName, secrets, commandToRun)
mongoPod, err := qp.createPreflightTestPod(clientset, namespace, podName, imageName, secrets, commandToRun)
if err != nil {
err = fmt.Errorf("unable to create pod : %v\n", err)
return err
}
defer p.CG.DeletePod(clientset, namespace, mongoPodName)
defer qp.deletePod(clientset, namespace, podName)
if err := p.CG.WaitForPod(clientset, namespace, mongoPod); err != nil {
if err := waitForPod(clientset, namespace, mongoPod); err != nil {
return err
}
if len(mongoPod.Spec.Containers) == 0 {
err := fmt.Errorf("there are no containers in the pod- %v\n", err)
return err
}
p.CG.WaitForPodToDie(clientset, namespace, mongoPod)
logStr, err := p.CG.GetPodLogs(clientset, mongoPod)
waitForPodToDie(clientset, namespace, mongoPod)
logStr, err := getPodLogs(clientset, mongoPod)
if err != nil {
err = fmt.Errorf("unable to execute mongo check in the cluster: %v\n", err)
return err
}
// check mongo server version
ok, err := p.checkMongoVersion(logStr)
if !ok || err != nil {
return err
}
// check if connection succeeded
stringToCheck := "qlik - connection succeeded!!"
stringToCheck := "Implicit session:"
if strings.Contains(logStr, stringToCheck) {
p.CG.LogVerboseMessage("Preflight mongo check: PASSED\n")
qp.P.LogVerboseMessage("Preflight mongo check: PASSED\n")
} else {
err = fmt.Errorf("Connection failed: %s\n", logStr)
return err
@@ -150,58 +144,16 @@ func (p *QliksensePreflight) mongoConnCheck(kubeConfigContents []byte, namespace
return nil
}
func (p *QliksensePreflight) checkMongoVersion(logStr string) (bool, error) {
// check mongo server version
api.LogDebugMessage("Minimum required mongo version: %s\n", p.GetPreflightConfigObj().GetMinMongoVersion())
mongoVersionStrToCheck := "qlik mongo server version:"
if strings.Contains(logStr, mongoVersionStrToCheck) {
logLines := strings.Split(logStr, "\n")
for _, eachline := range logLines {
if strings.Contains(eachline, mongoVersionStrToCheck) {
mongoVersionLog := strings.Split(eachline, ":")
if len(mongoVersionLog) < 2 {
continue
}
mongoVersionStr := strings.ReplaceAll(strings.TrimSpace(mongoVersionLog[1]), `"`, "")
api.LogDebugMessage("Extracted mongo version from pod log: %s\n", mongoVersionStr)
currentMongoVersionSemver, err := semver.NewVersion(mongoVersionStr)
if err != nil {
err = fmt.Errorf("Unable to convert minimum mongo version into semver version:%v\n", err)
return false, err
}
minMongoVersionSemver, err := semver.NewVersion(p.GetPreflightConfigObj().GetMinMongoVersion())
if err != nil {
err = fmt.Errorf("Unable to convert required minimum mongo version into semver version:%v\n", err)
return false, err
}
if currentMongoVersionSemver.GreaterThan(minMongoVersionSemver) || currentMongoVersionSemver.Equal(minMongoVersionSemver) {
p.CG.LogVerboseMessage("Current mongodb server version %s is greater than or equal to minimum required mongodb version: %s\n", currentMongoVersionSemver, minMongoVersionSemver)
return true, nil
}
err = fmt.Errorf("Current mongodb server version %s is less than minimum required mongodb version: %s", currentMongoVersionSemver, minMongoVersionSemver)
return false, err
}
}
}
err := errors.New("Unable to infer mongodb server version")
return false, err
}
func (p *QliksensePreflight) createSecret(clientset kubernetes.Interface, namespace, certFile, certSecretName string) (*apiv1.Secret, error) {
func (qp *QliksensePreflight) createSecret(clientset *kubernetes.Clientset, namespace, certFile, certSecretName string) (*apiv1.Secret, error) {
certBytes, err := ioutil.ReadFile(certFile)
if err != nil {
return nil, err
}
certSecret, err := p.CG.CreatePreflightTestSecret(clientset, namespace, certSecretName, certBytes)
certSecret, err := qp.createPreflightTestSecret(clientset, namespace, certSecretName, certBytes)
if err != nil {
err = fmt.Errorf("unable to create secret with cert : %v\n", err)
err = fmt.Errorf("unable to create secret with ca cert : %v\n", err)
return nil, err
}
return certSecret, nil
}
func (p *QliksensePreflight) runMongoCleanup(clientset kubernetes.Interface, namespace, mongoPodName, caCertSecretName string) {
p.CG.DeletePod(clientset, namespace, mongoPodName)
p.CG.DeleteK8sSecret(clientset, namespace, caCertSecretName)
}

View File

@@ -1,8 +1,30 @@
package preflight
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/qlik-oss/sense-installer/pkg/api"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
"k8s.io/api/rbac/v1beta1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/retry"
)
type PreflightOptions struct {
@@ -10,42 +32,683 @@ type PreflightOptions struct {
MongoOptions *MongoOptions
}
type MongoOptions struct {
MongodbUrl string
CaCertFile string
// 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
Username string
Password string
CaCertFile string
ClientCertFile string
Tls bool
}
var gracePeriod int64 = 0
type QliksensePreflight struct {
Q *qliksense.Qliksense
P *PreflightOptions
CG *api.ClientGoUtils
Q *qliksense.Qliksense
P *PreflightOptions
}
func (qp *QliksensePreflight) GetPreflightConfigObj() *api.PreflightConfig {
return api.NewPreflightConfig(qp.Q.QliksenseHome)
}
func (qp *QliksensePreflight) Cleanup(namespace string, kubeConfigContents []byte) error {
qp.CG.LogVerboseMessage("Preflight clean\n")
qp.CG.LogVerboseMessage("----------------\n")
func InitPreflight() (string, []byte, error) {
api.LogDebugMessage("Reading .kube/config file...")
qp.CG.LogVerboseMessage("Removing deployment...\n")
qp.CheckDeployment(namespace, kubeConfigContents, true)
qp.CG.LogVerboseMessage("Removing service...\n")
qp.CheckService(namespace, kubeConfigContents, true)
qp.CG.LogVerboseMessage("Removing pod...\n")
qp.CheckPod(namespace, kubeConfigContents, true)
homeDir, err := homedir.Dir()
if err != nil {
err = fmt.Errorf("Unable to deduce home dir\n")
return "", nil, err
}
api.LogDebugMessage("Kube config location: %s\n\n", filepath.Join(homeDir, ".kube", "config"))
qp.CG.LogVerboseMessage("Removing role...\n")
qp.CheckCreateRole(namespace, true)
qp.CG.LogVerboseMessage("Removing rolebinding...\n")
qp.CheckCreateRoleBinding(namespace, true)
qp.CG.LogVerboseMessage("Removing serviceaccount...\n")
qp.CheckCreateServiceAccount(namespace, true)
kubeConfig := filepath.Join(homeDir, ".kube", "config")
kubeConfigContents, err := ioutil.ReadFile(kubeConfig)
if err != nil {
err = fmt.Errorf("Unable to deduce home dir\n")
return "", nil, err
}
// retrieve namespace
namespace := api.GetKubectlNamespace()
// if namespace comes back empty, we will run checks in the default namespace
if namespace == "" {
namespace = "default"
}
api.LogDebugMessage("Namespace: %s\n", namespace)
return namespace, kubeConfigContents, nil
}
qp.CG.LogVerboseMessage("Removing DNS check components...\n")
qp.CheckDns(namespace, kubeConfigContents, true)
qp.CG.LogVerboseMessage("Removing mongo check components...\n")
qp.CheckMongo(kubeConfigContents, namespace, &PreflightOptions{MongoOptions: &MongoOptions{}}, true)
func initiateK8sOps(opr, namespace string) error {
opr1 := strings.Fields(opr)
_, err := api.KubectlDirectOps(opr1, namespace)
if err != nil {
fmt.Println(err)
return err
}
return nil
}
func int32Ptr(i int32) *int32 { return &i }
func retryOnError(mf func() error) error {
return retry.OnError(wait.Backoff{
Duration: 1 * time.Second,
Factor: 1,
Jitter: 0.1,
Steps: 5,
}, func(err error) bool {
return k8serrors.IsConflict(err) || k8serrors.IsGone(err) || k8serrors.IsServerTimeout(err) ||
k8serrors.IsServiceUnavailable(err) || k8serrors.IsTimeout(err) || k8serrors.IsTooManyRequests(err)
}, mf)
}
func getK8SClientSet(kubeconfig []byte, contextName string) (*kubernetes.Clientset, *rest.Config, error) {
var clientConfig *rest.Config
var err error
if len(kubeconfig) == 0 {
clientConfig, err = rest.InClusterConfig()
if err != nil {
err = errors.Wrap(err, "Unable to load in-cluster kubeconfig")
return nil, nil, err
}
} else {
config, err := clientcmd.Load(kubeconfig)
if err != nil {
err = errors.Wrap(err, "Unable to load kubeconfig")
return nil, nil, err
}
if contextName != "" {
config.CurrentContext = contextName
}
clientConfig, err = clientcmd.NewDefaultClientConfig(*config, &clientcmd.ConfigOverrides{}).ClientConfig()
if err != nil {
err = errors.Wrap(err, "Unable to create client config from config")
return nil, nil, err
}
}
clientset, err := kubernetes.NewForConfig(clientConfig)
if err != nil {
err = errors.Wrap(err, "Unable to create clientset")
return nil, nil, err
}
return clientset, clientConfig, nil
}
func (qp *QliksensePreflight) createPreflightTestDeployment(clientset *kubernetes.Clientset, namespace string, depName string, imageName string) (*appsv1.Deployment, error) {
deploymentsClient := clientset.AppsV1().Deployments(namespace)
deployment := &appsv1.Deployment{
ObjectMeta: v1.ObjectMeta{
Name: depName,
},
Spec: appsv1.DeploymentSpec{
Replicas: int32Ptr(1),
Selector: &v1.LabelSelector{
MatchLabels: map[string]string{
"app": "preflight-check",
},
},
Template: apiv1.PodTemplateSpec{
ObjectMeta: v1.ObjectMeta{
Labels: map[string]string{
"app": "preflight-check",
"label": "preflight-check-label",
},
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{
Name: "dep",
Image: imageName,
Ports: []apiv1.ContainerPort{
{
Name: "http",
Protocol: apiv1.ProtocolTCP,
ContainerPort: 80,
},
},
},
},
},
},
},
}
// Create Deployment
var result *appsv1.Deployment
if err := retryOnError(func() (err error) {
result, err = deploymentsClient.Create(deployment)
return err
}); err != nil {
err = errors.Wrapf(err, "unable to create deployments in the %s namespace", namespace)
return nil, err
}
qp.P.LogVerboseMessage("Created deployment %q\n", result.GetObjectMeta().GetName())
return deployment, nil
}
func getDeployment(clientset *kubernetes.Clientset, namespace, depName string) (*appsv1.Deployment, error) {
deploymentsClient := clientset.AppsV1().Deployments(namespace)
var deployment *appsv1.Deployment
if err := retryOnError(func() (err error) {
deployment, err = deploymentsClient.Get(depName, v1.GetOptions{})
return err
}); err != nil {
err = errors.Wrapf(err, "unable to get deployments in the %s namespace", namespace)
api.LogDebugMessage("%v\n", err)
return nil, err
}
return deployment, nil
}
func (qp *QliksensePreflight) deleteDeployment(clientset *kubernetes.Clientset, namespace, name string) error {
deploymentsClient := clientset.AppsV1().Deployments(namespace)
// Create Deployment
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
GracePeriodSeconds: &gracePeriod,
}
if err := retryOnError(func() (err error) {
return deploymentsClient.Delete(name, &deleteOptions)
}); err != nil {
return err
}
if err := waitForDeploymentToDelete(clientset, namespace, name); err != nil {
return err
}
qp.P.LogVerboseMessage("Deleted deployment: %s\n", name)
return nil
}
func (qp *QliksensePreflight) createPreflightTestService(clientset *kubernetes.Clientset, namespace string, svcName string) (*apiv1.Service, error) {
iptr := int32Ptr(80)
servicesClient := clientset.CoreV1().Services(namespace)
service := &apiv1.Service{
ObjectMeta: v1.ObjectMeta{
Name: svcName,
Namespace: namespace,
Labels: map[string]string{
"app": "preflight-check",
},
},
Spec: apiv1.ServiceSpec{
Ports: []apiv1.ServicePort{
{Name: "port1",
Port: *iptr,
},
},
Selector: map[string]string{
"app": "preflight-check",
},
ClusterIP: "",
},
}
var result *apiv1.Service
if err := retryOnError(func() (err error) {
result, err = servicesClient.Create(service)
return err
}); err != nil {
return nil, err
}
qp.P.LogVerboseMessage("Created service %q\n", result.GetObjectMeta().GetName())
return service, nil
}
func getService(clientset *kubernetes.Clientset, namespace, svcName string) (*apiv1.Service, error) {
servicesClient := clientset.CoreV1().Services(namespace)
var svc *apiv1.Service
if err := retryOnError(func() (err error) {
svc, err = servicesClient.Get(svcName, v1.GetOptions{})
return err
}); err != nil {
err = errors.Wrapf(err, "unable to get services in the %s namespace", namespace)
return nil, err
}
return svc, nil
}
func (qp *QliksensePreflight) deleteService(clientset *kubernetes.Clientset, namespace, name string) error {
servicesClient := clientset.CoreV1().Services(namespace)
// Create Deployment
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}
if err := retryOnError(func() (err error) {
return servicesClient.Delete(name, &deleteOptions)
}); err != nil {
fmt.Println(err)
return err
}
qp.P.LogVerboseMessage("Deleted service: %s\n", name)
return nil
}
func (qp *QliksensePreflight) deletePod(clientset *kubernetes.Clientset, namespace, name string) error {
podsClient := clientset.CoreV1().Pods(namespace)
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
GracePeriodSeconds: &gracePeriod,
}
if err := retryOnError(func() (err error) {
return podsClient.Delete(name, &deleteOptions)
}); err != nil {
return err
}
if err := waitForPodToDelete(clientset, namespace, name); err != nil {
return err
}
qp.P.LogVerboseMessage("Deleted pod: %s\n", name)
return nil
}
func (qp *QliksensePreflight) createPreflightTestPod(clientset *kubernetes.Clientset, namespace, podName, imageName string, secretNames []string, commandToRun []string) (*apiv1.Pod, error) {
// build the pod definition we want to deploy
pod := &apiv1.Pod{
ObjectMeta: v1.ObjectMeta{
Name: podName,
Namespace: namespace,
Labels: map[string]string{
"app": "preflight",
},
},
Spec: apiv1.PodSpec{
RestartPolicy: apiv1.RestartPolicyNever,
Containers: []apiv1.Container{
{
Name: "cnt",
Image: imageName,
ImagePullPolicy: apiv1.PullIfNotPresent,
Command: commandToRun,
},
},
},
}
if len(secretNames) > 0 {
for _, secretName := range secretNames {
pod.Spec.Volumes = append(pod.Spec.Volumes, apiv1.Volume{
Name: secretName,
VolumeSource: apiv1.VolumeSource{
Secret: &apiv1.SecretVolumeSource{
SecretName: secretName,
Items: []apiv1.KeyToPath{
{
Key: secretName,
Path: secretName,
},
},
},
},
})
if len(pod.Spec.Containers) > 0 {
pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, apiv1.VolumeMount{
Name: secretName,
MountPath: "/etc/ssl/" + secretName,
ReadOnly: true,
})
}
}
}
// now create the pod in kubernetes cluster using the clientset
if err := retryOnError(func() (err error) {
pod, err = clientset.CoreV1().Pods(namespace).Create(pod)
return err
}); err != nil {
return nil, err
}
qp.P.LogVerboseMessage("Created pod: %s\n", pod.Name)
return pod, nil
}
func getPod(clientset *kubernetes.Clientset, namespace, podName string) (*apiv1.Pod, error) {
api.LogDebugMessage("Fetching pod: %s\n", podName)
var pod *apiv1.Pod
if err := retryOnError(func() (err error) {
pod, err = clientset.CoreV1().Pods(namespace).Get(podName, v1.GetOptions{})
return err
}); err != nil {
api.LogDebugMessage("%v\n", err)
return nil, err
}
return pod, nil
}
func getPodLogs(clientset *kubernetes.Clientset, pod *apiv1.Pod) (string, error) {
podLogOpts := apiv1.PodLogOptions{}
api.LogDebugMessage("Retrieving logs for pod: %s namespace: %s\n", pod.GetName(), pod.Namespace)
req := clientset.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &podLogOpts)
podLogs, err := req.Stream()
if err != nil {
return "", err
}
defer podLogs.Close()
time.Sleep(15 * time.Second)
buf := new(bytes.Buffer)
_, err = io.Copy(buf, podLogs)
if err != nil {
return "", err
}
api.LogDebugMessage("Log from pod: %s\n", buf.String())
return buf.String(), nil
}
func waitForResource(checkFunc func() (interface{}, error), validateFunc func(interface{}) bool) error {
timeout := time.NewTicker(2 * time.Minute)
defer timeout.Stop()
OUT:
for {
r, err := checkFunc()
if err != nil {
return err
}
select {
case <-timeout.C:
break OUT
default:
if validateFunc(r) {
break OUT
}
}
time.Sleep(5 * time.Second)
}
return nil
}
func waitForDeployment(clientset *kubernetes.Clientset, namespace string, pfDeployment *appsv1.Deployment) error {
var err error
depName := pfDeployment.GetName()
checkFunc := func() (interface{}, error) {
pfDeployment, err = getDeployment(clientset, namespace, depName)
if err != nil {
err = fmt.Errorf("unable to retrieve deployment: %s\n", depName)
return nil, err
}
return pfDeployment, nil
}
validateFunc := func(data interface{}) bool {
d := data.(*appsv1.Deployment)
return int(d.Status.ReadyReplicas) > 0
}
if err := waitForResource(checkFunc, validateFunc); err != nil {
return err
}
if int(pfDeployment.Status.ReadyReplicas) == 0 {
err = fmt.Errorf("deployment took longer than expected to spin up pods")
return err
}
return nil
}
func waitForPod(clientset *kubernetes.Clientset, namespace string, pod *apiv1.Pod) error {
var err error
if len(pod.Spec.Containers) == 0 {
err = fmt.Errorf("there are no containers in the pod")
return err
}
podName := pod.Name
checkFunc := func() (interface{}, error) {
pod, err = getPod(clientset, namespace, podName)
if err != nil {
err = fmt.Errorf("unable to retrieve %s pod by name", podName)
return nil, err
}
return pod, nil
}
validateFunc := func(data interface{}) bool {
po := data.(*apiv1.Pod)
return len(po.Status.ContainerStatuses) > 0 && po.Status.ContainerStatuses[0].Ready
}
if err := waitForResource(checkFunc, validateFunc); err != nil {
return err
}
if len(pod.Status.ContainerStatuses) == 0 || !pod.Status.ContainerStatuses[0].Ready {
err = fmt.Errorf("container is taking much longer than expected")
return err
}
return nil
}
func waitForPodToDie(clientset *kubernetes.Clientset, namespace string, pod *apiv1.Pod) error {
podName := pod.Name
checkFunc := func() (interface{}, error) {
po, err := getPod(clientset, namespace, podName)
if err != nil {
err = fmt.Errorf("unable to retrieve %s pod by name", podName)
return nil, err
}
api.LogDebugMessage("pod status: %v\n", po.Status.Phase)
return po, nil
}
validateFunc := func(r interface{}) bool {
po := r.(*apiv1.Pod)
return po.Status.Phase == apiv1.PodFailed || po.Status.Phase == apiv1.PodSucceeded
}
if err := waitForResource(checkFunc, validateFunc); err != nil {
return err
}
return nil
}
func waitForPodToDelete(clientset *kubernetes.Clientset, namespace, podName string) error {
checkFunc := func() (interface{}, error) {
po, err := getPod(clientset, namespace, podName)
if err != nil {
return nil, err
}
return po, nil
}
validateFunc := func(po interface{}) bool {
return false
}
if err := waitForResource(checkFunc, validateFunc); err != nil {
return nil
}
err := fmt.Errorf("delete pod is taking unusually long")
return err
}
func waitForDeploymentToDelete(clientset *kubernetes.Clientset, namespace, deploymentName string) error {
checkFunc := func() (interface{}, error) {
dep, err := getDeployment(clientset, namespace, deploymentName)
if err != nil {
return nil, err
}
return dep, nil
}
validateFunc := func(po interface{}) bool {
return false
}
if err := waitForResource(checkFunc, validateFunc); err != nil {
return nil
}
err := fmt.Errorf("delete deployment is taking unusually long")
return err
}
func (qp *QliksensePreflight) createPfRole(clientset *kubernetes.Clientset, namespace, roleName string) (*v1beta1.Role, error) {
// build the role defination we want to create
var role *v1beta1.Role
roleSpec := &v1beta1.Role{
ObjectMeta: v1.ObjectMeta{
Name: roleName,
Namespace: namespace,
Labels: map[string]string{
"app": "preflight",
},
},
Rules: []v1beta1.PolicyRule{},
}
// now create the role in kubernetes cluster using the clientset
if err := retryOnError(func() (err error) {
role, err = clientset.RbacV1beta1().Roles(namespace).Create(roleSpec)
return err
}); err != nil {
return nil, err
}
qp.P.LogVerboseMessage("Created role: %s\n", role.Name)
return role, nil
}
func (qp *QliksensePreflight) deleteRole(clientset *kubernetes.Clientset, namespace string, role *v1beta1.Role) {
rolesClient := clientset.RbacV1beta1().Roles(namespace)
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}
err := rolesClient.Delete(role.GetName(), &deleteOptions)
if err != nil {
log.Fatal(err)
}
qp.P.LogVerboseMessage("Deleted role: %s\n\n", role.Name)
}
func (qp *QliksensePreflight) createPfRoleBinding(clientset *kubernetes.Clientset, namespace, roleBindingName string) (*v1beta1.RoleBinding, error) {
var roleBinding *v1beta1.RoleBinding
// build the rolebinding defination we want to create
roleBindingSpec := &v1beta1.RoleBinding{
ObjectMeta: v1.ObjectMeta{
Name: roleBindingName,
Namespace: namespace,
Labels: map[string]string{
"app": "preflight",
},
},
Subjects: []v1beta1.Subject{
{
Kind: "ServiceAccount",
APIGroup: "",
Name: "preflight-check-subject",
Namespace: namespace,
},
},
RoleRef: v1beta1.RoleRef{
APIGroup: "",
Kind: "Role",
Name: "preflight-check-roleref",
},
}
// now create the roleBinding in kubernetes cluster using the clientset
if err := retryOnError(func() (err error) {
roleBinding, err = clientset.RbacV1beta1().RoleBindings(namespace).Create(roleBindingSpec)
return err
}); err != nil {
return nil, err
}
qp.P.LogVerboseMessage("Created RoleBinding: %s\n", roleBindingSpec.Name)
return roleBinding, nil
}
func (qp *QliksensePreflight) deleteRoleBinding(clientset *kubernetes.Clientset, namespace string, roleBinding *v1beta1.RoleBinding) {
roleBindingClient := clientset.RbacV1beta1().RoleBindings(namespace)
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}
err := roleBindingClient.Delete(roleBinding.GetName(), &deleteOptions)
if err != nil {
log.Fatal(err)
}
qp.P.LogVerboseMessage("Deleted RoleBinding: %s\n\n", roleBinding.Name)
}
func (qp *QliksensePreflight) createPfServiceAccount(clientset *kubernetes.Clientset, namespace, serviceAccountName string) (*apiv1.ServiceAccount, error) {
var serviceAccount *apiv1.ServiceAccount
// build the serviceAccount defination we want to create
serviceAccountSpec := &apiv1.ServiceAccount{
ObjectMeta: v1.ObjectMeta{
Name: "preflight-check-test-serviceaccount",
Namespace: namespace,
Labels: map[string]string{
"app": "preflight",
},
},
}
// now create the serviceAccount in kubernetes cluster using the clientset
if err := retryOnError(func() (err error) {
serviceAccount, err = clientset.CoreV1().ServiceAccounts(namespace).Create(serviceAccountSpec)
return err
}); err != nil {
return nil, err
}
qp.P.LogVerboseMessage("Created Service Account: %s\n", serviceAccountSpec.Name)
return serviceAccount, nil
}
func (qp *QliksensePreflight) deleteServiceAccount(clientset *kubernetes.Clientset, namespace string, serviceAccount *apiv1.ServiceAccount) {
serviceAccountClient := clientset.CoreV1().ServiceAccounts(namespace)
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}
err := serviceAccountClient.Delete(serviceAccount.GetName(), &deleteOptions)
if err != nil {
log.Fatal(err)
}
qp.P.LogVerboseMessage("Deleted ServiceAccount: %s\n\n", serviceAccount.Name)
}
func (qp *QliksensePreflight) createPreflightTestSecret(clientset *kubernetes.Clientset, namespace, secretName string, secretData []byte) (*apiv1.Secret, error) {
var secret *apiv1.Secret
var err error
// build the secret defination we want to create
secretSpec := &apiv1.Secret{
ObjectMeta: v1.ObjectMeta{
Name: secretName,
Namespace: namespace,
Labels: map[string]string{
"app": "preflight",
},
},
Data: map[string][]byte{
secretName: secretData,
},
}
// now create the secret in kubernetes cluster using the clientset
if err = retryOnError(func() (err error) {
secret, err = clientset.CoreV1().Secrets(namespace).Create(secretSpec)
return err
}); err != nil {
return nil, err
}
qp.P.LogVerboseMessage("Created Secret: %s\n", secret.Name)
return secret, nil
}
func (qp *QliksensePreflight) deleteK8sSecret(clientset *kubernetes.Clientset, namespace string, k8sSecret *apiv1.Secret) {
secretClient := clientset.CoreV1().Secrets(namespace)
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}
err := secretClient.Delete(k8sSecret.GetName(), &deleteOptions)
if err != nil {
log.Fatal(err)
}
qp.P.LogVerboseMessage("Deleted Secret: %s\n", k8sSecret.Name)
}

View File

@@ -0,0 +1,43 @@
package preflight
import (
"fmt"
"testing"
)
func Test_initiateK8sOps(t *testing.T) {
t.Skip()
type args struct {
opr string
namespace string
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "valid case",
args: args{
opr: fmt.Sprintf("version"),
namespace: "test-ns",
},
wantErr: false,
},
{
name: "invalid case",
args: args{
opr: fmt.Sprintf("versions"),
namespace: "test-ns",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := initiateK8sOps(tt.args.opr, tt.args.namespace); (err != nil) != tt.wantErr {
t.Errorf("initiateK8sOps() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@@ -11,69 +11,58 @@ import (
"github.com/qlik-oss/sense-installer/pkg/qliksense"
)
func (qp *QliksensePreflight) CheckCreateRole(namespace string, cleanup bool) error {
var resultYamlBytes = []byte("")
func (qp *QliksensePreflight) CheckCreateRole(namespace string) error {
// create a Role
if !cleanup {
fmt.Print("Preflight role check... ")
qp.CG.LogVerboseMessage("\n--------------------- \n")
}
err := qp.checkCreateEntity(namespace, "Role", cleanup)
qp.P.LogVerboseMessage("Preflight role check: \n")
qp.P.LogVerboseMessage("--------------------- \n")
err := qp.checkCreateEntity(namespace, "Role")
if err != nil {
return err
}
if !cleanup {
qp.CG.LogVerboseMessage("Completed preflight role check\n")
}
qp.P.LogVerboseMessage("Completed preflight role check\n")
return nil
}
func (qp *QliksensePreflight) CheckCreateRoleBinding(namespace string, cleanup bool) error {
func (qp *QliksensePreflight) CheckCreateRoleBinding(namespace string) error {
// create a RoleBinding
if !cleanup {
fmt.Print("Preflight rolebinding check... ")
qp.CG.LogVerboseMessage("\n---------------------------- \n")
}
err := qp.checkCreateEntity(namespace, "RoleBinding", cleanup)
qp.P.LogVerboseMessage("Preflight rolebinding check: \n")
qp.P.LogVerboseMessage("---------------------------- \n")
err := qp.checkCreateEntity(namespace, "RoleBinding")
if err != nil {
return err
}
if !cleanup {
qp.CG.LogVerboseMessage("Completed preflight rolebinding check\n")
}
qp.P.LogVerboseMessage("Completed preflight rolebinding check\n")
return nil
}
func (qp *QliksensePreflight) CheckCreateServiceAccount(namespace string, cleanup bool) error {
func (qp *QliksensePreflight) CheckCreateServiceAccount(namespace string) error {
// create a service account
if !cleanup {
fmt.Print("Preflight serviceaccount check... ")
qp.CG.LogVerboseMessage("\n------------------------------- \n")
}
err := qp.checkCreateEntity(namespace, "ServiceAccount", cleanup)
qp.P.LogVerboseMessage("Preflight serviceaccount check: \n")
qp.P.LogVerboseMessage("------------------------------- \n")
err := qp.checkCreateEntity(namespace, "ServiceAccount")
if err != nil {
return err
}
if !cleanup {
qp.CG.LogVerboseMessage("Completed preflight serviceaccount check\n")
}
qp.P.LogVerboseMessage("Completed preflight serviceaccount check\n")
return nil
}
func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string, cleanup bool) error {
func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string) error {
qConfig := qapi.NewQConfig(qp.Q.QliksenseHome)
var currentCR *qapi.QliksenseCR
mfroot := ""
kusDir := ""
resultYamlBytes := []byte("")
var err error
currentCR, err = qConfig.GetCurrentCR()
if err != nil {
qp.CG.LogVerboseMessage("Unable to retrieve current CR: %v\n", err)
qp.P.LogVerboseMessage("Unable to retrieve current CR: %v\n", err)
return err
}
if currentCR.IsRepoExist() {
mfroot = currentCR.Spec.GetManifestsRoot()
} else if tempDownloadedDir, err := qliksense.DownloadFromGitRepoToTmpDir(qliksense.QLIK_GIT_REPO, "master"); err != nil {
qp.CG.LogVerboseMessage("Unable to Download from git repo to tmp dir: %v\n", err)
qp.P.LogVerboseMessage("Unable to Download from git repo to tmp dir: %v\n", err)
return err
} else {
mfroot = tempDownloadedDir
@@ -100,17 +89,11 @@ func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string,
}
namespace = "" // namespace is handled when generating the manifests
// check if entity already exists in the cluster, if so - delete it
api.KubectlDeleteVerbose(sa, namespace, qp.P.Verbose)
if cleanup {
return nil
}
defer func() {
qp.CG.LogVerboseMessage("Cleaning up resources...\n")
qp.P.LogVerboseMessage("Cleaning up resources...\n")
err := api.KubectlDeleteVerbose(sa, namespace, qp.P.Verbose)
if err != nil {
qp.CG.LogVerboseMessage("Preflight cleanup failed!\n")
qp.P.LogVerboseMessage("Preflight cleanup failed!\n")
}
}()
@@ -120,55 +103,55 @@ func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string,
return err
}
qp.CG.LogVerboseMessage("Preflight %s check: PASSED\n", entityToTest)
qp.P.LogVerboseMessage("Preflight %s check: PASSED\n", entityToTest)
return nil
}
func (qp *QliksensePreflight) CheckCreateRB(namespace string, kubeConfigContents []byte) error {
// create a role
qp.CG.LogVerboseMessage("Preflight createRole check: \n")
qp.CG.LogVerboseMessage("--------------------------- \n")
qp.P.LogVerboseMessage("Preflight createRole check: \n")
qp.P.LogVerboseMessage("--------------------------- \n")
errStr := strings.Builder{}
err1 := qp.checkCreateEntity(namespace, "Role", false)
err1 := qp.checkCreateEntity(namespace, "Role")
if err1 != nil {
errStr.WriteString(err1.Error())
errStr.WriteString("\n")
qp.CG.LogVerboseMessage("%v\n", err1)
qp.CG.LogVerboseMessage("Preflight role check: FAILED\n")
qp.P.LogVerboseMessage("%v\n", err1)
qp.P.LogVerboseMessage("Preflight role check: FAILED\n")
}
qp.CG.LogVerboseMessage("Completed preflight role check\n\n")
qp.P.LogVerboseMessage("Completed preflight role check\n\n")
// create a roleBinding
qp.CG.LogVerboseMessage("Preflight rolebinding check: \n")
qp.CG.LogVerboseMessage("---------------------------- \n")
err2 := qp.checkCreateEntity(namespace, "RoleBinding", false)
qp.P.LogVerboseMessage("Preflight rolebinding check: \n")
qp.P.LogVerboseMessage("---------------------------- \n")
err2 := qp.checkCreateEntity(namespace, "RoleBinding")
if err2 != nil {
errStr.WriteString(err2.Error())
errStr.WriteString("\n")
qp.CG.LogVerboseMessage("%v\n", err2)
qp.CG.LogVerboseMessage("Preflight rolebinding check: FAILED\n")
qp.P.LogVerboseMessage("%v\n", err2)
qp.P.LogVerboseMessage("Preflight rolebinding check: FAILED\n")
}
qp.CG.LogVerboseMessage("Completed preflight rolebinding check\n\n")
qp.P.LogVerboseMessage("Completed preflight rolebinding check\n\n")
// create a service account
qp.CG.LogVerboseMessage("Preflight serviceaccount check: \n")
qp.CG.LogVerboseMessage("------------------------------- \n")
err3 := qp.checkCreateEntity(namespace, "ServiceAccount", false)
qp.P.LogVerboseMessage("Preflight serviceaccount check: \n")
qp.P.LogVerboseMessage("------------------------------- \n")
err3 := qp.checkCreateEntity(namespace, "ServiceAccount")
if err3 != nil {
errStr.WriteString(err3.Error())
errStr.WriteString("\n")
qp.CG.LogVerboseMessage("%v\n", err3)
qp.CG.LogVerboseMessage("Preflight serviceaccount check: FAILED\n")
qp.P.LogVerboseMessage("%v\n", err3)
qp.P.LogVerboseMessage("Preflight serviceaccount check: FAILED\n")
}
qp.CG.LogVerboseMessage("Completed preflight serviceaccount check\n\n")
qp.P.LogVerboseMessage("Completed preflight serviceaccount check\n\n")
if err1 != nil || err2 != nil || err3 != nil {
qp.CG.LogVerboseMessage("Preflight authcheck: FAILED\n")
qp.CG.LogVerboseMessage("Completed preflight authcheck\n")
qp.P.LogVerboseMessage("Preflight authcheck: FAILED\n")
qp.P.LogVerboseMessage("Completed preflight authcheck\n")
return errors.New(errStr.String())
}
qp.CG.LogVerboseMessage("Preflight authcheck: PASSED\n")
qp.CG.LogVerboseMessage("Completed preflight authcheck\n")
qp.P.LogVerboseMessage("Preflight authcheck: PASSED\n")
qp.P.LogVerboseMessage("Completed preflight authcheck\n")
return nil
}

View File

@@ -8,25 +8,25 @@ import (
"k8s.io/apimachinery/pkg/version"
)
func (p *QliksensePreflight) CheckK8sVersion(namespace string, kubeConfigContents []byte) error {
fmt.Print("Preflight kubernetes version check... ")
p.CG.LogVerboseMessage("\n----------------------------------- \n")
func (qp *QliksensePreflight) CheckK8sVersion(namespace string, kubeConfigContents []byte) error {
qp.P.LogVerboseMessage("Preflight kubernetes version check: \n")
qp.P.LogVerboseMessage("----------------------------------- \n")
var currentVersion *semver.Version
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
if err != nil {
err = fmt.Errorf("Unable to create clientset: %v\n", err)
return err
}
var serverVersion *version.Info
if err := p.CG.RetryOnError(func() (err error) {
if err := retryOnError(func() (err error) {
serverVersion, err = clientset.ServerVersion()
return err
}); err != nil {
err = fmt.Errorf("Unable to get server version: %v\n", err)
return err
}
p.CG.LogVerboseMessage("Kubernetes API Server version: %s\n", serverVersion.String())
qp.P.LogVerboseMessage("Kubernetes API Server version: %s\n", serverVersion.String())
// Compare K8s version on the cluster with minimum supported k8s version
currentVersion, err = semver.NewVersion(serverVersion.String())
@@ -36,14 +36,14 @@ func (p *QliksensePreflight) CheckK8sVersion(namespace string, kubeConfigContent
}
api.LogDebugMessage("Current Kubernetes Version: %v\n", currentVersion)
minK8sVersionSemver, err := semver.NewVersion(p.GetPreflightConfigObj().GetMinK8sVersion())
minK8sVersionSemver, err := semver.NewVersion(qp.GetPreflightConfigObj().GetMinK8sVersion())
if err != nil {
err = fmt.Errorf("Unable to convert minimum Kubernetes version into semver version:%v\n", err)
return err
}
if currentVersion.GreaterThan(minK8sVersionSemver) {
p.CG.LogVerboseMessage("Current Kubernetes API Server version %s is greater than or equal to minimum required version: %s\n", currentVersion, minK8sVersionSemver)
qp.P.LogVerboseMessage("Current Kubernetes API Server version %s is greater than or equal to minimum required version: %s\n", currentVersion, minK8sVersionSemver)
} else {
err = fmt.Errorf("Current Kubernetes API Server version %s is less than minimum required version: %s", currentVersion, minK8sVersionSemver)
return err

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

@@ -7,7 +7,7 @@ import (
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func (q *Qliksense) ApplyCRFromReader(r io.Reader, opts *InstallCommandOptions, cleanPatchFiles, overwriteExistingContext, pull, push bool) error {
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
}
@@ -40,9 +40,9 @@ func (q *Qliksense) ApplyCRFromReader(r io.Reader, opts *InstallCommandOptions,
}
}
}
return q.UpgradeQK8s(cleanPatchFiles)
return q.UpgradeQK8s(keepPatchFiles)
}
return q.InstallQK8s(version, opts, cleanPatchFiles)
return q.InstallQK8s(version, opts, keepPatchFiles)
}
func IsQliksenseInstalled(crName string) bool {

View File

@@ -39,9 +39,9 @@ func (q *Qliksense) ConfigApplyQK8s() error {
return errors.New(agreementTempalte + "\nPlease do $ qliksense config set-configs qliksense.acceptEULA=yes\n")
}
// create patch dependent resources
fmt.Println("Installing resources used by the kuztomize patch")
if err := q.createK8sResourceBeforePatch(qcr); err != nil {
// create patch dependent resoruces
fmt.Println("Installing resoruces used kuztomize patch")
if err := q.createK8sResoruceBeforePatch(qcr); err != nil {
return err
}
@@ -89,13 +89,11 @@ func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error {
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)
mByte, err := ExecuteKustomizeBuild(profilePath)
if err != nil {
fmt.Printf("error generating manifests: %v\n", err)
fmt.Println("cannot generate manifests for "+profilePath, err)
return err
}
fmt.Println("Applying manifests to the cluster")
if err = qapi.KubectlApply(string(mByte), qcr.GetNamespace()); err != nil {
return err
}

View File

@@ -18,12 +18,12 @@ import (
b64 "encoding/base64"
. "github.com/logrusorgru/aurora"
ansi "github.com/mattn/go-colorable"
"github.com/qlik-oss/sense-installer/pkg/api"
_ "gopkg.in/yaml.v2"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
. "github.com/logrusorgru/aurora"
)
const (
@@ -62,7 +62,7 @@ func (q *Qliksense) SetSecrets(args []string, isSecretSet bool, base64Encoded bo
}
// Metadata name in qliksense CR is the name of the current context
api.LogDebugMessage("Current context: %s\n", qliksenseCR.GetName())
api.LogDebugMessage("Current context: %s", qliksenseCR.GetName())
encryptionKey, err := qConfig.GetEncryptionKeyForCurrent()
if err != nil {
return err
@@ -72,7 +72,7 @@ func (q *Qliksense) SetSecrets(args []string, isSecretSet bool, base64Encoded bo
return err
}
for _, ra := range resultArgs {
api.LogDebugMessage("value args to be encrypted: %s\n", ra.Value)
api.LogDebugMessage("value args to be encrypted: %s", ra.Value)
if err := q.processSecret(ra, encryptionKey, qliksenseCR, isSecretSet); err != nil {
return err
}
@@ -213,8 +213,8 @@ func validateCR(key string, keySub string, value string, crSpec *api.QliksenseCR
}
} else {
switch key {
case "opsrunner":
crSpec.Spec.OpsRunner = &config.OpsRunner{}
case "gitops":
crSpec.Spec.GitOps = &config.GitOps{}
case "git":
crSpec.Spec.Git = &config.Repo{}
}
@@ -248,8 +248,8 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
if err := q.processSetGit(arg, qliksenseCR); err != nil {
return err
}
} else if strings.HasPrefix(arg, "opsRunner.") {
if err := q.processSetOpsRunner(arg, qliksenseCR); err != nil {
} else if strings.HasPrefix(arg, "gitOps.") {
if err := q.processSetGitOps(arg, qliksenseCR); err != nil {
return err
}
} else {
@@ -337,27 +337,27 @@ func (q *Qliksense) processSetGit(arg string, cr *api.QliksenseCR) error {
return nil
}
func (q *Qliksense) processSetOpsRunner(arg string, cr *api.QliksenseCR) error {
func (q *Qliksense) processSetGitOps(arg string, cr *api.QliksenseCR) error {
args := strings.Split(arg, "=")
subs := strings.Split(args[0], ".")
if cr.Spec.OpsRunner == nil {
cr.Spec.OpsRunner = &config.OpsRunner{}
if cr.Spec.Git == nil {
cr.Spec.GitOps = &config.GitOps{}
}
switch subs[1] {
case "enabled":
if args[1] != "yes" && args[1] != "no" {
return errors.New("Please use yes or no for key enabled")
}
cr.Spec.OpsRunner.Enabled = args[1]
cr.Spec.GitOps.Enabled = args[1]
case "schedule":
if _, err := cron.ParseStandard(args[1]); err != nil {
return errors.New("Please enter string with standard cron scheduling syntax ")
}
cr.Spec.OpsRunner.Schedule = args[1]
cr.Spec.GitOps.Schedule = args[1]
case "watchBranch":
cr.Spec.OpsRunner.WatchBranch = args[1]
cr.Spec.GitOps.WatchBranch = args[1]
case "image":
cr.Spec.OpsRunner.Image = args[1]
cr.Spec.GitOps.Image = args[1]
default:
return errors.New(arg + " does not match any cr spec")
}
@@ -411,7 +411,7 @@ func (q *Qliksense) DeleteContextConfig(args []string, flag bool) error {
out := ansi.NewColorableStdout()
switch args[0] {
case qliksenseConfig.Spec.CurrentContext:
fmt.Fprintln(out, Yellow("Please switch contexts to be able to delete this context."))
fmt.Fprintln(out,Yellow("Please switch contexts to be able to delete this context."))
err := fmt.Errorf(Red("Cannot delete current context - %s").String(), White(Bold(qliksenseConfig.Spec.CurrentContext)))
return err
case DefaultQliksenseContext:
@@ -452,7 +452,7 @@ func (q *Qliksense) DeleteContextConfig(args []string, flag bool) error {
if ans == true {
api.WriteToFile(&qliksenseConfig, qliksenseConfigFile)
fmt.Fprintln(out, Yellow(Underline("Warning: Active resources may still be running in-cluster")))
fmt.Fprintln(out, Green("Successfully deleted context: "), Bold(args[0]))
fmt.Fprintln(out, Green("Successfully deleted context: "),Bold(args[0]))
} else {
return nil
}
@@ -519,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

@@ -29,7 +29,7 @@ const (
var targetFileStringTemplate = `
apiVersion: v1
data:
mongodbUri: %s
mongoDbUri: %s
kind: Secret
metadata:
name: testctx-qliksense-senseinstaller
@@ -244,7 +244,7 @@ func TestSetOtherConfigs(t *testing.T) {
q: &Qliksense{
QliksenseHome: testDir,
},
args: []string{"profile=minikube", "rotateKeys=yes", "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", "gitOps.enabled=yes", "gitOps.schedule=30 * * * *", "git.repository=master", "git.userName=foo", "git.accessToken=1234"},
},
wantErr: false,
},
@@ -254,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", "rotateKeys=bar"},
args: []string{"someconfig=somevalue, gitOps.schedule=bar", "gitOps.enabled=bar", "git.foo=bar", "rotateKeys=bar"},
},
wantErr: true,
},
@@ -296,7 +296,7 @@ func TestSetConfigs(t *testing.T) {
q: &Qliksense{
QliksenseHome: testDir,
},
args: []string{"qliksense.acceptEULA=\"yes\"", "qliksense.mongodbUri=\"mongo://mongo:3307\""},
args: []string{"qliksense.acceptEULA=\"yes\"", "qliksense.mongoDbUri=\"mongo://mongo:3307\""},
},
wantErr: false,
},
@@ -572,7 +572,7 @@ func Test_SetSecrets(t *testing.T) {
QliksenseHome: testDir,
},
args: args{
args: []string{"qliksense.mongodbUri=\"mongodb://qlik-default-mongodb:27017/qliksense?ssl=false\""},
args: []string{"qliksense.mongoDbUri=\"mongodb://qlik-default-mongodb:27017/qliksense?ssl=false\""},
isSecretSet: false,
},
wantErr: false,
@@ -583,7 +583,7 @@ func Test_SetSecrets(t *testing.T) {
QliksenseHome: testDir,
},
args: args{
args: []string{"qliksense.mongodbUri=bW9uZ29kYjovL3FsaWstZGVmYXVsdC1tb25nb2RiOjI3MDE3L3FsaWtzZW5zZT9zc2w9ZmFsc2U="},
args: []string{"qliksense.mongoDbUri=bW9uZ29kYjovL3FsaWstZGVmYXVsdC1tb25nb2RiOjI3MDE3L3FsaWtzZW5zZT9zc2w9ZmFsc2U="},
isSecretSet: false,
base64: true,
},
@@ -595,7 +595,7 @@ func Test_SetSecrets(t *testing.T) {
QliksenseHome: testDir,
},
args: args{
args: []string{"qliksense.mongodbUri=\"mongo://mongo:3307\""},
args: []string{"qliksense.mongoDbUri=\"mongo://mongo:3307\""},
isSecretSet: true,
},
wantErr: false,
@@ -606,7 +606,7 @@ func Test_SetSecrets(t *testing.T) {
QliksenseHome: testDir,
},
args: args{
args: []string{"qliksense.mongodbUri=\"mongodb://qlik-default-mongodb:27017/qliksense?ssl=false\""},
args: []string{"qliksense.mongoDbUri=\"mongodb://qlik-default-mongodb:27017/qliksense?ssl=false\""},
isSecretSet: true,
},
wantErr: false,

View File

@@ -1,109 +0,0 @@
package qliksense
import (
"fmt"
"strings"
"reflect"
kconfig "github.com/qlik-oss/k-apis/pkg/config"
"github.com/qlik-oss/sense-installer/pkg/api"
)
func (q *Qliksense) UnsetCmd(args []string) error {
return unsetAll(q.QliksenseHome, args)
}
func unsetAll(qHome string, args []string) error {
qConfig := api.NewQConfig(qHome)
qcr, err := qConfig.GetCurrentCR()
if err != nil {
return err
}
// either delete all args or none
for _, arg := range args {
isRemoved := false
// delete if key present
if !strings.Contains(arg, ".") {
if isRemoved = unsetOnlyKey(arg, qcr); isRemoved {
//continue to the next arg
continue
} else if isRemoved = unsetServiceName(arg, qcr); isRemoved {
//continue to the next arg
continue
} else {
return fmt.Errorf("%s not found in the context", arg)
}
}
// delete key inside configs if present
// delete key inside secrets if present
if isRemoved = unsetServiceKey(arg, qcr); !isRemoved {
return fmt.Errorf("%s not found in the context", arg)
}
}
return qConfig.WriteCR(qcr)
}
func unsetOnlyKey(key string, qcr *api.QliksenseCR) bool {
v := reflect.ValueOf(qcr.Spec).Elem().FieldByName(strings.Title(key))
if v.IsValid() && v.CanSet() {
v.SetString("")
return true
}
return false
}
func unsetServiceName(svc string, qcr *api.QliksenseCR) bool {
if qcr.Spec.Configs != nil && qcr.Spec.Configs[svc] != nil {
delete(qcr.Spec.Configs, svc)
return true
}
if qcr.Spec.Secrets != nil && qcr.Spec.Secrets[svc] != nil {
delete(qcr.Spec.Secrets, svc)
return true
}
return false
}
func unsetServiceKey(svcKey string, qcr *api.QliksenseCR) bool {
sk := strings.Split(svcKey, ".")
svc := sk[0]
key := sk[1]
if qcr.Spec.Configs != nil && qcr.Spec.Configs[svc] != nil {
index := findIndex(key, qcr.Spec.Configs[svc])
if index > -1 {
qcr.Spec.Configs[svc][index] = qcr.Spec.Configs[svc][len(qcr.Spec.Configs[svc])-1]
qcr.Spec.Configs[svc] = qcr.Spec.Configs[svc][:len(qcr.Spec.Configs[svc])-1]
if len(qcr.Spec.Configs[svc]) == 0 {
delete(qcr.Spec.Configs, svc)
}
return true
}
}
if qcr.Spec.Secrets != nil && qcr.Spec.Secrets[svc] != nil {
index := findIndex(key, qcr.Spec.Secrets[svc])
if index > -1 {
qcr.Spec.Secrets[svc][index] = qcr.Spec.Secrets[svc][len(qcr.Spec.Secrets[svc])-1]
qcr.Spec.Secrets[svc] = qcr.Spec.Secrets[svc][:len(qcr.Spec.Secrets[svc])-1]
if len(qcr.Spec.Secrets[svc]) == 0 {
delete(qcr.Spec.Secrets, svc)
}
return true
}
}
return false
}
func findIndex(elem string, nvs kconfig.NameValues) int {
for i, nv := range nvs {
if nv.Name == elem {
return i
}
}
return -1
}

View File

@@ -1,99 +0,0 @@
package qliksense
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/qlik-oss/sense-installer/pkg/api"
_ "gopkg.in/yaml.v2"
)
func TestUnsetAll(t *testing.T) {
qHome, _ := ioutil.TempDir("", "")
testPepareDir(qHome)
defer os.RemoveAll(qHome)
//fmt.Print(qHome)
args := []string{"rotateKeys", "qliksense", "qliksense2.acceptEula3", "serviceA.acceptEula"}
if err := unsetAll(qHome, args); err != nil {
t.Log("error during unset", err)
t.FailNow()
}
qc := api.NewQConfig(qHome)
qcr, err := qc.GetCurrentCR()
if err != nil {
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")
t.Fail()
}
if len(qcr.Spec.Configs["qliksense2"]) != 1 {
t.Log("qliksense2.acceptEula3 not deleted")
t.Fail()
}
if qcr.Spec.Configs["serviceA"] != nil {
t.Log("serviceA not deleted")
t.Fail()
}
}
func testPepareDir(qHome string) {
config :=
`
apiVersion: config.qlik.com/v1
kind: QliksenseConfig
metadata:
name: qliksenseConfig
spec:
contexts:
- name: qlik-default
crFile: contexts/qlik-default/qlik-default.yaml
currentContext: qlik-default
`
configFile := filepath.Join(qHome, "config.yaml")
// tests/config.yaml exists
ioutil.WriteFile(configFile, []byte(config), 0777)
contextYaml :=
`
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: qlik-default
spec:
profile: docker-desktop
rotateKeys: "yes"
configs:
qliksense:
- name: acceptEula
value: some
qliksense2:
- name: acceptEula2
value: some
- name: acceptEula3
value: some
serviceA:
- name: acceptEula
value: some
`
qlikDefaultContext := "qlik-default"
// create contexts/qlik-default/ under tests/
contexts := "contexts"
contextsDir := filepath.Join(qHome, contexts, qlikDefaultContext)
if err := os.MkdirAll(contextsDir, 0777); err != nil {
err = fmt.Errorf("Not able to create directories")
}
contextFile := filepath.Join(contextsDir, qlikDefaultContext+".yaml")
ioutil.WriteFile(contextFile, []byte(contextYaml), 0777)
}

View File

@@ -5,15 +5,7 @@ import (
"os"
"path/filepath"
"github.com/mitchellh/go-homedir"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
apixv1beta1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
)
type CrdCommandOptions struct {
@@ -28,11 +20,11 @@ func (q *Qliksense) ViewCrds(opts *CrdCommandOptions) error {
fmt.Println("cannot get the current-context cr", err)
return err
}
engineCRD, err := getQliksenseInitCrds(qcr)
engineCRD, err := getQliksenseInitCrd(qcr)
if err != nil {
return err
}
customCrd, err := getCustomCrds(qcr)
customCrd, err := getCustomCrd(qcr)
if err != nil {
return nil
}
@@ -59,12 +51,12 @@ func (q *Qliksense) InstallCrds(opts *CrdCommandOptions) error {
return err
}
if engineCRD, err := getQliksenseInitCrds(qcr); err != nil {
if engineCRD, err := getQliksenseInitCrd(qcr); err != nil {
return err
} else if err = qapi.KubectlApply(engineCRD, ""); err != nil {
return err
}
if customCrd, err := getCustomCrds(qcr); err != nil {
if customCrd, err := getCustomCrd(qcr); err != nil {
return err
} else if customCrd != "" {
if err = qapi.KubectlApply(customCrd, ""); err != nil {
@@ -81,7 +73,7 @@ func (q *Qliksense) InstallCrds(opts *CrdCommandOptions) error {
return nil
}
func getQliksenseInitCrds(qcr *qapi.QliksenseCR) (string, error) {
func getQliksenseInitCrd(qcr *qapi.QliksenseCR) (string, error) {
var repoPath string
var err error
@@ -106,7 +98,7 @@ func getQliksenseInitCrds(qcr *qapi.QliksenseCR) (string, error) {
return string(qInitByte), nil
}
func getCustomCrds(qcr *qapi.QliksenseCR) (string, error) {
func getCustomCrd(qcr *qapi.QliksenseCR) (string, error) {
crdPath := qcr.GetCustomCrdsPath()
if crdPath == "" {
return "", nil
@@ -118,77 +110,3 @@ func getCustomCrds(qcr *qapi.QliksenseCR) (string, error) {
}
return string(qInitByte), nil
}
func (q *Qliksense) CheckAllCrdsInstalled() (bool, error) {
qConfig := qapi.NewQConfig(q.QliksenseHome)
qcr, err := qConfig.GetCurrentCR()
if err != nil {
return false, err
}
customResourceDefinitionInterface, err := getCustomResourceDefinitionInterface()
if err != nil {
return false, err
}
if engineCRDs, err := getQliksenseInitCrds(qcr); err != nil {
return false, err
} else if allInstalled, err := checkCrdsInstalled(engineCRDs, customResourceDefinitionInterface); err != nil {
return false, err
} else if !allInstalled {
return false, nil
}
if customCrds, err := getCustomCrds(qcr); err != nil {
return false, err
} else if allInstalled, err := checkCrdsInstalled(customCrds, customResourceDefinitionInterface); err != nil {
return false, err
} else if !allInstalled {
return false, nil
}
if allInstalled, err := checkCrdsInstalled(q.GetOperatorCRDString(), customResourceDefinitionInterface); err != nil {
return false, err
} else if !allInstalled {
return false, nil
}
return true, nil
}
func checkCrdsInstalled(crds string, customResourceDefinitionInterface apixv1beta1client.CustomResourceDefinitionInterface) (bool, error) {
kuzResourceFactory := resmap.NewFactory(resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl()), nil)
if kuzResMap, err := kuzResourceFactory.NewResMapFromBytes([]byte(crds)); err != nil {
return false, err
} else {
for _, kuzRes := range kuzResMap.Resources() {
if customResourceDefinition, err := customResourceDefinitionInterface.Get(kuzRes.GetName(), v1.GetOptions{}); err != nil && apierrors.IsNotFound(err) {
return false, nil
} else if err != nil {
return false, err
} else if customResourceDefinition == nil {
return false, fmt.Errorf("failed looking up crd: %v", kuzRes.GetName())
}
}
return true, nil
}
}
func getCustomResourceDefinitionInterface() (apixv1beta1client.CustomResourceDefinitionInterface, error) {
homeDir, err := homedir.Dir()
if err != nil {
return nil, err
}
kubeconfigPath := filepath.Join(homeDir, ".kube", "config")
k8sRestConfig, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath)
if err != nil {
return nil, err
}
apixClient, err := apixv1beta1client.NewForConfig(k8sRestConfig)
if err != nil {
return nil, err
}
return apixClient.CustomResourceDefinitions(), nil
}

View File

@@ -1,18 +1,8 @@
package qliksense
import (
"io/ioutil"
"os"
"testing"
apixv1beta1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
"github.com/gobuffalo/packr/v2"
kapi_config "github.com/qlik-oss/k-apis/pkg/config"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
@@ -23,7 +13,7 @@ func TestGetQliksenseInitCrd(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
crdFromContextConfig, err := getQliksenseInitCrds(&qapi.QliksenseCR{
crdFromContextConfig, err := getQliksenseInitCrd(&qapi.QliksenseCR{
KApiCr: kapi_config.KApiCr{
Spec: &kapi_config.CRSpec{
ManifestsRoot: someTmpRepoPath,
@@ -34,7 +24,7 @@ func TestGetQliksenseInitCrd(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
crdFromDownloadedConfig, err := getQliksenseInitCrds(&qapi.QliksenseCR{
crdFromDownloadedConfig, err := getQliksenseInitCrd(&qapi.QliksenseCR{
KApiCr: kapi_config.KApiCr{
Spec: &kapi_config.CRSpec{
ManifestsRoot: "",
@@ -49,87 +39,3 @@ func TestGetQliksenseInitCrd(t *testing.T) {
t.Fatalf("expected %v to equal %v, but they didn't", crdFromContextConfig, crdFromDownloadedConfig)
}
}
func TestCheckAllCrdsInstalled(t *testing.T) {
t.Skip("Skipping this test because it makes kubernetes calls")
tmpQlikSenseHome, err := ioutil.TempDir("", "tmp-qlik-sense-home-")
if err != nil {
t.Fatalf("unexpected error creating tmp dir: %v", err)
}
defer os.RemoveAll(tmpQlikSenseHome)
setupQliksenseTestDefaultContext(t, tmpQlikSenseHome, `
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: qlik-default
spec:
profile: docker-desktop
`)
q := &Qliksense{
QliksenseHome: tmpQlikSenseHome,
CrdBox: packr.New("crds", "./crds"),
}
if err := q.FetchQK8s("v1.50.3"); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if allInstalled, err := q.CheckAllCrdsInstalled(); err != nil {
t.Fatalf("unexpected error: %v", err)
} else if allInstalled {
t.Fatal("expected crds to NOT be installed at this point")
}
if err := q.InstallCrds(&CrdCommandOptions{All: true}); err != nil {
t.Fatalf("unexpected error: %v", err)
} else if allInstalled, err := q.CheckAllCrdsInstalled(); err != nil {
t.Fatalf("unexpected error: %v", err)
} else if !allInstalled {
t.Fatal("expected crds to BE installed at this point")
}
//cleanup:
qConfig := qapi.NewQConfig(q.QliksenseHome)
qcr, err := qConfig.GetCurrentCR()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
customResourceDefinitionInterface, err := getCustomResourceDefinitionInterface()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if engineCRDs, err := getQliksenseInitCrds(qcr); err != nil {
t.Fatalf("unexpected error: %v", err)
} else if err := deleteCrds(engineCRDs, customResourceDefinitionInterface); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if customCrd, err := getCustomCrds(qcr); err != nil {
t.Fatalf("unexpected error: %v", err)
} else if err := deleteCrds(customCrd, customResourceDefinitionInterface); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := deleteCrds(q.GetOperatorCRDString(), customResourceDefinitionInterface); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func deleteCrds(crds string, customResourceDefinitionInterface apixv1beta1client.CustomResourceDefinitionInterface) error {
kuzResourceFactory := resmap.NewFactory(resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl()), nil)
if kuzResMap, err := kuzResourceFactory.NewResMapFromBytes([]byte(crds)); err != nil {
return err
} else {
for _, kuzRes := range kuzResMap.Resources() {
if err := customResourceDefinitionInterface.Delete(kuzRes.GetName(), &v1.DeleteOptions{}); err != nil {
return err
}
}
return nil
}
}

View File

@@ -95,9 +95,9 @@ func (q *Qliksense) PullImagesForCurrentCR() error {
return nil
}
func (q *Qliksense) appendOpsRunnerImage(images *[]string, qcr *qapi.QliksenseCR) {
if qcr.Spec.OpsRunner != nil && qcr.Spec.OpsRunner.Image != "" {
*images = append(*images, qcr.Spec.OpsRunner.Image)
func (q *Qliksense) appendGitOpsImage(images *[]string, qcr *qapi.QliksenseCR) {
if qcr.Spec.GitOps != nil && qcr.Spec.GitOps.Image != "" {
*images = append(*images, qcr.Spec.GitOps.Image)
}
}
@@ -212,7 +212,7 @@ func (q *Qliksense) appendAdditionalImages(images *[]string, qcr *qapi.Qliksense
if err := q.appendOperatorImages(images); err != nil {
return err
}
q.appendOpsRunnerImage(images, qcr)
q.appendGitOpsImage(images, qcr)
q.appendPreflightImages(images)
return nil
}

View File

@@ -186,7 +186,7 @@ kind: Qliksense
metadata:
name: qlik-default
spec:
opsRunner:
gitOps:
image: some-gitops-image
`)
@@ -225,7 +225,7 @@ spec:
return false
}
if !haveMatchingImage(func(image string) bool {
return strings.Contains(image, "qlik-docker-oss.bintray.io/qliksense-operator:")
return strings.Contains(image, "qlik-docker-oss.bintray.io/qliksense-operator:v")
}) {
t.Fatal("expected to find the operator image in the list, but it wasn't there")
}
@@ -245,9 +245,9 @@ spec:
t.Fatal("expected to find the netcat Preflight image in the list, but it wasn't there")
}
if !haveMatchingImage(func(image string) bool {
return image == "qlik-docker-oss.bintray.io/preflight-mongo"
return image == "mongo"
}) {
t.Fatal("expected to find the preflight-mongo image in the list, but it wasn't there")
t.Fatal("expected to find the mongo Preflight image in the list, but it wasn't there")
}
}
@@ -315,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
}

71
pkg/qliksense/export.go Normal file
View File

@@ -0,0 +1,71 @@
package qliksense
import (
"archive/zip"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
func (q *Qliksense) ExportContext(context string, output string) error {
qliksenseContextsDir := filepath.Join(q.QliksenseHome, QliksenseContextsDir)
qliksenseContextFile := filepath.Join(qliksenseContextsDir, context)
qliksenseSecretsDir := filepath.Join(q.QliksenseHome, QliksenseSecretsDir, QliksenseContextsDir)
qliksenseSecretsFile := filepath.Join(qliksenseSecretsDir, context)
// files := []string{qliksenseContextFile, qliksenseSecretsFile}
fmt.Println(q.QliksenseHome)
fmt.Println(qliksenseSecretsFile)
fmt.Println(qliksenseContextFile)
filename := "result.zip"
destinationFile, err := os.Create(output + "/" + filename)
var folders []string
if err != nil {
return err
}
folders = append(folders, qliksenseContextFile, qliksenseSecretsFile)
if err := RecursiveZip(folders, destinationFile); err != nil {
return err
}
return nil
}
func RecursiveZip(pathToZip []string, destinationFile *os.File) error {
s myZip := zip.NewWriter(destinationFile)
for _, element := range pathToZip {
err := filepath.Walk(element, func(filePath string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
}
if err != nil {
return err
}
relPath := strings.TrimPrefix(filePath, element)
zipFile, err := myZip.Create(relPath)
if err != nil {
return err
}
fsFile, err := os.Open(filePath)
if err != nil {
return err
}
_, err = io.Copy(zipFile, fsFile)
if err != nil {
return err
}
return nil
})xs
if err != nil {
return err
}
}
err := myZip.Close()
if err != nil {
return err
}
return nil
}

View File

@@ -2,6 +2,7 @@ package qliksense
import (
"bufio"
"errors"
"fmt"
"io/ioutil"
"os"
@@ -53,7 +54,10 @@ func (q *Qliksense) FetchK8sWithOpts(opts *FetchCommandOptions) error {
cr.SetFetchUrl(opts.GitUrl)
}
v := getVersion(opts, cr)
if v != "" && qConfig.IsRepoExistForCurrent(v) {
if v == "" {
return errors.New("Cannot find gitref/tag/branch/version to fetch")
}
if qConfig.IsRepoExistForCurrent(v) {
if opts.Overwrite || getVerionsOverwriteConfirmation(v) == "y" {
if err := qConfig.DeleteRepoForCurrent(v); err != nil {
return err
@@ -138,7 +142,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

@@ -144,7 +144,7 @@ func getLatestTag(repoUrl, accessToken string) (string, error) {
v, err := semver.NewVersion(sv)
if err != nil {
// it may happen, in the repo some tags may not conform to semver
//fmt.Println("the tag is not conform to semver: " + sv)
fmt.Print("Unconform tags: " + sv)
continue
}
if maxSem == nil || maxSem.LessThan(v) {

View File

@@ -17,9 +17,9 @@ func TestGetLatestTag(t *testing.T) {
t.Log(err)
t.Log(sv)
}
baseV, _ := semver.NewVersion("v0.0.8")
baseV, _ := semver.NewVersion("v0.0.7")
if !sv.GreaterThan(baseV) {
t.Log("Expected greater than v0.0.8, but got: " + s)
t.Log("Expected greater than v0.0.7, but got: " + s)
t.Fail()
}
}

View File

@@ -8,9 +8,7 @@ import (
"path"
"path/filepath"
"github.com/mitchellh/go-homedir"
"github.com/qlik-oss/k-apis/pkg/config"
"github.com/qlik-oss/k-apis/pkg/cr"
"sigs.k8s.io/kustomize/api/filesys"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
@@ -18,12 +16,11 @@ import (
type InstallCommandOptions struct {
StorageClass string
MongodbUri string
MongoDbUri string
RotateKeys string
DryRun bool
}
func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, cleanPatchFiles bool) error {
func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, keepPatchFiles bool) error {
// step1: fetch 1.0.0 # pull down qliksense-k8s@1.0.0
// step2: operator view | kubectl apply -f # operator manifest (CRD)
@@ -32,7 +29,7 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, cle
// fetch the version
qConfig := qapi.NewQConfig(q.QliksenseHome)
if cleanPatchFiles {
if !keepPatchFiles {
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
fmt.Printf("error removing temporary changes to the config: %v\n", err)
}
@@ -45,8 +42,8 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, cle
}
qcr.SetEULA("yes")
if opts.MongodbUri != "" {
qcr.Spec.AddToSecrets("qliksense", "mongodbUri", opts.MongodbUri, "")
if opts.MongoDbUri != "" {
qcr.Spec.AddToSecrets("qliksense", "mongoDbUri", opts.MongoDbUri, "")
}
if opts.StorageClass != "" {
qcr.Spec.StorageClassName = opts.StorageClass
@@ -54,26 +51,17 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, cle
if opts.RotateKeys != "" {
qcr.Spec.RotateKeys = opts.RotateKeys
}
// for debugging purpose
if opts.DryRun {
// generate patches
qcr.Spec.RotateKeys = "None"
userHomeDir, _ := homedir.Dir()
fmt.Println("Generating patches only")
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)
//if the docker pull secret exists on disk, install it in the cluster
//if it doesn't exist on disk, remove it in the cluster
if err := installOrRemoveImagePullSecret(qConfig); err != nil {
return err
} else if !installed {
return errors.New(`please install CRDs by executing: $ qliksense crds install`)
}
if err := applyImagePullSecret(qConfig); err != nil {
return err
// check if acceptEULA is yes or not
if !qcr.IsEULA() {
return errors.New(agreementTempalte + "\n Please do $ qliksense install --acceptEULA=yes\n")
}
//CRD will be installed outside of operator
@@ -87,13 +75,13 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, cle
return err
}
// create patch dependent resources
fmt.Println("Installing resources used by the kuztomize patch")
if err := q.createK8sResourceBeforePatch(qcr); err != nil {
// create patch dependent resoruces
fmt.Println("Installing resoruces used kuztomize patch")
if err := q.createK8sResoruceBeforePatch(qcr); err != nil {
return err
}
if qcr.Spec.OpsRunner != nil {
if qcr.Spec.Git != nil && qcr.Spec.Git.Repository != "" {
// fetching and applying manifest will be in the operator controller
// get decrypted cr
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
@@ -117,13 +105,13 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, cle
}
// install generated manifests into cluster
fmt.Println("Installing generated manifests into the cluster")
fmt.Println("Installing generated manifests into cluster")
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
return err
} else {
if IsQliksenseInstalled(dcr.GetName()) {
return q.UpgradeQK8s(cleanPatchFiles)
return q.UpgradeQK8s(keepPatchFiles)
}
if err := q.applyConfigToK8s(dcr); err != nil {
fmt.Println("cannot do kubectl apply on manifests")
@@ -144,13 +132,22 @@ func (q *Qliksense) getProcessedOperatorControllerString(qcr *qapi.QliksenseCR)
return operatorControllerString, nil
}
func applyImagePullSecret(qConfig *qapi.QliksenseConfig) error {
func installOrRemoveImagePullSecret(qConfig *qapi.QliksenseConfig) error {
if pullDockerConfigJsonSecret, err := qConfig.GetPullDockerConfigJsonSecret(); err == nil {
if dockerConfigJsonSecretYaml, err := pullDockerConfigJsonSecret.ToYaml(""); err != nil {
return err
} else if err := qapi.KubectlApply(string(dockerConfigJsonSecretYaml), ""); err != nil {
return err
}
} else {
deleteDockerConfigJsonSecret := qapi.DockerConfigJsonSecret{
Name: pullSecretName,
}
if deleteDockerConfigJsonSecretYaml, err := deleteDockerConfigJsonSecret.ToYaml(""); err != nil {
return err
} else if err := qapi.KubectlDelete(string(deleteDockerConfigJsonSecretYaml), ""); err != nil {
qapi.LogDebugMessage("failed deleting %v, error: %v\n", pullSecretName, err)
}
}
return nil
}
@@ -195,7 +192,7 @@ images:
func (q *Qliksense) applyCR(cr *qapi.QliksenseCR) error {
// install operator cr into cluster
//get the current context cr
fmt.Println("Installing operator CR into the cluster")
fmt.Println("Install operator CR into cluster")
r, err := cr.GetString()
if err != nil {
return err
@@ -207,7 +204,7 @@ func (q *Qliksense) applyCR(cr *qapi.QliksenseCR) error {
return nil
}
func (q *Qliksense) createK8sResourceBeforePatch(qcr *qapi.QliksenseCR) error {
func (q *Qliksense) createK8sResoruceBeforePatch(qcr *qapi.QliksenseCR) error {
for svc, nvs := range qcr.Spec.Secrets {
for _, nv := range nvs {
if isK8sSecretNeedToCreate(nv) {

View File

@@ -18,7 +18,7 @@ import (
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func TestCreateK8sResourceBeforePatch(t *testing.T) {
func TestCreateK8sResoruceBeforePatch(t *testing.T) {
td := setup()
sampleCr := `
apiVersion: qlik.com/v1
@@ -43,7 +43,7 @@ spec:
value: "yes"
secrets:
qliksense:
- name: mongodbUri
- name: mongoDbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
profile: docker-desktop
rotateKeys: "yes"`
@@ -66,7 +66,7 @@ spec:
t.Log(err)
t.FailNow()
}
if err = q.createK8sResourceBeforePatch(cr); err != nil {
if err = q.createK8sResoruceBeforePatch(cr); err != nil {
t.Log(err)
t.FailNow()
}

View File

@@ -22,7 +22,7 @@ import (
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
"github.com/Shopify/ejson"
"github.com/qlik-oss/k-apis/pkg/config"
@@ -310,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[string]interface{})["name"].(string), "users-secrets-") {
keyIdBase64 = resource["data"].(map[string]interface{})["tokenAuthPrivateKeyId"].(string)
break
}
}

View File

@@ -35,7 +35,7 @@ spec:
value: "yes"
secrets:
qliksense:
- name: mongodbUri
- name: mongoDbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
profile: docker-desktop
rotateKeys: "yes"`
@@ -62,7 +62,7 @@ spec:
value: "yes"
secrets:
qliksense:
- name: mongodbUri
- name: mongoDbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
profile: docker-desktop
rotateKeys: "yes"`

View File

@@ -41,7 +41,7 @@ func (q *Qliksense) getYamlFromPackrFile(packrFile string) string {
if err != nil {
fmt.Printf("Cannot read file %s", packrFile)
}
return fmt.Sprintln("#source: " + packrFile + "\n\n" + s + "\n---")
return fmt.Sprintln("#soruce: " + packrFile + "\n\n" + s + "\n---")
}
func (q *Qliksense) getFileList(resourceType string) []string {
var resList []string

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