Compare commits

..

64 Commits

Author SHA1 Message Date
Renovate Bot
83e83d38c3 Update module github.com/Shopify/ejson to v1.3.3 2022-04-25 01:03:24 +00:00
Boris Kuschel
6ab9317638 Merge pull request #444 from qlik-oss/fix_crds
Fix crds
2020-09-30 10:07:41 -04:00
Boris Kuschel
5899760c16 retain old qliksense-init func
Signed-off-by: Boris Kuschel <boris.kuschel@qlik.com>
2020-09-30 09:07:01 -04:00
Boris Kuschel
a63c400106 Use crds in profile, if exists
Signed-off-by: Boris Kuschel <boris.kuschel@qlik.com>
2020-09-30 09:03:56 -04:00
Ashwathi Shiva
568012edd8 Preflight openssl verify (#438)
* verify only server cert, not intermediate certs at this point
2020-06-25 16:50:51 -04:00
Ilir Bekteshi
34f702b183 Bump mkdocs deploy workflow version (#437)
* Bump mkdocs deploy workflow version

* Dummy commit (to trigger wf)
2020-06-25 09:17:12 +02:00
Ashwathi Shiva
d8cdbb30bb Preflight openssl verify (#436)
* qliksense preflight verify-ca-chain, included into all preflight checks and doc updates
2020-06-24 10:28:22 -04:00
Andriy Bulynko
616e759089 Rotate keys overhaul (#432) 2020-06-23 09:38:45 -04:00
Foysal Iqbal
c2d3f39542 Merge pull request #433 from qlik-oss/fix-editor
fix editor
2020-06-22 14:59:29 -04:00
Foysal Iqbal
0b6c1b8bc9 fix editor
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-19 14:26:03 -04:00
Foysal Iqbal
7091da099c Merge pull request #427 from qlik-oss/remove-fetchsource
remove fetch source
2020-06-18 15:00:21 -04:00
Foysal Iqbal
1f8bcf3469 Merge branch 'master' into remove-fetchsource 2020-06-18 14:22:33 -04:00
Boris Kuschel
627a4a14f9 Improve docs (#428)
* Improve docs

Signed-off-by: Boris Kuschel <dwb@qlik.com>

* Update docs/getting_started.md

* Update docs/getting_started.md

* Update docs/getting_started.md

* Update docs/getting_started.md

* Update docs/getting_started.md

* Update docs/getting_started.md

* Update docs/getting_started.md

* Update docs/getting_started.md

* Update docs/getting_started.md

* Update docs/getting_started.md

* Update docs/index.md

* Minor changes, formatting

Co-authored-by: Boris Kuschel <dwb@qlik.com>
Co-authored-by: Ilir Bekteshi <Ilir.Bekteshi@qlik.com>
2020-06-18 17:48:59 +02:00
Foysal Iqbal
66333e9e97 Merge branch 'master' into remove-fetchsource 2020-06-18 10:39:08 -04:00
Foysal Iqbal
36c91e9dab fix access token
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-18 10:30:08 -04:00
Ashwathi Shiva
b6ae0c9873 Autorun postflight checks (#429)
* postflight checks triggerred on qliksense install, updated docs, added a postflight all command
2020-06-18 10:03:55 -04:00
Andriy Bulynko
6899a7be77 go-git and test fixes (#431) 2020-06-18 08:40:38 -04:00
Foysal Iqbal
373d6499dc fix conflict
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-18 08:18:17 -04:00
Foysal Iqbal
746823c54b merge conflict 2020-06-18 02:02:42 -04:00
Andriy Bulynko
73b5da8c14 go-git and test fixes 2020-06-17 16:24:14 -04:00
Foysal Iqbal
7cc6e55779 remove fetch source
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-17 09:16:53 -04:00
Foysal Iqbal
27c8f3ee8e remove fetch source
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-16 11:46:35 -04:00
Foysal Iqbal
cb9f463f01 remove fetch source
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-16 10:44:08 -04:00
Foysal Iqbal
293e923c82 Merge pull request #409 from qlik-oss/fix-unset
fix unset
2020-06-12 23:30:07 -04:00
Foysal Iqbal
504190c75c fix unset
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-12 17:36:04 -04:00
Andriy Bulynko
fc62e5fea4 Upgrading kustomize API to version qlik/v0.0.28 (#408) 2020-06-11 23:18:31 -04:00
Andriy Bulynko
bf4e4b9bf1 Require EULA acceptance for install only (#405) 2020-06-11 10:35:41 -04:00
Ashwathi Shiva
4cb3231a08 Preflight images pinned to a specific tag (#404)
* Pinned netcat, nginx and preflight-mongo images to specific tags
2020-06-11 09:32:53 -04:00
Ashwathi Shiva
e1dbcfaac8 added a better error message to a specific scenario in preflight role/rolebinding/serviceaccount check (#403) 2020-06-09 23:32:05 -04:00
Foysal Iqbal
77a3bf4581 Merge pull request #401 from qlik-oss/upgrade-kapi
upgrade k-api and remove config-apply
2020-06-09 22:45:52 -04:00
Foysal Iqbal
a670b6c750 update to dev kapi v0.1.7
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-09 18:17:36 -04:00
Foysal Iqbal
492e4a1baa upgrade k-api and remove config-apply
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-09 17:21:09 -04:00
Foysal Iqbal
3b54a7f0b2 Merge pull request #400 from qlik-oss/fix-default-mongo
fix default mongo
2020-06-09 16:20:40 -04:00
Foysal Iqbal
55cfc42257 fix default mongo
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-09 15:41:52 -04:00
Foysal Iqbal
12e2bec618 fix default mongo
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-09 15:23:59 -04:00
Ashwathi Shiva
2ed59321e4 Preflight checks unit tests and bug fixes (#399)
* - committing changes lost during conflict resolution
- adding more tests
- marking tests to run in parallel to speed things up
- changed case of mongoDbUrl to mongodbUrl
- modified preflight -all output
2020-06-09 13:00:35 -04:00
Andriy Bulynko
98198a3c8b Renaming "keep-config-repo-patches" flag to "clean" and fixing a typo (#396) 2020-06-05 01:01:35 -04:00
Boris Kuschel
afab3e2939 Fix qliksense about (#397) 2020-06-04 17:19:23 -04:00
Andriy Bulynko
62fda8f2c6 Upgrading kustomize API to version qlik/v0.0.27 (#398) 2020-06-04 16:33:50 -04:00
Andriy Bulynko
6af87ab00a Upgrading kustomize API to version qlik/v0.0.26 (#395) 2020-06-04 10:41:31 -04:00
Foysal Iqbal
0b60838b52 add [] for special case (#392)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-04 09:48:10 -04:00
Foysal Iqbal
bb2974fe66 Merge branch 'master' into fix-regex 2020-06-04 09:20:19 -04:00
Andriy Bulynko
97b2239c2e crds view/install commands have option --all set to true by default (#391) 2020-06-04 04:00:23 -04:00
Foysal Iqbal
9eff54d9ec remove git functionality (#394)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-04 01:37:25 -04:00
Foysal Iqbal
2d0a2a32bf Merge branch 'master' into fix-regex 2020-06-03 16:19:48 -04:00
Foysal Iqbal
d7238e2b3c add [] for special case
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-03 16:18:51 -04:00
Ashwathi Shiva
1c0ded7f3d Operator Postflight check- db migration check (#382)
* postflight db-migration-check implemented, docs updated
* fixed typo: changed kube-version to k8s-version
2020-06-03 13:51:41 -04:00
Andriy Bulynko
ec8a9376e7 Checking if CRDs are installed before allowing install to proceed (#387) 2020-06-03 10:38:11 -04:00
Foysal Iqbal
bcc321e180 Fix fetch (#386) 2020-06-02 14:28:15 -04:00
Andriy Bulynko
0aabf63715 Fixing --acceptEULA=no behaviour (#385) 2020-06-02 12:45:47 -04:00
Foysal Iqbal
c9ca5c8be0 Dry run and fix mongo (#384) 2020-06-02 11:56:15 -04:00
Foysal Iqbal
9d0ac0290f add unset command (#381) 2020-06-01 00:08:08 -04:00
Andriy Bulynko
dd8a48b2b8 Fixing a test expecting a "v" at the start of the operator docker image version (#379) 2020-05-27 16:26:36 -04:00
Andriy Bulynko
9fb6800993 Upgrading kustomize to qlik/v0.0.25 (#378) 2020-05-27 15:19:20 -04:00
Ashwathi Shiva
bbb811a879 Preflight mongo mutual tls (#365)
* mongo check working when ca cert and client cert put in same file
* updated code to use image from bintray
2020-05-13 20:36:07 -04:00
Foysal Iqbal
8156b884ce Fix cipher (#363) 2020-05-12 09:54:04 -04:00
Ashwathi Shiva
7525c2e698 Preflight mongo mutual tls (#357)
* preflight mongo mutual tls working when cert is specified in CR
2020-05-04 09:42:43 -04:00
Andriy Bulynko
60763e034a Not deleting docker pull secret (#356) 2020-04-29 09:33:41 -04:00
Ashwathi Shiva
ce4081a422 Preflight mongo version check (#353)
* mongo minimum version check working
2020-04-28 16:51:02 -04:00
Sanat Nayar
dd503a40c1 Merge pull request #347 from qlik-oss/upgrade_libraries
upgrade/standardize yaml and semver
2020-04-28 09:02:10 -04:00
Sanat Nayar
b790419fc2 Merge branch 'master' into upgrade_libraries 2020-04-27 16:03:25 -04:00
Ashwathi Shiva
55f9c07c21 Preflight clean cmd (#345)
* Preflight clean as a command and cleanup before and after individual preflight checks
2020-04-27 10:26:50 -04:00
Sanat Nayar
ef77ea3a5f init 2020-04-27 08:49:42 -04:00
Sanat Nayar
7f70bfc7de init 2020-04-27 08:04:13 -04:00
71 changed files with 4930 additions and 2066 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,27 +0,0 @@
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

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

31
cmd/qliksense/keys.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,12 +2,12 @@ package main
import ( import (
"fmt" "fmt"
"io"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
. "github.com/logrusorgru/aurora"
ansi "github.com/mattn/go-colorable" ansi "github.com/mattn/go-colorable"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"github.com/qlik-oss/sense-installer/pkg" "github.com/qlik-oss/sense-installer/pkg"
@@ -15,7 +15,6 @@ import (
"github.com/qlik-oss/sense-installer/pkg/qliksense" "github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
. "github.com/logrusorgru/aurora"
) )
// To run this project in debug mode, run: // To run this project in debug mode, run:
@@ -23,17 +22,17 @@ import (
// qliksense <command> // qliksense <command>
const ( const (
qlikSenseHomeVar = "QLIKSENSE_HOME" qlikSenseHomeVar = "QLIKSENSE_HOME"
qlikSenseDirVar = ".qliksense" qlikSenseDirVar = ".qliksense"
keepPatchFilesFlagName = "keep-config-repo-patches" cleanPatchFilesFlagName = "clean"
keepPatchFilesFlagUsage = "Keep config repo patch files (for debugging)" cleanPatchFilesFlagUsage = "Set --clean=false to keep any prior config repo file changes on install (for debugging)"
pullFlagName = "pull" pullFlagName = "pull"
pullFlagShorthand = "d" pullFlagShorthand = "d"
pullFlagUsage = "If using private docker registry, pull (download) all required Qliksense images before install" pullFlagUsage = "If using private docker registry, pull (download) all required qliksense images before install"
pushFlagName = "push" pushFlagName = "push"
pushFlagShorthand = "u" pushFlagShorthand = "u"
pushFlagUsage = "If using private docker registry, push (upload) all downloaded Qliksense images to that registry before install" pushFlagUsage = "If using private docker registry, push (upload) all downloaded qliksense images to that registry before install"
rootCommandName = "qliksense" rootCommandName = "qliksense"
) )
func initAndExecute() error { func initAndExecute() error {
@@ -46,7 +45,7 @@ func initAndExecute() error {
log.Fatal(err) log.Fatal(err)
} }
// create dirs and appropriate files for setting up contexts // create dirs and appropriate files for setting up contexts
api.LogDebugMessage("QliksenseHomeDir: %s", qlikSenseHome) api.LogDebugMessage("QliksenseHomeDir: %s\n", qlikSenseHome)
qliksenseClient := qliksense.New(qlikSenseHome) qliksenseClient := qliksense.New(qlikSenseHome)
cmd := rootCmd(qliksenseClient) cmd := rootCmd(qliksenseClient)
@@ -101,12 +100,11 @@ func commandUsesContext(commandName string) bool {
func getRootCmd(p *qliksense.Qliksense) *cobra.Command { func getRootCmd(p *qliksense.Qliksense) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: rootCommandName, Use: rootCommandName,
Short: "Qliksense cli tool", Short: "qliksense cli tool",
Long: `qliksense cli tool provides functionality to perform operations on qliksense-k8s, qliksense operator, and kubernetes cluster`, Long: `qliksense cli tool provides functionality to perform operations on qliksense-k8s, qliksense operator, and kubernetes cluster`,
Args: cobra.ArbitraryArgs, Args: cobra.ArbitraryArgs,
PersistentPreRun: func(cmd *cobra.Command, args []string) { PersistentPreRun: func(cmd *cobra.Command, args []string) {
if commandUsesContext(cmd.CommandPath()) { if commandUsesContext(cmd.CommandPath()) {
globalEulaPreRun(cmd, p)
if err := p.SetUpQliksenseDefaultContext(); err != nil { if err := p.SetUpQliksenseDefaultContext(); err != nil {
panic(err) panic(err)
} }
@@ -114,24 +112,10 @@ func getRootCmd(p *qliksense.Qliksense) *cobra.Command {
if err := pf.Initialize(); err != nil { if err := pf.Initialize(); err != nil {
panic(err) panic(err)
} }
globalEulaPostRun(cmd, p)
}
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
if commandUsesContext(cmd.CommandPath()) {
globalEulaPostRun(cmd, p)
} }
}, },
SilenceUsage: true,
} }
origHelpFunc := cmd.HelpFunc()
cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
if !commandUsesContext(cmd.CommandPath()) {
cmd.Flags().MarkHidden("acceptEULA")
}
origHelpFunc(cmd, args)
})
accept := ""
cmd.PersistentFlags().StringVarP(&accept, "acceptEULA", "a", "", "Accept EULA for qliksense")
cmd.Flags().SetInterspersed(false) cmd.Flags().SetInterspersed(false)
return cmd return cmd
} }
@@ -167,7 +151,9 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
// add config command // add config command
configCmd := configCmd(p) configCmd := configCmd(p)
cmd.AddCommand(configCmd) cmd.AddCommand(configCmd)
/** disabling for now
configCmd.AddCommand(configApplyCmd(p)) configCmd.AddCommand(configApplyCmd(p))
**/
configCmd.AddCommand(configViewCmd(p)) configCmd.AddCommand(configViewCmd(p))
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
@@ -195,22 +181,22 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
// add clean-config-repo-patches command as a sub-command to the app config sub-command // add clean-config-repo-patches command as a sub-command to the app config sub-command
configCmd.AddCommand(cleanConfigRepoPatchesCmd(p)) configCmd.AddCommand(cleanConfigRepoPatchesCmd(p))
// open editor for config // open editor for config
configCmd.AddCommand(configEditCmd(p)) configCmd.AddCommand(configEditCmd(p))
// add unset for config
configCmd.AddCommand((unsetCmd(p)))
// add uninstall command // add uninstall command
cmd.AddCommand(uninstallCmd(p)) cmd.AddCommand(uninstallCmd(p))
// add export command
cmd.AddCommand(exportCmd(p))
// add crds // add crds
cmd.AddCommand(crdsCmd) cmd.AddCommand(crdsCmd)
crdsCmd.AddCommand(crdsViewCmd(p)) crdsCmd.AddCommand(crdsViewCmd(p))
crdsCmd.AddCommand(crdsInstallCmd(p)) crdsCmd.AddCommand(crdsInstallCmd(p))
// add preflight command // add preflight commands
preflightCmd := preflightCmd(p) preflightCmd := preflightCmd(p)
preflightCmd.AddCommand(pfDnsCheckCmd(p)) preflightCmd.AddCommand(pfDnsCheckCmd(p))
preflightCmd.AddCommand(pfK8sVersionCheckCmd(p)) preflightCmd.AddCommand(pfK8sVersionCheckCmd(p))
@@ -223,41 +209,26 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
preflightCmd.AddCommand(pfCreateRoleBindingCheckCmd(p)) preflightCmd.AddCommand(pfCreateRoleBindingCheckCmd(p))
preflightCmd.AddCommand(pfCreateServiceAccountCheckCmd(p)) preflightCmd.AddCommand(pfCreateServiceAccountCheckCmd(p))
preflightCmd.AddCommand(pfCreateAuthCheckCmd(p)) preflightCmd.AddCommand(pfCreateAuthCheckCmd(p))
preflightCmd.AddCommand(pfVerifyCAChainCmd(p))
preflightCmd.AddCommand(pfCleanupCmd(p))
cmd.AddCommand(preflightCmd) cmd.AddCommand(preflightCmd)
cmd.AddCommand(loadCrFile(p)) cmd.AddCommand(loadCrFile(p))
cmd.AddCommand((applyCmd(p))) cmd.AddCommand((applyCmd(p)))
// add postflight command
postflightCmd := postflightCmd(p)
postflightCmd.AddCommand(postflightMigrationCheck(p))
postflightCmd.AddCommand(AllPostflightChecks(p))
cmd.AddCommand(postflightCmd)
// add keys command
cmd.AddCommand(keysCmd)
keysCmd.AddCommand(keysRotateCmd(p))
return cmd return cmd
} }
func copy(src, dst string) (int64, error) {
var (
source, destination *os.File
sourceFileStat os.FileInfo
err error
nBytes int64
)
if sourceFileStat, err = os.Stat(src); err != nil {
return 0, err
}
if !sourceFileStat.Mode().IsRegular() {
return 0, fmt.Errorf("%s is not a regular file", src)
}
if source, err = os.Open(src); err != nil {
return 0, err
}
defer source.Close()
if destination, err = os.Create(dst); err != nil {
return 0, err
}
defer destination.Close()
nBytes, err = io.Copy(destination, source)
return nBytes, err
}
func levenstein(cmd *cobra.Command) { func levenstein(cmd *cobra.Command) {
cmd.SuggestionsMinimumDistance = 2 cmd.SuggestionsMinimumDistance = 2
if len(os.Args) > 1 { if len(os.Args) > 1 {

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. 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 the following tests at the moment as part of preflight checks, and the range of the suite will be expanded in future. 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.
Run the following command to view help about the commands supported by preflight at any moment: Run the following command to view help about the commands supported by preflight at any moment:
``` ```
@@ -23,6 +23,20 @@ Run the following command to execute a specific check
qliksense preflight dns 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
`qliksense load` command takes input from a file or from pipe `qliksense load` command takes input from a file or from pipe
@@ -57,10 +71,9 @@ spec:
value: "yes" value: "yes"
secrets: secrets:
qliksense: qliksense:
- name: mongoDbUri - name: mongodbUri
value: mongodb://qlik-test-mongodb:27017/qliksense?ssl=false value: mongodb://qlik-test-mongodb:27017/qliksense?ssl=false
profile: docker-desktop profile: docker-desktop
rotateKeys: "yes"
``` ```
`qliksense apply` does everything `qliksense load` does but will install Qlik Sense into the cluster as well `qliksense apply` does everything `qliksense load` does but will install Qlik Sense into the cluster as well
@@ -98,7 +111,7 @@ spec:
value: "yes" value: "yes"
secrets: secrets:
qliksense: qliksense:
- name: mongoDbUri - name: mongodbUri
value: "mongo://mongo:3307" value: "mongo://mongo:3307"
- name: messagingPassword - name: messagingPassword
valueFromKey: messagingPassword valueFromKey: messagingPassword
@@ -116,7 +129,6 @@ In this case, the result of `qliksense about` command would display information
It supports the following flags: 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 list-contexts` - get and list contexts
- `qliksense config set` - configure a key-value pair into the current context - `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 - `qliksense config set-configs` - set configurations into qliksense context as key-value pairs

View File

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

View File

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

View File

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

51
docs/postflight_checks.md Normal file
View File

@@ -0,0 +1,51 @@
# 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
```
### Run all postflight checks
This command runs all the postflight checks available.
```shell
$ qliksense postflight all
Running all postflight checks...
Postflight db migration check...
Logs from pod: qliksense-users-6977cb7788-qlgmv
{"caller":"main.go:39","environment":"qseok","error":"error parsing uri: scheme must be \"mongodb\" or \"mongodb+srv\"","level":"error","message":"failed to connect to ","timestamp":"2020-06-17T04:10:11.7891913Z","version":""}
To view more logs in this context, please run the command: kubectl logs -n test_ns qliksense-users-6977cb7788-qlgmv migration
PASSED
All postflight checks have PASSED
```
### DB migration check
This command checks init containers for successful database migrarion completions, and reports failure, if any to the user.
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":""}
To view more logs in this context, please run the command: kubectl logs -n test_ns qliksense-users-6977cb7788-qlgmv migration
PASSED
Postflight db_migration_check completed
```

View File

@@ -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. 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 the following tests at the moment as part of preflight checks, and the range of the suite will be expanded in future. 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.
Run the following command to view help about the commands supported by preflight at any moment: Run the following command to view help about the commands supported by preflight at any moment:
```shell ```shell
@@ -16,19 +16,29 @@ Examples:
qliksense preflight <preflight_check_to_run> qliksense preflight <preflight_check_to_run>
Available Commands: Available Commands:
all perform all checks all perform all checks
dns perform preflight dns check authcheck preflight authcheck
k8s-version check k8s version 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
Flags: Flags:
-h, --help help for preflight -h, --help help for preflight
-v, --verbose verbose mode
``` ```
### DNS check ### 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. 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. The expected output should be similar to the one shown below.
```shell ```shell
$ qliksense preflight dns $ qliksense preflight dns -v
Preflight DNS check Preflight DNS check
--------------------- ---------------------
@@ -51,7 +61,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. 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: The command to run this check and the expected similar output are as shown below:
```shell ```shell
$ qliksense preflight k8s-version $ qliksense preflight k8s-version -v
Preflight kubernetes minimum version check Preflight kubernetes minimum version check
------------------------------------------ ------------------------------------------
@@ -66,7 +76,7 @@ Completed Preflight kubernetes minimum version check
### Service check ### Service check
We use the commmand below to test if we are able to create a service in the cluster. We use the commmand below to test if we are able to create a service in the cluster.
```shell ```shell
$ qliksense preflight service $ qliksense preflight service -v
Preflight service check Preflight service check
----------------------- -----------------------
@@ -82,7 +92,7 @@ Completed preflight service check
### Deployment 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. 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 ```shell
$ qliksense preflight deployment $ qliksense preflight deployment -v
Preflight deployment check Preflight deployment check
----------------------- -----------------------
@@ -97,7 +107,7 @@ Completed preflight deployment check
### Pod check ### Pod check
We use the commmand below to test if we are able to create a pod in the cluster. We use the commmand below to test if we are able to create a pod in the cluster.
```shell ```shell
$ qliksense preflight pod $ qliksense preflight pod -v
Preflight pod check Preflight pod check
-------------------- --------------------
@@ -110,61 +120,61 @@ Deleted pod: pod-pf-check
Completed preflight pod check Completed preflight pod check
``` ```
### Create-Role check ### Role check
We use the command below to test if we are able to create a role in the cluster We use the command below to test if we are able to create a role in the cluster
```shell ```shell
$ qliksense preflight create-role $ qliksense preflight role -v
Preflight create-role check Preflight role check
--------------------------- ---------------------------
Preflight create-role check: Preflight role check:
Created role: role-preflight-check Created role: role-preflight-check
Preflight create-role check: PASSED Preflight role check: PASSED
Cleaning up resources... Cleaning up resources...
Deleted role: role-preflight-check Deleted role: role-preflight-check
Completed preflight create-role check Completed preflight role check
``` ```
### Create-RoleBinding check ### RoleBinding check
We use the command below to test if we are able to create a role binding in the cluster We use the command below to test if we are able to create a role binding in the cluster
```shell ```shell
$ qliksense preflight createRoleBinding $ qliksense preflight rolebinding -v
Preflight create roleBinding check Preflight rolebinding check
--------------------------- ---------------------------
Preflight createRoleBinding check: Preflight rolebinding check:
Created RoleBinding: role-binding-preflight-check Created RoleBinding: role-binding-preflight-check
Preflight createRoleBinding check: PASSED Preflight rolebinding check: PASSED
Cleaning up resources... Cleaning up resources...
Deleting RoleBinding: role-binding-preflight-check Deleting RoleBinding: role-binding-preflight-check
Deleted RoleBinding: role-binding-preflight-check Deleted RoleBinding: role-binding-preflight-check
Completed preflight createRoleBinding check Completed preflight rolebinding check
``` ```
### Create-ServiceAccount check ### Create-ServiceAccount check
We use the command below to test if we are able to create a service account in the cluster We use the command below to test if we are able to create a service account in the cluster
```shell ```shell
$ qliksense preflight createServiceAccount $ qliksense preflight serviceaccount -v
Preflight create ServiceAccount check Preflight ServiceAccount check
------------------------------------- -------------------------------------
Preflight createServiceAccount check: Preflight serviceaccount check:
Created Service Account: preflight-check-test-serviceaccount Created Service Account: preflight-check-test-serviceaccount
Preflight createServiceAccount check: PASSED Preflight serviceaccount check: PASSED
Cleaning up resources... Cleaning up resources...
Deleting ServiceAccount: preflight-check-test-serviceaccount Deleting ServiceAccount: preflight-check-test-serviceaccount
Deleted ServiceAccount: preflight-check-test-serviceaccount Deleted ServiceAccount: preflight-check-test-serviceaccount
Completed preflight createServiceAccount check Completed preflight serviceaccount check
``` ```
### CreateRB check ### Auth check
We use the command below to combine creation of role, role binding, and service account tests We use the command below to combine creation of role, role binding, and service account tests
```shell ```shell
$ qliksense preflight createRB $ qliksense preflight authcheck -v
Preflight createRB check Preflight auth check
------------------------------------- -------------------------------------
Preflight create-role check: Preflight create-role check:
Created role: role-preflight-check Created role: role-preflight-check
@@ -189,18 +199,18 @@ Cleaning up resources...
Deleted ServiceAccount: preflight-check-test-serviceaccount Deleted ServiceAccount: preflight-check-test-serviceaccount
Completed preflight createServiceAccount check Completed preflight createServiceAccount check
Completed preflight CreateRB check Completed preflight auth check
``` ```
### Mongodb 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. 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 ```shell
qliksense preflight mongo --url=<url> OR qliksense preflight mongo --url=<url> -v OR
qliksense preflight mongo qliksense preflight mongo -v
qliksense preflight mongo --url=<mongo-server url> --ca-cert=<path to ca-cert file> qliksense preflight mongo --url=<mongo-server url> --ca-cert=<path to ca-cert file> -v
```
```shell
Preflight mongo check Preflight mongo check
--------------------- ---------------------
Preflight mongodb check: Preflight mongodb check:
@@ -216,13 +226,35 @@ Deleted pod: pf-mongo-pod
Completed preflight mongodb check 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 ### Running all checks
Run the command shown below to execute all preflight checks. Run the command shown below to execute all preflight checks.
```shell ```shell
$ qliksense preflight all --mongodb-url=<url> OR $ qliksense preflight all --mongodb-url=<url> -v OR
$ qliksense preflight all --mongodb-url=<mongo-server url> --mongodb-ca-cert=<path to ca-cert file> $ qliksense preflight all --mongodb-url=<mongo-server url> --mongodb-ca-cert=<path to ca-cert file> -v
Running all preflight checks Running all preflight checks
@@ -253,4 +285,41 @@ Completed Preflight kubernetes minimum version check
All preflight checks have PASSED All preflight checks have PASSED
Completed running all preflight checks 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
```
### Verify-ca-chain check
We use the command below to verify the ca certificate chain and server certificate. We run this check over mongodbUrl and discoveryUrl we inferred from idpconfigs in the CR.
```shell
$ qliksense preflight preflight verify-ca-chain -v
Preflight verify-ca-chain check...
-----------------------------------
Openssl verify mongodbUrl:
Mongodb url inferred form CR: <mongodbUrl_from_CR>
Host: <host extracted from mongodbUrl>
Openssl verify discoveryUrl:
Discovery url: <discoveryUrl_from_CR>
Host: <host extracted from discoveryUrl>
Completed preflight verify-CA-chain check
PASSED
``` ```

17
go.mod
View File

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

25
go.sum
View File

@@ -72,6 +72,8 @@ 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/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 h1:znjIyLfpXEDQjOIEWh+ehwpTU14UzUPub3c3sm36u14=
github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 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 h1:wz22D0CiSctrliXiI9ZO3HoNApweeRGftyDN+BQa3B8=
github.com/Masterminds/sprig/v3 v3.0.2/go.mod h1:oesJ8kPONMONaZgtiHNzUShJbksypC5kWczhZAf6+aU= github.com/Masterminds/sprig/v3 v3.0.2/go.mod h1:oesJ8kPONMONaZgtiHNzUShJbksypC5kWczhZAf6+aU=
github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
@@ -296,6 +298,7 @@ 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 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 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-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/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-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= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
@@ -306,6 +309,8 @@ github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp
github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
github.com/go-git/go-git/v5 v5.0.0 h1:k5RWPm4iJwYtfWoxIJy4wJX9ON7ihPeZZYC1fLYDnpg= github.com/go-git/go-git/v5 v5.0.0 h1:k5RWPm4iJwYtfWoxIJy4wJX9ON7ihPeZZYC1fLYDnpg=
github.com/go-git/go-git/v5 v5.0.0/go.mod h1:oYD8y9kWsGINPFJoLdaScGCN6dlKg23blmClfZwtUVA= github.com/go-git/go-git/v5 v5.0.0/go.mod h1:oYD8y9kWsGINPFJoLdaScGCN6dlKg23blmClfZwtUVA=
github.com/go-git/go-git/v5 v5.1.0 h1:HxJn9g/E7eYvKW3Fm7Jt4ee8LXfPOm/H1cdDu8vEssk=
github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
@@ -366,6 +371,7 @@ 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-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 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 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-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-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= github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
@@ -467,10 +473,6 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-replayers/grpcreplay v0.1.0 h1:eNb1y9rZFmY4ax45uEEECSa8fsxGRU+8Bil52ASAwic= github.com/google/go-replayers/grpcreplay v0.1.0 h1:eNb1y9rZFmY4ax45uEEECSa8fsxGRU+8Bil52ASAwic=
github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE= github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
github.com/google/go-replayers/httpreplay v0.1.0 h1:AX7FUb4BjrrzNvblr/OlgwrmFiep6soj5K2QSDW7BGk= github.com/google/go-replayers/httpreplay v0.1.0 h1:AX7FUb4BjrrzNvblr/OlgwrmFiep6soj5K2QSDW7BGk=
@@ -620,6 +622,8 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@@ -881,10 +885,10 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= 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/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/qlik-oss/k-apis v0.1.1 h1:aZ4eTMB3mSn03Kuj7+RI0eFLkjK9+0qxADBioRb3qVA= github.com/qlik-oss/k-apis v0.1.16 h1:R3gCZs4A3EHPNx4B7p1idWD+OhyaU/bAlGYBWc0ZNz4=
github.com/qlik-oss/k-apis v0.1.1/go.mod h1:yoYGgPJ/H0t9H3NSq64dWfyQY6QWi2L9c+hCJoVO03U= github.com/qlik-oss/k-apis v0.1.16/go.mod h1:AkNa/kaZHpGVs9l+pHe6nvz99Sp9WO1f9ylBES95o+I=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200424070349-b0312eb71568 h1:wHOUCGfnmgYqW3aCjuP3fXmB2T/uZXMvltO+F3us83E= github.com/qlik-oss/kustomize/api v0.3.3-0.20200612023448-4c1f2f38ea9b h1:RDh3OZJOriy/ap1NUHVKsPG07N4DALaCzaqXFFK57T0=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200424070349-b0312eb71568/go.mod h1:Yg8bqX8Mq/eSgXfcenxCxhZuSXg+NCsKq6NBdch/oUc= github.com/qlik-oss/kustomize/api v0.3.3-0.20200612023448-4c1f2f38ea9b/go.mod h1:zh3yFgE5zFk1kreqzVyyj1eXyIxQJT53l4zSg8Wt4SA=
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= 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 h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
@@ -1047,6 +1051,7 @@ 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.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.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.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.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.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -1161,12 +1166,12 @@ golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8ou
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-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 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-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-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 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=

View File

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

View File

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

View File

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

727
pkg/api/clientgo_utils.go Normal file
View File

@@ -0,0 +1,727 @@
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,6 +7,7 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"strings"
"github.com/qlik-oss/k-apis/pkg/config" "github.com/qlik-oss/k-apis/pkg/config"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
@@ -22,10 +23,9 @@ const (
QliksenseKind = "Qliksense" QliksenseKind = "Qliksense"
QliksenseGroup = "qlik.com" QliksenseGroup = "qlik.com"
QliksenseDefaultProfile = "docker-desktop" QliksenseDefaultProfile = "docker-desktop"
DefaultRotateKeys = "yes"
QliksenseMetadataName = "QliksenseConfigMetadata" QliksenseMetadataName = "QliksenseConfigMetadata"
DefaultMongoDbUri = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false" DefaultMongodbUri = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
DefaultMongoDbUriKey = "mongoDbUri" DefaultMongodbUriKey = "mongodbUri"
) )
// AddCommonConfig adds common configs into CRs // AddCommonConfig adds common configs into CRs
@@ -37,10 +37,9 @@ func (qliksenseCR *QliksenseCR) AddCommonConfig(contextName string) {
}) })
qliksenseCR.SetName(contextName) qliksenseCR.SetName(contextName)
qliksenseCR.Spec = &config.CRSpec{ qliksenseCR.Spec = &config.CRSpec{
Profile: QliksenseDefaultProfile, Profile: QliksenseDefaultProfile,
RotateKeys: DefaultRotateKeys,
} }
qliksenseCR.Spec.AddToSecrets("qliksense", DefaultMongoDbUriKey, DefaultMongoDbUri, "") qliksenseCR.Spec.AddToSecrets("qliksense", DefaultMongodbUriKey, strings.Replace(DefaultMongodbUri, "qlik-default", contextName, 1), "")
} }
// AddBaseQliksenseConfigs adds configs into config.yaml // AddBaseQliksenseConfigs adds configs into config.yaml
@@ -96,7 +95,7 @@ func WriteToFile(content interface{}, targetFile string) error {
log.Println(err) log.Println(err)
return err return err
} }
LogDebugMessage("Wrote content into %s", targetFile) LogDebugMessage("Wrote content into %s\n", targetFile)
return nil return nil
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -45,3 +45,24 @@ func contains(arr []string, str string) bool {
} }
return false 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

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

View File

@@ -0,0 +1,74 @@
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 {
fmt.Printf("Postflight db migration check... \n")
p.CG.LogVerboseMessage("\n----------------------------------- \n")
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
if err != nil {
err = fmt.Errorf("unable to create a kubernetes client: %v", err)
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

@@ -0,0 +1,16 @@
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 ( import (
"fmt" "fmt"
. "github.com/logrusorgru/aurora"
ansi "github.com/mattn/go-colorable" ansi "github.com/mattn/go-colorable"
"github.com/pkg/errors" "github.com/pkg/errors"
. "github.com/logrusorgru/aurora"
) )
func (qp *QliksensePreflight) RunAllPreflightChecks(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions) error { func (qp *QliksensePreflight) RunAllPreflightChecks(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions) error {
@@ -15,90 +15,100 @@ func (qp *QliksensePreflight) RunAllPreflightChecks(kubeConfigContents []byte, n
out := ansi.NewColorableStdout() out := ansi.NewColorableStdout()
// Preflight minimum kuberenetes version check // Preflight minimum kuberenetes version check
if err := qp.CheckK8sVersion(namespace, kubeConfigContents); err != nil { if err := qp.CheckK8sVersion(namespace, kubeConfigContents); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight kubernetes minimum version check FAILED")) fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err) fmt.Printf("Error: %v\n\n", err)
} else { } else {
fmt.Fprintf(out, "%s\n\n", Green("Preflight kubernetes minimum version check PASSED")) fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++ checkCount++
} }
totalCount++ totalCount++
// Preflight deployment check // Preflight deployment check
if err := qp.CheckDeployment(namespace, kubeConfigContents); err != nil { if err := qp.CheckDeployment(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight deployment check FAILED")) fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err) fmt.Printf("Error: %v\n\n", err)
} else { } else {
fmt.Fprintf(out, "%s\n\n", Green("Preflight deployment check PASSED")) fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++ checkCount++
} }
totalCount++ totalCount++
// Preflight service check // Preflight service check
if err := qp.CheckService(namespace, kubeConfigContents); err != nil { if err := qp.CheckService(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight service check FAILED")) fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err) fmt.Printf("Error: %v\n\n", err)
} else { } else {
fmt.Fprintf(out, "%s\n\n", Green("Preflight service check PASSED")) fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++ checkCount++
} }
totalCount++ totalCount++
// Preflight pod check // Preflight pod check
if err := qp.CheckPod(namespace, kubeConfigContents); err != nil { if err := qp.CheckPod(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight pod check FAILED")) fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err) fmt.Printf("Error: %v\n\n", err)
} else { } else {
fmt.Fprintf(out, "%s\n\n", Green("Preflight pod check PASSED")) fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++ checkCount++
} }
totalCount++ totalCount++
// Preflight role check // Preflight role check
if err := qp.CheckCreateRole(namespace); err != nil { if err := qp.CheckCreateRole(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight role check FAILED")) fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err) fmt.Printf("Error: %v\n\n", err)
} else { } else {
fmt.Fprintf(out, "%s\n\n", Green("Preflight role check PASSED")) fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++ checkCount++
} }
totalCount++ totalCount++
// Preflight rolebinding check // Preflight rolebinding check
if err := qp.CheckCreateRoleBinding(namespace); err != nil { if err := qp.CheckCreateRoleBinding(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red(" Preflight rolebinding check FAILED")) fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err) fmt.Printf("Error: %v\n\n", err)
} else { } else {
fmt.Fprintf(out, "%s\n\n", Green("Preflight rolebinding check PASSED")) fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++ checkCount++
} }
totalCount++ totalCount++
// Preflight serviceaccount check // Preflight serviceaccount check
if err := qp.CheckCreateServiceAccount(namespace); err != nil { if err := qp.CheckCreateServiceAccount(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red(" Preflight serviceaccount check FAILED")) fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err) fmt.Printf("Error: %v\n\n", err)
} else { } else {
fmt.Fprintf(out, "%s\n\n", Green("Preflight serviceaccount check PASSED")) fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++ checkCount++
} }
totalCount++ totalCount++
// Preflight mongo check // Preflight mongo check
if err := qp.CheckMongo(kubeConfigContents, namespace, preflightOpts); err != nil { if err := qp.CheckMongo(kubeConfigContents, namespace, preflightOpts, false); err != nil {
fmt.Fprintf(out, "%s\n", Red(" Preflight mongo check FAILED")) fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err) fmt.Printf("Error: %v\n\n", err)
} else { } else {
fmt.Fprintf(out, "%s\n\n", Green("Preflight mongo check PASSED")) fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++ checkCount++
} }
totalCount++ totalCount++
// Preflight DNS check // Preflight DNS check
if err := qp.CheckDns(namespace, kubeConfigContents); err != nil { if err := qp.CheckDns(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red(" Preflight DNS check FAILED")) fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err) fmt.Printf("Error: %v\n\n", err)
} else { } else {
fmt.Fprintf(out, "%s\n\n", Green("Preflight DNS check PASSED")) fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++
}
totalCount++
// Preflight verify ca chain check
if err := qp.VerifyCAChain(kubeConfigContents, namespace, preflightOpts, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++ checkCount++
} }
totalCount++ totalCount++

View File

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

View File

@@ -3,6 +3,8 @@ package preflight
import ( import (
"fmt" "fmt"
"strings" "strings"
"k8s.io/client-go/kubernetes"
) )
const ( const (
@@ -10,58 +12,68 @@ const (
netcat = "netcat" netcat = "netcat"
) )
func (qp *QliksensePreflight) CheckDns(namespace string, kubeConfigContents []byte) error { func (p *QliksensePreflight) CheckDns(namespace string, kubeConfigContents []byte, cleanup bool) error {
qp.P.LogVerboseMessage("Preflight DNS check: \n") depName := "dep-dns-preflight-check"
qp.P.LogVerboseMessage("------------------- \n") serviceName := "svc-dns-pf-check"
clientset, _, err := getK8SClientSet(kubeConfigContents, "") podName := "pf-pod-1"
if !cleanup {
fmt.Print("Preflight DNS check... ")
p.CG.LogVerboseMessage("\n------------------- \n")
}
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
if err != nil { if err != nil {
err = fmt.Errorf("unable to create a kubernetes client: %v\n", err) err = fmt.Errorf("unable to create a kubernetes client: %v\n", err)
return 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 // creating deployment
depName := "dep-dns-preflight-check" nginxImageName, err := p.GetPreflightConfigObj().GetImageName(nginx, true)
nginxImageName, err := qp.GetPreflightConfigObj().GetImageName(nginx, true)
if err != nil { if err != nil {
return err return err
} }
dnsDeployment, err := qp.createPreflightTestDeployment(clientset, namespace, depName, nginxImageName)
dnsDeployment, err := p.CG.CreatePreflightTestDeployment(clientset, namespace, depName, nginxImageName)
if err != nil { if err != nil {
err = fmt.Errorf("unable to create deployment: %v\n", err) err = fmt.Errorf("unable to create deployment: %v\n", err)
return err return err
} }
defer qp.deleteDeployment(clientset, namespace, depName) defer p.CG.DeleteDeployment(clientset, namespace, depName)
if err := waitForDeployment(clientset, namespace, dnsDeployment); err != nil { if err := p.CG.WaitForDeployment(clientset, namespace, dnsDeployment); err != nil {
return err return err
} }
// creating service // creating service
serviceName := "svc-dns-pf-check" dnsService, err := p.CG.CreatePreflightTestService(clientset, namespace, serviceName)
dnsService, err := qp.createPreflightTestService(clientset, namespace, serviceName)
if err != nil { if err != nil {
err = fmt.Errorf("unable to create service : %s, %s\n", serviceName, err) err = fmt.Errorf("unable to create service : %s, %s\n", serviceName, err)
return err return err
} }
defer qp.deleteService(clientset, namespace, serviceName) defer p.CG.DeleteService(clientset, namespace, serviceName)
// create a pod // create a pod
podName := "pf-pod-1"
commandToRun := []string{"sh", "-c", "sleep 10; nc -z -v -w 1 " + dnsService.Name + " 80"} commandToRun := []string{"sh", "-c", "sleep 10; nc -z -v -w 1 " + dnsService.Name + " 80"}
netcatImageName, err := qp.GetPreflightConfigObj().GetImageName(netcat, true) netcatImageName, err := p.GetPreflightConfigObj().GetImageName(netcat, true)
if err != nil { if err != nil {
err = fmt.Errorf("unable to retrieve image : %v\n", err) err = fmt.Errorf("unable to retrieve image : %v\n", err)
return err return err
} }
dnsPod, err := qp.createPreflightTestPod(clientset, namespace, podName, netcatImageName, nil, commandToRun)
dnsPod, err := p.CG.CreatePreflightTestPod(clientset, namespace, podName, netcatImageName, nil, commandToRun)
if err != nil { if err != nil {
err = fmt.Errorf("unable to create pod : %s, %s\n", podName, err) err = fmt.Errorf("unable to create pod : %s, %s\n", podName, err)
return err return err
} }
defer qp.deletePod(clientset, namespace, podName) defer p.CG.DeletePod(clientset, namespace, podName)
if err := waitForPod(clientset, namespace, dnsPod); err != nil { if err := p.CG.WaitForPod(clientset, namespace, dnsPod); err != nil {
return err return err
} }
if len(dnsPod.Spec.Containers) == 0 { if len(dnsPod.Spec.Containers) == 0 {
@@ -69,23 +81,30 @@ func (qp *QliksensePreflight) CheckDns(namespace string, kubeConfigContents []by
return err return err
} }
waitForPodToDie(clientset, namespace, dnsPod) p.CG.WaitForPodToDie(clientset, namespace, dnsPod)
logStr, err := getPodLogs(clientset, dnsPod) logStr, err := p.CG.GetPodLogs(clientset, dnsPod)
if err != nil { if err != nil {
err = fmt.Errorf("unable to execute dns check in the cluster: %v", err) err = fmt.Errorf("unable to execute dns check in the cluster: %v", err)
return err return err
} }
if strings.HasSuffix(strings.TrimSpace(logStr), "succeeded!") { if strings.HasSuffix(strings.TrimSpace(logStr), "succeeded!") {
qp.P.LogVerboseMessage("Preflight DNS check: PASSED\n") p.CG.LogVerboseMessage("Preflight DNS check: PASSED\n")
} else { } else {
err = fmt.Errorf("Expected response not found\n") err = fmt.Errorf("Expected response not found\n")
return err return err
} }
if !cleanup {
qp.P.LogVerboseMessage("Completed preflight DNS check\n") p.CG.LogVerboseMessage("Completed preflight DNS check\n")
qp.P.LogVerboseMessage("Cleaning up resources...\n") p.CG.LogVerboseMessage("Cleaning up resources...\n")
}
return nil 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,8 +3,12 @@ package preflight
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"path/filepath"
"strings" "strings"
"github.com/Masterminds/semver/v3"
"github.com/pkg/errors"
"github.com/qlik-oss/sense-installer/pkg/api" "github.com/qlik-oss/sense-installer/pkg/api"
qapi "github.com/qlik-oss/sense-installer/pkg/api" qapi "github.com/qlik-oss/sense-installer/pkg/api"
apiv1 "k8s.io/api/core/v1" apiv1 "k8s.io/api/core/v1"
@@ -12,131 +16,133 @@ import (
) )
const ( const (
mongo = "mongo" preflight_mongo = "preflight-mongo"
caCertMountPath = "/etc/ssl/certs/ca-certificates.crt"
) )
func (qp *QliksensePreflight) CheckMongo(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions) error { func (qp *QliksensePreflight) CheckMongo(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions, cleanup bool) error {
qp.P.LogVerboseMessage("Preflight mongodb check: \n") if !cleanup {
qp.P.LogVerboseMessage("------------------------ \n") fmt.Print("Preflight mongodb check... ")
qp.CG.LogVerboseMessage("\n------------------------ \n")
if preflightOpts.MongoOptions.MongodbUrl == "" {
// infer mongoDbUrl from currentCR
qp.P.LogVerboseMessage("MongoDbUri is empty, infer from CR\n")
qConfig := qapi.NewQConfig(qp.Q.QliksenseHome)
var currentCR *qapi.QliksenseCR
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
}
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")
} }
var currentCR *qapi.QliksenseCR
qp.P.LogVerboseMessage("MongodbUrl: %s\n", preflightOpts.MongoOptions.MongodbUrl) var err error
if err := qp.mongoConnCheck(kubeConfigContents, namespace, preflightOpts); err != nil { 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 return err
} }
qp.P.LogVerboseMessage("Completed preflight mongodb check\n") 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 {
// infer mongoDbUrl from currentCR
qp.CG.LogVerboseMessage("mongodbUri is empty, infer from CR\n")
preflightOpts.MongoOptions.MongodbUrl = strings.TrimSpace(decryptedCR.Spec.GetFromSecrets("qliksense", "mongodbUri"))
}
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)
}
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")
}
}
if err := qp.mongoConnCheck(kubeConfigContents, namespace, preflightOpts, cleanup); err != nil {
return err
}
if !cleanup {
qp.CG.LogVerboseMessage("Completed preflight mongodb check\n")
}
return nil return nil
} }
func (qp *QliksensePreflight) mongoConnCheck(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions) error { func (p *QliksensePreflight) mongoConnCheck(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions, cleanup bool) error {
var caCertSecretName, clientCertSecretName string caCertSecretName := "ca-certificates-crt"
clientset, _, err := getK8SClientSet(kubeConfigContents, "") mongoPodName := "pf-mongo-pod"
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
if err != nil { if err != nil {
err = fmt.Errorf("unable to create a kubernetes client: %v\n", err) err = fmt.Errorf("unable to create a kubernetes client: %v\n", err)
return err return err
} }
var secrets []string
// cleanup before starting check
p.runMongoCleanup(clientset, namespace, mongoPodName, caCertSecretName)
if cleanup {
return nil
}
secrets := map[string]string{}
if preflightOpts.MongoOptions.CaCertFile != "" { if preflightOpts.MongoOptions.CaCertFile != "" {
caCertSecretName = "preflight-mongo-test-cacert" caCertSecret, err := p.createSecret(clientset, namespace, preflightOpts.MongoOptions.CaCertFile, caCertSecretName)
caCertSecret, err := qp.createSecret(clientset, namespace, preflightOpts.MongoOptions.CaCertFile, caCertSecretName)
if err != nil { if err != nil {
err = fmt.Errorf("unable to create a ca cert kubernetes secret: %v\n", err) err = fmt.Errorf("unable to create a ca cert kubernetes secret: %v\n", err)
return err return err
} }
defer qp.deleteK8sSecret(clientset, namespace, caCertSecret) defer p.CG.DeleteK8sSecret(clientset, namespace, caCertSecret.Name)
secrets = append(secrets, caCertSecretName) secrets[caCertSecretName] = caCertMountPath
}
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)
} }
mongoCommand := strings.Builder{} commandToRun := []string{"./preflight-mongo", fmt.Sprintf(`-url="%s"`, preflightOpts.MongoOptions.MongodbUrl)}
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, " ")) api.LogDebugMessage("Mongo command: %s\n", strings.Join(commandToRun, " "))
// create a pod // create a pod
podName := "pf-mongo-pod" imageName, err := p.GetPreflightConfigObj().GetImageName(preflight_mongo, true)
imageName, err := qp.GetPreflightConfigObj().GetImageName(mongo, true)
if err != nil { if err != nil {
err = fmt.Errorf("unable to retrieve image : %v\n", err) err = fmt.Errorf("unable to retrieve image : %v\n", err)
return err return err
} }
mongoPod, err := qp.createPreflightTestPod(clientset, namespace, podName, imageName, secrets, commandToRun) api.LogDebugMessage("image name to be used: %s\n", imageName)
mongoPod, err := p.CG.CreatePreflightTestPod(clientset, namespace, mongoPodName, imageName, secrets, commandToRun)
if err != nil { if err != nil {
err = fmt.Errorf("unable to create pod : %v\n", err) err = fmt.Errorf("unable to create pod : %v\n", err)
return err return err
} }
defer qp.deletePod(clientset, namespace, podName) defer p.CG.DeletePod(clientset, namespace, mongoPodName)
if err := waitForPod(clientset, namespace, mongoPod); err != nil { if err := p.CG.WaitForPod(clientset, namespace, mongoPod); err != nil {
return err return err
} }
if len(mongoPod.Spec.Containers) == 0 { if len(mongoPod.Spec.Containers) == 0 {
err := fmt.Errorf("there are no containers in the pod- %v\n", err) err := fmt.Errorf("there are no containers in the pod- %v\n", err)
return err return err
} }
waitForPodToDie(clientset, namespace, mongoPod) p.CG.WaitForPodToDie(clientset, namespace, mongoPod)
logStr, err := getPodLogs(clientset, mongoPod) logStr, err := p.CG.GetPodLogs(clientset, mongoPod)
if err != nil { if err != nil {
err = fmt.Errorf("unable to execute mongo check in the cluster: %v\n", err) err = fmt.Errorf("unable to execute mongo check in the cluster: %v\n", err)
return err return err
} }
stringToCheck := "Implicit session:" // check mongo server version
ok, err := p.checkMongoVersion(logStr)
if !ok || err != nil {
return err
}
// check if connection succeeded
stringToCheck := "qlik - connection succeeded!!"
if strings.Contains(logStr, stringToCheck) { if strings.Contains(logStr, stringToCheck) {
qp.P.LogVerboseMessage("Preflight mongo check: PASSED\n") p.CG.LogVerboseMessage("Preflight mongo check: PASSED\n")
} else { } else {
err = fmt.Errorf("Connection failed: %s\n", logStr) err = fmt.Errorf("Connection failed: %s\n", logStr)
return err return err
@@ -144,16 +150,58 @@ func (qp *QliksensePreflight) mongoConnCheck(kubeConfigContents []byte, namespac
return nil return nil
} }
func (qp *QliksensePreflight) createSecret(clientset *kubernetes.Clientset, namespace, certFile, certSecretName string) (*apiv1.Secret, error) { 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) {
certBytes, err := ioutil.ReadFile(certFile) certBytes, err := ioutil.ReadFile(certFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
certSecret, err := qp.createPreflightTestSecret(clientset, namespace, certSecretName, certBytes) certSecret, err := p.CG.CreatePreflightTestSecret(clientset, namespace, certSecretName, certBytes)
if err != nil { if err != nil {
err = fmt.Errorf("unable to create secret with ca cert : %v\n", err) err = fmt.Errorf("unable to create secret with cert : %v\n", err)
return nil, err return nil, err
} }
return certSecret, nil 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,30 +1,8 @@
package preflight package preflight
import ( 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/api"
"github.com/qlik-oss/sense-installer/pkg/qliksense" "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 { type PreflightOptions struct {
@@ -32,683 +10,42 @@ type PreflightOptions struct {
MongoOptions *MongoOptions MongoOptions *MongoOptions
} }
// LogVerboseMessage logs a verbose message
func (p *PreflightOptions) LogVerboseMessage(strMessage string, args ...interface{}) {
if p.Verbose || os.Getenv("QLIKSENSE_DEBUG") == "true" {
fmt.Printf(strMessage, args...)
}
}
type MongoOptions struct { type MongoOptions struct {
MongodbUrl string MongodbUrl string
Username string CaCertFile string
Password string
CaCertFile string
ClientCertFile string
Tls bool
} }
var gracePeriod int64 = 0
type QliksensePreflight struct { type QliksensePreflight struct {
Q *qliksense.Qliksense Q *qliksense.Qliksense
P *PreflightOptions P *PreflightOptions
CG *api.ClientGoUtils
} }
func (qp *QliksensePreflight) GetPreflightConfigObj() *api.PreflightConfig { func (qp *QliksensePreflight) GetPreflightConfigObj() *api.PreflightConfig {
return api.NewPreflightConfig(qp.Q.QliksenseHome) return api.NewPreflightConfig(qp.Q.QliksenseHome)
} }
func InitPreflight() (string, []byte, error) { func (qp *QliksensePreflight) Cleanup(namespace string, kubeConfigContents []byte) error {
api.LogDebugMessage("Reading .kube/config file...") qp.CG.LogVerboseMessage("Preflight clean\n")
qp.CG.LogVerboseMessage("----------------\n")
homeDir, err := homedir.Dir() qp.CG.LogVerboseMessage("Removing deployment...\n")
if err != nil { qp.CheckDeployment(namespace, kubeConfigContents, true)
err = fmt.Errorf("Unable to deduce home dir\n") qp.CG.LogVerboseMessage("Removing service...\n")
return "", nil, err qp.CheckService(namespace, kubeConfigContents, true)
} qp.CG.LogVerboseMessage("Removing pod...\n")
api.LogDebugMessage("Kube config location: %s\n\n", filepath.Join(homeDir, ".kube", "config")) qp.CheckPod(namespace, kubeConfigContents, true)
kubeConfig := filepath.Join(homeDir, ".kube", "config") qp.CG.LogVerboseMessage("Removing role...\n")
kubeConfigContents, err := ioutil.ReadFile(kubeConfig) qp.CheckCreateRole(namespace, true)
if err != nil { qp.CG.LogVerboseMessage("Removing rolebinding...\n")
err = fmt.Errorf("Unable to deduce home dir\n") qp.CheckCreateRoleBinding(namespace, true)
return "", nil, err qp.CG.LogVerboseMessage("Removing serviceaccount...\n")
} qp.CheckCreateServiceAccount(namespace, true)
// 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
}
func initiateK8sOps(opr, namespace string) error { qp.CG.LogVerboseMessage("Removing DNS check components...\n")
opr1 := strings.Fields(opr) qp.CheckDns(namespace, kubeConfigContents, true)
_, err := api.KubectlDirectOps(opr1, namespace) qp.CG.LogVerboseMessage("Removing mongo check components...\n")
if err != nil { qp.CheckMongo(kubeConfigContents, namespace, &PreflightOptions{MongoOptions: &MongoOptions{}}, true)
fmt.Println(err)
return err
}
return nil 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

@@ -1,43 +0,0 @@
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,58 +11,69 @@ import (
"github.com/qlik-oss/sense-installer/pkg/qliksense" "github.com/qlik-oss/sense-installer/pkg/qliksense"
) )
var resultYamlBytes = []byte("") func (qp *QliksensePreflight) CheckCreateRole(namespace string, cleanup bool) error {
func (qp *QliksensePreflight) CheckCreateRole(namespace string) error {
// create a Role // create a Role
qp.P.LogVerboseMessage("Preflight role check: \n") if !cleanup {
qp.P.LogVerboseMessage("--------------------- \n") fmt.Print("Preflight role check... ")
err := qp.checkCreateEntity(namespace, "Role") qp.CG.LogVerboseMessage("\n--------------------- \n")
}
err := qp.checkCreateEntity(namespace, "Role", cleanup)
if err != nil { if err != nil {
return err return err
} }
qp.P.LogVerboseMessage("Completed preflight role check\n") if !cleanup {
qp.CG.LogVerboseMessage("Completed preflight role check\n")
}
return nil return nil
} }
func (qp *QliksensePreflight) CheckCreateRoleBinding(namespace string) error { func (qp *QliksensePreflight) CheckCreateRoleBinding(namespace string, cleanup bool) error {
// create a RoleBinding // create a RoleBinding
qp.P.LogVerboseMessage("Preflight rolebinding check: \n") if !cleanup {
qp.P.LogVerboseMessage("---------------------------- \n") fmt.Print("Preflight rolebinding check... ")
err := qp.checkCreateEntity(namespace, "RoleBinding") qp.CG.LogVerboseMessage("\n---------------------------- \n")
}
err := qp.checkCreateEntity(namespace, "RoleBinding", cleanup)
if err != nil { if err != nil {
return err return err
} }
qp.P.LogVerboseMessage("Completed preflight rolebinding check\n") if !cleanup {
qp.CG.LogVerboseMessage("Completed preflight rolebinding check\n")
}
return nil return nil
} }
func (qp *QliksensePreflight) CheckCreateServiceAccount(namespace string) error { func (qp *QliksensePreflight) CheckCreateServiceAccount(namespace string, cleanup bool) error {
// create a service account // create a service account
qp.P.LogVerboseMessage("Preflight serviceaccount check: \n") if !cleanup {
qp.P.LogVerboseMessage("------------------------------- \n") fmt.Print("Preflight serviceaccount check... ")
err := qp.checkCreateEntity(namespace, "ServiceAccount") qp.CG.LogVerboseMessage("\n------------------------------- \n")
}
err := qp.checkCreateEntity(namespace, "ServiceAccount", cleanup)
if err != nil { if err != nil {
return err return err
} }
qp.P.LogVerboseMessage("Completed preflight serviceaccount check\n") if !cleanup {
qp.CG.LogVerboseMessage("Completed preflight serviceaccount check\n")
}
return nil return nil
} }
func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string) error { func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string, cleanup bool) error {
qConfig := qapi.NewQConfig(qp.Q.QliksenseHome) qConfig := qapi.NewQConfig(qp.Q.QliksenseHome)
var currentCR *qapi.QliksenseCR var currentCR *qapi.QliksenseCR
mfroot := "" mfroot := ""
kusDir := "" kusDir := ""
resultYamlBytes := []byte("")
var err error var err error
currentCR, err = qConfig.GetCurrentCR() currentCR, err = qConfig.GetCurrentCR()
if err != nil { if err != nil {
qp.P.LogVerboseMessage("Unable to retrieve current CR: %v\n", err) qp.CG.LogVerboseMessage("Unable to retrieve current CR: %v\n", err)
return err return err
} }
if currentCR.IsRepoExist() { if currentCR.IsRepoExist() {
mfroot = currentCR.Spec.GetManifestsRoot() mfroot = currentCR.Spec.GetManifestsRoot()
} else if tempDownloadedDir, err := qliksense.DownloadFromGitRepoToTmpDir(qliksense.QLIK_GIT_REPO, "master"); err != nil { } else if tempDownloadedDir, err := qliksense.DownloadFromGitRepoToTmpDir(qliksense.QLIK_GIT_REPO, "master"); err != nil {
qp.P.LogVerboseMessage("Unable to Download from git repo to tmp dir: %v\n", err) qp.CG.LogVerboseMessage("Unable to Download from git repo to tmp dir: %v\n", err)
return err return err
} else { } else {
mfroot = tempDownloadedDir mfroot = tempDownloadedDir
@@ -84,16 +95,23 @@ func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string)
if sa != "" { if sa != "" {
sa = strings.Replace(sa, "name: qliksense", "name: preflight", -1) sa = strings.Replace(sa, "name: qliksense", "name: preflight", -1)
} else { } else {
err := fmt.Errorf("Unable to retrieve yamls to apply on cluster from dir: %s, error: %v", kusDir, err) err = fmt.Errorf(`We were unable to retrieve valid %ss from running "kustomize" in your %s directory.
Please check the value in the "Profile" field of your CR. `, strings.ToLower(entityToTest), kusDir)
return err return err
} }
namespace = "" // namespace is handled when generating the manifests 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() { defer func() {
qp.P.LogVerboseMessage("Cleaning up resources...\n") qp.CG.LogVerboseMessage("Cleaning up resources...\n")
err := api.KubectlDeleteVerbose(sa, namespace, qp.P.Verbose) err := api.KubectlDeleteVerbose(sa, namespace, qp.P.Verbose)
if err != nil { if err != nil {
qp.P.LogVerboseMessage("Preflight cleanup failed!\n") qp.CG.LogVerboseMessage("Preflight cleanup failed!\n")
} }
}() }()
@@ -103,55 +121,55 @@ func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string)
return err return err
} }
qp.P.LogVerboseMessage("Preflight %s check: PASSED\n", entityToTest) qp.CG.LogVerboseMessage("Preflight %s check: PASSED\n", entityToTest)
return nil return nil
} }
func (qp *QliksensePreflight) CheckCreateRB(namespace string, kubeConfigContents []byte) error { func (qp *QliksensePreflight) CheckCreateRB(namespace string, kubeConfigContents []byte) error {
// create a role // create a role
qp.P.LogVerboseMessage("Preflight createRole check: \n") qp.CG.LogVerboseMessage("Preflight createRole check: \n")
qp.P.LogVerboseMessage("--------------------------- \n") qp.CG.LogVerboseMessage("--------------------------- \n")
errStr := strings.Builder{} errStr := strings.Builder{}
err1 := qp.checkCreateEntity(namespace, "Role") err1 := qp.checkCreateEntity(namespace, "Role", false)
if err1 != nil { if err1 != nil {
errStr.WriteString(err1.Error()) errStr.WriteString(err1.Error())
errStr.WriteString("\n") errStr.WriteString("\n")
qp.P.LogVerboseMessage("%v\n", err1) qp.CG.LogVerboseMessage("%v\n", err1)
qp.P.LogVerboseMessage("Preflight role check: FAILED\n") qp.CG.LogVerboseMessage("Preflight role check: FAILED\n")
} }
qp.P.LogVerboseMessage("Completed preflight role check\n\n") qp.CG.LogVerboseMessage("Completed preflight role check\n\n")
// create a roleBinding // create a roleBinding
qp.P.LogVerboseMessage("Preflight rolebinding check: \n") qp.CG.LogVerboseMessage("Preflight rolebinding check: \n")
qp.P.LogVerboseMessage("---------------------------- \n") qp.CG.LogVerboseMessage("---------------------------- \n")
err2 := qp.checkCreateEntity(namespace, "RoleBinding") err2 := qp.checkCreateEntity(namespace, "RoleBinding", false)
if err2 != nil { if err2 != nil {
errStr.WriteString(err2.Error()) errStr.WriteString(err2.Error())
errStr.WriteString("\n") errStr.WriteString("\n")
qp.P.LogVerboseMessage("%v\n", err2) qp.CG.LogVerboseMessage("%v\n", err2)
qp.P.LogVerboseMessage("Preflight rolebinding check: FAILED\n") qp.CG.LogVerboseMessage("Preflight rolebinding check: FAILED\n")
} }
qp.P.LogVerboseMessage("Completed preflight rolebinding check\n\n") qp.CG.LogVerboseMessage("Completed preflight rolebinding check\n\n")
// create a service account // create a service account
qp.P.LogVerboseMessage("Preflight serviceaccount check: \n") qp.CG.LogVerboseMessage("Preflight serviceaccount check: \n")
qp.P.LogVerboseMessage("------------------------------- \n") qp.CG.LogVerboseMessage("------------------------------- \n")
err3 := qp.checkCreateEntity(namespace, "ServiceAccount") err3 := qp.checkCreateEntity(namespace, "ServiceAccount", false)
if err3 != nil { if err3 != nil {
errStr.WriteString(err3.Error()) errStr.WriteString(err3.Error())
errStr.WriteString("\n") errStr.WriteString("\n")
qp.P.LogVerboseMessage("%v\n", err3) qp.CG.LogVerboseMessage("%v\n", err3)
qp.P.LogVerboseMessage("Preflight serviceaccount check: FAILED\n") qp.CG.LogVerboseMessage("Preflight serviceaccount check: FAILED\n")
} }
qp.P.LogVerboseMessage("Completed preflight serviceaccount check\n\n") qp.CG.LogVerboseMessage("Completed preflight serviceaccount check\n\n")
if err1 != nil || err2 != nil || err3 != nil { if err1 != nil || err2 != nil || err3 != nil {
qp.P.LogVerboseMessage("Preflight authcheck: FAILED\n") qp.CG.LogVerboseMessage("Preflight authcheck: FAILED\n")
qp.P.LogVerboseMessage("Completed preflight authcheck\n") qp.CG.LogVerboseMessage("Completed preflight authcheck\n")
return errors.New(errStr.String()) return errors.New(errStr.String())
} }
qp.P.LogVerboseMessage("Preflight authcheck: PASSED\n") qp.CG.LogVerboseMessage("Preflight authcheck: PASSED\n")
qp.P.LogVerboseMessage("Completed preflight authcheck\n") qp.CG.LogVerboseMessage("Completed preflight authcheck\n")
return nil return nil
} }

123
pkg/preflight/verify_ca.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

@@ -5,16 +5,19 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/qlik-oss/k-apis/pkg/config"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"gopkg.in/yaml.v2"
"github.com/qlik-oss/k-apis/pkg/cr" "github.com/qlik-oss/k-apis/pkg/cr"
"github.com/qlik-oss/sense-installer/pkg/api" "github.com/qlik-oss/sense-installer/pkg/api"
qapi "github.com/qlik-oss/sense-installer/pkg/api" qapi "github.com/qlik-oss/sense-installer/pkg/api"
"k8s.io/kubectl/pkg/cmd/util/editor"
) )
const ( const (
@@ -39,9 +42,9 @@ func (q *Qliksense) ConfigApplyQK8s() error {
return errors.New(agreementTempalte + "\nPlease do $ qliksense config set-configs qliksense.acceptEULA=yes\n") return errors.New(agreementTempalte + "\nPlease do $ qliksense config set-configs qliksense.acceptEULA=yes\n")
} }
// create patch dependent resoruces // create patch dependent resources
fmt.Println("Installing resoruces used kuztomize patch") fmt.Println("Installing resources used by the kuztomize patch")
if err := q.createK8sResoruceBeforePatch(qcr); err != nil { if err := q.createK8sResourceBeforePatch(qcr); err != nil {
return err return err
} }
@@ -73,27 +76,30 @@ func (q *Qliksense) configEjson() error {
} }
func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error { func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error {
if qcr.Spec.RotateKeys != "None" { if err := q.configEjson(); err != nil {
if err := q.configEjson(); err != nil { return err
return err
}
} }
userHomeDir, err := homedir.Dir() userHomeDir, err := homedir.Dir()
if err != nil { if err != nil {
fmt.Printf(`error fetching user's home directory: %v\n`, err) fmt.Printf(`error fetching user's home directory: %v\n`, err)
return err return err
} }
fmt.Println("Manifests root: " + qcr.Spec.GetManifestsRoot())
qcr.SetNamespace(qapi.GetKubectlNamespace()) qcr.SetNamespace(qapi.GetKubectlNamespace())
b, _ := yaml.Marshal(qcr.KApiCr)
fmt.Printf("%v", string(b))
// os.Exit(0)
// generate patches // generate patches
cr.GeneratePatches(&qcr.KApiCr, path.Join(userHomeDir, ".kube", "config")) cr.GeneratePatches(&qcr.KApiCr, config.KeysActionRestoreOrRotate, path.Join(userHomeDir, ".kube", "config"))
// apply generated manifests // apply generated manifests
profilePath := filepath.Join(qcr.Spec.GetManifestsRoot(), qcr.Spec.GetProfileDir()) profilePath := filepath.Join(qcr.Spec.GetManifestsRoot(), qcr.Spec.GetProfileDir())
fmt.Printf("Generating manifests for profile: %v\n", profilePath)
mByte, err := ExecuteKustomizeBuild(profilePath) mByte, err := ExecuteKustomizeBuild(profilePath)
if err != nil { if err != nil {
fmt.Println("cannot generate manifests for "+profilePath, err) fmt.Printf("error generating manifests: %v\n", err)
return err return err
} }
fmt.Println("Applying manifests to the cluster")
if err = qapi.KubectlApply(string(mByte), qcr.GetNamespace()); err != nil { if err = qapi.KubectlApply(string(mByte), qcr.GetNamespace()); err != nil {
return err return err
} }
@@ -190,13 +196,12 @@ func (q *Qliksense) EditCR(contextName string) error {
if err := ioutil.WriteFile(tempFile.Name(), crContent, os.ModePerm); err != nil { if err := ioutil.WriteFile(tempFile.Name(), crContent, os.ModePerm); err != nil {
return nil return nil
} }
cmd := exec.Command(getKubeEditorTool(), tempFile.Name())
cmd.Stdin = os.Stdin currentEditor := editor.NewDefaultEditor([]string{"KUBE_EDITOR", "EDITOR"})
cmd.Stdout = os.Stdout if err = currentEditor.Launch(tempFile.Name()); err != nil {
err = cmd.Run()
if err != nil {
return err return err
} }
newCr, err := qapi.GetCRObject(tempFile.Name()) newCr, err := qapi.GetCRObject(tempFile.Name())
if err != nil { if err != nil {
return errors.New("cannot save the cr. Someting wrong in the file format. It is not saved\n" + err.Error()) return errors.New("cannot save the cr. Someting wrong in the file format. It is not saved\n" + err.Error())
@@ -211,11 +216,3 @@ func (q *Qliksense) EditCR(contextName string) error {
} }
return nil return nil
} }
func getKubeEditorTool() string {
editor := os.Getenv("KUBE_EDITOR")
if editor == "" {
editor = "vim"
}
return editor
}

View File

@@ -18,12 +18,12 @@ import (
b64 "encoding/base64" b64 "encoding/base64"
. "github.com/logrusorgru/aurora"
ansi "github.com/mattn/go-colorable" ansi "github.com/mattn/go-colorable"
"github.com/qlik-oss/sense-installer/pkg/api" "github.com/qlik-oss/sense-installer/pkg/api"
_ "gopkg.in/yaml.v2" _ "gopkg.in/yaml.v2"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
. "github.com/logrusorgru/aurora"
) )
const ( 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 // Metadata name in qliksense CR is the name of the current context
api.LogDebugMessage("Current context: %s", qliksenseCR.GetName()) api.LogDebugMessage("Current context: %s\n", qliksenseCR.GetName())
encryptionKey, err := qConfig.GetEncryptionKeyForCurrent() encryptionKey, err := qConfig.GetEncryptionKeyForCurrent()
if err != nil { if err != nil {
return err return err
@@ -72,7 +72,7 @@ func (q *Qliksense) SetSecrets(args []string, isSecretSet bool, base64Encoded bo
return err return err
} }
for _, ra := range resultArgs { for _, ra := range resultArgs {
api.LogDebugMessage("value args to be encrypted: %s", ra.Value) api.LogDebugMessage("value args to be encrypted: %s\n", ra.Value)
if err := q.processSecret(ra, encryptionKey, qliksenseCR, isSecretSet); err != nil { if err := q.processSecret(ra, encryptionKey, qliksenseCR, isSecretSet); err != nil {
return err return err
} }
@@ -176,53 +176,6 @@ func caseInsenstiveFieldByName(v reflect.Value, name string) reflect.Value {
return v.FieldByNameFunc(func(n string) bool { return strings.ToLower(n) == name }) return v.FieldByNameFunc(func(n string) bool { return strings.ToLower(n) == name })
} }
func validateCR(key string, keySub string, value string, crSpec *api.QliksenseCR) (bool, *api.QliksenseCR) {
cr := reflect.ValueOf(crSpec.Spec)
keyValid := caseInsenstiveFieldByName(reflect.Indirect(cr), key)
if !keyValid.IsValid() {
//not in main spec
fmt.Println(key, "is an invalid key")
return false, crSpec
} else if keySub == "" {
if key == "rotatekeys" {
if _, err := validateInput(value); err != nil {
return false, crSpec
}
}
}
// checks if it is git or gitops
if keySub != "" {
if !keyValid.IsNil() {
if !caseInsenstiveFieldByName(reflect.Indirect(keyValid), keySub).IsValid() {
fmt.Println(keySub, "is an invalid key")
return false, crSpec
} else {
// verify gitops enabled and gitops schedule
switch keySub {
case "schedule":
if _, err := cron.ParseStandard(value); err != nil {
fmt.Println("Please enter string with standard cron scheduling syntax ")
return false, crSpec
}
case "enabled":
if !strings.EqualFold(value, "yes") && !strings.EqualFold(value, "no") {
fmt.Println("Please use yes or no for key enabled")
return false, crSpec
}
}
}
} else {
switch key {
case "gitops":
crSpec.Spec.GitOps = &config.GitOps{}
case "git":
crSpec.Spec.Git = &config.Repo{}
}
}
}
return true, crSpec
}
// SetOtherConfigs - set profile/storageclassname/git.repository/manifestRoot commands // SetOtherConfigs - set profile/storageclassname/git.repository/manifestRoot commands
func (q *Qliksense) SetOtherConfigs(args []string) error { func (q *Qliksense) SetOtherConfigs(args []string) error {
// retieve current context from config.yaml // retieve current context from config.yaml
@@ -240,16 +193,12 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
} }
for _, arg := range args { for _, arg := range args {
if strings.HasPrefix(arg, "fetchSource.") { if strings.HasPrefix(arg, "git.") {
if err := q.processSetFetchSource(arg, qliksenseCR); err != nil {
return err
}
} else if strings.HasPrefix(arg, "git.") {
if err := q.processSetGit(arg, qliksenseCR); err != nil { if err := q.processSetGit(arg, qliksenseCR); err != nil {
return err return err
} }
} else if strings.HasPrefix(arg, "gitOps.") { } else if strings.HasPrefix(arg, "opsRunner.") {
if err := q.processSetGitOps(arg, qliksenseCR); err != nil { if err := q.processSetOpsRunner(arg, qliksenseCR); err != nil {
return err return err
} }
} else { } else {
@@ -273,91 +222,61 @@ func processSetSingleArg(arg string, cr *api.QliksenseCR) error {
cr.Spec.Profile = nv[1] cr.Spec.Profile = nv[1]
case "storageClassName": case "storageClassName":
cr.Spec.StorageClassName = nv[1] cr.Spec.StorageClassName = nv[1]
case "rotateKeys":
valid := false
for _, v := range []string{"yes", "no", "None"} {
if nv[1] == v {
valid = true
}
}
if !valid {
return errors.New("please povide rotateKeys=yes|no|None")
}
cr.Spec.RotateKeys = nv[1]
default: default:
return errors.New("Please enter one of: profile, storageClassName,rotateKeys, manifestRoot to configure the current context") return errors.New("Please enter one of: profile, storageClassName, manifestRoot, git to configure the current context")
} }
return nil return nil
} }
func (q *Qliksense) processSetFetchSource(arg string, cr *api.QliksenseCR) error { func (q *Qliksense) processSetGit(arg string, cr *api.QliksenseCR) error {
args := strings.Split(arg, "=") s := strings.Split(arg, "=")
subs := strings.Split(args[0], ".") tArg0 := strings.TrimSpace(s[0])
if cr.Spec.FetchSource == nil { tArg1 := strings.TrimSpace(s[1])
cr.Spec.FetchSource = &config.Repo{} subs := strings.Split(tArg0, ".")
if cr.Spec.Git == nil {
cr.Spec.Git = &config.Repo{}
} }
switch subs[1] { switch subs[1] {
case "repository": case "repository":
cr.Spec.FetchSource.Repository = args[1] cr.Spec.Git.Repository = tArg1
case "accessToken": case "accessToken":
qConfig := api.NewQConfig(q.QliksenseHome) qConfig := api.NewQConfig(q.QliksenseHome)
key, err := qConfig.GetEncryptionKeyFor(cr.GetName()) key, err := qConfig.GetEncryptionKeyFor(cr.GetName())
if err != nil { if err != nil {
return err return err
} }
return cr.SetFetchAccessToken(args[1], key) return cr.SetFetchAccessToken(tArg1, key)
case "secretName": case "secretName":
cr.Spec.FetchSource.SecretName = args[1] cr.Spec.Git.SecretName = tArg1
case "userName": case "userName":
cr.Spec.FetchSource.UserName = args[1] cr.Spec.Git.UserName = tArg1
default: default:
return errors.New(arg + " does not match any cr spec") return errors.New(arg + " does not match any cr spec")
} }
return nil return nil
} }
func (q *Qliksense) processSetGit(arg string, cr *api.QliksenseCR) error { func (q *Qliksense) processSetOpsRunner(arg string, cr *api.QliksenseCR) error {
args := strings.Split(arg, "=") args := strings.Split(arg, "=")
subs := strings.Split(args[0], ".") subs := strings.Split(args[0], ".")
if cr.Spec.Git == nil { if cr.Spec.OpsRunner == nil {
cr.Spec.Git = &config.Repo{} cr.Spec.OpsRunner = &config.OpsRunner{}
}
switch subs[1] {
case "repository":
cr.Spec.Git.Repository = args[1]
case "accessToken":
cr.Spec.Git.AccessToken = args[1]
case "secretName":
cr.Spec.Git.SecretName = args[1]
case "userName":
cr.Spec.Git.UserName = args[1]
default:
return errors.New(arg + " does not match any cr spec")
}
return nil
}
func (q *Qliksense) processSetGitOps(arg string, cr *api.QliksenseCR) error {
args := strings.Split(arg, "=")
subs := strings.Split(args[0], ".")
if cr.Spec.Git == nil {
cr.Spec.GitOps = &config.GitOps{}
} }
switch subs[1] { switch subs[1] {
case "enabled": case "enabled":
if args[1] != "yes" && args[1] != "no" { if args[1] != "yes" && args[1] != "no" {
return errors.New("Please use yes or no for key enabled") return errors.New("Please use yes or no for key enabled")
} }
cr.Spec.GitOps.Enabled = args[1] cr.Spec.OpsRunner.Enabled = args[1]
case "schedule": case "schedule":
if _, err := cron.ParseStandard(args[1]); err != nil { if _, err := cron.ParseStandard(args[1]); err != nil {
return errors.New("Please enter string with standard cron scheduling syntax ") return errors.New("Please enter string with standard cron scheduling syntax ")
} }
cr.Spec.GitOps.Schedule = args[1] cr.Spec.OpsRunner.Schedule = args[1]
case "watchBranch": case "watchBranch":
cr.Spec.GitOps.WatchBranch = args[1] cr.Spec.OpsRunner.WatchBranch = args[1]
case "image": case "image":
cr.Spec.GitOps.Image = args[1] cr.Spec.OpsRunner.Image = args[1]
default: default:
return errors.New(arg + " does not match any cr spec") return errors.New(arg + " does not match any cr spec")
} }
@@ -411,7 +330,7 @@ func (q *Qliksense) DeleteContextConfig(args []string, flag bool) error {
out := ansi.NewColorableStdout() out := ansi.NewColorableStdout()
switch args[0] { switch args[0] {
case qliksenseConfig.Spec.CurrentContext: 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))) err := fmt.Errorf(Red("Cannot delete current context - %s").String(), White(Bold(qliksenseConfig.Spec.CurrentContext)))
return err return err
case DefaultQliksenseContext: case DefaultQliksenseContext:
@@ -452,7 +371,7 @@ func (q *Qliksense) DeleteContextConfig(args []string, flag bool) error {
if ans == true { if ans == true {
api.WriteToFile(&qliksenseConfig, qliksenseConfigFile) api.WriteToFile(&qliksenseConfig, qliksenseConfigFile)
fmt.Fprintln(out, Yellow(Underline("Warning: Active resources may still be running in-cluster"))) 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 { } else {
return nil return nil
} }
@@ -519,8 +438,8 @@ func (q *Qliksense) SetUpQliksenseContext(contextName string) error {
return err return err
} }
// set the encrypted default mongo // set the encrypted default mongo for the context in current CR
return q.SetSecrets([]string{`qliksense.mongoDbUri="mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"`}, false, false) return q.SetSecrets([]string{fmt.Sprintf("qliksense.mongodbUri=mongodb://%s-mongodb:27017/qliksense?ssl=false", contextName)}, false, false)
} }
func validateInput(input string) (string, error) { func validateInput(input string) (string, error) {

View File

@@ -29,7 +29,7 @@ const (
var targetFileStringTemplate = ` var targetFileStringTemplate = `
apiVersion: v1 apiVersion: v1
data: data:
mongoDbUri: %s mongodbUri: %s
kind: Secret kind: Secret
metadata: metadata:
name: testctx-qliksense-senseinstaller name: testctx-qliksense-senseinstaller
@@ -96,7 +96,6 @@ metadata:
name: qlik-default name: qlik-default
spec: spec:
profile: docker-desktop profile: docker-desktop
rotateKeys: "yes"
releaseName: qlik-default releaseName: qlik-default
` `
qlikDefaultContext := "qlik-default" qlikDefaultContext := "qlik-default"
@@ -244,7 +243,7 @@ func TestSetOtherConfigs(t *testing.T) {
q: &Qliksense{ q: &Qliksense{
QliksenseHome: testDir, QliksenseHome: testDir,
}, },
args: []string{"profile=minikube", "rotateKeys=yes", "storageClassName=efs", "gitOps.enabled=yes", "gitOps.schedule=30 * * * *", "git.repository=master", "git.userName=foo", "git.accessToken=1234"}, args: []string{"profile=minikube", "storageClassName=efs", "opsRunner.enabled=yes", "opsRunner.schedule=30 * * * *", "git.repository=master", "git.userName=foo", "git.accessToken=1234"},
}, },
wantErr: false, wantErr: false,
}, },
@@ -254,7 +253,7 @@ func TestSetOtherConfigs(t *testing.T) {
q: &Qliksense{ q: &Qliksense{
QliksenseHome: testDir, QliksenseHome: testDir,
}, },
args: []string{"someconfig=somevalue, gitOps.schedule=bar", "gitOps.enabled=bar", "git.foo=bar", "rotateKeys=bar"}, args: []string{"someconfig=somevalue, opsRunner.schedule=bar", "opsRunner.enabled=bar", "git.foo=bar"},
}, },
wantErr: true, wantErr: true,
}, },
@@ -296,7 +295,7 @@ func TestSetConfigs(t *testing.T) {
q: &Qliksense{ q: &Qliksense{
QliksenseHome: testDir, QliksenseHome: testDir,
}, },
args: []string{"qliksense.acceptEULA=\"yes\"", "qliksense.mongoDbUri=\"mongo://mongo:3307\""}, args: []string{"qliksense.acceptEULA=\"yes\"", "qliksense.mongodbUri=\"mongo://mongo:3307\""},
}, },
wantErr: false, wantErr: false,
}, },
@@ -572,7 +571,7 @@ func Test_SetSecrets(t *testing.T) {
QliksenseHome: testDir, QliksenseHome: testDir,
}, },
args: args{ 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, isSecretSet: false,
}, },
wantErr: false, wantErr: false,
@@ -583,7 +582,7 @@ func Test_SetSecrets(t *testing.T) {
QliksenseHome: testDir, QliksenseHome: testDir,
}, },
args: args{ args: args{
args: []string{"qliksense.mongoDbUri=bW9uZ29kYjovL3FsaWstZGVmYXVsdC1tb25nb2RiOjI3MDE3L3FsaWtzZW5zZT9zc2w9ZmFsc2U="}, args: []string{"qliksense.mongodbUri=bW9uZ29kYjovL3FsaWstZGVmYXVsdC1tb25nb2RiOjI3MDE3L3FsaWtzZW5zZT9zc2w9ZmFsc2U="},
isSecretSet: false, isSecretSet: false,
base64: true, base64: true,
}, },
@@ -595,7 +594,7 @@ func Test_SetSecrets(t *testing.T) {
QliksenseHome: testDir, QliksenseHome: testDir,
}, },
args: args{ args: args{
args: []string{"qliksense.mongoDbUri=\"mongo://mongo:3307\""}, args: []string{"qliksense.mongodbUri=\"mongo://mongo:3307\""},
isSecretSet: true, isSecretSet: true,
}, },
wantErr: false, wantErr: false,
@@ -606,7 +605,7 @@ func Test_SetSecrets(t *testing.T) {
QliksenseHome: testDir, QliksenseHome: testDir,
}, },
args: args{ 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, isSecretSet: true,
}, },
wantErr: false, wantErr: false,
@@ -744,7 +743,6 @@ metadata:
name: qlik-default name: qlik-default
spec: spec:
profile: docker-desktop profile: docker-desktop
rotateKeys: "yes"
releaseName: qlik-default releaseName: qlik-default
` `
qlikDefaultContext := "qlik-default" qlikDefaultContext := "qlik-default"
@@ -763,7 +761,6 @@ metadata:
name: qlik1 name: qlik1
spec: spec:
profile: docker-desktop profile: docker-desktop
rotateKeys: "yes"
releaseName: qlik1` releaseName: qlik1`
contextYaml2 := contextYaml2 :=
@@ -774,7 +771,6 @@ metadata:
name: qlik2 name: qlik2
spec: spec:
profile: docker-desktop profile: docker-desktop
rotateKeys: "yes"
releaseName: qlik2` releaseName: qlik2`
contextsDir := filepath.Join(testDir, contexts, "qlik1") contextsDir := filepath.Join(testDir, contexts, "qlik1")

View File

@@ -0,0 +1,128 @@
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 qConfig.WriteCR(qcr)
continue
}
if isRemoved = unsetTopAttrKey(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.Set(reflect.Zero(v.Type()))
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 unsetTopAttrKey(attKey string, qcr *api.QliksenseCR) bool {
sk := strings.Split(attKey, ".")
attStruct := sk[0]
key := sk[1]
attV := reflect.ValueOf(qcr.Spec).Elem().FieldByName(strings.Title(attStruct))
if !attV.IsValid() || attV.IsZero() || attV.IsNil() {
return false
}
v := attV.Elem().FieldByName(strings.Title(key))
if v.IsValid() && v.CanSet() {
v.Set(reflect.Zero(v.Type()))
return true
}
return false
}
func findIndex(elem string, nvs kconfig.NameValues) int {
for i, nv := range nvs {
if nv.Name == elem {
return i
}
}
return -1
}

View File

@@ -0,0 +1,107 @@
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{"qliksense", "qliksense2.acceptEula3", "serviceA.acceptEula", "opsRunner.watchBranch"}
//args := []string{"opsRunner"}
//args := []string{"opsRunner.watchBranch"}
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.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()
}
if qcr.Spec.OpsRunner == nil {
t.Log("opsRunner not deleted")
t.Fail()
}
if qcr.Spec.OpsRunner.WatchBranch != "" {
t.Log("opsRunner.watchBranch not deleted")
t.Fail()
}
}
func testPepareDir(qHome string) {
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
opsRunner:
enabled: "yes"
watchBranch: something
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,7 +5,15 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/mitchellh/go-homedir"
qapi "github.com/qlik-oss/sense-installer/pkg/api" 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 { type CrdCommandOptions struct {
@@ -20,11 +28,11 @@ func (q *Qliksense) ViewCrds(opts *CrdCommandOptions) error {
fmt.Println("cannot get the current-context cr", err) fmt.Println("cannot get the current-context cr", err)
return err return err
} }
engineCRD, err := getQliksenseInitCrd(qcr) engineCRD, err := getQliksenseInitCrds(qcr)
if err != nil { if err != nil {
return err return err
} }
customCrd, err := getCustomCrd(qcr) customCrd, err := getCustomCrds(qcr)
if err != nil { if err != nil {
return nil return nil
} }
@@ -51,12 +59,12 @@ func (q *Qliksense) InstallCrds(opts *CrdCommandOptions) error {
return err return err
} }
if engineCRD, err := getQliksenseInitCrd(qcr); err != nil { if engineCRD, err := getQliksenseInitCrds(qcr); err != nil {
return err return err
} else if err = qapi.KubectlApply(engineCRD, ""); err != nil { } else if err = qapi.KubectlApply(engineCRD, ""); err != nil {
return err return err
} }
if customCrd, err := getCustomCrd(qcr); err != nil { if customCrd, err := getCustomCrds(qcr); err != nil {
return err return err
} else if customCrd != "" { } else if customCrd != "" {
if err = qapi.KubectlApply(customCrd, ""); err != nil { if err = qapi.KubectlApply(customCrd, ""); err != nil {
@@ -73,7 +81,7 @@ func (q *Qliksense) InstallCrds(opts *CrdCommandOptions) error {
return nil return nil
} }
func getQliksenseInitCrd(qcr *qapi.QliksenseCR) (string, error) { func getQliksenseInitCrds(qcr *qapi.QliksenseCR) (string, error) {
var repoPath string var repoPath string
var err error var err error
@@ -85,11 +93,15 @@ func getQliksenseInitCrd(qcr *qapi.QliksenseCR) (string, error) {
} }
} }
qInitMsPath := filepath.Join(repoPath, Q_INIT_CRD_PATH) qInitMsPath := filepath.Join(repoPath, "manifests", qcr.Spec.Profile, "crds")
if _, err := os.Lstat(qInitMsPath); err != nil { if _, err := os.Lstat(qInitMsPath); err != nil {
// older version of qliksense-init used qInitMsPath = filepath.Join(repoPath, Q_INIT_CRD_PATH)
qInitMsPath = filepath.Join(repoPath, "manifests/base/manifests/qliksense-init") if _, err := os.Lstat(qInitMsPath); err != nil {
// older version of qliksense-init used
qInitMsPath = filepath.Join(repoPath, "manifests/base/manifests/qliksense-init")
}
} }
qInitByte, err := ExecuteKustomizeBuild(qInitMsPath) qInitByte, err := ExecuteKustomizeBuild(qInitMsPath)
if err != nil { if err != nil {
fmt.Println("cannot generate crds for qliksense-init", err) fmt.Println("cannot generate crds for qliksense-init", err)
@@ -98,7 +110,7 @@ func getQliksenseInitCrd(qcr *qapi.QliksenseCR) (string, error) {
return string(qInitByte), nil return string(qInitByte), nil
} }
func getCustomCrd(qcr *qapi.QliksenseCR) (string, error) { func getCustomCrds(qcr *qapi.QliksenseCR) (string, error) {
crdPath := qcr.GetCustomCrdsPath() crdPath := qcr.GetCustomCrdsPath()
if crdPath == "" { if crdPath == "" {
return "", nil return "", nil
@@ -110,3 +122,77 @@ func getCustomCrd(qcr *qapi.QliksenseCR) (string, error) {
} }
return string(qInitByte), nil 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,8 +1,18 @@
package qliksense package qliksense
import ( import (
"io/ioutil"
"os"
"testing" "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" kapi_config "github.com/qlik-oss/k-apis/pkg/config"
qapi "github.com/qlik-oss/sense-installer/pkg/api" qapi "github.com/qlik-oss/sense-installer/pkg/api"
) )
@@ -13,7 +23,7 @@ func TestGetQliksenseInitCrd(t *testing.T) {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
crdFromContextConfig, err := getQliksenseInitCrd(&qapi.QliksenseCR{ crdFromContextConfig, err := getQliksenseInitCrds(&qapi.QliksenseCR{
KApiCr: kapi_config.KApiCr{ KApiCr: kapi_config.KApiCr{
Spec: &kapi_config.CRSpec{ Spec: &kapi_config.CRSpec{
ManifestsRoot: someTmpRepoPath, ManifestsRoot: someTmpRepoPath,
@@ -24,7 +34,7 @@ func TestGetQliksenseInitCrd(t *testing.T) {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
crdFromDownloadedConfig, err := getQliksenseInitCrd(&qapi.QliksenseCR{ crdFromDownloadedConfig, err := getQliksenseInitCrds(&qapi.QliksenseCR{
KApiCr: kapi_config.KApiCr{ KApiCr: kapi_config.KApiCr{
Spec: &kapi_config.CRSpec{ Spec: &kapi_config.CRSpec{
ManifestsRoot: "", ManifestsRoot: "",
@@ -39,3 +49,87 @@ func TestGetQliksenseInitCrd(t *testing.T) {
t.Fatalf("expected %v to equal %v, but they didn't", crdFromContextConfig, crdFromDownloadedConfig) 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

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

View File

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

View File

@@ -1,71 +0,0 @@
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,7 +2,6 @@ package qliksense
import ( import (
"bufio" "bufio"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@@ -54,10 +53,7 @@ func (q *Qliksense) FetchK8sWithOpts(opts *FetchCommandOptions) error {
cr.SetFetchUrl(opts.GitUrl) cr.SetFetchUrl(opts.GitUrl)
} }
v := getVersion(opts, cr) v := getVersion(opts, cr)
if v == "" { if v != "" && qConfig.IsRepoExistForCurrent(v) {
return errors.New("Cannot find gitref/tag/branch/version to fetch")
}
if qConfig.IsRepoExistForCurrent(v) {
if opts.Overwrite || getVerionsOverwriteConfirmation(v) == "y" { if opts.Overwrite || getVerionsOverwriteConfirmation(v) == "y" {
if err := qConfig.DeleteRepoForCurrent(v); err != nil { if err := qConfig.DeleteRepoForCurrent(v); err != nil {
return err return err
@@ -98,7 +94,6 @@ func fetchAndUpdateCR(qConfig *qapi.QliksenseConfig, version string) error {
if err != nil { if err != nil {
return err return err
} }
destDir := qConfig.BuildRepoPath(version) destDir := qConfig.BuildRepoPath(version)
fmt.Printf("fetching version [%s] from %s\n", version, qcr.GetFetchUrl()) fmt.Printf("fetching version [%s] from %s\n", version, qcr.GetFetchUrl())
if err := qapi.CopyDirectory(tempDest, destDir); err != nil { if err := qapi.CopyDirectory(tempDest, destDir); err != nil {
@@ -142,7 +137,7 @@ func getVersion(opts *FetchCommandOptions, qcr *qapi.QliksenseCR) string {
func getVerionsOverwriteConfirmation(version string) string { func getVerionsOverwriteConfirmation(version string) string {
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
fmt.Println("The version [" + version + "] already exist") fmt.Println("The version [" + version + "] already exists")
cfm := "n" cfm := "n"
for { for {
fmt.Print("Do you want to delete and fetch again [y/N]: ") fmt.Print("Do you want to delete and fetch again [y/N]: ")

View File

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

View File

@@ -5,6 +5,8 @@ import (
"fmt" "fmt"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/qlik-oss/k-apis/pkg/git" "github.com/qlik-oss/k-apis/pkg/git"
qapi "github.com/qlik-oss/sense-installer/pkg/api" qapi "github.com/qlik-oss/sense-installer/pkg/api"
) )
@@ -22,8 +24,20 @@ func (q *Qliksense) GetInstallableVersions(opts *LsRemoteCmdOptions) error {
} }
var repoPath string var repoPath string
var auth transport.AuthMethod
if qcr.Spec.GetManifestsRoot() != "" { if qcr.Spec.GetManifestsRoot() != "" {
repoPath = qcr.Spec.GetManifestsRoot() repoPath = qcr.Spec.GetManifestsRoot()
encKey, err := qConfig.GetEncryptionKeyFor(qcr.GetName())
if err != nil {
return err
}
accessToken := qcr.GetFetchAccessToken(encKey)
if accessToken != "" {
auth = &http.BasicAuth{
Username: "something",
Password: accessToken,
}
}
} else { } else {
repoPath, err = DownloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master") repoPath, err = DownloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master")
if err != nil { if err != nil {
@@ -36,7 +50,7 @@ func (q *Qliksense) GetInstallableVersions(opts *LsRemoteCmdOptions) error {
return err return err
} }
remoteRefsList, err := git.GetRemoteRefs(r, nil, remoteRefsList, err := git.GetRemoteRefs(r, auth,
&git.RemoteRefConstraints{ &git.RemoteRefConstraints{
Include: true, Include: true,
Sort: true, Sort: true,
@@ -96,13 +110,18 @@ func getLatestTag(repoUrl, accessToken string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
r, err := git.OpenRepository(repoPath) r, err := git.OpenRepository(repoPath)
if err != nil { if err != nil {
return "", err return "", err
} }
var auth transport.AuthMethod
remoteRefsList, err := git.GetRemoteRefs(r, nil, if accessToken != "" {
auth = &http.BasicAuth{
Username: "something",
Password: accessToken,
}
}
remoteRefsList, err := git.GetRemoteRefs(r, auth,
&git.RemoteRefConstraints{ &git.RemoteRefConstraints{
Include: true, Include: true,
Sort: true, Sort: true,
@@ -144,7 +163,7 @@ func getLatestTag(repoUrl, accessToken string) (string, error) {
v, err := semver.NewVersion(sv) v, err := semver.NewVersion(sv)
if err != nil { if err != nil {
// it may happen, in the repo some tags may not conform to semver // it may happen, in the repo some tags may not conform to semver
fmt.Print("Unconform tags: " + sv) //fmt.Println("the tag is not conform to semver: " + sv)
continue continue
} }
if maxSem == nil || maxSem.LessThan(v) { if maxSem == nil || maxSem.LessThan(v) {

View File

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

View File

@@ -7,61 +7,112 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strconv"
"strings"
"time"
"github.com/mattn/go-tty"
"github.com/mitchellh/go-homedir"
"github.com/qlik-oss/k-apis/pkg/config" "github.com/qlik-oss/k-apis/pkg/config"
"github.com/qlik-oss/k-apis/pkg/cr"
"sigs.k8s.io/kustomize/api/filesys" "sigs.k8s.io/kustomize/api/filesys"
qapi "github.com/qlik-oss/sense-installer/pkg/api" qapi "github.com/qlik-oss/sense-installer/pkg/api"
) )
type InstallCommandOptions struct { type InstallCommandOptions struct {
StorageClass string StorageClass string
MongoDbUri string MongodbUri string
RotateKeys string AcceptEULA string
DryRun bool
Pull bool
Push bool
CleanPatchFiles bool
RotateKeys bool
} }
func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, keepPatchFiles bool) error { const (
eulaText = "Please read the end user license agreement at: https://www.qlik.com/us/legal/license-terms"
eulaPrompt = "Do you accept our EULA? (y/n): "
eulaErrorInstruction = `You must enter "y" to continue or execute the command with the acceptEULA flag set to "yes"`
)
// step1: fetch 1.0.0 # pull down qliksense-k8s@1.0.0 func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) error {
// step2: operator view | kubectl apply -f # operator manifest (CRD)
// step3: config apply | kubectl apply -f # generates patches (if required) in configuration directory, applies manifest
// step4: config view | kubectl apply -f # generates Custom Resource manifest (CR)
// fetch the version
qConfig := qapi.NewQConfig(q.QliksenseHome) qConfig := qapi.NewQConfig(q.QliksenseHome)
if !keepPatchFiles {
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
fmt.Printf("error removing temporary changes to the config: %v\n", err)
}
}
qcr, err := qConfig.GetCurrentCR() qcr, err := qConfig.GetCurrentCR()
if err != nil { if err != nil {
fmt.Println("cannot get the current-context cr", err) fmt.Println("cannot get the current-context cr", err)
return err return err
} }
if !qcr.IsRepoExist() {
if err := fetchAndUpdateCR(qConfig, version); err != nil {
return err
} else if qcr, err = qConfig.GetCurrentCR(); err != nil {
return err
}
}
if opts.AcceptEULA != "" && opts.AcceptEULA != "yes" {
enforceEula()
} else if opts.AcceptEULA == "" && !qcr.IsEULA() {
enforceEula()
}
qcr.SetEULA("yes") 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 != "" { if opts.StorageClass != "" {
qcr.Spec.StorageClassName = opts.StorageClass qcr.Spec.StorageClassName = opts.StorageClass
} }
if opts.RotateKeys != "" {
qcr.Spec.RotateKeys = opts.RotateKeys
}
qConfig.WriteCurrentContextCR(qcr)
//if the docker pull secret exists on disk, install it in the cluster if err := qConfig.WriteCurrentContextCR(qcr); err != nil {
//if it doesn't exist on disk, remove it in the cluster
if err := installOrRemoveImagePullSecret(qConfig); err != nil {
return err return err
} }
// check if acceptEULA is yes or not if opts.CleanPatchFiles {
if !qcr.IsEULA() { if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
return errors.New(agreementTempalte + "\n Please do $ qliksense install --acceptEULA=yes\n") fmt.Printf("error removing temporary changes to the config: %v\n", err)
}
}
// for debugging purpose
if opts.DryRun {
// generate patches
userHomeDir, _ := homedir.Dir()
fmt.Println("Generating patches only")
cr.GeneratePatches(&qcr.KApiCr, config.KeysActionDoNothing, path.Join(userHomeDir, ".kube", "config"))
return nil
}
if installed, err := q.CheckAllCrdsInstalled(); err != nil {
fmt.Println("error verifying whether CRDs are installed", err)
return err
} else if !installed {
return errors.New(`please install CRDs by executing: $ qliksense crds install`)
}
if err := validatePullPushFlagsOnInstall(qcr, opts.Pull, opts.Push); err != nil {
return err
}
if opts.Pull {
fmt.Println("Pulling images...")
if err := q.PullImages(version, ""); err != nil {
return err
}
}
if opts.Push {
fmt.Println("Pushing images...")
if err := q.PushImagesForCurrentCR(); err != nil {
return err
}
}
if err := applyImagePullSecret(qConfig); err != nil {
return err
} }
//CRD will be installed outside of operator //CRD will be installed outside of operator
@@ -75,13 +126,25 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, kee
return err return err
} }
// create patch dependent resoruces // create patch dependent resources
fmt.Println("Installing resoruces used kuztomize patch") fmt.Println("Installing resources used by the kuztomize patch")
if err := q.createK8sResoruceBeforePatch(qcr); err != nil { if err := q.createK8sResourceBeforePatch(qcr); err != nil {
return err return err
} }
if qcr.Spec.Git != nil && qcr.Spec.Git.Repository != "" { if opts.RotateKeys {
fmt.Println("Deleting stored application keys")
if err := q.DeleteKeysClusterBackup(); err != nil {
return err
} else {
qcr.AddLabelToCr("keys-rotated", strconv.FormatInt(time.Now().Unix(), 10))
if err := qConfig.WriteCurrentContextCR(qcr); err != nil {
return err
}
}
}
if qcr.Spec.OpsRunner != nil {
// fetching and applying manifest will be in the operator controller // fetching and applying manifest will be in the operator controller
// get decrypted cr // get decrypted cr
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil { if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
@@ -90,35 +153,16 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, kee
return q.applyCR(dcr) return q.applyCR(dcr)
} }
} }
if !qcr.IsRepoExist() {
if err := fetchAndUpdateCR(qConfig, version); err != nil {
return err
}
}
qcr, err = qConfig.GetCurrentCR()
if err != nil {
fmt.Println("cannot get the current-context cr", err)
return err
} else if qcr.Spec.GetManifestsRoot() == "" {
return errors.New("cannot get the manifest root. Use qliksense fetch <version> or qliksense set manifestsRoot")
}
// install generated manifests into cluster // install generated manifests into cluster
fmt.Println("Installing generated manifests into cluster") fmt.Println("Installing generated manifests into the cluster")
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil { if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
return err return err
} else if err := q.applyConfigToK8s(dcr); err != nil {
fmt.Println("cannot do kubectl apply on manifests")
return err
} else { } else {
if IsQliksenseInstalled(dcr.GetName()) { return q.applyCR(dcr)
return q.UpgradeQK8s(keepPatchFiles)
}
if err := q.applyConfigToK8s(dcr); err != nil {
fmt.Println("cannot do kubectl apply on manifests")
return err
} else {
return q.applyCR(dcr)
}
} }
} }
@@ -132,22 +176,13 @@ func (q *Qliksense) getProcessedOperatorControllerString(qcr *qapi.QliksenseCR)
return operatorControllerString, nil return operatorControllerString, nil
} }
func installOrRemoveImagePullSecret(qConfig *qapi.QliksenseConfig) error { func applyImagePullSecret(qConfig *qapi.QliksenseConfig) error {
if pullDockerConfigJsonSecret, err := qConfig.GetPullDockerConfigJsonSecret(); err == nil { if pullDockerConfigJsonSecret, err := qConfig.GetPullDockerConfigJsonSecret(); err == nil {
if dockerConfigJsonSecretYaml, err := pullDockerConfigJsonSecret.ToYaml(""); err != nil { if dockerConfigJsonSecretYaml, err := pullDockerConfigJsonSecret.ToYaml(""); err != nil {
return err return err
} else if err := qapi.KubectlApply(string(dockerConfigJsonSecretYaml), ""); err != nil { } else if err := qapi.KubectlApply(string(dockerConfigJsonSecretYaml), ""); err != nil {
return err 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 return nil
} }
@@ -192,7 +227,7 @@ images:
func (q *Qliksense) applyCR(cr *qapi.QliksenseCR) error { func (q *Qliksense) applyCR(cr *qapi.QliksenseCR) error {
// install operator cr into cluster // install operator cr into cluster
//get the current context cr //get the current context cr
fmt.Println("Install operator CR into cluster") fmt.Println("Installing operator CR into the cluster")
r, err := cr.GetString() r, err := cr.GetString()
if err != nil { if err != nil {
return err return err
@@ -204,7 +239,7 @@ func (q *Qliksense) applyCR(cr *qapi.QliksenseCR) error {
return nil return nil
} }
func (q *Qliksense) createK8sResoruceBeforePatch(qcr *qapi.QliksenseCR) error { func (q *Qliksense) createK8sResourceBeforePatch(qcr *qapi.QliksenseCR) error {
for svc, nvs := range qcr.Spec.Secrets { for svc, nvs := range qcr.Spec.Secrets {
for _, nv := range nvs { for _, nv := range nvs {
if isK8sSecretNeedToCreate(nv) { if isK8sSecretNeedToCreate(nv) {
@@ -223,3 +258,26 @@ func (q *Qliksense) createK8sResoruceBeforePatch(qcr *qapi.QliksenseCR) error {
func isK8sSecretNeedToCreate(nv config.NameValue) bool { func isK8sSecretNeedToCreate(nv config.NameValue) bool {
return nv.ValueFrom != nil return nv.ValueFrom != nil
} }
func enforceEula() {
fmt.Println(eulaText)
fmt.Print(eulaPrompt)
answer := readAnswerFromTty()
if strings.ToLower(answer) != "y" {
fmt.Println(eulaErrorInstruction)
os.Exit(1)
}
}
func readAnswerFromTty() string {
t, err := tty.Open()
if err != nil {
panic(err)
}
defer t.Close()
answer, err := t.ReadString()
if err != nil {
panic(err)
}
return answer
}

View File

@@ -5,20 +5,18 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
"path/filepath"
"strings" "strings"
"testing" "testing"
"github.com/gobuffalo/packr/v2"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct" "sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/api/resid" "sigs.k8s.io/kustomize/api/resid"
"sigs.k8s.io/kustomize/api/resmap" "sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource" "sigs.k8s.io/kustomize/api/resource"
"github.com/gobuffalo/packr/v2"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
) )
func TestCreateK8sResoruceBeforePatch(t *testing.T) { func TestCreateK8sResourceBeforePatch(t *testing.T) {
td := setup() td := setup()
sampleCr := ` sampleCr := `
apiVersion: qlik.com/v1 apiVersion: qlik.com/v1
@@ -43,20 +41,12 @@ spec:
value: "yes" value: "yes"
secrets: secrets:
qliksense: qliksense:
- name: mongoDbUri - name: mongodbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
profile: docker-desktop profile: docker-desktop`
rotateKeys: "yes"`
crFile := filepath.Join(testDir, "install_test.yaml")
ioutil.WriteFile(crFile, []byte(sampleCr), 0644)
q := New(testDir) q := New(testDir)
file, e := os.Open(crFile) if err := q.LoadCr([]byte(sampleCr), false); err != nil {
if e != nil {
t.Log(e)
t.FailNow()
}
if err := q.LoadCr(file, false); err != nil {
t.Log(err) t.Log(err)
t.FailNow() t.FailNow()
} }
@@ -66,7 +56,7 @@ spec:
t.Log(err) t.Log(err)
t.FailNow() t.FailNow()
} }
if err = q.createK8sResoruceBeforePatch(cr); err != nil { if err = q.createK8sResourceBeforePatch(cr); err != nil {
t.Log(err) t.Log(err)
t.FailNow() t.FailNow()
} }

21
pkg/qliksense/keys.go Normal file
View File

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

View File

@@ -22,7 +22,7 @@ import (
"sigs.k8s.io/kustomize/api/resource" "sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/api/types"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v2"
"github.com/Shopify/ejson" "github.com/Shopify/ejson"
"github.com/qlik-oss/k-apis/pkg/config" "github.com/qlik-oss/k-apis/pkg/config"
@@ -276,9 +276,11 @@ func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
configPath := filepath.Join(tmpDir, "config") configPath := filepath.Join(tmpDir, "config")
if repo, err := kapis_git.CloneRepository(configPath, defaultConfigRepoGitUrl, nil); err != nil { if repo, err := kapis_git.CloneRepository(configPath, defaultConfigRepoGitUrl, nil); err != nil {
t.Fatalf("unexpected error: %v\n", err) t.Fatalf("unexpected error: %v\n", err)
} else if err := kapis_git.Checkout(repo, "e38df644e759abf0b5941c1511d1a2cd5e3c42fa", "", nil); err != nil { } else if err := kapis_git.Checkout(repo, "e38df644e759abf0b5941c1511d1a2cd5e3c42fa", "commit-e38df644e759abf0b5941c1511d1a2cd5e3c42fa", nil); err != nil {
t.Fatalf("unexpected error: %v\n", err) t.Fatalf("unexpected error: %v\n", err)
} }
//tmpDir := "/var/folders/mf/5hs1qkq508q_scjbhxhmf9qwjrp346/T/679268230"
//configPath := "/var/folders/mf/5hs1qkq508q_scjbhxhmf9qwjrp346/T/679268230/config"
cr := &config.CRSpec{ cr := &config.CRSpec{
ManifestsRoot: configPath, ManifestsRoot: configPath,
@@ -310,8 +312,8 @@ func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
} }
break break
} }
if resource["kind"].(string) == "Secret" && strings.Contains(resource["metadata"].(map[string]interface{})["name"].(string), "users-secrets-") { if resource["kind"].(string) == "Secret" && strings.Contains(resource["metadata"].(map[interface{}]interface{})["name"].(string), "users-secrets-") {
keyIdBase64 = resource["data"].(map[string]interface{})["tokenAuthPrivateKeyId"].(string) keyIdBase64 = resource["data"].(map[interface{}]interface{})["tokenAuthPrivateKeyId"].(string)
break break
} }
} }

View File

@@ -3,17 +3,13 @@ package qliksense
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil"
"strings" "strings"
qapi "github.com/qlik-oss/sense-installer/pkg/api" qapi "github.com/qlik-oss/sense-installer/pkg/api"
) )
func (q *Qliksense) LoadCr(reader io.Reader, overwriteExistingContext bool) error { func (q *Qliksense) LoadCr(crBytes []byte, overwriteExistingContext bool) error {
if crBytes, err := ioutil.ReadAll(reader); err != nil { if crName, err := q.loadCrStringIntoFileSystem(string(crBytes), overwriteExistingContext); err != nil {
return err
} else if crName, err := q.loadCrStringIntoFileSystem(string(crBytes), overwriteExistingContext); err != nil {
return err return err
} else { } else {
fmt.Println("cr name: [ " + crName + " ] has been loaded") fmt.Println("cr name: [ " + crName + " ] has been loaded")
@@ -21,16 +17,6 @@ func (q *Qliksense) LoadCr(reader io.Reader, overwriteExistingContext bool) erro
return nil return nil
} }
func (q *Qliksense) IsEulaAcceptedInCrFile(reader io.Reader) (bool, error) {
if crBytes, err := ioutil.ReadAll(reader); err != nil {
return false, err
} else if cr, err := qapi.CreateCRObjectFromString(string(crBytes)); err != nil {
return false, err
} else {
return cr.IsEULA(), nil
}
}
func (q *Qliksense) loadCrStringIntoFileSystem(crstr string, overwriteExistingContext bool) (string, error) { func (q *Qliksense) loadCrStringIntoFileSystem(crstr string, overwriteExistingContext bool) (string, error) {
cr, err := qapi.CreateCRObjectFromString(crstr) cr, err := qapi.CreateCRObjectFromString(crstr)
if err != nil { if err != nil {
@@ -69,8 +55,8 @@ func (q *Qliksense) loadCrStringIntoFileSystem(crstr string, overwriteExistingCo
} }
} }
} }
if cr.Spec.FetchSource != nil && cr.Spec.FetchSource.AccessToken != "" { if cr.Spec.Git != nil && cr.Spec.Git.AccessToken != "" {
if err := cr.SetFetchAccessToken(cr.Spec.FetchSource.AccessToken, encryptionKey); err != nil { if err := cr.SetFetchAccessToken(cr.Spec.Git.AccessToken, encryptionKey); err != nil {
return "", err return "", err
} }
} }

View File

@@ -1,9 +1,6 @@
package qliksense package qliksense
import ( import (
"io/ioutil"
"os"
"path/filepath"
"testing" "testing"
qapi "github.com/qlik-oss/sense-installer/pkg/api" qapi "github.com/qlik-oss/sense-installer/pkg/api"
@@ -35,10 +32,9 @@ spec:
value: "yes" value: "yes"
secrets: secrets:
qliksense: qliksense:
- name: mongoDbUri - name: mongodbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
profile: docker-desktop profile: docker-desktop`
rotateKeys: "yes"`
sampleCr2 := ` sampleCr2 := `
apiVersion: qlik.com/v1 apiVersion: qlik.com/v1
kind: Qliksense kind: Qliksense
@@ -62,10 +58,9 @@ spec:
value: "yes" value: "yes"
secrets: secrets:
qliksense: qliksense:
- name: mongoDbUri - name: mongodbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
profile: docker-desktop profile: docker-desktop`
rotateKeys: "yes"`
duplicateCr := ` duplicateCr := `
apiVersion: qlik.com/v1 apiVersion: qlik.com/v1
@@ -79,30 +74,13 @@ spec:
repository: https://github.com/ffoysal/qliksense-k8s repository: https://github.com/ffoysal/qliksense-k8s
accessToken: abababababababaab accessToken: abababababababaab
userName: "blblbl"` userName: "blblbl"`
crFile1 := filepath.Join(testDir, "testcr1.yaml")
ioutil.WriteFile(crFile1, []byte(sampleCr1), 0644)
crFile2 := filepath.Join(testDir, "testcr2.yaml")
ioutil.WriteFile(crFile2, []byte(sampleCr2), 0644)
dupCrFile := filepath.Join(testDir, "dupcr.yaml")
ioutil.WriteFile(dupCrFile, []byte(duplicateCr), 0644)
q := New(testDir) q := New(testDir)
file1, e := os.Open(crFile1) if err := q.LoadCr([]byte(sampleCr1), false); err != nil {
if e != nil {
t.Log(e)
t.FailNow()
}
if err := q.LoadCr(file1, false); err != nil {
t.Log(err) t.Log(err)
t.FailNow() t.FailNow()
} }
file2, e := os.Open(crFile2) if err := q.LoadCr([]byte(sampleCr2), false); err != nil {
if e != nil {
t.Log(e)
t.FailNow()
}
if err := q.LoadCr(file2, false); err != nil {
t.Log(err) t.Log(err)
t.FailNow() t.FailNow()
} }
@@ -128,12 +106,7 @@ spec:
if qConfig.Spec.CurrentContext != "qlik-test3" { if qConfig.Spec.CurrentContext != "qlik-test3" {
t.FailNow() t.FailNow()
} }
file, e := os.Open(dupCrFile) if err := q.LoadCr([]byte(duplicateCr), false); err == nil {
if e != nil {
t.Log(e)
t.FailNow()
}
if err := q.LoadCr(file, false); err == nil {
t.FailNow() t.FailNow()
} }
td() td()

View File

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

View File

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