Compare commits
56 Commits
fix-cipher
...
update-kus
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbfee1eee6 | ||
|
|
6ab9317638 | ||
|
|
5899760c16 | ||
|
|
a63c400106 | ||
|
|
568012edd8 | ||
|
|
34f702b183 | ||
|
|
d8cdbb30bb | ||
|
|
616e759089 | ||
|
|
c2d3f39542 | ||
|
|
0b6c1b8bc9 | ||
|
|
7091da099c | ||
|
|
1f8bcf3469 | ||
|
|
627a4a14f9 | ||
|
|
66333e9e97 | ||
|
|
36c91e9dab | ||
|
|
b6ae0c9873 | ||
|
|
6899a7be77 | ||
|
|
373d6499dc | ||
|
|
746823c54b | ||
|
|
73b5da8c14 | ||
|
|
7cc6e55779 | ||
|
|
27c8f3ee8e | ||
|
|
cb9f463f01 | ||
|
|
293e923c82 | ||
|
|
504190c75c | ||
|
|
fc62e5fea4 | ||
|
|
bf4e4b9bf1 | ||
|
|
4cb3231a08 | ||
|
|
e1dbcfaac8 | ||
|
|
77a3bf4581 | ||
|
|
a670b6c750 | ||
|
|
492e4a1baa | ||
|
|
3b54a7f0b2 | ||
|
|
55cfc42257 | ||
|
|
12e2bec618 | ||
|
|
2ed59321e4 | ||
|
|
98198a3c8b | ||
|
|
afab3e2939 | ||
|
|
62fda8f2c6 | ||
|
|
6af87ab00a | ||
|
|
0b60838b52 | ||
|
|
bb2974fe66 | ||
|
|
97b2239c2e | ||
|
|
9eff54d9ec | ||
|
|
2d0a2a32bf | ||
|
|
d7238e2b3c | ||
|
|
1c0ded7f3d | ||
|
|
ec8a9376e7 | ||
|
|
bcc321e180 | ||
|
|
0aabf63715 | ||
|
|
c9ca5c8be0 | ||
|
|
9d0ac0290f | ||
|
|
dd8a48b2b8 | ||
|
|
9fb6800993 | ||
|
|
bbb811a879 | ||
|
|
8156b884ce |
2
.github/workflows/mkdocs.yml
vendored
2
.github/workflows/mkdocs.yml
vendored
@@ -16,6 +16,6 @@ jobs:
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Deploy docs
|
||||
uses: mhausenblas/mkdocs-deploy-gh-pages@1.11
|
||||
uses: mhausenblas/mkdocs-deploy-gh-pages@1.12
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -19,7 +19,7 @@ func about(q *qliksense.Qliksense) *cobra.Command {
|
||||
|
||||
c := &cobra.Command{
|
||||
Use: "about ref",
|
||||
Short: "Displays information pertaining to Qliksense on Kubernetes",
|
||||
Short: "Displays information pertaining to qliksense on Kubernetes",
|
||||
Long: "Gives the version of QLik Sense on Kubernetes and versions of images.",
|
||||
Example: `
|
||||
qliksense about 1.0.0
|
||||
|
||||
@@ -1,74 +1,44 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func applyCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
opts := &qliksense.InstallCommandOptions{}
|
||||
opts := &qliksense.InstallCommandOptions{
|
||||
CleanPatchFiles: true,
|
||||
}
|
||||
filePath := ""
|
||||
keepPatchFiles, pull, push := false, false, false
|
||||
c := &cobra.Command{
|
||||
Use: "apply",
|
||||
Short: "install qliksense based on provided cr file",
|
||||
Long: `install qliksense based on provided cr file`,
|
||||
Example: `qliksense apply -f file_name or cat cr_file | qliksense apply -f -`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runLoadOrApplyCommandE(cmd, func(crBytes []byte) error {
|
||||
if cr, crBytesWithEula, err := getCrWithEulaInserted(crBytes); err != nil {
|
||||
return err
|
||||
} else if err := validatePullPushFlagsOnApply(cr, pull, push); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return q.ApplyCRFromReader(bytes.NewReader(crBytesWithEula), opts, keepPatchFiles, true, pull, push)
|
||||
}
|
||||
})
|
||||
return apply(q, cmd, opts)
|
||||
},
|
||||
}
|
||||
|
||||
f := c.Flags()
|
||||
f.StringVarP(&filePath, "file", "f", "", "Install from a CR file")
|
||||
c.MarkFlagRequired("file")
|
||||
f.StringVarP(&opts.StorageClass, "storageClass", "s", "", "Storage class for qliksense")
|
||||
f.StringVarP(&opts.MongoDbUri, "mongoDbUri", "m", "", "mongoDbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
|
||||
f.StringVarP(&opts.RotateKeys, "rotateKeys", "r", "", "Rotate JWT keys for qliksense (yes:rotate keys/ no:use exising keys from cluster/ None: use default EJSON_KEY from env")
|
||||
f.BoolVar(&keepPatchFiles, keepPatchFilesFlagName, keepPatchFiles, keepPatchFilesFlagUsage)
|
||||
f.BoolVarP(&pull, pullFlagName, pullFlagShorthand, pull, pullFlagUsage)
|
||||
f.BoolVarP(&push, pushFlagName, pushFlagShorthand, push, pushFlagUsage)
|
||||
|
||||
eulaPreRunHooks.addValidator(fmt.Sprintf("%v %v", rootCommandName, c.Name()), loadOrApplyCommandEulaPreRunHook)
|
||||
f.StringVarP(&opts.MongodbUri, "mongodbUri", "m", "", "mongodbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
|
||||
f.BoolVar(&opts.CleanPatchFiles, cleanPatchFilesFlagName, opts.CleanPatchFiles, cleanPatchFilesFlagUsage)
|
||||
f.BoolVarP(&opts.Pull, pullFlagName, pullFlagShorthand, opts.Pull, pullFlagUsage)
|
||||
f.BoolVarP(&opts.Push, pushFlagName, pushFlagShorthand, opts.Push, pushFlagUsage)
|
||||
f.StringVarP(&opts.AcceptEULA, "acceptEULA", "a", opts.AcceptEULA, "Accept EULA for qliksense")
|
||||
|
||||
if err := c.MarkFlagRequired("file"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func validatePullPushFlagsOnApply(cr *qapi.QliksenseCR, pull, push bool) error {
|
||||
if pull && !push {
|
||||
fmt.Printf("WARNING: pulling images without pushing them")
|
||||
}
|
||||
if push {
|
||||
if registry := cr.Spec.GetImageRegistry(); registry == "" {
|
||||
return errors.New("no image registry set in the CR; to set it use: qliksense config set-image-registry")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCrWithEulaInserted(crBytes []byte) (*qapi.QliksenseCR, []byte, error) {
|
||||
if cr, err := qapi.CreateCRObjectFromString(string(crBytes)); err != nil {
|
||||
return nil, nil, err
|
||||
func apply(q *qliksense.Qliksense, cmd *cobra.Command, opts *qliksense.InstallCommandOptions) error {
|
||||
if crBytes, err := getCrBytesFromFileFlag(cmd); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cr.SetEULA("yes")
|
||||
if crBytesWithEula, err := qapi.K8sToYaml(cr); err != nil {
|
||||
return nil, nil, err
|
||||
} else {
|
||||
return cr, crBytesWithEula, nil
|
||||
}
|
||||
return q.ApplyCRFromBytes(crBytes, opts, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -12,31 +12,35 @@ var crdsCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func crdsViewCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
opts := &qliksense.CrdCommandOptions{}
|
||||
opts := &qliksense.CrdCommandOptions{
|
||||
All: true,
|
||||
}
|
||||
c := &cobra.Command{
|
||||
Use: "view",
|
||||
Short: "View CRDs for qliksense application. use view --all to see opearator crd as well ",
|
||||
Long: `View CRDs for qliksense application. use view --all to see opearator crd as well`,
|
||||
Short: "View CRDs for qliksense application. Use view --all=false to exclude the operator CRD",
|
||||
Long: "View CRDs for qliksense application. Use view --all=false to exclude the operator CRD",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.ViewCrds(opts)
|
||||
},
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func crdsInstallCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
opts := &qliksense.CrdCommandOptions{}
|
||||
opts := &qliksense.CrdCommandOptions{
|
||||
All: true,
|
||||
}
|
||||
c := &cobra.Command{
|
||||
Use: "install",
|
||||
Short: "Install CRDs fro Qliksense application. Use install --all to include operator crd",
|
||||
Long: `Install CRDs fro Qliksense application. Use install --all to include operator crd`,
|
||||
Short: "Install CRDs for qliksense application. Use install --all=false to exclude the operator CRD",
|
||||
Long: "Install CRDs for qliksense application. Use install --all=false to exclude the operator CRD",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.InstallCrds(opts)
|
||||
},
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,19 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func installCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
opts := &qliksense.InstallCommandOptions{}
|
||||
opts := &qliksense.InstallCommandOptions{
|
||||
CleanPatchFiles: true,
|
||||
}
|
||||
filePath := ""
|
||||
keepPatchFiles, pull, push := false, false, false
|
||||
c := &cobra.Command{
|
||||
Use: "install",
|
||||
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 -
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
version := ""
|
||||
if len(args) != 0 {
|
||||
version = args[0]
|
||||
}
|
||||
|
||||
if filePath != "" {
|
||||
return runLoadOrApplyCommandE(cmd, func(crBytes []byte) error {
|
||||
if cr, crBytesWithEula, err := getCrWithEulaInserted(crBytes); err != nil {
|
||||
return err
|
||||
} else if err := validatePullPushFlagsOnApply(cr, pull, push); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return q.ApplyCRFromReader(bytes.NewReader(crBytesWithEula), opts, keepPatchFiles, true, pull, push)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
version := ""
|
||||
if len(args) != 0 {
|
||||
version = args[0]
|
||||
}
|
||||
if err := validatePullPushFlagsOnInstall(q, pull, push); err != nil {
|
||||
if err := apply(q, cmd, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
if pull {
|
||||
fmt.Println("Pulling images...")
|
||||
if err := q.PullImages(version, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := q.InstallQK8s(version, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
if push {
|
||||
fmt.Println("Pushing images...")
|
||||
if err := q.PushImagesForCurrentCR(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return q.InstallQK8s(version, opts, keepPatchFiles)
|
||||
}
|
||||
postflightChecksCmd := AllPostflightChecks(q)
|
||||
postflightChecksCmd.DisableFlagParsing = true
|
||||
return postflightChecksCmd.Execute()
|
||||
},
|
||||
}
|
||||
|
||||
eulaPreRunHooks.addValidator(fmt.Sprintf("%v %v", rootCommandName, c.Name()), func(cmd *cobra.Command, q *qliksense.Qliksense) (b bool, err error) {
|
||||
if filePath != "" {
|
||||
return loadOrApplyCommandEulaPreRunHook(cmd, q)
|
||||
} else if qConfig, err := qapi.NewQConfigE(q.QliksenseHome); err != nil {
|
||||
return false, err
|
||||
} else if qcr, err := qConfig.GetCurrentCR(); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
return qcr.IsEULA(), nil
|
||||
}
|
||||
})
|
||||
|
||||
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.BoolVarP(&pull, pullFlagName, pullFlagShorthand, pull, pullFlagUsage)
|
||||
f.BoolVarP(&push, pushFlagName, pushFlagShorthand, push, pushFlagUsage)
|
||||
f.StringVarP(&opts.StorageClass, "storageClass", "s", "", "Storage class for qliksense")
|
||||
f.StringVarP(&opts.MongodbUri, "mongodbUri", "m", "", "mongodbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
|
||||
f.BoolVar(&opts.CleanPatchFiles, cleanPatchFilesFlagName, opts.CleanPatchFiles, cleanPatchFilesFlagUsage)
|
||||
f.BoolVarP(&opts.Pull, pullFlagName, pullFlagShorthand, opts.Pull, pullFlagUsage)
|
||||
f.BoolVarP(&opts.Push, pushFlagName, pushFlagShorthand, opts.Push, pushFlagUsage)
|
||||
f.StringVarP(&opts.AcceptEULA, "acceptEULA", "a", opts.AcceptEULA, "Accept EULA for qliksense")
|
||||
f.BoolVarP(&opts.DryRun, "dry-run", "", false, "Dry run will generate the patches without rotating keys")
|
||||
|
||||
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
31
cmd/qliksense/keys.go
Normal 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
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"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`,
|
||||
Example: `qliksense load -f file_name or cat cr_file | qliksense load -f -`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runLoadOrApplyCommandE(cmd, func(buffer []byte) error {
|
||||
return q.LoadCr(bytes.NewReader(buffer), overwriteExistingContext)
|
||||
})
|
||||
if crBytes, err := getCrBytesFromFileFlag(cmd); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return q.LoadCr(crBytes, overwriteExistingContext)
|
||||
}
|
||||
},
|
||||
}
|
||||
f := c.Flags()
|
||||
f.StringVarP(&filePath, "file", "f", "", "File to load CR from")
|
||||
c.MarkFlagRequired("file")
|
||||
f.BoolVarP(&overwriteExistingContext, "overwrite", "o", overwriteExistingContext, "Overwrite any existing contexts with the same name")
|
||||
|
||||
eulaPreRunHooks.addValidator(fmt.Sprintf("%v %v", rootCommandName, c.Name()), loadOrApplyCommandEulaPreRunHook)
|
||||
if err := c.MarkFlagRequired("file"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func getCrFileFromFlag(cmd *cobra.Command, flagName string) (*os.File, error) {
|
||||
filePath := cmd.Flag(flagName).Value.String()
|
||||
if filePath == "-" {
|
||||
if !isInputFromPipe() {
|
||||
return nil, errors.New("No input pipe present")
|
||||
}
|
||||
return os.Stdin, nil
|
||||
}
|
||||
file, e := os.Open(filePath)
|
||||
if e != nil {
|
||||
return nil, errors.Wrapf(e,
|
||||
"unable to read the file %s", filePath)
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func isInputFromPipe() bool {
|
||||
fileInfo, _ := os.Stdin.Stat()
|
||||
return fileInfo.Mode()&os.ModeCharDevice == 0
|
||||
}
|
||||
|
||||
func loadOrApplyCommandEulaPreRunHook(cmd *cobra.Command, q *qliksense.Qliksense) (bool, error) {
|
||||
file, err := getCrFileFromFlag(cmd, "file")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if crBytes, err := ioutil.ReadAll(file); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
eulaPreRunHooks.addPostValidationArtifact("CR", crBytes)
|
||||
return q.IsEulaAcceptedInCrFile(bytes.NewBuffer(crBytes))
|
||||
}
|
||||
}
|
||||
|
||||
func runLoadOrApplyCommandE(cmd *cobra.Command, callBack func(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
|
||||
func getCrFileFromFlag(cmd *cobra.Command, flagName string) (*os.File, error) {
|
||||
filePath := cmd.Flag(flagName).Value.String()
|
||||
if filePath == "-" {
|
||||
if !isInputFromPipe() {
|
||||
return nil, errors.New("No input pipe present")
|
||||
} else {
|
||||
return 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)
|
||||
}
|
||||
}
|
||||
|
||||
96
cmd/qliksense/postflight.go
Normal file
96
cmd/qliksense/postflight.go
Normal 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
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
. "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"
|
||||
@@ -37,12 +38,12 @@ func pfDnsCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
Long: `perform preflight dns check to check DNS connectivity status in the cluster`,
|
||||
Example: `qliksense preflight dns`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts}
|
||||
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
|
||||
|
||||
// Preflight DNS check
|
||||
namespace, kubeConfigContents, err := preflight.InitPreflight()
|
||||
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
|
||||
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)
|
||||
return nil
|
||||
}
|
||||
@@ -50,11 +51,11 @@ func pfDnsCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
namespace = "default"
|
||||
}
|
||||
if err = qp.CheckDns(namespace, kubeConfigContents, false); err != nil {
|
||||
fmt.Fprintf(out, "%s\n", Red("Preflight DNS check FAILED"))
|
||||
fmt.Fprintf(out, "%s\n", Red("FAILED"))
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(out, "%s\n", Green("Preflight DNS check PASSED"))
|
||||
fmt.Fprintf(out, "%s\n", Green("PASSED"))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -70,26 +71,26 @@ func pfK8sVersionCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
}
|
||||
|
||||
var preflightCheckK8sVersionCmd = &cobra.Command{
|
||||
Use: "kube-version",
|
||||
Use: "k8s-version",
|
||||
Short: "check kubernetes version",
|
||||
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 {
|
||||
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
|
||||
namespace, kubeConfigContents, err := preflight.InitPreflight()
|
||||
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
|
||||
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)
|
||||
return 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)
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(out, "%s\n", Green("Preflight kubernetes minimum version check PASSED"))
|
||||
fmt.Fprintf(out, "%s\n", Green("PASSED"))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -111,11 +112,11 @@ func pfAllChecksCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
Long: `perform all preflight checks on the target cluster`,
|
||||
Example: `qliksense preflight all`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts}
|
||||
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
|
||||
|
||||
// Preflight run all checks
|
||||
fmt.Printf("Running all preflight checks...\n\n")
|
||||
namespace, kubeConfigContents, err := preflight.InitPreflight()
|
||||
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
|
||||
if err != nil {
|
||||
fmt.Fprintf(out, "%s\n", Red("Unable to run the preflight checks suite"))
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
@@ -136,12 +137,7 @@ func pfAllChecksCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
f := preflightAllChecksCmd.Flags()
|
||||
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
|
||||
f.StringVarP(&preflightOpts.MongoOptions.MongodbUrl, "mongodb-url", "", "", "mongodbUrl to try connecting to")
|
||||
f.StringVarP(&preflightOpts.MongoOptions.Username, "mongodb-username", "", "", "username to connect to mongodb")
|
||||
f.StringVarP(&preflightOpts.MongoOptions.Password, "mongodb-password", "", "", "password to connect to mongodb")
|
||||
f.StringVarP(&preflightOpts.MongoOptions.CaCertFile, "mongodb-ca-cert", "", "", "certificate to use for mongodb check")
|
||||
f.StringVarP(&preflightOpts.MongoOptions.ClientCertFile, "mongodb-client-cert", "", "", "client-certificate to use for mongodb check")
|
||||
f.BoolVar(&preflightOpts.MongoOptions.Tls, "mongodb-tls", false, "enable tls?")
|
||||
|
||||
return preflightAllChecksCmd
|
||||
}
|
||||
|
||||
@@ -152,16 +148,16 @@ func pfDeploymentCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
}
|
||||
var pfDeploymentCheckCmd = &cobra.Command{
|
||||
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`,
|
||||
Example: `qliksense preflight deployment`,
|
||||
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
|
||||
namespace, kubeConfigContents, err := preflight.InitPreflight()
|
||||
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
|
||||
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)
|
||||
return nil
|
||||
}
|
||||
@@ -169,11 +165,11 @@ func pfDeploymentCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
namespace = "default"
|
||||
}
|
||||
if err = qp.CheckDeployment(namespace, kubeConfigContents, false); err != nil {
|
||||
fmt.Fprintf(out, "%s\n", Red("Preflight deployment check FAILED"))
|
||||
fmt.Fprintf(out, "%s\n", Red("FAILED"))
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(out, "%s\n", Green("Preflight deployment check PASSED"))
|
||||
fmt.Fprintf(out, "%s\n", Green("PASSED"))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -194,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`,
|
||||
Example: `qliksense preflight service`,
|
||||
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
|
||||
namespace, kubeConfigContents, err := preflight.InitPreflight()
|
||||
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
|
||||
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)
|
||||
return nil
|
||||
}
|
||||
@@ -208,11 +204,11 @@ func pfServiceCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
namespace = "default"
|
||||
}
|
||||
if err = qp.CheckService(namespace, kubeConfigContents, false); err != nil {
|
||||
fmt.Fprintf(out, "%s\n", Red("Preflight service check FAILED"))
|
||||
fmt.Fprintf(out, "%s\n", Red("FAILED"))
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(out, "%s\n", Green("Preflight service check PASSED"))
|
||||
fmt.Fprintf(out, "%s\n", Green("PASSED"))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -233,12 +229,12 @@ func pfPodCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
Long: `perform preflight pod check to ensure we can create pods in the cluster`,
|
||||
Example: `qliksense preflight pod`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts}
|
||||
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
|
||||
|
||||
// Preflight pod check
|
||||
namespace, kubeConfigContents, err := preflight.InitPreflight()
|
||||
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
|
||||
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)
|
||||
return nil
|
||||
}
|
||||
@@ -246,11 +242,11 @@ func pfPodCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
namespace = "default"
|
||||
}
|
||||
if err = qp.CheckPod(namespace, kubeConfigContents, false); err != nil {
|
||||
fmt.Fprintf(out, "%s\n", Red("Preflight pod check FAILED"))
|
||||
fmt.Fprintf(out, "%s\n", Red("FAILED"))
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(out, "%s\n", Green("Preflight pod check PASSED"))
|
||||
fmt.Fprintf(out, "%s\n", Green("PASSED"))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -271,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`,
|
||||
Example: `qliksense preflight createRole`,
|
||||
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
|
||||
namespace, _, err := preflight.InitPreflight()
|
||||
namespace, _, err := qp.CG.LoadKubeConfigAndNamespace()
|
||||
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)
|
||||
return 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)
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(out, "%s\n", Green("Preflight role check PASSED"))
|
||||
fmt.Fprintf(out, "%s\n", Green("PASSED"))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -306,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`,
|
||||
Example: `qliksense preflight rolebinding`,
|
||||
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
|
||||
namespace, _, err := preflight.InitPreflight()
|
||||
namespace, _, err := qp.CG.LoadKubeConfigAndNamespace()
|
||||
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)
|
||||
return 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)
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(out, "%s\n", Green("Preflight rolebinding check PASSED"))
|
||||
fmt.Fprintf(out, "%s\n", Green("PASSED"))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -337,25 +333,25 @@ func pfCreateServiceAccountCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
|
||||
var preflightServiceAccountCmd = &cobra.Command{
|
||||
Use: "serviceaccount",
|
||||
Short: "preflight create ServiceAccount check",
|
||||
Short: "preflight create serviceaccount check",
|
||||
Long: `perform preflight serviceaccount check to ensure we are able to create a service account in the cluster`,
|
||||
Example: `qliksense preflight serviceaccount`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts}
|
||||
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
|
||||
|
||||
// Preflight createServiceAccount check
|
||||
namespace, _, err := preflight.InitPreflight()
|
||||
namespace, _, err := qp.CG.LoadKubeConfigAndNamespace()
|
||||
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)
|
||||
return 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)
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(out, "%s\n", Green("Preflight ServiceAccount check PASSED"))
|
||||
fmt.Fprintf(out, "%s\n", Green("PASSED"))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -375,21 +371,21 @@ func pfCreateAuthCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
Long: `perform preflight authcheck that combines the role, rolebinding and serviceaccount checks`,
|
||||
Example: `qliksense preflight authcheck`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts}
|
||||
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
|
||||
|
||||
// Preflight authcheck
|
||||
namespace, kubeConfigContents, err := preflight.InitPreflight()
|
||||
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
|
||||
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)
|
||||
return 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)
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(out, "%s\n", Green("Preflight authcheck PASSED"))
|
||||
fmt.Fprintf(out, "%s\n", Green("PASSED"))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -410,12 +406,12 @@ func pfMongoCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
Long: `perform preflight mongo check to ensure we are able to connect to a mongodb instance in the cluster`,
|
||||
Example: `qliksense preflight mongo OR preflight mongo --url=<url>`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts}
|
||||
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
|
||||
|
||||
// Preflight mongo check
|
||||
namespace, kubeConfigContents, err := preflight.InitPreflight()
|
||||
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
|
||||
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)
|
||||
return nil
|
||||
}
|
||||
@@ -423,22 +419,18 @@ func pfMongoCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
namespace = "default"
|
||||
}
|
||||
if err = qp.CheckMongo(kubeConfigContents, namespace, preflightOpts, false); err != nil {
|
||||
fmt.Fprintf(out, "%s\n", Red("Preflight mongo check FAILED"))
|
||||
fmt.Fprintf(out, "%s\n", Red("FAILED"))
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(out, "%s\n", Green("Preflight mongo check PASSED"))
|
||||
fmt.Fprintf(out, "%s\n", Green("PASSED"))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
f := preflightMongoCmd.Flags()
|
||||
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
|
||||
f.StringVarP(&preflightOpts.MongoOptions.MongodbUrl, "url", "", "", "mongodbUrl to try connecting to")
|
||||
f.StringVarP(&preflightOpts.MongoOptions.Username, "username", "", "", "username to connect to mongodb")
|
||||
f.StringVarP(&preflightOpts.MongoOptions.Password, "password", "", "", "password to connect to mongodb")
|
||||
f.StringVarP(&preflightOpts.MongoOptions.CaCertFile, "ca-cert", "", "", "ca certificate to use for mongodb check")
|
||||
f.StringVarP(&preflightOpts.MongoOptions.ClientCertFile, "client-cert", "", "", "client-certificate to use for mongodb check")
|
||||
f.BoolVar(&preflightOpts.MongoOptions.Tls, "tls", false, "enable tls?")
|
||||
return preflightMongoCmd
|
||||
}
|
||||
|
||||
@@ -454,10 +446,10 @@ func pfCleanupCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
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}
|
||||
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
|
||||
|
||||
// Preflight clean
|
||||
namespace, kubeConfigContents, err := preflight.InitPreflight()
|
||||
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
|
||||
if err != nil {
|
||||
fmt.Fprintf(out, "%s\n", Red("Preflight cleanup FAILED"))
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
@@ -480,3 +472,42 @@ func pfCleanupCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
|
||||
return pfCleanCmd
|
||||
}
|
||||
|
||||
func pfVerifyCAChainCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
out := ansi.NewColorableStdout()
|
||||
preflightOpts := &preflight.PreflightOptions{
|
||||
MongoOptions: &preflight.MongoOptions{},
|
||||
}
|
||||
|
||||
var pfVerifyCAChainCmd = &cobra.Command{
|
||||
Use: "verify-ca-chain",
|
||||
Short: "verify-ca-chain using openssl verify",
|
||||
Long: `verify the CA chain using openssl verify to ensure that mongodb certificate is valid`,
|
||||
Example: `qliksense preflight verify-ca-chain`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
|
||||
|
||||
// Preflight service check
|
||||
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
|
||||
if err != nil {
|
||||
fmt.Fprintf(out, "%s\n", Red("FAILED"))
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
if err = qp.VerifyCAChain(kubeConfigContents, namespace, preflightOpts, false); err != nil {
|
||||
fmt.Fprintf(out, "%s\n", Red("FAILED"))
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(out, "%s\n", Green("PASSED"))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
f := pfVerifyCAChainCmd.Flags()
|
||||
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
|
||||
return pfVerifyCAChainCmd
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -34,22 +31,8 @@ func pushQliksenseImages(q *qliksense.Qliksense) *cobra.Command {
|
||||
Short: "Push docker images for offline install",
|
||||
Example: `qliksense push`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := ensureImageRegistrySetInCR(q); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return q.PushImagesForCurrentCR()
|
||||
}
|
||||
return q.PushImagesForCurrentCR()
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func ensureImageRegistrySetInCR(q *qliksense.Qliksense) error {
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
if qcr, err := qConfig.GetCurrentCR(); err != nil {
|
||||
return err
|
||||
} else if registry := qcr.Spec.GetImageRegistry(); registry == "" {
|
||||
return errors.New("no image registry set in the CR; to set it use: qliksense config set-image-registry")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -23,17 +22,17 @@ import (
|
||||
// qliksense <command>
|
||||
|
||||
const (
|
||||
qlikSenseHomeVar = "QLIKSENSE_HOME"
|
||||
qlikSenseDirVar = ".qliksense"
|
||||
keepPatchFilesFlagName = "keep-config-repo-patches"
|
||||
keepPatchFilesFlagUsage = "Keep config repo patch files (for debugging)"
|
||||
pullFlagName = "pull"
|
||||
pullFlagShorthand = "d"
|
||||
pullFlagUsage = "If using private docker registry, pull (download) all required Qliksense images before install"
|
||||
pushFlagName = "push"
|
||||
pushFlagShorthand = "u"
|
||||
pushFlagUsage = "If using private docker registry, push (upload) all downloaded Qliksense images to that registry before install"
|
||||
rootCommandName = "qliksense"
|
||||
qlikSenseHomeVar = "QLIKSENSE_HOME"
|
||||
qlikSenseDirVar = ".qliksense"
|
||||
cleanPatchFilesFlagName = "clean"
|
||||
cleanPatchFilesFlagUsage = "Set --clean=false to keep any prior config repo file changes on install (for debugging)"
|
||||
pullFlagName = "pull"
|
||||
pullFlagShorthand = "d"
|
||||
pullFlagUsage = "If using private docker registry, pull (download) all required qliksense images before install"
|
||||
pushFlagName = "push"
|
||||
pushFlagShorthand = "u"
|
||||
pushFlagUsage = "If using private docker registry, push (upload) all downloaded qliksense images to that registry before install"
|
||||
rootCommandName = "qliksense"
|
||||
)
|
||||
|
||||
func initAndExecute() error {
|
||||
@@ -101,12 +100,11 @@ func commandUsesContext(commandName string) bool {
|
||||
func getRootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: rootCommandName,
|
||||
Short: "Qliksense cli tool",
|
||||
Short: "qliksense cli tool",
|
||||
Long: `qliksense cli tool provides functionality to perform operations on qliksense-k8s, qliksense operator, and kubernetes cluster`,
|
||||
Args: cobra.ArbitraryArgs,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
if commandUsesContext(cmd.CommandPath()) {
|
||||
globalEulaPreRun(cmd, p)
|
||||
if err := p.SetUpQliksenseDefaultContext(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -114,24 +112,10 @@ func getRootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||
if err := pf.Initialize(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
globalEulaPostRun(cmd, p)
|
||||
}
|
||||
},
|
||||
PersistentPostRun: func(cmd *cobra.Command, args []string) {
|
||||
if commandUsesContext(cmd.CommandPath()) {
|
||||
globalEulaPostRun(cmd, p)
|
||||
}
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
origHelpFunc := cmd.HelpFunc()
|
||||
cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
|
||||
if !commandUsesContext(cmd.CommandPath()) {
|
||||
cmd.Flags().MarkHidden("acceptEULA")
|
||||
}
|
||||
origHelpFunc(cmd, args)
|
||||
})
|
||||
accept := ""
|
||||
cmd.PersistentFlags().StringVarP(&accept, "acceptEULA", "a", "", "Accept EULA for qliksense")
|
||||
cmd.Flags().SetInterspersed(false)
|
||||
return cmd
|
||||
}
|
||||
@@ -167,7 +151,9 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||
// add config command
|
||||
configCmd := configCmd(p)
|
||||
cmd.AddCommand(configCmd)
|
||||
/** disabling for now
|
||||
configCmd.AddCommand(configApplyCmd(p))
|
||||
**/
|
||||
configCmd.AddCommand(configViewCmd(p))
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
@@ -198,6 +184,10 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||
|
||||
// open editor for config
|
||||
configCmd.AddCommand(configEditCmd(p))
|
||||
|
||||
// add unset for config
|
||||
configCmd.AddCommand((unsetCmd(p)))
|
||||
|
||||
// add uninstall command
|
||||
cmd.AddCommand(uninstallCmd(p))
|
||||
|
||||
@@ -206,7 +196,7 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||
crdsCmd.AddCommand(crdsViewCmd(p))
|
||||
crdsCmd.AddCommand(crdsInstallCmd(p))
|
||||
|
||||
// add preflight command
|
||||
// add preflight commands
|
||||
preflightCmd := preflightCmd(p)
|
||||
preflightCmd.AddCommand(pfDnsCheckCmd(p))
|
||||
preflightCmd.AddCommand(pfK8sVersionCheckCmd(p))
|
||||
@@ -219,42 +209,26 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||
preflightCmd.AddCommand(pfCreateRoleBindingCheckCmd(p))
|
||||
preflightCmd.AddCommand(pfCreateServiceAccountCheckCmd(p))
|
||||
preflightCmd.AddCommand(pfCreateAuthCheckCmd(p))
|
||||
preflightCmd.AddCommand(pfVerifyCAChainCmd(p))
|
||||
preflightCmd.AddCommand(pfCleanupCmd(p))
|
||||
|
||||
cmd.AddCommand(preflightCmd)
|
||||
cmd.AddCommand(loadCrFile(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
|
||||
}
|
||||
|
||||
func copy(src, dst string) (int64, error) {
|
||||
var (
|
||||
source, destination *os.File
|
||||
sourceFileStat os.FileInfo
|
||||
err error
|
||||
nBytes int64
|
||||
)
|
||||
if sourceFileStat, err = os.Stat(src); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if !sourceFileStat.Mode().IsRegular() {
|
||||
return 0, fmt.Errorf("%s is not a regular file", src)
|
||||
}
|
||||
|
||||
if source, err = os.Open(src); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer source.Close()
|
||||
|
||||
if destination, err = os.Create(dst); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer destination.Close()
|
||||
nBytes, err = io.Copy(destination, source)
|
||||
return nBytes, err
|
||||
}
|
||||
|
||||
func levenstein(cmd *cobra.Command) {
|
||||
cmd.SuggestionsMinimumDistance = 2
|
||||
if len(os.Args) > 1 {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
Preflight checks provide pre-installation cluster conformance testing and validation before we install qliksense on the cluster. We gather a suite of conformance tests that can be easily written and run on the target cluster to verify that cluster-specific requirements are met.
|
||||
|
||||
We support 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:
|
||||
```
|
||||
@@ -29,6 +29,14 @@ Run the following command to cleanup entities created for preflight checks that
|
||||
qliksense preflight clean
|
||||
```
|
||||
|
||||
### qliksense postflight
|
||||
Postflight checks are performed after qliksense is installed on the cluster and during normal operating mode of the product. Such checks can range from validating certain conditions to checking the status of certain operations or entities.
|
||||
|
||||
Run the following command to view help about the commands supported by postflight at any moment:
|
||||
```
|
||||
qliksense postflight
|
||||
```
|
||||
|
||||
### qliksense load
|
||||
|
||||
`qliksense load` command takes input from a file or from pipe
|
||||
@@ -63,10 +71,9 @@ spec:
|
||||
value: "yes"
|
||||
secrets:
|
||||
qliksense:
|
||||
- name: mongoDbUri
|
||||
- name: mongodbUri
|
||||
value: mongodb://qlik-test-mongodb:27017/qliksense?ssl=false
|
||||
profile: docker-desktop
|
||||
rotateKeys: "yes"
|
||||
```
|
||||
|
||||
`qliksense apply` does everything `qliksense load` does but will install Qlik Sense into the cluster as well
|
||||
@@ -104,7 +111,7 @@ spec:
|
||||
value: "yes"
|
||||
secrets:
|
||||
qliksense:
|
||||
- name: mongoDbUri
|
||||
- name: mongodbUri
|
||||
value: "mongo://mongo:3307"
|
||||
- name: messagingPassword
|
||||
valueFromKey: messagingPassword
|
||||
@@ -122,7 +129,6 @@ In this case, the result of `qliksense about` command would display information
|
||||
|
||||
It supports the following flags:
|
||||
|
||||
- `qliksense config apply` - generate the patches and apply manifests to K8s
|
||||
- `qliksense config list-contexts` - get and list contexts
|
||||
- `qliksense config set` - configure a key-value pair into the current context
|
||||
- `qliksense config set-configs` - set configurations into qliksense context as key-value pairs
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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
|
||||
.qliksense
|
||||
@@ -23,9 +23,8 @@ spec:
|
||||
profile: docker-desktop
|
||||
secrets:
|
||||
qliksense:
|
||||
- name: mongoDbUri
|
||||
- name: mongodbUri
|
||||
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
|
||||
rotateKeys: "yes"
|
||||
releaseName: qlik-default
|
||||
```
|
||||
|
||||
@@ -50,7 +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.
|
||||
|
||||
`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
|
||||
- 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>"
|
||||
```
|
||||
|
||||
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
|
||||
- Generate kustomize patches
|
||||
|
||||
@@ -1,53 +1,162 @@
|
||||
# Getting started
|
||||
|
||||
To get familiar with the Qlik Sense on Kubernetes Operator Command Line Interface (CLI), we will install Qlik Sense on Kubernetes on docker desktop. In subsequent sections we will enhance this configuration to include an Identity Provider (keycloak) and demonstrate air gapped capabilities as well.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Kubernetes cluster (Docker Desktop with enabled Kubernetes)
|
||||
- `kubectl` installed, configured and able to communicate with kubernetes cluster. _`qliksense` CLI uses `kubectl` 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
|
||||
|
||||
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
|
||||
curl -Lo qliksense https://github.com/qlik-oss/sense-installer/releases/download/v0.7.0/qliksense-linux-amd64
|
||||
chmod +x qliksense
|
||||
sudo mv qliksense /usr/local/bin
|
||||
# bash
|
||||
|
||||
curl -LOJ https://storage.googleapis.com/kubernetes-release/release/v1.16.8/bin/linux/amd64/kubectl
|
||||
curl -LOJ https://github.com/qlik-oss/sense-installer/releases/latest/download/qliksense-linux-amd64
|
||||
sudo mv qliksense-linux-amd64 kubectl /usr/local/bin
|
||||
sudo chmod ugo+x /usr/local/bin/qliksense-linux-amd64 /usr/local/bin/kubectl
|
||||
sudo ln -s /usr/local/bin/qliksense-linux-amd64 /usr/local/bin/qliksense
|
||||
sudo ln -s /usr/local/bin/qliksense-linux-amd64 /usr/local/bin/kubectl-qliksense
|
||||
```
|
||||
|
||||
??? tldr "MacOS"
|
||||
=== "MacOS"
|
||||
|
||||
``` bash
|
||||
curl -Lo qliksense https://github.com/qlik-oss/sense-installer/releases/download/v0.7.0/qliksense-darwin-amd64
|
||||
chmod +x qliksense
|
||||
sudo mv qliksense /usr/local/bin
|
||||
# bash
|
||||
|
||||
curl -LOJ https://storage.googleapis.com/kubernetes-release/release/v1.16.8/bin/darwin/amd64/kubectl
|
||||
curl -LOJ https://github.com/qlik-oss/sense-installer/releases/latest/download/qliksense-darwin-amd64
|
||||
sudo mv qliksense-darwin-amd64 kubectl /usr/local/bin
|
||||
sudo chmod ugo+x /usr/local/bin/qliksense-darwin-amd64 /usr/local/bin/kubectl
|
||||
sudo ln -s /usr/local/bin/qliksense-darwin-amd64 /usr/local/bin/qliksense
|
||||
sudo ln -s /usr/local/bin/qliksense-darwin-amd64 /usr/local/bin/kubectl-qliksense
|
||||
```
|
||||
|
||||
??? tldr "Windows"
|
||||
Download Windows executable and add it in your `PATH` as `qliksense.exe`
|
||||
=== "Windows"
|
||||
|
||||
[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
|
||||
|
||||
- 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
|
||||
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
|
||||
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
|
||||
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
|
||||
```
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
# 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 CLI facilitates:
|
||||
The Qlik Sense on Kubernetes Operator CLI (`qliksense`) facilitates:
|
||||
|
||||
- Installation of QSEoK
|
||||
- Installation of qliksense operator to manage QSEoK
|
||||
- Installation of Qliksense operator to manage the QSEoK installation
|
||||
- Air gapped installation of QSEoK
|
||||
- Cluster configuration management
|
||||
- Pre-flight and Post-flight environment and configuration checks
|
||||
|
||||
The Qlik Sense on Kubernetes Operator CLI provides an imperative interface to many of the configurations that need to be applied against the declarative structure described in the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository
|
||||
|
||||
To get start quickly go to the [Getting Started page](getting_started.md).
|
||||
|
||||
To learn more about the internal workings of the Qlik Sense on Kubernetes Operator, go to [How CLI works](concepts.md).
|
||||
|
||||
!!! info ""
|
||||
This is a technology preview that uses Qlik modified [kustomize](https://github.com/qlik-oss/kustomize) for Kubernetes manifests on [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository
|
||||
|
||||
|
||||
!!! info ""
|
||||
See QlikSense [edge releases on qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s/releases) repository
|
||||
|
||||
51
docs/postflight_checks.md
Normal file
51
docs/postflight_checks.md
Normal 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
|
||||
```
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Preflight checks provide pre-installation cluster conformance testing and validation before we install qliksense on the cluster. We gather a suite of conformance tests that can be easily written and run on the target cluster to verify that cluster-specific requirements are met.
|
||||
|
||||
We support 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:
|
||||
```shell
|
||||
@@ -16,9 +16,18 @@ Examples:
|
||||
qliksense preflight <preflight_check_to_run>
|
||||
|
||||
Available Commands:
|
||||
all perform all checks
|
||||
dns perform preflight dns check
|
||||
k8s-version check k8s version
|
||||
all perform all checks
|
||||
authcheck preflight authcheck
|
||||
clean perform preflight clean
|
||||
deployment perform preflight deployment check
|
||||
dns perform preflight dns check
|
||||
k8s-version check kubernetes version
|
||||
mongo preflight mongo OR preflight mongo --url=<url>
|
||||
pod perform preflight pod check
|
||||
role preflight create role check
|
||||
rolebinding preflight create rolebinding check
|
||||
service perform preflight service check
|
||||
serviceaccount preflight create ServiceAccount check
|
||||
|
||||
Flags:
|
||||
-h, --help help for preflight
|
||||
@@ -200,8 +209,8 @@ We can check if we are able to connect to an instance of mongodb on the cluster
|
||||
qliksense preflight mongo --url=<url> -v OR
|
||||
qliksense preflight mongo -v
|
||||
qliksense preflight mongo --url=<mongo-server url> --ca-cert=<path to ca-cert file> -v
|
||||
|
||||
|
||||
```
|
||||
```shell
|
||||
Preflight mongo check
|
||||
---------------------
|
||||
Preflight mongodb check:
|
||||
@@ -217,7 +226,29 @@ Deleted pod: pf-mongo-pod
|
||||
Completed preflight mongodb check
|
||||
```
|
||||
|
||||
#### Mongodb check with mutual tls
|
||||
In order to perform mutual tls with mongo we need to:
|
||||
- append client certificate to the beginning/end of CA certificate. Make sure to include the beginning and end tags on each certificate.
|
||||
The CA certificate file should look like this in the end:
|
||||
```shell
|
||||
<existing contents of CA cert>
|
||||
...
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
<private key>
|
||||
-----END RSA PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
<public key>
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
- Run the command below to set the ca certificate into the CR
|
||||
```shell
|
||||
cat <path_to_ca.crt> | base64 | qliksense config set-secrets qliksense.caCertificates --base64
|
||||
```
|
||||
|
||||
Next, run:
|
||||
```shell
|
||||
qliksense preflight mongo -v
|
||||
```
|
||||
|
||||
### Running all checks
|
||||
Run the command shown below to execute all preflight checks.
|
||||
@@ -274,3 +305,21 @@ 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
|
||||
```
|
||||
16
go.mod
16
go.mod
@@ -10,7 +10,7 @@ replace (
|
||||
k8s.io/client-go => k8s.io/client-go v0.17.0
|
||||
k8s.io/kubectl => k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd
|
||||
|
||||
sigs.k8s.io/kustomize/api => github.com/qlik-oss/kustomize/api v0.3.3-0.20200424070349-b0312eb71568
|
||||
sigs.k8s.io/kustomize/api => github.com/qlik-oss/kustomize/api v0.5.2-0.20200820111149-1a59db58525f
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -22,7 +22,7 @@ require (
|
||||
github.com/bugsnag/bugsnag-go v1.5.3 // indirect
|
||||
github.com/containers/image/v5 v5.1.0
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/go-git/go-git/v5 v5.0.0
|
||||
github.com/go-git/go-git/v5 v5.1.0
|
||||
github.com/gobuffalo/envy v1.9.0 // indirect
|
||||
github.com/gobuffalo/logger v1.0.3 // indirect
|
||||
github.com/gobuffalo/packd v1.0.0 // indirect
|
||||
@@ -40,22 +40,24 @@ require (
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/otiai10/copy v1.1.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/qlik-oss/k-apis v0.1.2
|
||||
github.com/qlik-oss/k-apis v0.1.16
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/rogpeppe/go-internal v1.5.2 // indirect
|
||||
github.com/spf13/cobra v0.0.6
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/viper v1.6.1
|
||||
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 // indirect
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a // indirect
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
|
||||
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b
|
||||
golang.org/x/tools v0.0.0-20200312194400-c312e98713c2 // indirect
|
||||
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
k8s.io/api v0.17.2
|
||||
k8s.io/apiextensions-apiserver v0.17.2
|
||||
k8s.io/apimachinery v0.17.2
|
||||
k8s.io/client-go v11.0.0+incompatible
|
||||
k8s.io/kubectl v0.17.2
|
||||
sigs.k8s.io/kustomize/api v0.3.2
|
||||
sigs.k8s.io/yaml v1.1.0
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
)
|
||||
|
||||
exclude github.com/Azure/go-autorest v12.0.0+incompatible
|
||||
|
||||
64
go.sum
64
go.sum
@@ -32,6 +32,7 @@ contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcig
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774 h1:SCbEWT58NSt7d2mcFdvxC9uyrdcTfvBbPLThhkDmXzg=
|
||||
github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774/go.mod h1:6/0dYRLLXyJjbkIPeeGyoJ/eKOSI0eU6eTlCBYibgd0=
|
||||
github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE=
|
||||
github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuEd+YuKoUiazDC/N96FiDEU=
|
||||
github.com/Azure/azure-pipeline-go v0.2.1 h1:OLBdZJ3yvOn2MezlWvbrBMTEUQC72zAftRZOMdj5HYo=
|
||||
github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
|
||||
@@ -84,6 +85,7 @@ github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEY
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
|
||||
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
|
||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
@@ -106,6 +108,7 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6 h1:bZ28Hqta7TFAK3Q08CMvv8y3/8ATaEqv2nGoc6yff6c=
|
||||
github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U=
|
||||
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
@@ -123,6 +126,7 @@ github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06
|
||||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||
github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||
github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
|
||||
github.com/aws/aws-sdk-go v1.17.4/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.19.45/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
@@ -161,6 +165,7 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=
|
||||
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
@@ -258,6 +263,7 @@ github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:Htrtb
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad h1:Qk76DOWdOp+GlyDKBAG3Klr9cn7N+LcYc82AZ2S7+cA=
|
||||
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad/go.mod h1:mPKfmRa823oBIgl2r20LeMSpTAteW5j7FLkc0vjmzyQ=
|
||||
github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e/go.mod h1:CgNC6SGbT+Xb8wGGvzilttZL1mc5sQ/5KkcxsZttMIk=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e h1:p1yVGRW3nmb85p1Sh1ZJSDm4A4iKLS5QNbvUHMgGu/M=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
@@ -298,8 +304,11 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
|
||||
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
|
||||
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
|
||||
github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
|
||||
@@ -308,6 +317,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/v5 v5.0.0 h1:k5RWPm4iJwYtfWoxIJy4wJX9ON7ihPeZZYC1fLYDnpg=
|
||||
github.com/go-git/go-git/v5 v5.0.0/go.mod h1:oYD8y9kWsGINPFJoLdaScGCN6dlKg23blmClfZwtUVA=
|
||||
github.com/go-git/go-git/v5 v5.1.0 h1:HxJn9g/E7eYvKW3Fm7Jt4ee8LXfPOm/H1cdDu8vEssk=
|
||||
github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
@@ -353,10 +364,13 @@ github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcs
|
||||
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
|
||||
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/spec v0.19.5 h1:Xm0Ao53uqnk9QE/LlYV5DEU09UAgpliA85QoT9LzqPw=
|
||||
github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
|
||||
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||
github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||
github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
|
||||
github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
|
||||
github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
|
||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
@@ -366,8 +380,10 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
|
||||
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
|
||||
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
|
||||
github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
|
||||
github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
|
||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
|
||||
@@ -469,10 +485,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.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-replayers/grpcreplay v0.1.0 h1:eNb1y9rZFmY4ax45uEEECSa8fsxGRU+8Bil52ASAwic=
|
||||
github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
|
||||
github.com/google/go-replayers/httpreplay v0.1.0 h1:AX7FUb4BjrrzNvblr/OlgwrmFiep6soj5K2QSDW7BGk=
|
||||
@@ -551,6 +563,8 @@ github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVo
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-getter v1.4.1 h1:3A2Mh8smGFcf5M+gmcv898mZdrxpseik45IpcyISLsA=
|
||||
github.com/hashicorp/go-getter v1.4.1/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY=
|
||||
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
||||
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
@@ -622,6 +636,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.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
|
||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
|
||||
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
@@ -719,6 +735,7 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
||||
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k=
|
||||
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI=
|
||||
@@ -769,6 +786,7 @@ github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lN
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
||||
@@ -826,6 +844,7 @@ github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/paulmach/orb v0.1.3/go.mod h1:VFlX/8C+IQ1p6FTRRKzKoOPJnvEtA5G0Veuqwbu//Vk=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
@@ -883,12 +902,14 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
|
||||
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
|
||||
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/qlik-oss/k-apis v0.1.1 h1:aZ4eTMB3mSn03Kuj7+RI0eFLkjK9+0qxADBioRb3qVA=
|
||||
github.com/qlik-oss/k-apis v0.1.1/go.mod h1:yoYGgPJ/H0t9H3NSq64dWfyQY6QWi2L9c+hCJoVO03U=
|
||||
github.com/qlik-oss/k-apis v0.1.2 h1:BBcrXl+NxdsvuRsZuJbvIFxMv5QIXqWBzhXOcr5KUX8=
|
||||
github.com/qlik-oss/k-apis v0.1.2/go.mod h1:yoYGgPJ/H0t9H3NSq64dWfyQY6QWi2L9c+hCJoVO03U=
|
||||
github.com/qlik-oss/kustomize/api v0.3.3-0.20200424070349-b0312eb71568 h1:wHOUCGfnmgYqW3aCjuP3fXmB2T/uZXMvltO+F3us83E=
|
||||
github.com/qlik-oss/kustomize/api v0.3.3-0.20200424070349-b0312eb71568/go.mod h1:Yg8bqX8Mq/eSgXfcenxCxhZuSXg+NCsKq6NBdch/oUc=
|
||||
github.com/qlik-oss/k-apis v0.1.16 h1:R3gCZs4A3EHPNx4B7p1idWD+OhyaU/bAlGYBWc0ZNz4=
|
||||
github.com/qlik-oss/k-apis v0.1.16/go.mod h1:AkNa/kaZHpGVs9l+pHe6nvz99Sp9WO1f9ylBES95o+I=
|
||||
github.com/qlik-oss/kustomize/api v0.3.3-0.20200612023448-4c1f2f38ea9b h1:RDh3OZJOriy/ap1NUHVKsPG07N4DALaCzaqXFFK57T0=
|
||||
github.com/qlik-oss/kustomize/api v0.3.3-0.20200612023448-4c1f2f38ea9b/go.mod h1:zh3yFgE5zFk1kreqzVyyj1eXyIxQJT53l4zSg8Wt4SA=
|
||||
github.com/qlik-oss/kustomize/api v0.5.2-0.20200820111149-1a59db58525f h1:a6TNAGEyKqlMzDQwwoVwBjcRZ47ZnXxk3ue+zy5I4Qk=
|
||||
github.com/qlik-oss/kustomize/api v0.5.2-0.20200820111149-1a59db58525f/go.mod h1:H7m776WsaBG9jFljChQXd2bGlzTJvQRHDESRQ96psXU=
|
||||
github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d h1:K6eOUihrFLdZjZnA4XlRp864fmWXv9YTIk7VPLhRacA=
|
||||
github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4domFU579Ga6E61sB9VFNaniPVwJP5C4bBCu3wA=
|
||||
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
|
||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
|
||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
|
||||
@@ -959,6 +980,8 @@ github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v0.0.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs=
|
||||
github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
@@ -979,6 +1002,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.3-0.20181224173747-660f15d67dbb/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
@@ -1033,6 +1057,8 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofm
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
|
||||
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI=
|
||||
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yujunz/go-getter v1.4.1-lite h1:FhvNc94AXMZkfqUwfMKhnQEC9phkphSGdPTL7tIdhOM=
|
||||
github.com/yujunz/go-getter v1.4.1-lite/go.mod h1:sbmqxXjyLunH1PkF3n7zSlnVeMvmYUuIl9ZVs/7NyCc=
|
||||
@@ -1051,6 +1077,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.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.mongodb.org/mongo-driver v1.1.2 h1:jxcFYjlkl8xaERsgLo+RNquI0epW6zuy/ZRQs6jnrFA=
|
||||
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
@@ -1058,6 +1085,9 @@ go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.starlark.net v0.0.0-20190528202925-30ae18b8564f/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg=
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc=
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
@@ -1129,6 +1159,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
|
||||
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1165,12 +1196,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-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b h1:IYiJPiJfzktmDAO1HQiwjMjwjlYKHAL7KzeD544RJPs=
|
||||
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1341,6 +1372,7 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
@@ -1373,8 +1405,12 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20190924164351-c8b7dadae555 h1:4Yrwvx9yMvZx+vK3wdX7aX2UCNZJJn0TDc+BNOJTE00=
|
||||
gopkg.in/yaml.v3 v3.0.0-20190924164351-c8b7dadae555/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 h1:XZx7nhd5GMaZpmDaEHFVafUZC7ya0fuo7cSJ3UCKYmM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
gotest.tools/v3 v3.0.0 h1:d+tVGRu6X0ZBQ+kyAR8JKi6AXhTP2gmQaoIYaGFz634=
|
||||
@@ -1436,9 +1472,13 @@ rsc.io/letsencrypt v0.0.3 h1:H7xDfhkaFFSYEJlKeq38RwX2jYcnTeHuDQyT+mMNMwM=
|
||||
rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY=
|
||||
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
|
||||
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
|
||||
sigs.k8s.io/kustomize/kyaml v0.4.1 h1:NEqA/35upoAjb+I5vh1ODUqxoX4DOrezeQa9BhhG5Co=
|
||||
sigs.k8s.io/kustomize/kyaml v0.4.1/go.mod h1:XJL84E6sOFeNrQ7CADiemc1B0EjIxHo3OhW4o1aJYNw=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
site_name: Qlik Sense on Kubernetes CLI
|
||||
repo_url: 'https://github.com/qlik-oss/sense-installer'
|
||||
strict: true
|
||||
|
||||
theme:
|
||||
name: "material"
|
||||
palette:
|
||||
primary: 'green'
|
||||
accent: 'indigo'
|
||||
|
||||
markdown_extensions:
|
||||
- toc:
|
||||
permalink: true
|
||||
@@ -14,6 +16,8 @@ markdown_extensions:
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.superfences
|
||||
- pymdownx.details
|
||||
- pymdownx.tabbed
|
||||
|
||||
nav:
|
||||
- Overview: index.md
|
||||
- getting_started.md
|
||||
|
||||
@@ -144,17 +144,17 @@ func (cr *QliksenseCR) IsRepoExist() bool {
|
||||
}
|
||||
|
||||
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 cr.Spec.FetchSource.Repository
|
||||
return cr.Spec.Git.Repository
|
||||
}
|
||||
|
||||
func (cr *QliksenseCR) GetFetchAccessToken(encryptionKey string) string {
|
||||
if cr.Spec.FetchSource == nil {
|
||||
if cr.Spec.Git == nil {
|
||||
return ""
|
||||
}
|
||||
if tok, err := cr.Spec.FetchSource.GetAccessToken(); err != nil {
|
||||
if tok, err := cr.Spec.Git.GetAccessToken(); err != nil {
|
||||
fmt.Println(err)
|
||||
return ""
|
||||
} else if tok == "" {
|
||||
@@ -171,29 +171,29 @@ func (cr *QliksenseCR) GetFetchAccessToken(encryptionKey string) string {
|
||||
}
|
||||
|
||||
func (cr *QliksenseCR) SetFetchUrl(url string) {
|
||||
if cr.Spec.FetchSource == nil {
|
||||
cr.Spec.FetchSource = &config.Repo{}
|
||||
if cr.Spec.Git == nil {
|
||||
cr.Spec.Git = &config.Repo{}
|
||||
}
|
||||
cr.Spec.FetchSource.Repository = url
|
||||
cr.Spec.Git.Repository = url
|
||||
}
|
||||
|
||||
func (cr *QliksenseCR) SetFetchAccessToken(token, encryptionKey string) error {
|
||||
if cr.Spec.FetchSource == nil {
|
||||
cr.Spec.FetchSource = &config.Repo{}
|
||||
if cr.Spec.Git == nil {
|
||||
cr.Spec.Git = &config.Repo{}
|
||||
}
|
||||
res, err := EncryptData([]byte(token), encryptionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cr.Spec.FetchSource.AccessToken = b64.StdEncoding.EncodeToString(res)
|
||||
cr.Spec.Git.AccessToken = b64.StdEncoding.EncodeToString(res)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cr *QliksenseCR) SetFetchAccessSecretName(sec string) {
|
||||
if cr.Spec.FetchSource == nil {
|
||||
cr.Spec.FetchSource = &config.Repo{}
|
||||
if cr.Spec.Git == nil {
|
||||
cr.Spec.Git = &config.Repo{}
|
||||
}
|
||||
cr.Spec.FetchSource.SecretName = sec
|
||||
cr.Spec.Git.SecretName = sec
|
||||
}
|
||||
|
||||
//DeleteRepo delete the manifest repo and unset manifestsRoot
|
||||
@@ -524,9 +524,9 @@ func (qc *QliksenseConfig) GetDecryptedCr(cr *QliksenseCR) (*QliksenseCR, error)
|
||||
}
|
||||
newCr.Spec.Secrets = finalSecrets
|
||||
|
||||
if newCr.Spec.FetchSource != nil && newCr.Spec.FetchSource.AccessToken != "" {
|
||||
if newCr.Spec.Git != nil && newCr.Spec.Git.AccessToken != "" {
|
||||
decData := cr.GetFetchAccessToken(encryptionKey)
|
||||
newCr.Spec.FetchSource.AccessToken = decData
|
||||
newCr.Spec.Git.AccessToken = decData
|
||||
}
|
||||
return newCr, nil
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ func TestGetDecryptedCr(t *testing.T) {
|
||||
key, _ := setupGenerateKey(dir)
|
||||
ecn, _ := EncryptData([]byte("mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"), key)
|
||||
b := b64.StdEncoding.EncodeToString(ecn)
|
||||
qcr.Spec.AddToSecrets("qliksense", "mongoDbUri", b, "")
|
||||
qcr.Spec.AddToSecrets("qliksense", "mongodbUri", b, "")
|
||||
|
||||
qcr.SetFetchAccessToken("mytoken", key)
|
||||
|
||||
@@ -117,8 +117,8 @@ func TestGetDecryptedCr(t *testing.T) {
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
decryptedValue := newCr.Spec.GetFromSecrets("qliksense", "mongoDbUri")
|
||||
orignalValue := qcr.Spec.GetFromSecrets("qliksense", "mongoDbUri")
|
||||
decryptedValue := newCr.Spec.GetFromSecrets("qliksense", "mongodbUri")
|
||||
orignalValue := qcr.Spec.GetFromSecrets("qliksense", "mongodbUri")
|
||||
if decryptedValue != "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false" {
|
||||
t.Fail()
|
||||
b, _ := K8sToYaml(newCr)
|
||||
@@ -127,7 +127,7 @@ func TestGetDecryptedCr(t *testing.T) {
|
||||
if decryptedValue == orignalValue {
|
||||
t.Fail()
|
||||
}
|
||||
if newCr.Spec.FetchSource.AccessToken != "mytoken" {
|
||||
if newCr.Spec.Git.AccessToken != "mytoken" {
|
||||
t.Fail()
|
||||
}
|
||||
td()
|
||||
|
||||
727
pkg/api/clientgo_utils.go
Normal file
727
pkg/api/clientgo_utils.go
Normal 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
|
||||
}
|
||||
1950
pkg/api/clientgo_utils_test.go
Normal file
1950
pkg/api/clientgo_utils_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/qlik-oss/k-apis/pkg/config"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -22,10 +23,9 @@ const (
|
||||
QliksenseKind = "Qliksense"
|
||||
QliksenseGroup = "qlik.com"
|
||||
QliksenseDefaultProfile = "docker-desktop"
|
||||
DefaultRotateKeys = "yes"
|
||||
QliksenseMetadataName = "QliksenseConfigMetadata"
|
||||
DefaultMongoDbUri = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
|
||||
DefaultMongoDbUriKey = "mongoDbUri"
|
||||
DefaultMongodbUri = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
|
||||
DefaultMongodbUriKey = "mongodbUri"
|
||||
)
|
||||
|
||||
// AddCommonConfig adds common configs into CRs
|
||||
@@ -37,10 +37,9 @@ func (qliksenseCR *QliksenseCR) AddCommonConfig(contextName string) {
|
||||
})
|
||||
qliksenseCR.SetName(contextName)
|
||||
qliksenseCR.Spec = &config.CRSpec{
|
||||
Profile: QliksenseDefaultProfile,
|
||||
RotateKeys: DefaultRotateKeys,
|
||||
Profile: QliksenseDefaultProfile,
|
||||
}
|
||||
qliksenseCR.Spec.AddToSecrets("qliksense", DefaultMongoDbUriKey, DefaultMongoDbUri, "")
|
||||
qliksenseCR.Spec.AddToSecrets("qliksense", DefaultMongodbUriKey, strings.Replace(DefaultMongodbUri, "qlik-default", contextName, 1), "")
|
||||
}
|
||||
|
||||
// AddBaseQliksenseConfigs adds configs into config.yaml
|
||||
|
||||
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/qlik-oss/k-apis/pkg/config"
|
||||
@@ -22,12 +23,11 @@ func TestAddCommonConfig(t *testing.T) {
|
||||
q.SetName("myqliksense")
|
||||
q.SetGroupVersionKind(gvk)
|
||||
q.Spec = &config.CRSpec{
|
||||
Profile: QliksenseDefaultProfile,
|
||||
RotateKeys: DefaultRotateKeys,
|
||||
Profile: QliksenseDefaultProfile,
|
||||
Secrets: map[string]config.NameValues{
|
||||
"qliksense": []config.NameValue{{
|
||||
Name: DefaultMongoDbUriKey,
|
||||
Value: DefaultMongoDbUri,
|
||||
Name: DefaultMongodbUriKey,
|
||||
Value: strings.Replace(DefaultMongodbUri, "qlik-default", "myqliksense", 1),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -69,7 +70,7 @@ func TestCopyDirectory_withGit_withKuz(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -78,9 +79,15 @@ func TestCopyDirectory_withGit_withKuz(t *testing.T) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if string(repo2Manifest) != string(repo1Manifest) {
|
||||
t.Logf("manifest generated on the original config:\n%v", string(repo1Manifest))
|
||||
t.Logf("manifest generated on the copied config:\n%v", string(repo2Manifest))
|
||||
re, err := regexp.Compile(`name: qliksense-ca-certificates-[a-z]{5}`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
repo1ManifestTweaked := re.ReplaceAllString(string(repo1Manifest), "name: qliksense-ca-certificates")
|
||||
repo2ManifestTweaked := re.ReplaceAllString(string(repo2Manifest), "name: qliksense-ca-certificates")
|
||||
if repo2ManifestTweaked != repo1ManifestTweaked {
|
||||
t.Logf("manifest generated on the original config:\n%v", repo1ManifestTweaked)
|
||||
t.Logf("manifest generated on the copied config:\n%v", repo2ManifestTweaked)
|
||||
t.Fatal("expected manifests to be equal, but they were not")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// KubectlApply create resoruces in the provided namespace,
|
||||
// KubectlApply create resources in the provided namespace,
|
||||
// if namespace="" then use whatever the kubectl default is
|
||||
func KubectlApply(manifests, namespace string) error {
|
||||
return kubectlOperation(manifests, "apply", namespace)
|
||||
@@ -19,7 +19,7 @@ func KubectlApplyVerbose(manifests, namespace string, verbose bool) error {
|
||||
return kubectlOperationVerbose(manifests, "apply", namespace, verbose)
|
||||
}
|
||||
|
||||
// KubectlDelete delete resoruces in the provided namespace,
|
||||
// KubectlDelete delete resources in the provided namespace,
|
||||
// if namespace="" then use whatever the kubectl default is
|
||||
func KubectlDelete(manifests, namespace string) error {
|
||||
return kubectlOperation(manifests, "delete", namespace)
|
||||
|
||||
@@ -131,8 +131,8 @@ func (p *PreflightConfig) Initialize() error {
|
||||
}
|
||||
p.AddMinK8sV("1.15")
|
||||
p.AddMinMongoV("3.6")
|
||||
p.AddImage("nginx", "nginx")
|
||||
p.AddImage("netcat", "subfuzion/netcat")
|
||||
p.AddImage("mongo", "mongo")
|
||||
p.AddImage("nginx", "nginx:1.19.0-alpine")
|
||||
p.AddImage("netcat", "qlik-docker-oss.bintray.io/preflight-netcat:v1.0.0")
|
||||
p.AddImage("preflight-mongo", "qlik-docker-oss.bintray.io/preflight-mongo:v1.0.0")
|
||||
return p.Write()
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -78,8 +79,9 @@ func ProcessConfigArgs(args []string, base64Encoded bool) ([]*ServiceKeyValue, e
|
||||
if len(first) != 2 {
|
||||
return nil, notValidErr
|
||||
}
|
||||
second := strings.SplitN(first[0], ".", 2)
|
||||
if len(second) != 2 {
|
||||
|
||||
svcKey := getSvcAndKey(first[0])
|
||||
if len(svcKey) != 2 {
|
||||
return nil, notValidErr
|
||||
}
|
||||
resultValue := strings.Trim(first[1], "\"")
|
||||
@@ -91,14 +93,33 @@ func ProcessConfigArgs(args []string, base64Encoded bool) ([]*ServiceKeyValue, e
|
||||
}
|
||||
}
|
||||
resultSvcKV[i] = &ServiceKeyValue{
|
||||
SvcName: second[0],
|
||||
Key: second[1],
|
||||
SvcName: svcKey[0],
|
||||
Key: svcKey[1],
|
||||
Value: resultValue,
|
||||
}
|
||||
}
|
||||
return resultSvcKV, nil
|
||||
}
|
||||
|
||||
// input should be svc[key]
|
||||
func getSvcAndKey(arg string) []string {
|
||||
// for key
|
||||
re := regexp.MustCompile(`\[(.*)\]`)
|
||||
// for service
|
||||
re2 := regexp.MustCompile(`(.*)\[`)
|
||||
|
||||
keys := re.FindStringSubmatch(arg)
|
||||
|
||||
svcs := re2.FindStringSubmatch(arg)
|
||||
if len(svcs) != 2 || len(keys) != 2 {
|
||||
return strings.SplitN(arg, ".", 2)
|
||||
}
|
||||
if svcs[1] == "" || keys[1] == "" {
|
||||
return []string{}
|
||||
}
|
||||
return []string{svcs[1], keys[1]}
|
||||
}
|
||||
|
||||
func ExecuteTaskWithBlinkingStdoutFeedback(task func() (interface{}, error), feedback string) (result interface{}, err error) {
|
||||
taskDone := make(chan bool)
|
||||
go func() {
|
||||
|
||||
@@ -45,3 +45,24 @@ func contains(arr []string, str string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestGetSvcAndKey(t *testing.T) {
|
||||
s1 := "qliksense[tls.cert]"
|
||||
sa := getSvcAndKey(s1)
|
||||
if sa[0] != "qliksense" || sa[1] != "tls.cert" {
|
||||
t.Fail()
|
||||
t.Logf("expected service: qliksense but got %s", sa[0])
|
||||
t.Logf("expected key: tls.cert but got %s", sa[1])
|
||||
}
|
||||
s1 = "qliksense-idps.tls"
|
||||
sa = getSvcAndKey(s1)
|
||||
for _, s := range sa {
|
||||
t.Logf("|%s|", s)
|
||||
}
|
||||
if sa[0] != "qliksense-idps" || sa[1] != "tls" {
|
||||
t.Fail()
|
||||
t.Logf("expected service: qliksense-idps but got %s", sa[0])
|
||||
t.Logf("expected key: tls but got %s", sa[1])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
31
pkg/postflight/all_postflight_checks.go
Normal file
31
pkg/postflight/all_postflight_checks.go
Normal 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")
|
||||
}
|
||||
74
pkg/postflight/db_migration_check.go
Normal file
74
pkg/postflight/db_migration_check.go
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
16
pkg/postflight/postflight_utils.go
Normal file
16
pkg/postflight/postflight_utils.go
Normal 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
|
||||
}
|
||||
@@ -15,90 +15,100 @@ func (qp *QliksensePreflight) RunAllPreflightChecks(kubeConfigContents []byte, n
|
||||
out := ansi.NewColorableStdout()
|
||||
// Preflight minimum kuberenetes version check
|
||||
if err := qp.CheckK8sVersion(namespace, kubeConfigContents); err != nil {
|
||||
fmt.Fprintf(out, "%s\n", Red("Preflight kubernetes minimum version check FAILED"))
|
||||
fmt.Fprintf(out, "%s\n", Red("FAILED"))
|
||||
fmt.Printf("Error: %v\n\n", err)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%s\n\n", Green("Preflight kubernetes minimum version check PASSED"))
|
||||
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
|
||||
checkCount++
|
||||
}
|
||||
totalCount++
|
||||
|
||||
// Preflight deployment check
|
||||
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)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%s\n\n", Green("Preflight deployment check PASSED"))
|
||||
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
|
||||
checkCount++
|
||||
}
|
||||
totalCount++
|
||||
|
||||
// Preflight service check
|
||||
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)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%s\n\n", Green("Preflight service check PASSED"))
|
||||
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
|
||||
checkCount++
|
||||
}
|
||||
totalCount++
|
||||
|
||||
// Preflight pod check
|
||||
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)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%s\n\n", Green("Preflight pod check PASSED"))
|
||||
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
|
||||
checkCount++
|
||||
}
|
||||
totalCount++
|
||||
|
||||
// Preflight role check
|
||||
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)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%s\n\n", Green("Preflight role check PASSED"))
|
||||
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
|
||||
checkCount++
|
||||
}
|
||||
totalCount++
|
||||
|
||||
// Preflight rolebinding check
|
||||
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)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%s\n\n", Green("Preflight rolebinding check PASSED"))
|
||||
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
|
||||
checkCount++
|
||||
}
|
||||
totalCount++
|
||||
|
||||
// Preflight serviceaccount check
|
||||
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)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%s\n\n", Green("Preflight serviceaccount check PASSED"))
|
||||
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
|
||||
checkCount++
|
||||
}
|
||||
totalCount++
|
||||
|
||||
// Preflight mongo check
|
||||
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)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%s\n\n", Green("Preflight mongo check PASSED"))
|
||||
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
|
||||
checkCount++
|
||||
}
|
||||
totalCount++
|
||||
|
||||
// Preflight DNS check
|
||||
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)
|
||||
} 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++
|
||||
}
|
||||
totalCount++
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
func (qp *QliksensePreflight) CheckDeployment(namespace string, kubeConfigContents []byte, cleanup bool) error {
|
||||
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
|
||||
func (p *QliksensePreflight) CheckDeployment(namespace string, kubeConfigContents []byte, cleanup bool) error {
|
||||
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Kube config error: %v\n", err)
|
||||
return err
|
||||
@@ -15,142 +15,142 @@ func (qp *QliksensePreflight) CheckDeployment(namespace string, kubeConfigConten
|
||||
|
||||
// Deployment check
|
||||
if !cleanup {
|
||||
qp.P.LogVerboseMessage("Preflight deployment check: \n")
|
||||
qp.P.LogVerboseMessage("--------------------------- \n")
|
||||
fmt.Print("Preflight deployment check... ")
|
||||
p.CG.LogVerboseMessage("\n--------------------------- \n")
|
||||
}
|
||||
err = qp.checkPfDeployment(clientset, namespace, cleanup)
|
||||
err = p.checkPfDeployment(clientset, namespace, cleanup)
|
||||
if err != nil {
|
||||
qp.P.LogVerboseMessage("Preflight Deployment check: FAILED\n")
|
||||
p.CG.LogVerboseMessage("Preflight Deployment check: FAILED\n")
|
||||
return err
|
||||
}
|
||||
if !cleanup {
|
||||
qp.P.LogVerboseMessage("Completed preflight deployment check\n")
|
||||
p.CG.LogVerboseMessage("Completed preflight deployment check\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) CheckService(namespace string, kubeConfigContents []byte, cleanup bool) error {
|
||||
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
|
||||
func (p *QliksensePreflight) CheckService(namespace string, kubeConfigContents []byte, cleanup bool) error {
|
||||
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to create a kubernetes client: %v\n", err)
|
||||
return err
|
||||
}
|
||||
// Service check
|
||||
if !cleanup {
|
||||
qp.P.LogVerboseMessage("Preflight service check: \n")
|
||||
qp.P.LogVerboseMessage("------------------------ \n")
|
||||
fmt.Print("Preflight service check... ")
|
||||
p.CG.LogVerboseMessage("\n------------------------ \n")
|
||||
}
|
||||
err = qp.checkPfService(clientset, namespace, cleanup)
|
||||
err = p.checkPfService(clientset, namespace, cleanup)
|
||||
if err != nil {
|
||||
qp.P.LogVerboseMessage("Preflight Service check: FAILED\n")
|
||||
p.CG.LogVerboseMessage("Preflight Service check: FAILED\n")
|
||||
return err
|
||||
}
|
||||
|
||||
if !cleanup {
|
||||
qp.P.LogVerboseMessage("Completed preflight service check\n")
|
||||
p.CG.LogVerboseMessage("Completed preflight service check\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) CheckPod(namespace string, kubeConfigContents []byte, cleanup bool) error {
|
||||
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
|
||||
func (p *QliksensePreflight) CheckPod(namespace string, kubeConfigContents []byte, cleanup bool) error {
|
||||
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error: unable to create a kubernetes client: %v\n", err)
|
||||
return err
|
||||
}
|
||||
// Pod check
|
||||
if !cleanup {
|
||||
qp.P.LogVerboseMessage("Preflight pod check: \n")
|
||||
qp.P.LogVerboseMessage("-------------------- \n")
|
||||
fmt.Print("Preflight pod check... ")
|
||||
p.CG.LogVerboseMessage("\n-------------------- \n")
|
||||
}
|
||||
err = qp.checkPfPod(clientset, namespace, cleanup)
|
||||
err = p.checkPfPod(clientset, namespace, cleanup)
|
||||
if err != nil {
|
||||
qp.P.LogVerboseMessage("Preflight Pod check: FAILED\n")
|
||||
p.CG.LogVerboseMessage("Preflight Pod check: FAILED\n")
|
||||
return err
|
||||
}
|
||||
if !cleanup {
|
||||
qp.P.LogVerboseMessage("Completed preflight pod check\n")
|
||||
p.CG.LogVerboseMessage("Completed preflight pod check\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) checkPfPod(clientset *kubernetes.Clientset, namespace string, cleanup bool) error {
|
||||
func (p *QliksensePreflight) checkPfPod(clientset kubernetes.Interface, namespace string, cleanup bool) error {
|
||||
// delete the pod we are going to create, if it already exists in the cluster
|
||||
podName := "pod-pf-check"
|
||||
qp.deletePod(clientset, namespace, podName)
|
||||
p.CG.DeletePod(clientset, namespace, podName)
|
||||
if cleanup {
|
||||
return nil
|
||||
}
|
||||
commandToRun := []string{}
|
||||
imageName, err := qp.GetPreflightConfigObj().GetImageName(nginx, true)
|
||||
imageName, err := p.GetPreflightConfigObj().GetImageName(nginx, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// create a pod
|
||||
pod, err := qp.createPreflightTestPod(clientset, namespace, podName, imageName, nil, commandToRun)
|
||||
pod, err := p.CG.CreatePreflightTestPod(clientset, namespace, podName, imageName, nil, commandToRun)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to create pod - %v\n", 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
|
||||
}
|
||||
|
||||
qp.P.LogVerboseMessage("Preflight pod creation check: PASSED\n")
|
||||
qp.P.LogVerboseMessage("Cleaning up resources...\n")
|
||||
p.CG.LogVerboseMessage("Preflight pod creation check: PASSED\n")
|
||||
p.CG.LogVerboseMessage("Cleaning up resources...\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) checkPfService(clientset *kubernetes.Clientset, namespace string, cleanup bool) error {
|
||||
func (p *QliksensePreflight) checkPfService(clientset kubernetes.Interface, namespace string, cleanup bool) error {
|
||||
// delete the service we are going to create, if it already exists in the cluster
|
||||
serviceName := "svc-pf-check"
|
||||
qp.deleteService(clientset, namespace, serviceName)
|
||||
p.CG.DeleteService(clientset, namespace, serviceName)
|
||||
if cleanup {
|
||||
return nil
|
||||
}
|
||||
// creating service
|
||||
pfService, err := qp.createPreflightTestService(clientset, namespace, serviceName)
|
||||
pfService, err := p.CG.CreatePreflightTestService(clientset, namespace, serviceName)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to create service - %v\n", err)
|
||||
return err
|
||||
}
|
||||
defer qp.deleteService(clientset, namespace, serviceName)
|
||||
_, err = getService(clientset, namespace, pfService.GetName())
|
||||
defer p.CG.DeleteService(clientset, namespace, serviceName)
|
||||
_, err = p.CG.GetService(clientset, namespace, pfService.GetName())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to retrieve service - %v\n", err)
|
||||
return err
|
||||
}
|
||||
qp.P.LogVerboseMessage("Preflight service creation check: PASSED\n")
|
||||
qp.P.LogVerboseMessage("Cleaning up resources...\n")
|
||||
p.CG.LogVerboseMessage("Preflight service creation check: PASSED\n")
|
||||
p.CG.LogVerboseMessage("Cleaning up resources...\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) checkPfDeployment(clientset *kubernetes.Clientset, namespace string, cleanup bool) 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"
|
||||
qp.deleteDeployment(clientset, namespace, depName)
|
||||
p.CG.DeleteDeployment(clientset, namespace, depName)
|
||||
if cleanup {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
pfDeployment, err := qp.createPreflightTestDeployment(clientset, namespace, depName, imageName)
|
||||
pfDeployment, err := p.CG.CreatePreflightTestDeployment(clientset, namespace, depName, imageName)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to create deployment - %v\n", err)
|
||||
return err
|
||||
}
|
||||
defer qp.deleteDeployment(clientset, namespace, depName)
|
||||
if err := waitForDeployment(clientset, namespace, pfDeployment); err != nil {
|
||||
defer p.CG.DeleteDeployment(clientset, namespace, depName)
|
||||
if err := p.CG.WaitForDeployment(clientset, namespace, pfDeployment); err != nil {
|
||||
return err
|
||||
}
|
||||
qp.P.LogVerboseMessage("Preflight Deployment check: PASSED\n")
|
||||
qp.P.LogVerboseMessage("Cleaning up resources...\n")
|
||||
p.CG.LogVerboseMessage("Preflight Deployment check: PASSED\n")
|
||||
p.CG.LogVerboseMessage("Cleaning up resources...\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -12,68 +12,68 @@ const (
|
||||
netcat = "netcat"
|
||||
)
|
||||
|
||||
func (qp *QliksensePreflight) CheckDns(namespace string, kubeConfigContents []byte, cleanup bool) error {
|
||||
func (p *QliksensePreflight) CheckDns(namespace string, kubeConfigContents []byte, cleanup bool) error {
|
||||
depName := "dep-dns-preflight-check"
|
||||
serviceName := "svc-dns-pf-check"
|
||||
podName := "pf-pod-1"
|
||||
|
||||
if !cleanup {
|
||||
qp.P.LogVerboseMessage("Preflight DNS check: \n")
|
||||
qp.P.LogVerboseMessage("------------------- \n")
|
||||
fmt.Print("Preflight DNS check... ")
|
||||
p.CG.LogVerboseMessage("\n------------------- \n")
|
||||
}
|
||||
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
|
||||
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to create a kubernetes client: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// delete the deployment we are going to create, if it already exists in the cluster
|
||||
qp.runDNSCleanup(clientset, namespace, podName, serviceName, depName)
|
||||
p.runDNSCleanup(clientset, namespace, podName, serviceName, depName)
|
||||
if cleanup {
|
||||
return nil
|
||||
}
|
||||
// creating deployment
|
||||
nginxImageName, err := qp.GetPreflightConfigObj().GetImageName(nginx, true)
|
||||
nginxImageName, err := p.GetPreflightConfigObj().GetImageName(nginx, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dnsDeployment, err := qp.createPreflightTestDeployment(clientset, namespace, depName, nginxImageName)
|
||||
dnsDeployment, err := p.CG.CreatePreflightTestDeployment(clientset, namespace, depName, nginxImageName)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to create deployment: %v\n", 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
|
||||
}
|
||||
|
||||
// creating service
|
||||
dnsService, err := qp.createPreflightTestService(clientset, namespace, serviceName)
|
||||
dnsService, err := p.CG.CreatePreflightTestService(clientset, namespace, serviceName)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to create service : %s, %s\n", serviceName, err)
|
||||
return err
|
||||
}
|
||||
defer qp.deleteService(clientset, namespace, serviceName)
|
||||
defer p.CG.DeleteService(clientset, namespace, serviceName)
|
||||
|
||||
// create a pod
|
||||
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 {
|
||||
err = fmt.Errorf("unable to retrieve image : %v\n", 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 {
|
||||
err = fmt.Errorf("unable to create pod : %s, %s\n", podName, 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
|
||||
}
|
||||
if len(dnsPod.Spec.Containers) == 0 {
|
||||
@@ -81,30 +81,30 @@ func (qp *QliksensePreflight) CheckDns(namespace string, kubeConfigContents []by
|
||||
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 {
|
||||
err = fmt.Errorf("unable to execute dns check in the cluster: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.HasSuffix(strings.TrimSpace(logStr), "succeeded!") {
|
||||
qp.P.LogVerboseMessage("Preflight DNS check: PASSED\n")
|
||||
p.CG.LogVerboseMessage("Preflight DNS check: PASSED\n")
|
||||
} else {
|
||||
err = fmt.Errorf("Expected response not found\n")
|
||||
return err
|
||||
}
|
||||
if !cleanup {
|
||||
qp.P.LogVerboseMessage("Completed preflight DNS check\n")
|
||||
qp.P.LogVerboseMessage("Cleaning up resources...\n")
|
||||
p.CG.LogVerboseMessage("Completed preflight DNS check\n")
|
||||
p.CG.LogVerboseMessage("Cleaning up resources...\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) runDNSCleanup(clientset *kubernetes.Clientset, namespace, podName, serviceName, depName string) {
|
||||
qp.deleteDeployment(clientset, namespace, depName)
|
||||
qp.deletePod(clientset, namespace, podName)
|
||||
qp.deleteService(clientset, namespace, serviceName)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -16,174 +16,133 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
mongo = "mongo"
|
||||
preflight_mongo = "preflight-mongo"
|
||||
caCertMountPath = "/etc/ssl/certs/ca-certificates.crt"
|
||||
)
|
||||
|
||||
func (qp *QliksensePreflight) CheckMongo(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions, cleanup bool) error {
|
||||
if !cleanup {
|
||||
qp.P.LogVerboseMessage("Preflight mongodb check: \n")
|
||||
qp.P.LogVerboseMessage("------------------------ \n")
|
||||
fmt.Print("Preflight mongodb check... ")
|
||||
qp.CG.LogVerboseMessage("\n------------------------ \n")
|
||||
}
|
||||
if preflightOpts != nil && preflightOpts.MongoOptions.MongodbUrl == "" && !cleanup {
|
||||
var currentCR *qapi.QliksenseCR
|
||||
var err error
|
||||
qConfig := qapi.NewQConfig(qp.Q.QliksenseHome)
|
||||
qConfig.SetNamespace(namespace)
|
||||
currentCR, err = qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
qp.CG.LogVerboseMessage("Unable to retrieve current CR: %v\n", err)
|
||||
return err
|
||||
}
|
||||
decryptedCR, err := qConfig.GetDecryptedCr(currentCR)
|
||||
if err != nil {
|
||||
qp.CG.LogVerboseMessage("An error occurred while retrieving mongodbUrl from current CR: %v\n", err)
|
||||
return err
|
||||
}
|
||||
if preflightOpts.MongoOptions.MongodbUrl == "" && !cleanup {
|
||||
// infer mongoDbUrl from currentCR
|
||||
qp.P.LogVerboseMessage("MongoDbUri is empty, infer from CR\n")
|
||||
qConfig := qapi.NewQConfig(qp.Q.QliksenseHome)
|
||||
var currentCR *qapi.QliksenseCR
|
||||
qp.CG.LogVerboseMessage("mongodbUri is empty, infer from CR\n")
|
||||
preflightOpts.MongoOptions.MongodbUrl = strings.TrimSpace(decryptedCR.Spec.GetFromSecrets("qliksense", "mongodbUri"))
|
||||
}
|
||||
|
||||
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 = 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")
|
||||
clientCrtFile := filepath.Join(tmpDir, "mongoClient.crt")
|
||||
caCertStr := decryptedCR.Spec.GetFromSecrets("qliksense", "caCertificates")
|
||||
clientCertStr := decryptedCR.Spec.GetFromSecrets("qliksense", "mongoDbClientCrt")
|
||||
|
||||
if preflightOpts.MongoOptions.CaCertFile == "" && caCertStr != "" {
|
||||
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 preflightOpts.MongoOptions.ClientCertFile == "" && clientCertStr != "" {
|
||||
api.LogDebugMessage("received client crt: %s\n", clientCertStr)
|
||||
if err := ioutil.WriteFile(clientCrtFile, []byte(clientCertStr), 0644); err != nil {
|
||||
return fmt.Errorf("unable to write client crt to file: %v", err)
|
||||
}
|
||||
preflightOpts.MongoOptions.ClientCertFile = clientCrtFile
|
||||
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.P.LogVerboseMessage("MongodbUrl: %s\n", preflightOpts.MongoOptions.MongodbUrl)
|
||||
qp.CG.LogVerboseMessage("MongodbUrl: %s\n", preflightOpts.MongoOptions.MongodbUrl)
|
||||
|
||||
// if mongoDbUrl is empty, abort check
|
||||
// if mongodbUrl is empty, abort check
|
||||
if preflightOpts.MongoOptions.MongodbUrl == "" {
|
||||
qp.P.LogVerboseMessage("Mongodb Url is empty, hence aborting preflight check\n")
|
||||
return errors.New("MongoDbUrl is empty")
|
||||
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.P.LogVerboseMessage("Completed preflight mongodb check\n")
|
||||
qp.CG.LogVerboseMessage("Completed preflight mongodb check\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) mongoConnCheck(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions, cleanup bool) error {
|
||||
caCertSecretName := "preflight-mongo-test-cacert"
|
||||
clientCertSecretName := "preflight-mongo-test-clientcert"
|
||||
func (p *QliksensePreflight) mongoConnCheck(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions, cleanup bool) error {
|
||||
caCertSecretName := "ca-certificates-crt"
|
||||
mongoPodName := "pf-mongo-pod"
|
||||
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
|
||||
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to create a kubernetes client: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// cleanup before starting check
|
||||
qp.runMongoCleanup(clientset, namespace, mongoPodName, caCertSecretName, clientCertSecretName)
|
||||
p.runMongoCleanup(clientset, namespace, mongoPodName, caCertSecretName)
|
||||
if cleanup {
|
||||
return nil
|
||||
}
|
||||
var secrets []string
|
||||
secrets := map[string]string{}
|
||||
if preflightOpts.MongoOptions.CaCertFile != "" {
|
||||
caCertSecret, err := qp.createSecret(clientset, namespace, preflightOpts.MongoOptions.CaCertFile, caCertSecretName)
|
||||
caCertSecret, err := p.createSecret(clientset, namespace, preflightOpts.MongoOptions.CaCertFile, caCertSecretName)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to create a ca cert kubernetes secret: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
defer qp.deleteK8sSecret(clientset, namespace, caCertSecret.Name)
|
||||
secrets = append(secrets, caCertSecretName)
|
||||
}
|
||||
if preflightOpts.MongoOptions.ClientCertFile != "" {
|
||||
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.Name)
|
||||
secrets = append(secrets, clientCertSecretName)
|
||||
defer p.CG.DeleteK8sSecret(clientset, namespace, caCertSecret.Name)
|
||||
secrets[caCertSecretName] = caCertMountPath
|
||||
}
|
||||
|
||||
mongoCommand := strings.Builder{}
|
||||
mongoCommand.WriteString(fmt.Sprintf("sleep 10;mongo %s", preflightOpts.MongoOptions.MongodbUrl))
|
||||
if preflightOpts.MongoOptions.Username != "" {
|
||||
mongoCommand.WriteString(fmt.Sprintf(" --username %s", preflightOpts.MongoOptions.Username))
|
||||
api.LogDebugMessage("Adding username: Mongo command: %s\n", mongoCommand.String())
|
||||
}
|
||||
if preflightOpts.MongoOptions.Password != "" {
|
||||
mongoCommand.WriteString(fmt.Sprintf(" --password %s", preflightOpts.MongoOptions.Password))
|
||||
api.LogDebugMessage("Adding username and password\n")
|
||||
}
|
||||
if preflightOpts.MongoOptions.Tls || preflightOpts.MongoOptions.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()}
|
||||
commandToRun := []string{"./preflight-mongo", fmt.Sprintf(`-url="%s"`, preflightOpts.MongoOptions.MongodbUrl)}
|
||||
api.LogDebugMessage("Mongo command: %s\n", strings.Join(commandToRun, " "))
|
||||
|
||||
// create a pod
|
||||
imageName, err := qp.GetPreflightConfigObj().GetImageName(mongo, true)
|
||||
imageName, err := p.GetPreflightConfigObj().GetImageName(preflight_mongo, true)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to retrieve image : %v\n", err)
|
||||
return err
|
||||
}
|
||||
mongoPod, err := qp.createPreflightTestPod(clientset, namespace, mongoPodName, 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 {
|
||||
err = fmt.Errorf("unable to create pod : %v\n", err)
|
||||
return err
|
||||
}
|
||||
defer qp.deletePod(clientset, namespace, mongoPodName)
|
||||
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
|
||||
}
|
||||
if len(mongoPod.Spec.Containers) == 0 {
|
||||
err := fmt.Errorf("there are no containers in the pod- %v\n", err)
|
||||
return err
|
||||
}
|
||||
waitForPodToDie(clientset, namespace, mongoPod)
|
||||
logStr, err := getPodLogs(clientset, mongoPod)
|
||||
p.CG.WaitForPodToDie(clientset, namespace, mongoPod)
|
||||
logStr, err := p.CG.GetPodLogs(clientset, mongoPod)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to execute mongo check in the cluster: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// check mongo server version
|
||||
ok, err := qp.checkMongoVersion(logStr)
|
||||
ok, err := p.checkMongoVersion(logStr)
|
||||
if !ok || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if connection succeeded
|
||||
stringToCheck := "Implicit session:"
|
||||
stringToCheck := "qlik - connection succeeded!!"
|
||||
if strings.Contains(logStr, stringToCheck) {
|
||||
qp.P.LogVerboseMessage("Preflight mongo check: PASSED\n")
|
||||
p.CG.LogVerboseMessage("Preflight mongo check: PASSED\n")
|
||||
} else {
|
||||
err = fmt.Errorf("Connection failed: %s\n", logStr)
|
||||
return err
|
||||
@@ -191,10 +150,10 @@ func (qp *QliksensePreflight) mongoConnCheck(kubeConfigContents []byte, namespac
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) checkMongoVersion(logStr string) (bool, error) {
|
||||
func (p *QliksensePreflight) checkMongoVersion(logStr string) (bool, error) {
|
||||
// check mongo server version
|
||||
api.LogDebugMessage("Minimum required mongo version: %s\n", qp.GetPreflightConfigObj().GetMinMongoVersion())
|
||||
mongoVersionStrToCheck := "MongoDB 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 {
|
||||
@@ -203,20 +162,20 @@ func (qp *QliksensePreflight) checkMongoVersion(logStr string) (bool, error) {
|
||||
if len(mongoVersionLog) < 2 {
|
||||
continue
|
||||
}
|
||||
mongoVersionStr := strings.TrimSpace(mongoVersionLog[1])
|
||||
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(qp.GetPreflightConfigObj().GetMinMongoVersion())
|
||||
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) {
|
||||
qp.P.LogVerboseMessage("Current mongodb server version %s is greater than or equal to minimum required mongodb version: %s\n", currentMongoVersionSemver, 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)
|
||||
@@ -228,13 +187,13 @@ func (qp *QliksensePreflight) checkMongoVersion(logStr string) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) createSecret(clientset *kubernetes.Clientset, namespace, certFile, certSecretName string) (*apiv1.Secret, error) {
|
||||
func (p *QliksensePreflight) createSecret(clientset kubernetes.Interface, namespace, certFile, certSecretName string) (*apiv1.Secret, error) {
|
||||
certBytes, err := ioutil.ReadFile(certFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certSecret, err := qp.createPreflightTestSecret(clientset, namespace, certSecretName, certBytes)
|
||||
certSecret, err := p.CG.CreatePreflightTestSecret(clientset, namespace, certSecretName, certBytes)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to create secret with cert : %v\n", err)
|
||||
return nil, err
|
||||
@@ -242,8 +201,7 @@ func (qp *QliksensePreflight) createSecret(clientset *kubernetes.Clientset, name
|
||||
return certSecret, nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) runMongoCleanup(clientset *kubernetes.Clientset, namespace, mongoPodName, caCertSecretName, clientCertSecretName string) {
|
||||
qp.deletePod(clientset, namespace, mongoPodName)
|
||||
qp.deleteK8sSecret(clientset, namespace, caCertSecretName)
|
||||
qp.deleteK8sSecret(clientset, namespace, clientCertSecretName)
|
||||
func (p *QliksensePreflight) runMongoCleanup(clientset kubernetes.Interface, namespace, mongoPodName, caCertSecretName string) {
|
||||
p.CG.DeletePod(clientset, namespace, mongoPodName)
|
||||
p.CG.DeleteK8sSecret(clientset, namespace, caCertSecretName)
|
||||
}
|
||||
|
||||
@@ -1,30 +1,8 @@
|
||||
package preflight
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/rbac/v1beta1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/client-go/util/retry"
|
||||
)
|
||||
|
||||
type PreflightOptions struct {
|
||||
@@ -32,714 +10,42 @@ type PreflightOptions struct {
|
||||
MongoOptions *MongoOptions
|
||||
}
|
||||
|
||||
// LogVerboseMessage logs a verbose message
|
||||
func (p *PreflightOptions) LogVerboseMessage(strMessage string, args ...interface{}) {
|
||||
if p.Verbose || os.Getenv("QLIKSENSE_DEBUG") == "true" {
|
||||
fmt.Printf(strMessage, args...)
|
||||
}
|
||||
}
|
||||
|
||||
type MongoOptions struct {
|
||||
MongodbUrl string
|
||||
Username string
|
||||
Password string
|
||||
CaCertFile string
|
||||
ClientCertFile string
|
||||
Tls bool
|
||||
MongodbUrl string
|
||||
CaCertFile string
|
||||
}
|
||||
|
||||
var gracePeriod int64 = 0
|
||||
|
||||
type QliksensePreflight struct {
|
||||
Q *qliksense.Qliksense
|
||||
P *PreflightOptions
|
||||
Q *qliksense.Qliksense
|
||||
P *PreflightOptions
|
||||
CG *api.ClientGoUtils
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) GetPreflightConfigObj() *api.PreflightConfig {
|
||||
return api.NewPreflightConfig(qp.Q.QliksenseHome)
|
||||
}
|
||||
|
||||
func InitPreflight() (string, []byte, error) {
|
||||
api.LogDebugMessage("Reading .kube/config file...")
|
||||
|
||||
homeDir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Unable to deduce home dir\n")
|
||||
return "", nil, err
|
||||
}
|
||||
api.LogDebugMessage("Kube config location: %s\n\n", filepath.Join(homeDir, ".kube", "config"))
|
||||
|
||||
kubeConfig := filepath.Join(homeDir, ".kube", "config")
|
||||
kubeConfigContents, err := ioutil.ReadFile(kubeConfig)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Unable to deduce home dir\n")
|
||||
return "", nil, err
|
||||
}
|
||||
// retrieve namespace
|
||||
namespace := api.GetKubectlNamespace()
|
||||
// if namespace comes back empty, we will run checks in the default namespace
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
api.LogDebugMessage("Namespace: %s\n", namespace)
|
||||
return namespace, kubeConfigContents, nil
|
||||
}
|
||||
|
||||
func initiateK8sOps(opr, namespace string) error {
|
||||
opr1 := strings.Fields(opr)
|
||||
_, err := api.KubectlDirectOps(opr1, namespace)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func int32Ptr(i int32) *int32 { return &i }
|
||||
|
||||
func retryOnError(mf func() error) error {
|
||||
return retry.OnError(wait.Backoff{
|
||||
Duration: 1 * time.Second,
|
||||
Factor: 1,
|
||||
Jitter: 0.1,
|
||||
Steps: 5,
|
||||
}, func(err error) bool {
|
||||
return k8serrors.IsConflict(err) || k8serrors.IsGone(err) || k8serrors.IsServerTimeout(err) ||
|
||||
k8serrors.IsServiceUnavailable(err) || k8serrors.IsTimeout(err) || k8serrors.IsTooManyRequests(err)
|
||||
}, mf)
|
||||
}
|
||||
|
||||
func getK8SClientSet(kubeconfig []byte, contextName string) (*kubernetes.Clientset, *rest.Config, error) {
|
||||
var clientConfig *rest.Config
|
||||
var err error
|
||||
if len(kubeconfig) == 0 {
|
||||
clientConfig, err = rest.InClusterConfig()
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "Unable to load in-cluster kubeconfig")
|
||||
return nil, nil, err
|
||||
}
|
||||
} else {
|
||||
config, err := clientcmd.Load(kubeconfig)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "Unable to load kubeconfig")
|
||||
return nil, nil, err
|
||||
}
|
||||
if contextName != "" {
|
||||
config.CurrentContext = contextName
|
||||
}
|
||||
clientConfig, err = clientcmd.NewDefaultClientConfig(*config, &clientcmd.ConfigOverrides{}).ClientConfig()
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "Unable to create client config from config")
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
clientset, err := kubernetes.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "Unable to create clientset")
|
||||
return nil, nil, err
|
||||
}
|
||||
return clientset, clientConfig, nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) createPreflightTestDeployment(clientset *kubernetes.Clientset, namespace string, depName string, imageName string) (*appsv1.Deployment, error) {
|
||||
deploymentsClient := clientset.AppsV1().Deployments(namespace)
|
||||
deployment := &appsv1.Deployment{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: depName,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Replicas: int32Ptr(1),
|
||||
Selector: &v1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"app": "preflight-check",
|
||||
},
|
||||
},
|
||||
Template: apiv1.PodTemplateSpec{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app": "preflight-check",
|
||||
"label": "preflight-check-label",
|
||||
},
|
||||
},
|
||||
Spec: apiv1.PodSpec{
|
||||
Containers: []apiv1.Container{
|
||||
{
|
||||
Name: "dep",
|
||||
Image: imageName,
|
||||
Ports: []apiv1.ContainerPort{
|
||||
{
|
||||
Name: "http",
|
||||
Protocol: apiv1.ProtocolTCP,
|
||||
ContainerPort: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create Deployment
|
||||
var result *appsv1.Deployment
|
||||
if err := retryOnError(func() (err error) {
|
||||
result, err = deploymentsClient.Create(deployment)
|
||||
return err
|
||||
}); err != nil {
|
||||
err = errors.Wrapf(err, "unable to create deployments in the %s namespace", namespace)
|
||||
return nil, err
|
||||
}
|
||||
qp.P.LogVerboseMessage("Created deployment %q\n", result.GetObjectMeta().GetName())
|
||||
|
||||
return deployment, nil
|
||||
}
|
||||
|
||||
func getDeployment(clientset *kubernetes.Clientset, namespace, depName string) (*appsv1.Deployment, error) {
|
||||
deploymentsClient := clientset.AppsV1().Deployments(namespace)
|
||||
var deployment *appsv1.Deployment
|
||||
if err := retryOnError(func() (err error) {
|
||||
deployment, err = deploymentsClient.Get(depName, v1.GetOptions{})
|
||||
return err
|
||||
}); err != nil {
|
||||
err = errors.Wrapf(err, "unable to get deployments in the %s namespace", namespace)
|
||||
api.LogDebugMessage("%v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
return deployment, nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) deleteDeployment(clientset *kubernetes.Clientset, namespace, name string) error {
|
||||
deploymentsClient := clientset.AppsV1().Deployments(namespace)
|
||||
// Create Deployment
|
||||
deletePolicy := v1.DeletePropagationForeground
|
||||
deleteOptions := v1.DeleteOptions{
|
||||
PropagationPolicy: &deletePolicy,
|
||||
GracePeriodSeconds: &gracePeriod,
|
||||
}
|
||||
|
||||
if err := retryOnError(func() (err error) {
|
||||
return deploymentsClient.Delete(name, &deleteOptions)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := waitForDeploymentToDelete(clientset, namespace, name); err != nil {
|
||||
return err
|
||||
}
|
||||
qp.P.LogVerboseMessage("Deleted deployment: %s\n", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) createPreflightTestService(clientset *kubernetes.Clientset, namespace string, svcName string) (*apiv1.Service, error) {
|
||||
iptr := int32Ptr(80)
|
||||
servicesClient := clientset.CoreV1().Services(namespace)
|
||||
service := &apiv1.Service{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: svcName,
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{
|
||||
"app": "preflight-check",
|
||||
},
|
||||
},
|
||||
Spec: apiv1.ServiceSpec{
|
||||
Ports: []apiv1.ServicePort{
|
||||
{Name: "port1",
|
||||
Port: *iptr,
|
||||
},
|
||||
},
|
||||
Selector: map[string]string{
|
||||
"app": "preflight-check",
|
||||
},
|
||||
ClusterIP: "",
|
||||
},
|
||||
}
|
||||
var result *apiv1.Service
|
||||
if err := retryOnError(func() (err error) {
|
||||
result, err = servicesClient.Create(service)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qp.P.LogVerboseMessage("Created service %q\n", result.GetObjectMeta().GetName())
|
||||
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func getService(clientset *kubernetes.Clientset, namespace, svcName string) (*apiv1.Service, error) {
|
||||
servicesClient := clientset.CoreV1().Services(namespace)
|
||||
var svc *apiv1.Service
|
||||
if err := retryOnError(func() (err error) {
|
||||
svc, err = servicesClient.Get(svcName, v1.GetOptions{})
|
||||
return err
|
||||
}); err != nil {
|
||||
err = errors.Wrapf(err, "unable to get services in the %s namespace", namespace)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) deleteService(clientset *kubernetes.Clientset, namespace, name string) error {
|
||||
servicesClient := clientset.CoreV1().Services(namespace)
|
||||
// Create Deployment
|
||||
deletePolicy := v1.DeletePropagationForeground
|
||||
deleteOptions := v1.DeleteOptions{
|
||||
PropagationPolicy: &deletePolicy,
|
||||
}
|
||||
if err := retryOnError(func() (err error) {
|
||||
return servicesClient.Delete(name, &deleteOptions)
|
||||
}); err != nil {
|
||||
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, roleName string) error {
|
||||
rolesClient := clientset.RbacV1beta1().Roles(namespace)
|
||||
|
||||
deletePolicy := v1.DeletePropagationForeground
|
||||
deleteOptions := v1.DeleteOptions{
|
||||
PropagationPolicy: &deletePolicy,
|
||||
}
|
||||
err := rolesClient.Delete(roleName, &deleteOptions)
|
||||
if err != nil {
|
||||
log.Printf("Error: %v\n", err)
|
||||
return err
|
||||
}
|
||||
qp.P.LogVerboseMessage("Deleted role: %s\n\n", roleName)
|
||||
return nil
|
||||
}
|
||||
|
||||
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, roleBindingName string) error {
|
||||
roleBindingClient := clientset.RbacV1beta1().RoleBindings(namespace)
|
||||
|
||||
deletePolicy := v1.DeletePropagationForeground
|
||||
deleteOptions := v1.DeleteOptions{
|
||||
PropagationPolicy: &deletePolicy,
|
||||
}
|
||||
err := roleBindingClient.Delete(roleBindingName, &deleteOptions)
|
||||
if err != nil {
|
||||
log.Printf("Error: %v\n", err)
|
||||
return err
|
||||
}
|
||||
qp.P.LogVerboseMessage("Deleted RoleBinding: %s\n\n", roleBindingName)
|
||||
return nil
|
||||
}
|
||||
|
||||
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, serviceAccountName string) error {
|
||||
serviceAccountClient := clientset.CoreV1().ServiceAccounts(namespace)
|
||||
|
||||
deletePolicy := v1.DeletePropagationForeground
|
||||
deleteOptions := v1.DeleteOptions{
|
||||
PropagationPolicy: &deletePolicy,
|
||||
}
|
||||
err := serviceAccountClient.Delete(serviceAccountName, &deleteOptions)
|
||||
if err != nil {
|
||||
log.Printf("Error: %v\n", err)
|
||||
return err
|
||||
}
|
||||
qp.P.LogVerboseMessage("Deleted ServiceAccount: %s\n\n", serviceAccountName)
|
||||
return nil
|
||||
}
|
||||
|
||||
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, 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
|
||||
}
|
||||
qp.P.LogVerboseMessage("Deleted Secret: %s\n", secretName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) Cleanup(namespace string, kubeConfigContents []byte) error {
|
||||
qp.P.LogVerboseMessage("Preflight clean\n")
|
||||
qp.P.LogVerboseMessage("----------------\n")
|
||||
qp.CG.LogVerboseMessage("Preflight clean\n")
|
||||
qp.CG.LogVerboseMessage("----------------\n")
|
||||
|
||||
qp.P.LogVerboseMessage("Removing deployment...\n")
|
||||
qp.CG.LogVerboseMessage("Removing deployment...\n")
|
||||
qp.CheckDeployment(namespace, kubeConfigContents, true)
|
||||
qp.P.LogVerboseMessage("Removing service...\n")
|
||||
qp.CG.LogVerboseMessage("Removing service...\n")
|
||||
qp.CheckService(namespace, kubeConfigContents, true)
|
||||
qp.P.LogVerboseMessage("Removing pod...\n")
|
||||
qp.CG.LogVerboseMessage("Removing pod...\n")
|
||||
qp.CheckPod(namespace, kubeConfigContents, true)
|
||||
|
||||
qp.P.LogVerboseMessage("Removing role...\n")
|
||||
qp.CG.LogVerboseMessage("Removing role...\n")
|
||||
qp.CheckCreateRole(namespace, true)
|
||||
qp.P.LogVerboseMessage("Removing rolebinding...\n")
|
||||
qp.CG.LogVerboseMessage("Removing rolebinding...\n")
|
||||
qp.CheckCreateRoleBinding(namespace, true)
|
||||
qp.P.LogVerboseMessage("Removing serviceaccount...\n")
|
||||
qp.CG.LogVerboseMessage("Removing serviceaccount...\n")
|
||||
qp.CheckCreateServiceAccount(namespace, true)
|
||||
|
||||
qp.P.LogVerboseMessage("Removing DNS check components...\n")
|
||||
qp.CG.LogVerboseMessage("Removing DNS check components...\n")
|
||||
qp.CheckDns(namespace, kubeConfigContents, true)
|
||||
qp.P.LogVerboseMessage("Removing mongo check components...\n")
|
||||
qp.CG.LogVerboseMessage("Removing mongo check components...\n")
|
||||
qp.CheckMongo(kubeConfigContents, namespace, &PreflightOptions{MongoOptions: &MongoOptions{}}, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -14,15 +14,15 @@ import (
|
||||
func (qp *QliksensePreflight) CheckCreateRole(namespace string, cleanup bool) error {
|
||||
// create a Role
|
||||
if !cleanup {
|
||||
qp.P.LogVerboseMessage("Preflight role check: \n")
|
||||
qp.P.LogVerboseMessage("--------------------- \n")
|
||||
fmt.Print("Preflight role check... ")
|
||||
qp.CG.LogVerboseMessage("\n--------------------- \n")
|
||||
}
|
||||
err := qp.checkCreateEntity(namespace, "Role", cleanup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !cleanup {
|
||||
qp.P.LogVerboseMessage("Completed preflight role check\n")
|
||||
qp.CG.LogVerboseMessage("Completed preflight role check\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -30,15 +30,15 @@ func (qp *QliksensePreflight) CheckCreateRole(namespace string, cleanup bool) er
|
||||
func (qp *QliksensePreflight) CheckCreateRoleBinding(namespace string, cleanup bool) error {
|
||||
// create a RoleBinding
|
||||
if !cleanup {
|
||||
qp.P.LogVerboseMessage("Preflight rolebinding check: \n")
|
||||
qp.P.LogVerboseMessage("---------------------------- \n")
|
||||
fmt.Print("Preflight rolebinding check... ")
|
||||
qp.CG.LogVerboseMessage("\n---------------------------- \n")
|
||||
}
|
||||
err := qp.checkCreateEntity(namespace, "RoleBinding", cleanup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !cleanup {
|
||||
qp.P.LogVerboseMessage("Completed preflight rolebinding check\n")
|
||||
qp.CG.LogVerboseMessage("Completed preflight rolebinding check\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -46,15 +46,15 @@ func (qp *QliksensePreflight) CheckCreateRoleBinding(namespace string, cleanup b
|
||||
func (qp *QliksensePreflight) CheckCreateServiceAccount(namespace string, cleanup bool) error {
|
||||
// create a service account
|
||||
if !cleanup {
|
||||
qp.P.LogVerboseMessage("Preflight serviceaccount check: \n")
|
||||
qp.P.LogVerboseMessage("------------------------------- \n")
|
||||
fmt.Print("Preflight serviceaccount check... ")
|
||||
qp.CG.LogVerboseMessage("\n------------------------------- \n")
|
||||
}
|
||||
err := qp.checkCreateEntity(namespace, "ServiceAccount", cleanup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !cleanup {
|
||||
qp.P.LogVerboseMessage("Completed preflight serviceaccount check\n")
|
||||
qp.CG.LogVerboseMessage("Completed preflight serviceaccount check\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -67,13 +67,13 @@ func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string,
|
||||
var err error
|
||||
currentCR, err = qConfig.GetCurrentCR()
|
||||
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
|
||||
}
|
||||
if currentCR.IsRepoExist() {
|
||||
mfroot = currentCR.Spec.GetManifestsRoot()
|
||||
} 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
|
||||
} else {
|
||||
mfroot = tempDownloadedDir
|
||||
@@ -95,7 +95,8 @@ func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string,
|
||||
if sa != "" {
|
||||
sa = strings.Replace(sa, "name: qliksense", "name: preflight", -1)
|
||||
} 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
|
||||
}
|
||||
namespace = "" // namespace is handled when generating the manifests
|
||||
@@ -107,10 +108,10 @@ func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string,
|
||||
}
|
||||
|
||||
defer func() {
|
||||
qp.P.LogVerboseMessage("Cleaning up resources...\n")
|
||||
qp.CG.LogVerboseMessage("Cleaning up resources...\n")
|
||||
err := api.KubectlDeleteVerbose(sa, namespace, qp.P.Verbose)
|
||||
if err != nil {
|
||||
qp.P.LogVerboseMessage("Preflight cleanup failed!\n")
|
||||
qp.CG.LogVerboseMessage("Preflight cleanup failed!\n")
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -120,55 +121,55 @@ func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string,
|
||||
return err
|
||||
}
|
||||
|
||||
qp.P.LogVerboseMessage("Preflight %s check: PASSED\n", entityToTest)
|
||||
qp.CG.LogVerboseMessage("Preflight %s check: PASSED\n", entityToTest)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) CheckCreateRB(namespace string, kubeConfigContents []byte) error {
|
||||
|
||||
// create a role
|
||||
qp.P.LogVerboseMessage("Preflight createRole check: \n")
|
||||
qp.P.LogVerboseMessage("--------------------------- \n")
|
||||
qp.CG.LogVerboseMessage("Preflight createRole check: \n")
|
||||
qp.CG.LogVerboseMessage("--------------------------- \n")
|
||||
errStr := strings.Builder{}
|
||||
err1 := qp.checkCreateEntity(namespace, "Role", false)
|
||||
if err1 != nil {
|
||||
errStr.WriteString(err1.Error())
|
||||
errStr.WriteString("\n")
|
||||
qp.P.LogVerboseMessage("%v\n", err1)
|
||||
qp.P.LogVerboseMessage("Preflight role check: FAILED\n")
|
||||
qp.CG.LogVerboseMessage("%v\n", err1)
|
||||
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
|
||||
qp.P.LogVerboseMessage("Preflight rolebinding check: \n")
|
||||
qp.P.LogVerboseMessage("---------------------------- \n")
|
||||
qp.CG.LogVerboseMessage("Preflight rolebinding check: \n")
|
||||
qp.CG.LogVerboseMessage("---------------------------- \n")
|
||||
err2 := qp.checkCreateEntity(namespace, "RoleBinding", false)
|
||||
if err2 != nil {
|
||||
errStr.WriteString(err2.Error())
|
||||
errStr.WriteString("\n")
|
||||
qp.P.LogVerboseMessage("%v\n", err2)
|
||||
qp.P.LogVerboseMessage("Preflight rolebinding check: FAILED\n")
|
||||
qp.CG.LogVerboseMessage("%v\n", err2)
|
||||
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
|
||||
qp.P.LogVerboseMessage("Preflight serviceaccount check: \n")
|
||||
qp.P.LogVerboseMessage("------------------------------- \n")
|
||||
qp.CG.LogVerboseMessage("Preflight serviceaccount check: \n")
|
||||
qp.CG.LogVerboseMessage("------------------------------- \n")
|
||||
err3 := qp.checkCreateEntity(namespace, "ServiceAccount", false)
|
||||
if err3 != nil {
|
||||
errStr.WriteString(err3.Error())
|
||||
errStr.WriteString("\n")
|
||||
qp.P.LogVerboseMessage("%v\n", err3)
|
||||
qp.P.LogVerboseMessage("Preflight serviceaccount check: FAILED\n")
|
||||
qp.CG.LogVerboseMessage("%v\n", err3)
|
||||
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 {
|
||||
qp.P.LogVerboseMessage("Preflight authcheck: FAILED\n")
|
||||
qp.P.LogVerboseMessage("Completed preflight authcheck\n")
|
||||
qp.CG.LogVerboseMessage("Preflight authcheck: FAILED\n")
|
||||
qp.CG.LogVerboseMessage("Completed preflight authcheck\n")
|
||||
return errors.New(errStr.String())
|
||||
}
|
||||
qp.P.LogVerboseMessage("Preflight authcheck: PASSED\n")
|
||||
qp.P.LogVerboseMessage("Completed preflight authcheck\n")
|
||||
qp.CG.LogVerboseMessage("Preflight authcheck: PASSED\n")
|
||||
qp.CG.LogVerboseMessage("Completed preflight authcheck\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
123
pkg/preflight/verify_ca.go
Normal file
123
pkg/preflight/verify_ca.go
Normal 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
|
||||
}
|
||||
@@ -8,25 +8,25 @@ import (
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
)
|
||||
|
||||
func (qp *QliksensePreflight) CheckK8sVersion(namespace string, kubeConfigContents []byte) error {
|
||||
qp.P.LogVerboseMessage("Preflight kubernetes version check: \n")
|
||||
qp.P.LogVerboseMessage("----------------------------------- \n")
|
||||
func (p *QliksensePreflight) CheckK8sVersion(namespace string, kubeConfigContents []byte) error {
|
||||
fmt.Print("Preflight kubernetes version check... ")
|
||||
p.CG.LogVerboseMessage("\n----------------------------------- \n")
|
||||
var currentVersion *semver.Version
|
||||
|
||||
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
|
||||
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Unable to create clientset: %v\n", err)
|
||||
return err
|
||||
}
|
||||
var serverVersion *version.Info
|
||||
if err := retryOnError(func() (err error) {
|
||||
if err := p.CG.RetryOnError(func() (err error) {
|
||||
serverVersion, err = clientset.ServerVersion()
|
||||
return err
|
||||
}); err != nil {
|
||||
err = fmt.Errorf("Unable to get server version: %v\n", err)
|
||||
return err
|
||||
}
|
||||
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
|
||||
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)
|
||||
|
||||
minK8sVersionSemver, err := semver.NewVersion(qp.GetPreflightConfigObj().GetMinK8sVersion())
|
||||
minK8sVersionSemver, err := semver.NewVersion(p.GetPreflightConfigObj().GetMinK8sVersion())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Unable to convert minimum Kubernetes version into semver version:%v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if currentVersion.GreaterThan(minK8sVersionSemver) {
|
||||
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 {
|
||||
err = fmt.Errorf("Current Kubernetes API Server version %s is less than minimum required version: %s", currentVersion, minK8sVersionSemver)
|
||||
return err
|
||||
|
||||
@@ -23,13 +23,12 @@ type patch struct {
|
||||
Patch string `yaml:"patch"`
|
||||
}
|
||||
|
||||
type selectivePatch struct {
|
||||
type annotationTransformer struct {
|
||||
APIVersion string `yaml:"apiVersion"`
|
||||
Metadata struct {
|
||||
Name string `yaml:"name"`
|
||||
} `yaml:"metadata"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Patches []patch `yaml:"patches"`
|
||||
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
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) {
|
||||
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
|
||||
} else if kuzManifest, err := executeKustomizeBuildWithStdoutProgress(filepath.Join(configDirectory, "manifests", profile)); err != nil {
|
||||
return nil, err
|
||||
@@ -223,22 +222,16 @@ func traverseYamlDecodedMapRecursively(val reflect.Value, path []string, visitor
|
||||
}
|
||||
}
|
||||
|
||||
func getChartVersion(versionFile, chartName string) (string, error) {
|
||||
var patchInst patch
|
||||
var selPatch selectivePatch
|
||||
var chart helmChart
|
||||
func getChartVersion(versionFile, versionAnnotation string) (string, error) {
|
||||
var annTransformer annotationTransformer
|
||||
|
||||
if bytes, err := ioutil.ReadFile(versionFile); err != nil {
|
||||
return "", err
|
||||
} else if err = yaml.Unmarshal(bytes, &selPatch); err != nil {
|
||||
} else if err = yaml.Unmarshal(bytes, &annTransformer); err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, patchInst = range selPatch.Patches {
|
||||
if err := yaml.Unmarshal([]byte(patchInst.Patch), &chart); err == nil {
|
||||
if chart.ChartName == chartName {
|
||||
return chart.ChartVersion, nil
|
||||
}
|
||||
}
|
||||
if version, ok := annTransformer.Annotations[versionAnnotation]; ok {
|
||||
return version, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -1,61 +1,8 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
func (q *Qliksense) ApplyCRFromReader(r io.Reader, opts *InstallCommandOptions, keepPatchFiles, overwriteExistingContext, pull, push bool) error {
|
||||
if err := q.LoadCr(r, overwriteExistingContext); err != nil {
|
||||
func (q *Qliksense) ApplyCRFromBytes(crBytes []byte, opts *InstallCommandOptions, overwriteExistingContext bool) error {
|
||||
if err := q.LoadCr(crBytes, overwriteExistingContext); err != nil {
|
||||
return err
|
||||
}
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
cr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
version := cr.GetLabelFromCr("version")
|
||||
|
||||
if pull {
|
||||
fmt.Println("Pulling images...")
|
||||
if err := q.PullImages(version, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if push {
|
||||
fmt.Println("Pushing images...")
|
||||
if err := q.PushImagesForCurrentCR(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if IsQliksenseInstalled(cr.GetName()) {
|
||||
// it is needed in case want to upgrade from one version to another
|
||||
if cr.Spec.ManifestsRoot == "" && cr.Spec.Git == nil {
|
||||
if !qConfig.IsRepoExistForCurrent(version) {
|
||||
if err := q.FetchQK8s(version); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return q.UpgradeQK8s(keepPatchFiles)
|
||||
}
|
||||
return q.InstallQK8s(version, opts, keepPatchFiles)
|
||||
}
|
||||
|
||||
func IsQliksenseInstalled(crName string) bool {
|
||||
args := []string{
|
||||
"get",
|
||||
"qliksense",
|
||||
crName,
|
||||
"-ogo-template",
|
||||
`--template='{{ .metadata.name}}'`,
|
||||
}
|
||||
_, err := qapi.KubectlDirectOps(args, "")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return q.InstallQK8s("", opts)
|
||||
}
|
||||
|
||||
@@ -5,16 +5,19 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/qlik-oss/k-apis/pkg/config"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/qlik-oss/k-apis/pkg/cr"
|
||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
"k8s.io/kubectl/pkg/cmd/util/editor"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -39,9 +42,9 @@ func (q *Qliksense) ConfigApplyQK8s() error {
|
||||
return errors.New(agreementTempalte + "\nPlease do $ qliksense config set-configs qliksense.acceptEULA=yes\n")
|
||||
}
|
||||
|
||||
// create patch dependent resoruces
|
||||
fmt.Println("Installing resoruces used kuztomize patch")
|
||||
if err := q.createK8sResoruceBeforePatch(qcr); err != nil {
|
||||
// create patch dependent resources
|
||||
fmt.Println("Installing resources used by the kuztomize patch")
|
||||
if err := q.createK8sResourceBeforePatch(qcr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -73,27 +76,30 @@ func (q *Qliksense) configEjson() error {
|
||||
}
|
||||
|
||||
func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error {
|
||||
if qcr.Spec.RotateKeys != "None" {
|
||||
if err := q.configEjson(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := q.configEjson(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userHomeDir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
fmt.Printf(`error fetching user's home directory: %v\n`, err)
|
||||
return err
|
||||
}
|
||||
fmt.Println("Manifests root: " + qcr.Spec.GetManifestsRoot())
|
||||
qcr.SetNamespace(qapi.GetKubectlNamespace())
|
||||
b, _ := yaml.Marshal(qcr.KApiCr)
|
||||
fmt.Printf("%v", string(b))
|
||||
// os.Exit(0)
|
||||
// generate patches
|
||||
cr.GeneratePatches(&qcr.KApiCr, path.Join(userHomeDir, ".kube", "config"))
|
||||
cr.GeneratePatches(&qcr.KApiCr, config.KeysActionRestoreOrRotate, path.Join(userHomeDir, ".kube", "config"))
|
||||
// apply generated manifests
|
||||
profilePath := filepath.Join(qcr.Spec.GetManifestsRoot(), qcr.Spec.GetProfileDir())
|
||||
fmt.Printf("Generating manifests for profile: %v\n", profilePath)
|
||||
mByte, err := ExecuteKustomizeBuild(profilePath)
|
||||
if err != nil {
|
||||
fmt.Println("cannot generate manifests for "+profilePath, err)
|
||||
fmt.Printf("error generating manifests: %v\n", err)
|
||||
return err
|
||||
}
|
||||
fmt.Println("Applying manifests to the cluster")
|
||||
if err = qapi.KubectlApply(string(mByte), qcr.GetNamespace()); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -190,13 +196,12 @@ func (q *Qliksense) EditCR(contextName string) error {
|
||||
if err := ioutil.WriteFile(tempFile.Name(), crContent, os.ModePerm); err != nil {
|
||||
return nil
|
||||
}
|
||||
cmd := exec.Command(getKubeEditorTool(), tempFile.Name())
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
|
||||
currentEditor := editor.NewDefaultEditor([]string{"KUBE_EDITOR", "EDITOR"})
|
||||
if err = currentEditor.Launch(tempFile.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newCr, err := qapi.GetCRObject(tempFile.Name())
|
||||
if err != nil {
|
||||
return errors.New("cannot save the cr. Someting wrong in the file format. It is not saved\n" + err.Error())
|
||||
@@ -211,11 +216,3 @@ func (q *Qliksense) EditCR(contextName string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getKubeEditorTool() string {
|
||||
editor := os.Getenv("KUBE_EDITOR")
|
||||
if editor == "" {
|
||||
editor = "vim"
|
||||
}
|
||||
return editor
|
||||
}
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
|
||||
func validateCR(key string, keySub string, value string, crSpec *api.QliksenseCR) (bool, *api.QliksenseCR) {
|
||||
cr := reflect.ValueOf(crSpec.Spec)
|
||||
keyValid := caseInsenstiveFieldByName(reflect.Indirect(cr), key)
|
||||
if !keyValid.IsValid() {
|
||||
//not in main spec
|
||||
fmt.Println(key, "is an invalid key")
|
||||
return false, crSpec
|
||||
} else if keySub == "" {
|
||||
if key == "rotatekeys" {
|
||||
if _, err := validateInput(value); err != nil {
|
||||
return false, crSpec
|
||||
}
|
||||
}
|
||||
}
|
||||
// checks if it is git or gitops
|
||||
if keySub != "" {
|
||||
if !keyValid.IsNil() {
|
||||
if !caseInsenstiveFieldByName(reflect.Indirect(keyValid), keySub).IsValid() {
|
||||
fmt.Println(keySub, "is an invalid key")
|
||||
return false, crSpec
|
||||
} else {
|
||||
// verify gitops enabled and gitops schedule
|
||||
switch keySub {
|
||||
case "schedule":
|
||||
if _, err := cron.ParseStandard(value); err != nil {
|
||||
fmt.Println("Please enter string with standard cron scheduling syntax ")
|
||||
return false, crSpec
|
||||
}
|
||||
case "enabled":
|
||||
if !strings.EqualFold(value, "yes") && !strings.EqualFold(value, "no") {
|
||||
fmt.Println("Please use yes or no for key enabled")
|
||||
return false, crSpec
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch key {
|
||||
case "opsrunner":
|
||||
crSpec.Spec.OpsRunner = &config.OpsRunner{}
|
||||
case "git":
|
||||
crSpec.Spec.Git = &config.Repo{}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, crSpec
|
||||
}
|
||||
|
||||
// SetOtherConfigs - set profile/storageclassname/git.repository/manifestRoot commands
|
||||
func (q *Qliksense) SetOtherConfigs(args []string) error {
|
||||
// retieve current context from config.yaml
|
||||
@@ -240,11 +193,7 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
|
||||
}
|
||||
|
||||
for _, arg := range args {
|
||||
if strings.HasPrefix(arg, "fetchSource.") {
|
||||
if err := q.processSetFetchSource(arg, qliksenseCR); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if strings.HasPrefix(arg, "git.") {
|
||||
if strings.HasPrefix(arg, "git.") {
|
||||
if err := q.processSetGit(arg, qliksenseCR); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -273,64 +222,34 @@ func processSetSingleArg(arg string, cr *api.QliksenseCR) error {
|
||||
cr.Spec.Profile = nv[1]
|
||||
case "storageClassName":
|
||||
cr.Spec.StorageClassName = nv[1]
|
||||
case "rotateKeys":
|
||||
valid := false
|
||||
for _, v := range []string{"yes", "no", "None"} {
|
||||
if nv[1] == v {
|
||||
valid = true
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
return errors.New("please povide rotateKeys=yes|no|None")
|
||||
}
|
||||
cr.Spec.RotateKeys = nv[1]
|
||||
default:
|
||||
return errors.New("Please enter one of: profile, storageClassName,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
|
||||
}
|
||||
|
||||
func (q *Qliksense) processSetFetchSource(arg string, cr *api.QliksenseCR) error {
|
||||
args := strings.Split(arg, "=")
|
||||
subs := strings.Split(args[0], ".")
|
||||
if cr.Spec.FetchSource == nil {
|
||||
cr.Spec.FetchSource = &config.Repo{}
|
||||
func (q *Qliksense) processSetGit(arg string, cr *api.QliksenseCR) error {
|
||||
s := strings.Split(arg, "=")
|
||||
tArg0 := strings.TrimSpace(s[0])
|
||||
tArg1 := strings.TrimSpace(s[1])
|
||||
subs := strings.Split(tArg0, ".")
|
||||
if cr.Spec.Git == nil {
|
||||
cr.Spec.Git = &config.Repo{}
|
||||
}
|
||||
switch subs[1] {
|
||||
case "repository":
|
||||
cr.Spec.FetchSource.Repository = args[1]
|
||||
cr.Spec.Git.Repository = tArg1
|
||||
case "accessToken":
|
||||
qConfig := api.NewQConfig(q.QliksenseHome)
|
||||
key, err := qConfig.GetEncryptionKeyFor(cr.GetName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cr.SetFetchAccessToken(args[1], key)
|
||||
return cr.SetFetchAccessToken(tArg1, key)
|
||||
case "secretName":
|
||||
cr.Spec.FetchSource.SecretName = args[1]
|
||||
cr.Spec.Git.SecretName = tArg1
|
||||
case "userName":
|
||||
cr.Spec.FetchSource.UserName = args[1]
|
||||
default:
|
||||
return errors.New(arg + " does not match any cr spec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) processSetGit(arg string, cr *api.QliksenseCR) error {
|
||||
args := strings.Split(arg, "=")
|
||||
subs := strings.Split(args[0], ".")
|
||||
if cr.Spec.Git == nil {
|
||||
cr.Spec.Git = &config.Repo{}
|
||||
}
|
||||
switch subs[1] {
|
||||
case "repository":
|
||||
cr.Spec.Git.Repository = args[1]
|
||||
case "accessToken":
|
||||
cr.Spec.Git.AccessToken = args[1]
|
||||
case "secretName":
|
||||
cr.Spec.Git.SecretName = args[1]
|
||||
case "userName":
|
||||
cr.Spec.Git.UserName = args[1]
|
||||
cr.Spec.Git.UserName = tArg1
|
||||
default:
|
||||
return errors.New(arg + " does not match any cr spec")
|
||||
}
|
||||
@@ -358,8 +277,6 @@ func (q *Qliksense) processSetOpsRunner(arg string, cr *api.QliksenseCR) error {
|
||||
cr.Spec.OpsRunner.WatchBranch = args[1]
|
||||
case "image":
|
||||
cr.Spec.OpsRunner.Image = args[1]
|
||||
case "crPvc":
|
||||
cr.Spec.OpsRunner.CrPvc = args[1]
|
||||
default:
|
||||
return errors.New(arg + " does not match any cr spec")
|
||||
}
|
||||
@@ -521,8 +438,8 @@ func (q *Qliksense) SetUpQliksenseContext(contextName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// set the encrypted default mongo
|
||||
return q.SetSecrets([]string{`qliksense.mongoDbUri="mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"`}, false, false)
|
||||
// set the encrypted default mongo for the context in current CR
|
||||
return q.SetSecrets([]string{fmt.Sprintf("qliksense.mongodbUri=mongodb://%s-mongodb:27017/qliksense?ssl=false", contextName)}, false, false)
|
||||
}
|
||||
|
||||
func validateInput(input string) (string, error) {
|
||||
|
||||
@@ -29,7 +29,7 @@ const (
|
||||
var targetFileStringTemplate = `
|
||||
apiVersion: v1
|
||||
data:
|
||||
mongoDbUri: %s
|
||||
mongodbUri: %s
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: testctx-qliksense-senseinstaller
|
||||
@@ -96,7 +96,6 @@ metadata:
|
||||
name: qlik-default
|
||||
spec:
|
||||
profile: docker-desktop
|
||||
rotateKeys: "yes"
|
||||
releaseName: qlik-default
|
||||
`
|
||||
qlikDefaultContext := "qlik-default"
|
||||
@@ -244,7 +243,7 @@ func TestSetOtherConfigs(t *testing.T) {
|
||||
q: &Qliksense{
|
||||
QliksenseHome: testDir,
|
||||
},
|
||||
args: []string{"profile=minikube", "rotateKeys=yes", "storageClassName=efs", "opsRunner.enabled=yes", "opsRunner.schedule=30 * * * *", "git.repository=master", "git.userName=foo", "git.accessToken=1234"},
|
||||
args: []string{"profile=minikube", "storageClassName=efs", "opsRunner.enabled=yes", "opsRunner.schedule=30 * * * *", "git.repository=master", "git.userName=foo", "git.accessToken=1234"},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
@@ -254,7 +253,7 @@ func TestSetOtherConfigs(t *testing.T) {
|
||||
q: &Qliksense{
|
||||
QliksenseHome: testDir,
|
||||
},
|
||||
args: []string{"someconfig=somevalue, opsRunner.schedule=bar", "opsRunner.enabled=bar", "git.foo=bar", "rotateKeys=bar"},
|
||||
args: []string{"someconfig=somevalue, opsRunner.schedule=bar", "opsRunner.enabled=bar", "git.foo=bar"},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -296,7 +295,7 @@ func TestSetConfigs(t *testing.T) {
|
||||
q: &Qliksense{
|
||||
QliksenseHome: testDir,
|
||||
},
|
||||
args: []string{"qliksense.acceptEULA=\"yes\"", "qliksense.mongoDbUri=\"mongo://mongo:3307\""},
|
||||
args: []string{"qliksense.acceptEULA=\"yes\"", "qliksense.mongodbUri=\"mongo://mongo:3307\""},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
@@ -572,7 +571,7 @@ func Test_SetSecrets(t *testing.T) {
|
||||
QliksenseHome: testDir,
|
||||
},
|
||||
args: args{
|
||||
args: []string{"qliksense.mongoDbUri=\"mongodb://qlik-default-mongodb:27017/qliksense?ssl=false\""},
|
||||
args: []string{"qliksense.mongodbUri=\"mongodb://qlik-default-mongodb:27017/qliksense?ssl=false\""},
|
||||
isSecretSet: false,
|
||||
},
|
||||
wantErr: false,
|
||||
@@ -583,7 +582,7 @@ func Test_SetSecrets(t *testing.T) {
|
||||
QliksenseHome: testDir,
|
||||
},
|
||||
args: args{
|
||||
args: []string{"qliksense.mongoDbUri=bW9uZ29kYjovL3FsaWstZGVmYXVsdC1tb25nb2RiOjI3MDE3L3FsaWtzZW5zZT9zc2w9ZmFsc2U="},
|
||||
args: []string{"qliksense.mongodbUri=bW9uZ29kYjovL3FsaWstZGVmYXVsdC1tb25nb2RiOjI3MDE3L3FsaWtzZW5zZT9zc2w9ZmFsc2U="},
|
||||
isSecretSet: false,
|
||||
base64: true,
|
||||
},
|
||||
@@ -595,7 +594,7 @@ func Test_SetSecrets(t *testing.T) {
|
||||
QliksenseHome: testDir,
|
||||
},
|
||||
args: args{
|
||||
args: []string{"qliksense.mongoDbUri=\"mongo://mongo:3307\""},
|
||||
args: []string{"qliksense.mongodbUri=\"mongo://mongo:3307\""},
|
||||
isSecretSet: true,
|
||||
},
|
||||
wantErr: false,
|
||||
@@ -606,7 +605,7 @@ func Test_SetSecrets(t *testing.T) {
|
||||
QliksenseHome: testDir,
|
||||
},
|
||||
args: args{
|
||||
args: []string{"qliksense.mongoDbUri=\"mongodb://qlik-default-mongodb:27017/qliksense?ssl=false\""},
|
||||
args: []string{"qliksense.mongodbUri=\"mongodb://qlik-default-mongodb:27017/qliksense?ssl=false\""},
|
||||
isSecretSet: true,
|
||||
},
|
||||
wantErr: false,
|
||||
@@ -744,7 +743,6 @@ metadata:
|
||||
name: qlik-default
|
||||
spec:
|
||||
profile: docker-desktop
|
||||
rotateKeys: "yes"
|
||||
releaseName: qlik-default
|
||||
`
|
||||
qlikDefaultContext := "qlik-default"
|
||||
@@ -763,7 +761,6 @@ metadata:
|
||||
name: qlik1
|
||||
spec:
|
||||
profile: docker-desktop
|
||||
rotateKeys: "yes"
|
||||
releaseName: qlik1`
|
||||
|
||||
contextYaml2 :=
|
||||
@@ -774,7 +771,6 @@ metadata:
|
||||
name: qlik2
|
||||
spec:
|
||||
profile: docker-desktop
|
||||
rotateKeys: "yes"
|
||||
releaseName: qlik2`
|
||||
|
||||
contextsDir := filepath.Join(testDir, contexts, "qlik1")
|
||||
|
||||
128
pkg/qliksense/context_configs_unset.go
Normal file
128
pkg/qliksense/context_configs_unset.go
Normal 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
|
||||
}
|
||||
107
pkg/qliksense/context_configs_unset_test.go
Normal file
107
pkg/qliksense/context_configs_unset_test.go
Normal 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)
|
||||
}
|
||||
@@ -5,7 +5,15 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
apixv1beta1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
)
|
||||
|
||||
type CrdCommandOptions struct {
|
||||
@@ -20,11 +28,11 @@ func (q *Qliksense) ViewCrds(opts *CrdCommandOptions) error {
|
||||
fmt.Println("cannot get the current-context cr", err)
|
||||
return err
|
||||
}
|
||||
engineCRD, err := getQliksenseInitCrd(qcr)
|
||||
engineCRD, err := getQliksenseInitCrds(qcr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
customCrd, err := getCustomCrd(qcr)
|
||||
customCrd, err := getCustomCrds(qcr)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -51,12 +59,12 @@ func (q *Qliksense) InstallCrds(opts *CrdCommandOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if engineCRD, err := getQliksenseInitCrd(qcr); err != nil {
|
||||
if engineCRD, err := getQliksenseInitCrds(qcr); err != nil {
|
||||
return err
|
||||
} else if err = qapi.KubectlApply(engineCRD, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
if customCrd, err := getCustomCrd(qcr); err != nil {
|
||||
if customCrd, err := getCustomCrds(qcr); err != nil {
|
||||
return err
|
||||
} else if customCrd != "" {
|
||||
if err = qapi.KubectlApply(customCrd, ""); err != nil {
|
||||
@@ -73,7 +81,7 @@ func (q *Qliksense) InstallCrds(opts *CrdCommandOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getQliksenseInitCrd(qcr *qapi.QliksenseCR) (string, error) {
|
||||
func getQliksenseInitCrds(qcr *qapi.QliksenseCR) (string, error) {
|
||||
var repoPath string
|
||||
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 {
|
||||
// older version of qliksense-init used
|
||||
qInitMsPath = filepath.Join(repoPath, "manifests/base/manifests/qliksense-init")
|
||||
qInitMsPath = filepath.Join(repoPath, Q_INIT_CRD_PATH)
|
||||
if _, err := os.Lstat(qInitMsPath); err != nil {
|
||||
// older version of qliksense-init used
|
||||
qInitMsPath = filepath.Join(repoPath, "manifests/base/manifests/qliksense-init")
|
||||
}
|
||||
}
|
||||
|
||||
qInitByte, err := ExecuteKustomizeBuild(qInitMsPath)
|
||||
if err != nil {
|
||||
fmt.Println("cannot generate crds for qliksense-init", err)
|
||||
@@ -98,7 +110,7 @@ func getQliksenseInitCrd(qcr *qapi.QliksenseCR) (string, error) {
|
||||
return string(qInitByte), nil
|
||||
}
|
||||
|
||||
func getCustomCrd(qcr *qapi.QliksenseCR) (string, error) {
|
||||
func getCustomCrds(qcr *qapi.QliksenseCR) (string, error) {
|
||||
crdPath := qcr.GetCustomCrdsPath()
|
||||
if crdPath == "" {
|
||||
return "", nil
|
||||
@@ -110,3 +122,77 @@ func getCustomCrd(qcr *qapi.QliksenseCR) (string, error) {
|
||||
}
|
||||
return string(qInitByte), nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) CheckAllCrdsInstalled() (bool, error) {
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
customResourceDefinitionInterface, err := getCustomResourceDefinitionInterface()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if engineCRDs, err := getQliksenseInitCrds(qcr); err != nil {
|
||||
return false, err
|
||||
} else if allInstalled, err := checkCrdsInstalled(engineCRDs, customResourceDefinitionInterface); err != nil {
|
||||
return false, err
|
||||
} else if !allInstalled {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if customCrds, err := getCustomCrds(qcr); err != nil {
|
||||
return false, err
|
||||
} else if allInstalled, err := checkCrdsInstalled(customCrds, customResourceDefinitionInterface); err != nil {
|
||||
return false, err
|
||||
} else if !allInstalled {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if allInstalled, err := checkCrdsInstalled(q.GetOperatorCRDString(), customResourceDefinitionInterface); err != nil {
|
||||
return false, err
|
||||
} else if !allInstalled {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func checkCrdsInstalled(crds string, customResourceDefinitionInterface apixv1beta1client.CustomResourceDefinitionInterface) (bool, error) {
|
||||
kuzResourceFactory := resmap.NewFactory(resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl()), nil)
|
||||
if kuzResMap, err := kuzResourceFactory.NewResMapFromBytes([]byte(crds)); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
for _, kuzRes := range kuzResMap.Resources() {
|
||||
if customResourceDefinition, err := customResourceDefinitionInterface.Get(kuzRes.GetName(), v1.GetOptions{}); err != nil && apierrors.IsNotFound(err) {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
} else if customResourceDefinition == nil {
|
||||
return false, fmt.Errorf("failed looking up crd: %v", kuzRes.GetName())
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getCustomResourceDefinitionInterface() (apixv1beta1client.CustomResourceDefinitionInterface, error) {
|
||||
homeDir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kubeconfigPath := filepath.Join(homeDir, ".kube", "config")
|
||||
k8sRestConfig, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apixClient, err := apixv1beta1client.NewForConfig(k8sRestConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return apixClient.CustomResourceDefinitions(), nil
|
||||
}
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
apixv1beta1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
|
||||
kapi_config "github.com/qlik-oss/k-apis/pkg/config"
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
@@ -13,7 +23,7 @@ func TestGetQliksenseInitCrd(t *testing.T) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
crdFromContextConfig, err := getQliksenseInitCrd(&qapi.QliksenseCR{
|
||||
crdFromContextConfig, err := getQliksenseInitCrds(&qapi.QliksenseCR{
|
||||
KApiCr: kapi_config.KApiCr{
|
||||
Spec: &kapi_config.CRSpec{
|
||||
ManifestsRoot: someTmpRepoPath,
|
||||
@@ -24,7 +34,7 @@ func TestGetQliksenseInitCrd(t *testing.T) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
crdFromDownloadedConfig, err := getQliksenseInitCrd(&qapi.QliksenseCR{
|
||||
crdFromDownloadedConfig, err := getQliksenseInitCrds(&qapi.QliksenseCR{
|
||||
KApiCr: kapi_config.KApiCr{
|
||||
Spec: &kapi_config.CRSpec{
|
||||
ManifestsRoot: "",
|
||||
@@ -39,3 +49,87 @@ func TestGetQliksenseInitCrd(t *testing.T) {
|
||||
t.Fatalf("expected %v to equal %v, but they didn't", crdFromContextConfig, crdFromDownloadedConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAllCrdsInstalled(t *testing.T) {
|
||||
t.Skip("Skipping this test because it makes kubernetes calls")
|
||||
|
||||
tmpQlikSenseHome, err := ioutil.TempDir("", "tmp-qlik-sense-home-")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating tmp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpQlikSenseHome)
|
||||
|
||||
setupQliksenseTestDefaultContext(t, tmpQlikSenseHome, `
|
||||
apiVersion: qlik.com/v1
|
||||
kind: Qliksense
|
||||
metadata:
|
||||
name: qlik-default
|
||||
spec:
|
||||
profile: docker-desktop
|
||||
`)
|
||||
|
||||
q := &Qliksense{
|
||||
QliksenseHome: tmpQlikSenseHome,
|
||||
CrdBox: packr.New("crds", "./crds"),
|
||||
}
|
||||
|
||||
if err := q.FetchQK8s("v1.50.3"); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if allInstalled, err := q.CheckAllCrdsInstalled(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
} else if allInstalled {
|
||||
t.Fatal("expected crds to NOT be installed at this point")
|
||||
}
|
||||
|
||||
if err := q.InstallCrds(&CrdCommandOptions{All: true}); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
} else if allInstalled, err := q.CheckAllCrdsInstalled(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
} else if !allInstalled {
|
||||
t.Fatal("expected crds to BE installed at this point")
|
||||
}
|
||||
|
||||
//cleanup:
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
customResourceDefinitionInterface, err := getCustomResourceDefinitionInterface()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if engineCRDs, err := getQliksenseInitCrds(qcr); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
} else if err := deleteCrds(engineCRDs, customResourceDefinitionInterface); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if customCrd, err := getCustomCrds(qcr); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
} else if err := deleteCrds(customCrd, customResourceDefinitionInterface); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := deleteCrds(q.GetOperatorCRDString(), customResourceDefinitionInterface); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteCrds(crds string, customResourceDefinitionInterface apixv1beta1client.CustomResourceDefinitionInterface) error {
|
||||
kuzResourceFactory := resmap.NewFactory(resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl()), nil)
|
||||
if kuzResMap, err := kuzResourceFactory.NewResMapFromBytes([]byte(crds)); err != nil {
|
||||
return err
|
||||
} else {
|
||||
for _, kuzRes := range kuzResMap.Resources() {
|
||||
if err := customResourceDefinitionInterface.Delete(kuzRes.GetName(), &v1.DeleteOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,19 +30,14 @@ const (
|
||||
|
||||
func (q *Qliksense) PullImages(version, profile string) error {
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
if version != "" {
|
||||
if !qConfig.IsRepoExistForCurrent(version) {
|
||||
if err := q.FetchQK8s(version); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !qcr.IsRepoExist() {
|
||||
return errors.New("ManifestsRoot not found")
|
||||
if err := fetchAndUpdateCR(qConfig, version); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if profile != "" {
|
||||
qcr.Spec.Profile = profile
|
||||
@@ -160,7 +155,10 @@ func (q *Qliksense) PushImagesForCurrentCR() error {
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
return err
|
||||
} else if err := ensureImageRegistrySetInCR(qcr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
version := qcr.GetLabelFromCr("version")
|
||||
profile := qcr.Spec.Profile
|
||||
repoDir := qcr.Spec.ManifestsRoot
|
||||
@@ -344,3 +342,20 @@ func (q *Qliksense) writeVersionOutput(versionOut *VersionOutput, imagesDir, ver
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validatePullPushFlagsOnInstall(cr *qapi.QliksenseCR, pull, push bool) error {
|
||||
if pull && !push {
|
||||
fmt.Printf("WARNING: pulling images without pushing them\n")
|
||||
}
|
||||
if push {
|
||||
return ensureImageRegistrySetInCR(cr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureImageRegistrySetInCR(cr *qapi.QliksenseCR) error {
|
||||
if registry := cr.Spec.GetImageRegistry(); registry == "" {
|
||||
return errors.New("no image registry set in the CR; to set it use: qliksense config set-image-registry")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ spec:
|
||||
return false
|
||||
}
|
||||
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")
|
||||
}
|
||||
@@ -235,19 +235,19 @@ spec:
|
||||
t.Fatal("expected to find the GitOps image in the list, but it wasn't there")
|
||||
}
|
||||
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")
|
||||
}
|
||||
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")
|
||||
}
|
||||
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
|
||||
value: %s
|
||||
manifestsRoot: %s
|
||||
rotateKeys: "yes"
|
||||
releaseName: qlik-default
|
||||
`, version, registry.url, manifestsRootDir)
|
||||
setupQliksenseTestDefaultContext(t, tmpQlikSenseHome, cr)
|
||||
@@ -315,23 +314,23 @@ spec:
|
||||
return err
|
||||
}
|
||||
|
||||
transformersDir := path.Join(manifestsRootDir, "transformers")
|
||||
transformersDir := path.Join(manifestsRootDir, "manifests", "base", "transformers", "release")
|
||||
if err := os.MkdirAll(transformersDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(transformersDir, "qseokversion.yaml"), []byte(`
|
||||
apiVersion: qlik.com/v1
|
||||
kind: SelectivePatch
|
||||
if err := ioutil.WriteFile(path.Join(transformersDir, "annotations.yaml"), []byte(`
|
||||
apiVersion: builtin
|
||||
kind: AnnotationsTransformer
|
||||
metadata:
|
||||
name: qseokversion
|
||||
enabled: true
|
||||
patches:
|
||||
- target:
|
||||
kind: HelmChart
|
||||
labelSelector: name!=qliksense-init
|
||||
patch: |-
|
||||
chartName: qliksense
|
||||
chartVersion: 1.21.23
|
||||
name: common-annotations
|
||||
annotations:
|
||||
app.kubernetes.io/name: qliksense
|
||||
app.kubernetes.io/instance: $(PREFIX)
|
||||
app.kubernetes.io/version: 1.21.23
|
||||
app.kubernetes.io/managed-by: qliksense-operator
|
||||
fieldSpecs:
|
||||
- path: metadata/annotations
|
||||
create: true
|
||||
`), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package qliksense
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -54,10 +53,7 @@ func (q *Qliksense) FetchK8sWithOpts(opts *FetchCommandOptions) error {
|
||||
cr.SetFetchUrl(opts.GitUrl)
|
||||
}
|
||||
v := getVersion(opts, cr)
|
||||
if v == "" {
|
||||
return errors.New("Cannot find gitref/tag/branch/version to fetch")
|
||||
}
|
||||
if qConfig.IsRepoExistForCurrent(v) {
|
||||
if v != "" && qConfig.IsRepoExistForCurrent(v) {
|
||||
if opts.Overwrite || getVerionsOverwriteConfirmation(v) == "y" {
|
||||
if err := qConfig.DeleteRepoForCurrent(v); err != nil {
|
||||
return err
|
||||
@@ -98,7 +94,6 @@ func fetchAndUpdateCR(qConfig *qapi.QliksenseConfig, version string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
destDir := qConfig.BuildRepoPath(version)
|
||||
fmt.Printf("fetching version [%s] from %s\n", version, qcr.GetFetchUrl())
|
||||
if err := qapi.CopyDirectory(tempDest, destDir); err != nil {
|
||||
@@ -142,7 +137,7 @@ func getVersion(opts *FetchCommandOptions, qcr *qapi.QliksenseCR) string {
|
||||
|
||||
func getVerionsOverwriteConfirmation(version string) string {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Println("The version [" + version + "] already exist")
|
||||
fmt.Println("The version [" + version + "] already exists")
|
||||
cfm := "n"
|
||||
for {
|
||||
fmt.Print("Do you want to delete and fetch again [y/N]: ")
|
||||
|
||||
@@ -16,7 +16,7 @@ func TestFetchAndUpdateCR(t *testing.T) {
|
||||
}
|
||||
q.SetUpQliksenseContext("test1")
|
||||
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.FailNow()
|
||||
}
|
||||
@@ -28,8 +28,8 @@ func TestFetchAndUpdateCR(t *testing.T) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if cr.Spec.ManifestsRoot != "contexts/test1/qlik-k8s/v0.0.2" {
|
||||
t.Log("actual path: " + cr.Spec.ManifestsRoot + ", expected path: contexts/test1/qlik-k8s/v0.0.2")
|
||||
if cr.Spec.ManifestsRoot != "contexts/test1/qlik-k8s/v0.0.8" {
|
||||
t.Log("actual path: " + cr.Spec.ManifestsRoot + ", expected path: contexts/test1/qlik-k8s/v0.0.8")
|
||||
t.FailNow()
|
||||
}
|
||||
//testing latest tag is fetched
|
||||
@@ -43,7 +43,7 @@ func TestFetchAndUpdateCR(t *testing.T) {
|
||||
cr = &qapi.QliksenseCR{}
|
||||
qapi.ReadFromFile(cr, actualCrFile)
|
||||
v := cr.GetLabelFromCr("version")
|
||||
if v == "" || v == "v0.0.2" {
|
||||
if v == "" || v == "v0.0.8" {
|
||||
t.Log("should get latest but got version: " + v)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
"github.com/qlik-oss/k-apis/pkg/git"
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
@@ -22,8 +24,20 @@ func (q *Qliksense) GetInstallableVersions(opts *LsRemoteCmdOptions) error {
|
||||
}
|
||||
|
||||
var repoPath string
|
||||
var auth transport.AuthMethod
|
||||
if qcr.Spec.GetManifestsRoot() != "" {
|
||||
repoPath = qcr.Spec.GetManifestsRoot()
|
||||
encKey, err := qConfig.GetEncryptionKeyFor(qcr.GetName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
accessToken := qcr.GetFetchAccessToken(encKey)
|
||||
if accessToken != "" {
|
||||
auth = &http.BasicAuth{
|
||||
Username: "something",
|
||||
Password: accessToken,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repoPath, err = DownloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master")
|
||||
if err != nil {
|
||||
@@ -36,7 +50,7 @@ func (q *Qliksense) GetInstallableVersions(opts *LsRemoteCmdOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
remoteRefsList, err := git.GetRemoteRefs(r, nil,
|
||||
remoteRefsList, err := git.GetRemoteRefs(r, auth,
|
||||
&git.RemoteRefConstraints{
|
||||
Include: true,
|
||||
Sort: true,
|
||||
@@ -96,13 +110,18 @@ func getLatestTag(repoUrl, accessToken string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
r, err := git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
remoteRefsList, err := git.GetRemoteRefs(r, nil,
|
||||
var auth transport.AuthMethod
|
||||
if accessToken != "" {
|
||||
auth = &http.BasicAuth{
|
||||
Username: "something",
|
||||
Password: accessToken,
|
||||
}
|
||||
}
|
||||
remoteRefsList, err := git.GetRemoteRefs(r, auth,
|
||||
&git.RemoteRefConstraints{
|
||||
Include: true,
|
||||
Sort: true,
|
||||
@@ -144,7 +163,7 @@ func getLatestTag(repoUrl, accessToken string) (string, error) {
|
||||
v, err := semver.NewVersion(sv)
|
||||
if err != nil {
|
||||
// it may happen, in the repo some tags may not conform to semver
|
||||
fmt.Print("Unconform tags: " + sv)
|
||||
//fmt.Println("the tag is not conform to semver: " + sv)
|
||||
continue
|
||||
}
|
||||
if maxSem == nil || maxSem.LessThan(v) {
|
||||
|
||||
@@ -17,9 +17,9 @@ func TestGetLatestTag(t *testing.T) {
|
||||
t.Log(err)
|
||||
t.Log(sv)
|
||||
}
|
||||
baseV, _ := semver.NewVersion("v0.0.7")
|
||||
baseV, _ := semver.NewVersion("v0.0.8")
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,59 +7,112 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mattn/go-tty"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/qlik-oss/k-apis/pkg/config"
|
||||
"github.com/qlik-oss/k-apis/pkg/cr"
|
||||
"sigs.k8s.io/kustomize/api/filesys"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
type InstallCommandOptions struct {
|
||||
StorageClass string
|
||||
MongoDbUri string
|
||||
RotateKeys string
|
||||
StorageClass string
|
||||
MongodbUri 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
|
||||
// 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)
|
||||
func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) error {
|
||||
|
||||
// fetch the version
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
if !keepPatchFiles {
|
||||
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
|
||||
fmt.Printf("error removing temporary changes to the config: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
fmt.Println("cannot get the current-context cr", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !qcr.IsRepoExist() {
|
||||
if err := fetchAndUpdateCR(qConfig, version); err != nil {
|
||||
return err
|
||||
} else if qcr, err = qConfig.GetCurrentCR(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.AcceptEULA != "" && opts.AcceptEULA != "yes" {
|
||||
enforceEula()
|
||||
} else if opts.AcceptEULA == "" && !qcr.IsEULA() {
|
||||
enforceEula()
|
||||
}
|
||||
qcr.SetEULA("yes")
|
||||
if opts.MongoDbUri != "" {
|
||||
qcr.Spec.AddToSecrets("qliksense", "mongoDbUri", opts.MongoDbUri, "")
|
||||
|
||||
if opts.MongodbUri != "" {
|
||||
qcr.Spec.AddToSecrets("qliksense", "mongodbUri", opts.MongodbUri, "")
|
||||
}
|
||||
if opts.StorageClass != "" {
|
||||
qcr.Spec.StorageClassName = opts.StorageClass
|
||||
}
|
||||
if opts.RotateKeys != "" {
|
||||
qcr.Spec.RotateKeys = opts.RotateKeys
|
||||
}
|
||||
qConfig.WriteCurrentContextCR(qcr)
|
||||
|
||||
if err := applyImagePullSecret(qConfig); err != nil {
|
||||
if err := qConfig.WriteCurrentContextCR(qcr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if acceptEULA is yes or not
|
||||
if !qcr.IsEULA() {
|
||||
return errors.New(agreementTempalte + "\n Please do $ qliksense install --acceptEULA=yes\n")
|
||||
if opts.CleanPatchFiles {
|
||||
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
|
||||
fmt.Printf("error removing temporary changes to the config: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// for debugging purpose
|
||||
if opts.DryRun {
|
||||
// generate patches
|
||||
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
|
||||
@@ -73,13 +126,25 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, kee
|
||||
return err
|
||||
}
|
||||
|
||||
// create patch dependent resoruces
|
||||
fmt.Println("Installing resoruces used kuztomize patch")
|
||||
if err := q.createK8sResoruceBeforePatch(qcr); err != nil {
|
||||
// create patch dependent resources
|
||||
fmt.Println("Installing resources used by the kuztomize patch")
|
||||
if err := q.createK8sResourceBeforePatch(qcr); err != nil {
|
||||
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
|
||||
// get decrypted cr
|
||||
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
|
||||
@@ -88,35 +153,16 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, kee
|
||||
return q.applyCR(dcr)
|
||||
}
|
||||
}
|
||||
if !qcr.IsRepoExist() {
|
||||
if err := fetchAndUpdateCR(qConfig, version); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
qcr, err = qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
fmt.Println("cannot get the current-context cr", err)
|
||||
return err
|
||||
} else if qcr.Spec.GetManifestsRoot() == "" {
|
||||
return errors.New("cannot get the manifest root. Use qliksense fetch <version> or qliksense set manifestsRoot")
|
||||
}
|
||||
|
||||
// install generated manifests into cluster
|
||||
fmt.Println("Installing generated manifests into cluster")
|
||||
|
||||
fmt.Println("Installing generated manifests into the cluster")
|
||||
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
|
||||
return err
|
||||
} else if err := q.applyConfigToK8s(dcr); err != nil {
|
||||
fmt.Println("cannot do kubectl apply on manifests")
|
||||
return err
|
||||
} else {
|
||||
if IsQliksenseInstalled(dcr.GetName()) {
|
||||
return q.UpgradeQK8s(keepPatchFiles)
|
||||
}
|
||||
if err := q.applyConfigToK8s(dcr); err != nil {
|
||||
fmt.Println("cannot do kubectl apply on manifests")
|
||||
return err
|
||||
} else {
|
||||
return q.applyCR(dcr)
|
||||
}
|
||||
return q.applyCR(dcr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +227,7 @@ images:
|
||||
func (q *Qliksense) applyCR(cr *qapi.QliksenseCR) error {
|
||||
// install operator cr into cluster
|
||||
//get the current context cr
|
||||
fmt.Println("Install operator CR into cluster")
|
||||
fmt.Println("Installing operator CR into the cluster")
|
||||
r, err := cr.GetString()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -193,7 +239,7 @@ func (q *Qliksense) applyCR(cr *qapi.QliksenseCR) error {
|
||||
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 _, nv := range nvs {
|
||||
if isK8sSecretNeedToCreate(nv) {
|
||||
@@ -212,3 +258,26 @@ func (q *Qliksense) createK8sResoruceBeforePatch(qcr *qapi.QliksenseCR) error {
|
||||
func isK8sSecretNeedToCreate(nv config.NameValue) bool {
|
||||
return nv.ValueFrom != nil
|
||||
}
|
||||
|
||||
func enforceEula() {
|
||||
fmt.Println(eulaText)
|
||||
fmt.Print(eulaPrompt)
|
||||
answer := readAnswerFromTty()
|
||||
if strings.ToLower(answer) != "y" {
|
||||
fmt.Println(eulaErrorInstruction)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func readAnswerFromTty() string {
|
||||
t, err := tty.Open()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer t.Close()
|
||||
answer, err := t.ReadString()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return answer
|
||||
}
|
||||
|
||||
@@ -5,20 +5,18 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
func TestCreateK8sResoruceBeforePatch(t *testing.T) {
|
||||
func TestCreateK8sResourceBeforePatch(t *testing.T) {
|
||||
td := setup()
|
||||
sampleCr := `
|
||||
apiVersion: qlik.com/v1
|
||||
@@ -43,20 +41,12 @@ spec:
|
||||
value: "yes"
|
||||
secrets:
|
||||
qliksense:
|
||||
- name: mongoDbUri
|
||||
- name: mongodbUri
|
||||
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
|
||||
profile: docker-desktop
|
||||
rotateKeys: "yes"`
|
||||
profile: docker-desktop`
|
||||
|
||||
crFile := filepath.Join(testDir, "install_test.yaml")
|
||||
ioutil.WriteFile(crFile, []byte(sampleCr), 0644)
|
||||
q := New(testDir)
|
||||
file, e := os.Open(crFile)
|
||||
if e != nil {
|
||||
t.Log(e)
|
||||
t.FailNow()
|
||||
}
|
||||
if err := q.LoadCr(file, false); err != nil {
|
||||
if err := q.LoadCr([]byte(sampleCr), false); err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
@@ -66,7 +56,7 @@ spec:
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
if err = q.createK8sResoruceBeforePatch(cr); err != nil {
|
||||
if err = q.createK8sResourceBeforePatch(cr); err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
21
pkg/qliksense/keys.go
Normal file
21
pkg/qliksense/keys.go
Normal 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
|
||||
}
|
||||
@@ -276,9 +276,11 @@ func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
|
||||
configPath := filepath.Join(tmpDir, "config")
|
||||
if repo, err := kapis_git.CloneRepository(configPath, defaultConfigRepoGitUrl, nil); err != nil {
|
||||
t.Fatalf("unexpected error: %v\n", err)
|
||||
} else if err := kapis_git.Checkout(repo, "e38df644e759abf0b5941c1511d1a2cd5e3c42fa", "", nil); err != nil {
|
||||
} else if err := kapis_git.Checkout(repo, "e38df644e759abf0b5941c1511d1a2cd5e3c42fa", "commit-e38df644e759abf0b5941c1511d1a2cd5e3c42fa", nil); err != nil {
|
||||
t.Fatalf("unexpected error: %v\n", err)
|
||||
}
|
||||
//tmpDir := "/var/folders/mf/5hs1qkq508q_scjbhxhmf9qwjrp346/T/679268230"
|
||||
//configPath := "/var/folders/mf/5hs1qkq508q_scjbhxhmf9qwjrp346/T/679268230/config"
|
||||
|
||||
cr := &config.CRSpec{
|
||||
ManifestsRoot: configPath,
|
||||
@@ -310,8 +312,8 @@ func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
|
||||
}
|
||||
break
|
||||
}
|
||||
if resource["kind"].(string) == "Secret" && strings.Contains(resource["metadata"].(map[interface {}]interface {})["name"].(string), "users-secrets-") {
|
||||
keyIdBase64 = resource["data"].(map[interface {}]interface {})["tokenAuthPrivateKeyId"].(string)
|
||||
if resource["kind"].(string) == "Secret" && strings.Contains(resource["metadata"].(map[interface{}]interface{})["name"].(string), "users-secrets-") {
|
||||
keyIdBase64 = resource["data"].(map[interface{}]interface{})["tokenAuthPrivateKeyId"].(string)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,17 +3,13 @@ package qliksense
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
func (q *Qliksense) LoadCr(reader io.Reader, overwriteExistingContext bool) error {
|
||||
if crBytes, err := ioutil.ReadAll(reader); err != nil {
|
||||
return err
|
||||
} else if crName, err := q.loadCrStringIntoFileSystem(string(crBytes), overwriteExistingContext); err != nil {
|
||||
func (q *Qliksense) LoadCr(crBytes []byte, overwriteExistingContext bool) error {
|
||||
if crName, err := q.loadCrStringIntoFileSystem(string(crBytes), overwriteExistingContext); err != nil {
|
||||
return err
|
||||
} else {
|
||||
fmt.Println("cr name: [ " + crName + " ] has been loaded")
|
||||
@@ -21,16 +17,6 @@ func (q *Qliksense) LoadCr(reader io.Reader, overwriteExistingContext bool) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) IsEulaAcceptedInCrFile(reader io.Reader) (bool, error) {
|
||||
if crBytes, err := ioutil.ReadAll(reader); err != nil {
|
||||
return false, err
|
||||
} else if cr, err := qapi.CreateCRObjectFromString(string(crBytes)); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
return cr.IsEULA(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Qliksense) loadCrStringIntoFileSystem(crstr string, overwriteExistingContext bool) (string, error) {
|
||||
cr, err := qapi.CreateCRObjectFromString(crstr)
|
||||
if err != nil {
|
||||
@@ -69,8 +55,8 @@ func (q *Qliksense) loadCrStringIntoFileSystem(crstr string, overwriteExistingCo
|
||||
}
|
||||
}
|
||||
}
|
||||
if cr.Spec.FetchSource != nil && cr.Spec.FetchSource.AccessToken != "" {
|
||||
if err := cr.SetFetchAccessToken(cr.Spec.FetchSource.AccessToken, encryptionKey); err != nil {
|
||||
if cr.Spec.Git != nil && cr.Spec.Git.AccessToken != "" {
|
||||
if err := cr.SetFetchAccessToken(cr.Spec.Git.AccessToken, encryptionKey); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
@@ -35,10 +32,9 @@ spec:
|
||||
value: "yes"
|
||||
secrets:
|
||||
qliksense:
|
||||
- name: mongoDbUri
|
||||
- name: mongodbUri
|
||||
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
|
||||
profile: docker-desktop
|
||||
rotateKeys: "yes"`
|
||||
profile: docker-desktop`
|
||||
sampleCr2 := `
|
||||
apiVersion: qlik.com/v1
|
||||
kind: Qliksense
|
||||
@@ -62,10 +58,9 @@ spec:
|
||||
value: "yes"
|
||||
secrets:
|
||||
qliksense:
|
||||
- name: mongoDbUri
|
||||
- name: mongodbUri
|
||||
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
|
||||
profile: docker-desktop
|
||||
rotateKeys: "yes"`
|
||||
profile: docker-desktop`
|
||||
|
||||
duplicateCr := `
|
||||
apiVersion: qlik.com/v1
|
||||
@@ -79,30 +74,13 @@ spec:
|
||||
repository: https://github.com/ffoysal/qliksense-k8s
|
||||
accessToken: abababababababaab
|
||||
userName: "blblbl"`
|
||||
crFile1 := filepath.Join(testDir, "testcr1.yaml")
|
||||
ioutil.WriteFile(crFile1, []byte(sampleCr1), 0644)
|
||||
crFile2 := filepath.Join(testDir, "testcr2.yaml")
|
||||
ioutil.WriteFile(crFile2, []byte(sampleCr2), 0644)
|
||||
|
||||
dupCrFile := filepath.Join(testDir, "dupcr.yaml")
|
||||
ioutil.WriteFile(dupCrFile, []byte(duplicateCr), 0644)
|
||||
|
||||
q := New(testDir)
|
||||
file1, e := os.Open(crFile1)
|
||||
if e != nil {
|
||||
t.Log(e)
|
||||
t.FailNow()
|
||||
}
|
||||
if err := q.LoadCr(file1, false); err != nil {
|
||||
if err := q.LoadCr([]byte(sampleCr1), false); err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
file2, e := os.Open(crFile2)
|
||||
if e != nil {
|
||||
t.Log(e)
|
||||
t.FailNow()
|
||||
}
|
||||
if err := q.LoadCr(file2, false); err != nil {
|
||||
if err := q.LoadCr([]byte(sampleCr2), false); err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
@@ -128,12 +106,7 @@ spec:
|
||||
if qConfig.Spec.CurrentContext != "qlik-test3" {
|
||||
t.FailNow()
|
||||
}
|
||||
file, e := os.Open(dupCrFile)
|
||||
if e != nil {
|
||||
t.Log(e)
|
||||
t.FailNow()
|
||||
}
|
||||
if err := q.LoadCr(file, false); err == nil {
|
||||
if err := q.LoadCr([]byte(duplicateCr), false); err == nil {
|
||||
t.FailNow()
|
||||
}
|
||||
td()
|
||||
|
||||
@@ -41,7 +41,7 @@ func (q *Qliksense) getYamlFromPackrFile(packrFile string) string {
|
||||
if err != nil {
|
||||
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 {
|
||||
var resList []string
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
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
|
||||
// step2: run kustomize
|
||||
@@ -14,12 +14,10 @@ func (q *Qliksense) UpgradeQK8s(keepPatchFiles bool) error {
|
||||
|
||||
// fetch the version
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
if !keepPatchFiles {
|
||||
defer func() {
|
||||
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
|
||||
fmt.Printf("error removing temporary changes to the config: %v\n", err)
|
||||
}
|
||||
}()
|
||||
if cleanPatchFiles {
|
||||
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
|
||||
fmt.Printf("error removing temporary changes to the config: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
@@ -27,7 +25,6 @@ func (q *Qliksense) UpgradeQK8s(keepPatchFiles bool) error {
|
||||
fmt.Println("cannot get the current-context cr", err)
|
||||
return err
|
||||
}
|
||||
qcr.Spec.RotateKeys = "no"
|
||||
|
||||
dcr, err := qConfig.GetDecryptedCr(qcr)
|
||||
if err != nil {
|
||||
|
||||
23
update_kustomize.md
Normal file
23
update_kustomize.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Update Kustomize dependency
|
||||
|
||||
To update the kustomize dependency in `go.mod` file, run the following command for the vesion `v0.0.43`
|
||||
|
||||
```console
|
||||
GOPROXY=direct go get github.com/qlik-oss/kustomize/api@qlik/v0.0.43
|
||||
```
|
||||
|
||||
The above command will generate output something like this
|
||||
|
||||
```console
|
||||
go: downloading github.com/qlik-oss/kustomize v3.3.2-0.20200820111149-1a59db58525f+incompatible
|
||||
go: github.com/qlik-oss/kustomize/api qlik/v0.0.43 => v0.5.2-0.20200820111149-1a59db58525f
|
||||
go get: github.com/qlik-oss/kustomize/api@v0.5.2-0.20200820111149-1a59db58525f: parsing go.mod:
|
||||
module declares its path as: sigs.k8s.io/kustomize/api
|
||||
but was required as: github.com/qlik-oss/kustomize/api
|
||||
```
|
||||
|
||||
So in the `go.mod` file write
|
||||
|
||||
```console
|
||||
sigs.k8s.io/kustomize/api => github.com/qlik-oss/kustomize/api v0.5.2-0.20200820111149-1a59db58525f
|
||||
```
|
||||
Reference in New Issue
Block a user