Compare commits

...

66 Commits

Author SHA1 Message Date
Sanat Nayar
fcbf22bcd5 test 2020-02-28 11:07:51 -05:00
Sanat Nayar
b4daf52ef5 readme config change 2020-02-20 14:46:49 -05:00
Sanat Nayar
a7e757e15f added readme for config 2020-02-20 14:30:55 -05:00
Sanat Nayar
c47aabc066 added readme for config 2020-02-20 14:28:20 -05:00
Sanat Nayar
78422af050 added readme for config 2020-02-20 14:23:05 -05:00
Boris Kuschel
cb515f216d Merge pull request #121 from qlik-oss/windows_ansi
Add ansi translator for windows
2020-02-20 08:46:21 -05:00
Boris Kuschel
f6eacefd82 Add ansi translator for windows
Signed-off-by: Boris Kuschel <boris.kuschel@qlik.com>
2020-02-20 07:40:27 -05:00
Sanat Nayar
2dd37ab985 Merge pull request #118 from qlik-oss/context_list
QlikSense config list-contexts
2020-02-19 14:58:50 -05:00
Sanat Nayar
8142bb5fa9 added formatting changes 2020-02-19 11:48:50 -05:00
Sanat Nayar
22ea225b8c added configs sub-command 2020-02-19 09:56:50 -05:00
Andriy Bulynko
2f854fd6e4 Fixing about command output 2020-02-19 09:38:07 -05:00
Andriy Bulynko
47bcc016fc About from current context (#117) 2020-02-19 09:28:21 -05:00
Ashwathi Shiva
4e2083309e Encrypt secret in the case: secret=false (#114)
qliksense config set-secrets <key>=<value> --secrets=false
2020-02-18 16:16:48 -05:00
Sanat Nayar
017aa63726 Merge pull request #98 from qlik-oss/upgrade_commmand
QlikSense Upgrade Command
2020-02-18 11:14:05 -05:00
Sanat Nayar
f4275a47ad added upgrade func 2020-02-14 15:39:06 -05:00
Sanat Nayar
75e4e43f9b added upgrade func 2020-02-14 15:14:48 -05:00
Sanat Nayar
21cbc0b44d added upgrade func 2020-02-14 14:22:54 -05:00
Sanat Nayar
003f7f31fc added upgrade func 2020-02-14 14:22:54 -05:00
Sanat Nayar
c3b8837402 added upgrade func 2020-02-14 14:22:54 -05:00
Sanat Nayar
838ed3069c init 2020-02-14 14:22:54 -05:00
Andriy Bulynko
5668da13a7 Creating uniquely named branches (#94) 2020-02-14 14:22:54 -05:00
Andriy Bulynko
2ed9bcb7bf Placing ejson ENV var mangling behind a check (#93) 2020-02-14 14:22:54 -05:00
Ashwathi Shiva
505bb51f95 Added tests (#90)
* Tests added
2020-02-14 14:22:54 -05:00
Andriy Bulynko
60feff3292 Faster-running kuz test (#89) 2020-02-14 14:22:54 -05:00
Ashwathi Shiva
af1afbef8f Support configuration through multiple args (#81)
* support configuration with multiple args
2020-02-14 14:22:54 -05:00
Boris Kuschel
3c4ada848a Make file paths portable
Signed-off-by: Boris Kuschel <boris.kuschel@qlik.com>
2020-02-14 14:22:54 -05:00
Boris Kuschel
6da6415c44 Make makefile portable:
Signed-off-by: Boris Kuschel <boris.kuschel@qlik.com>
2020-02-14 14:22:54 -05:00
Andriy Bulynko
a3287fc1a9 Storing/referencing ejson keys at path: ${qliksenseHome}/ejson/keys (#77) 2020-02-14 14:22:54 -05:00
Foysal Iqbal
39607652a8 Fix default install (#79) 2020-02-14 14:22:54 -05:00
Ashwathi Shiva
6768f74d40 Add support for rotateKeys and included releaseName as part of CRSpec (#78)
* added support for rotateKeys, and included releaseName as part of CRSpec
2020-02-14 14:22:54 -05:00
Ashwathi Shiva
e159e8bd90 Imperative config through cli (#75)
* fixed a regexp bug and another one where qliksense-context was set as default context every time
* modified file creation permissions
2020-02-14 14:22:54 -05:00
Ashwathi Shiva
65bf3fb185 Imperative config through cli (#70)
* minor fix
2020-02-14 14:22:54 -05:00
Ashwathi Shiva
1f245546cd Imperative config through cli (#69)
* removed unnecessary check
2020-02-14 14:22:54 -05:00
Ashwathi Shiva
e38b66f039 Imperative config through cli (#66)
qliksense config commands implemented
2020-02-14 14:22:54 -05:00
Foysal Iqbal
4fd7f2ecbf Install flags (#68) 2020-02-14 14:22:54 -05:00
Andriy Bulynko
b07995dfb1 Setting kubeConfigPath based on homedir.Dir() for consistency (#65) 2020-02-14 14:22:54 -05:00
Foysal Iqbal
caf318410d Fetch install command (#62) 2020-02-14 14:22:54 -05:00
Andriy Bulynko
78fde72c92 Setting kubeConfigPath based on os.UserHomeDir() (#64) 2020-02-14 14:22:54 -05:00
Andriy Bulynko
4b0543b7b0 upgrading k-apis to v0.0.2 2020-02-14 14:22:54 -05:00
Andriy Bulynko
3ff45e47d7 Using k-apis and underlying go-git for the about command (#56) 2020-02-14 14:22:54 -05:00
Foysal Iqbal
72f7a450cf Config view apply (#55) 2020-02-14 14:22:54 -05:00
Foysal Iqbal
0371fa0d9b Clean porter (#53) 2020-02-14 14:22:54 -05:00
Andriy Bulynko
baf394160f About command (#50) 2020-02-14 14:22:54 -05:00
Foysal Iqbal
e411219da8 bring static files into cli (#49) 2020-02-14 14:22:54 -05:00
Andriy Bulynko
a1cb7eda9f kustomize API in-process (#48) 2020-02-14 14:22:54 -05:00
Boris Kuschel
240b9242fa Pull defaults for no version and no directory 2020-02-14 14:22:54 -05:00
Foysal Iqbal
314ff5a14d About doc (#46) 2020-02-14 14:22:54 -05:00
Boris Kuschel
766a2babc7 Make pull/push Docker engineless (#42) 2020-02-14 14:22:54 -05:00
Sanat Nayar
3c1709dcb5 added upgrade func 2020-02-14 14:22:37 -05:00
Sanat Nayar
48e8c997e4 added upgrade func 2020-02-14 11:11:21 -05:00
Jacob Martin
a9b5599d35 update release to include tars (#92)
* update release to include tars
2020-02-14 06:59:32 -05:00
Sanat Nayar
b092356fba added upgrade func 2020-02-13 14:49:24 -05:00
Foysal Iqbal
488f162dff uninstall command (#97)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-02-13 14:06:06 -05:00
Sanat Nayar
a29e7acf70 init 2020-02-12 17:03:34 -05:00
Andriy Bulynko
4a6e49f393 Creating uniquely named branches (#94) 2020-02-11 16:32:56 -05:00
Andriy Bulynko
50b2712456 Placing ejson ENV var mangling behind a check (#93) 2020-02-11 15:09:49 -05:00
Ashwathi Shiva
477f049c3e Added tests (#90)
* Tests added
2020-02-11 14:46:26 -05:00
Andriy Bulynko
b2ce12bd62 Faster-running kuz test (#89) 2020-02-10 14:07:07 -05:00
Ashwathi Shiva
ee4352e9d6 Support configuration through multiple args (#81)
* support configuration with multiple args
2020-02-10 09:27:44 -05:00
Boris Kuschel
11822db2cb Merge pull request #82 from qlik-oss/fix_Makefile
Make makefile portable
2020-02-08 08:16:30 -05:00
Boris Kuschel
58b027f361 Merge pull request #83 from qlik-oss/portable_paths
Make file paths portable
2020-02-08 08:16:03 -05:00
Boris Kuschel
25fb2c2407 Make file paths portable
Signed-off-by: Boris Kuschel <boris.kuschel@qlik.com>
2020-02-08 08:13:35 -05:00
Boris Kuschel
05b90314e4 Make makefile portable:
Signed-off-by: Boris Kuschel <boris.kuschel@qlik.com>
2020-02-08 07:40:29 -05:00
Andriy Bulynko
5825ba127b Storing/referencing ejson keys at path: ${qliksenseHome}/ejson/keys (#77) 2020-02-07 21:19:28 -05:00
Foysal Iqbal
156f21fab2 Fix default install (#79) 2020-02-07 16:05:11 -05:00
Ashwathi Shiva
835235a109 Add support for rotateKeys and included releaseName as part of CRSpec (#78)
* added support for rotateKeys, and included releaseName as part of CRSpec
2020-02-07 15:05:11 -05:00
33 changed files with 1588 additions and 364 deletions

4
.gitignore vendored
View File

@@ -1,2 +1,6 @@
bin
.vscode
cmd/qliksense/__debug_bin
pkg/qliksense/crds
pkg/qliksense/packrd
pkg/qliksense/qliksense-packr.go

View File

@@ -13,6 +13,7 @@ VERSION ?= $(shell git describe --tags 2> /dev/null || echo v0)
PERMALINK ?= $(shell git describe --tags --exact-match &> /dev/null && echo latest || echo canary)
BUILDTAGS = netgo containers_image_ostree_stub exclude_graphdriver_devicemapper exclude_graphdriver_btrfs containers_image_openpgp
CLIENT_PLATFORM ?= $(shell go env GOOS)
CLIENT_ARCH ?= $(shell go env GOARCH)
RUNTIME_PLATFORM ?= linux
@@ -51,12 +52,14 @@ xbuild: $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EX
$(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT):
mkdir -p $(dir $@)
GOOS=$(CLIENT_PLATFORM) GOARCH=$(CLIENT_ARCH) $(XBUILD) -o $@ ./cmd/$(MIXIN)
tar -czvf $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).tar.gz -C $(BINDIR)/$(VERSION)/ $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
#tar -C $(BINDIR)/$(VERSION)/ -cvf $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).tar.gz $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
generate: get-crds packr2
go generate ./...
HAS_PACKR2 := $(shell command -v packr2)
HAS_PACKR2 := $(shell packr2)
packr2:
ifndef HAS_PACKR2
go get -u github.com/gobuffalo/packr/v2/packr2
@@ -69,11 +72,12 @@ clean: clean-packr
clean-packr: packr2
cd pkg/qliksense && packr2 clean
get-crds:
git clone --depth=1 git@github.com:qlik-oss/qliksense-operator.git -b ms-3 /tmp/operator
get-crds:
$(eval TMP := $(shell mktemp -d))
git clone git@github.com:qlik-oss/qliksense-operator.git -b ms-3 $(TMP)/operator
mkdir -p pkg/qliksense/crds/cr
mkdir -p pkg/qliksense/crds/crd
mkdir -p pkg/qliksense/crds/crd-deploy
cp /tmp/operator/deploy/*.yaml pkg/qliksense/crds/crd-deploy
cp /tmp/operator/deploy/crds/*_crd.yaml pkg/qliksense/crds/crd
cp /tmp/operator/deploy/crds/*_cr.yaml pkg/qliksense/crds/cr
cp $(TMP)/operator/deploy/*.yaml pkg/qliksense/crds/crd-deploy
cp $(TMP)/operator/deploy/crds/*_crd.yaml pkg/qliksense/crds/crd
cp $(TMP)/operator/deploy/crds/*_cr.yaml pkg/qliksense/crds/cr

31
action_config.md Normal file
View File

@@ -0,0 +1,31 @@
# qliksense config
Config action will perform operations on configurations and contexts regarding the [qliksense-k8](https://github.com/qlik-oss/qliksense-k8s) release.
it will support following commands:
- `qliksense config apply` - generate the patchs and apply manifests to k8s
- `qliksense config list-contexts` - retrieves the contexts and lists them
- `qliksense config set` - configure a key value pair into the current context
- `qliksense config set-configs` - set configurations into the qliksense context
- `qliksense config set-context` - sets the context in which the Kubernetes cluster and resources live in
- `qliksense config set-secrets` - set secrets configurations into the qliksense context
- `qliksense config view` - view the qliksense operator CR
the global file that abstracts all the contexts is `config.yaml`, located at: `~/.qliksense/config.yaml`:
```yaml
apiVersion: config.qlik.com/v1
kind: QliksenseConfig
metadata:
name: QliksenseConfigMetadata
spec:
contexts:
- name: qlik-default
crFile: /Users/fff/.qliksense/contexts/qlik-default/qlik-default.yaml
- name: myqliksense
crFile: /Users/fff/.qliksense/contexts/myqliksense/myqliksense.yaml
- name: hello
crFile: /Users/fff/.qliksense/contexts/hello/hello.yaml
currentContext: hello
```

View File

@@ -3,10 +3,11 @@ package main
import (
"errors"
"fmt"
"strings"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
"strings"
)
type aboutCommandOptions struct {
@@ -44,7 +45,7 @@ qliksense about --profile=test
return err
} else if out, err := yaml.Marshal(vout); err != nil {
return err
} else if _, err := fmt.Println(out); err != nil {
} else if _, err := fmt.Println(string(out)); err != nil {
return err
}
return nil

View File

@@ -2,7 +2,6 @@ package main
import (
"github.com/qlik-oss/sense-installer/pkg/qliksense"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -16,8 +15,23 @@ func setContextConfigCmd(q *qliksense.Qliksense) *cobra.Command {
Short: "Sets the context in which the Kubernetes cluster and resources live in",
Example: `qliksense config set-context <context_name>`,
RunE: func(cmd *cobra.Command, args []string) error {
log.Debug("In set Context Config Command")
return qliksense.SetContextConfig(q, args)
return q.SetContextConfig(args)
},
}
return cmd
}
func listContextConfigCmd(q *qliksense.Qliksense) *cobra.Command {
var (
cmd *cobra.Command
)
cmd = &cobra.Command{
Use: "list-contexts",
Short: "retrieves the contexts and lists them",
Example: `qliksense config list-contexts`,
RunE: func(cmd *cobra.Command, args []string) error {
return q.ListContextConfigs()
},
}
return cmd
@@ -33,7 +47,7 @@ func setOtherConfigsCmd(q *qliksense.Qliksense) *cobra.Command {
Short: "configure a key value pair into the current context",
Example: `qliksense config set <key>=<value>`,
RunE: func(cmd *cobra.Command, args []string) error {
return qliksense.SetOtherConfigs(q, args)
return q.SetOtherConfigs(args)
},
}
return cmd
@@ -49,7 +63,7 @@ func setConfigsCmd(q *qliksense.Qliksense) *cobra.Command {
Short: "set configurations into the qliksense context",
Example: `qliksense config set-configs <key>=<value>`,
RunE: func(cmd *cobra.Command, args []string) error {
return qliksense.SetConfigs(q, args)
return q.SetConfigs(args)
},
}
return cmd
@@ -57,16 +71,19 @@ func setConfigsCmd(q *qliksense.Qliksense) *cobra.Command {
func setSecretsCmd(q *qliksense.Qliksense) *cobra.Command {
var (
cmd *cobra.Command
cmd *cobra.Command
secret bool
)
cmd = &cobra.Command{
Use: "set-secrets",
Short: "set secrets configurations into the qliksense context",
Example: `qliksense config set-secrets <key>=<value> --secret`,
Example: `qliksense config set-secrets <key>=<value> --secret=true`,
RunE: func(cmd *cobra.Command, args []string) error {
return qliksense.SetSecrets(q, args)
return q.SetSecrets(args, secret)
},
}
f := cmd.Flags()
f.BoolVar(&secret, "secret", false, "Whether secrets should be encrypted as a Kubernetes Secret resource")
return cmd
}

View File

@@ -2,7 +2,6 @@ package main
import (
"errors"
"github.com/Masterminds/semver/v3"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
@@ -17,9 +16,6 @@ func fetchCmd(q *qliksense.Qliksense) *cobra.Command {
if len(args) != 1 {
return errors.New("requires a version (i.e. v1.0.0)")
}
if _, err := semver.NewVersion(args[0]); err != nil {
return errors.New("is it not a valid version. should be something like this v1.0.0")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {

View File

@@ -2,7 +2,6 @@ package main
import (
"errors"
"github.com/Masterminds/semver/v3"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
@@ -18,9 +17,6 @@ func installCmd(q *qliksense.Qliksense) *cobra.Command {
if len(args) != 1 {
return errors.New("requires a version (i.e. v1.0.0)")
}
if _, err := semver.NewVersion(args[0]); err != nil {
return errors.New("is it not a valid version. should be something like this v1.0.0")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
@@ -32,6 +28,7 @@ func installCmd(q *qliksense.Qliksense) *cobra.Command {
f.StringVarP(&opts.AcceptEULA, "acceptEULA", "a", "", "AcceptEULA for qliksense")
f.StringVarP(&opts.Namespace, "namespace", "n", "", "Namespace where to install the qliksense")
f.StringVarP(&opts.StorageClass, "storageClass", "s", "", "Storage class for qliksense")
f.StringVarP(&opts.MongoDbUri, "mongoDbUri", "m", "", "mongoDbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
f.StringVarP(&opts.RotateKeys, "rotateKeys", "r", "", "Rotate JWT keys for qliksense (yes:rotate keys/ no:use exising keys from cluster/ None: use default EJSON_KEY from env")
return c
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/mitchellh/go-homedir"
"github.com/qlik-oss/sense-installer/pkg"
"github.com/qlik-oss/sense-installer/pkg/api"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
@@ -38,10 +39,14 @@ func initAndExecute() error {
}
// create dirs and appropriate files for setting up contexts
qliksense.LogDebugMessage("QliksenseHomeDir: %s", qlikSenseHome)
qliksense.SetUpQliksenseDefaultContext(qlikSenseHome)
api.LogDebugMessage("QliksenseHomeDir: %s", qlikSenseHome)
if err = rootCmd(qliksense.New(qlikSenseHome)).Execute(); err != nil {
qliksenseClient, err := qliksense.New(qlikSenseHome)
if err != nil {
return err
}
qliksenseClient.SetUpQliksenseDefaultContext()
if err := rootCmd(qliksenseClient).Execute(); err != nil {
return err
}
@@ -63,7 +68,11 @@ func setUpPaths() (string, error) {
}
qlikSenseHome = filepath.Join(homeDir, qlikSenseDirVar)
}
os.Mkdir(qlikSenseHome, os.ModePerm)
if err := os.MkdirAll(qlikSenseHome, os.ModePerm); err != nil {
return "", err
}
return qlikSenseHome, nil
}
@@ -117,6 +126,9 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
//add upgrade command
cmd.AddCommand(upgradeCmd(p))
// add the set-context config command as a sub-command to the app config command
configCmd.AddCommand(setContextConfigCmd(p))
@@ -129,6 +141,11 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
// add the set ### config command as a sub-command to the app config sub-command
configCmd.AddCommand(setSecretsCmd(p))
// add the list config command as a sub-command to the app config sub-command
configCmd.AddCommand(listContextConfigCmd(p))
// add uninstall command
cmd.AddCommand(uninstallCmd(p))
return cmd
}

View File

@@ -0,0 +1,23 @@
package main
import (
qapi "github.com/qlik-oss/sense-installer/pkg/api"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
func uninstallCmd(q *qliksense.Qliksense) *cobra.Command {
c := &cobra.Command{
Use: "uninstall",
Short: "Uninstall the deployed qliksense with release name [ " + qapi.NewQConfig(q.QliksenseHome).Spec.CurrentContext + " ]",
Long: `Uninstall the deployed qliksense. By default uninstall the current context`,
Example: `qliksense uninstall <context-name>`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
return q.UninstallQK8s(args[0])
}
return q.UninstallQK8s("")
},
}
return c
}

20
cmd/qliksense/upgrade.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import (
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
func upgradeCmd(q *qliksense.Qliksense) *cobra.Command {
c := &cobra.Command{
Use: "upgrade",
Short: "upgrade qliksense release",
Long: `upgrade qliksesne release`,
Example: `qliksense upgrade <version>`,
RunE: func(cmd *cobra.Command, args []string) error {
return q.UpgradeQK8s()
},
}
return c
}

25
go.mod
View File

@@ -3,26 +3,20 @@ module github.com/qlik-oss/sense-installer
go 1.13
replace (
github.com/Sirupsen/logrus v1.0.5 => github.com/sirupsen/logrus v1.0.5
github.com/Sirupsen/logrus v1.3.0 => github.com/Sirupsen/logrus v1.0.6
github.com/Sirupsen/logrus v1.4.0 => github.com/sirupsen/logrus v1.0.6
// github.com/containerd/containerd v1.3.0-0.20190507210959-7c1e88399ec0 => github.com/containerd/containerd v1.3.2
github.com/docker/docker => github.com/moby/moby v0.7.3-0.20190826074503-38ab9da00309
// github.com/jaguilar/vt100 => github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305
// golang.org/x/crypto v0.0.0-20190129210102-0709b304e793 => golang.org/x/crypto v0.0.0-20180904163835-0709b304e793
golang.org/x/sys => golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8
k8s.io/client-go => k8s.io/client-go v0.0.0-20191016111102-bec269661e48
k8s.io/kubectl => k8s.io/kubectl v0.0.0-20191016120415-2ed914427d51
sigs.k8s.io/kustomize/api => github.com/qlik-oss/kustomize/api v0.3.3-0.20200129153315-09eb26c762c8
sigs.k8s.io/kustomize/api => github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9
)
require (
cloud.google.com/go v0.52.0 // indirect
cloud.google.com/go/storage v1.5.0 // indirect
github.com/Masterminds/semver/v3 v3.0.3
github.com/Shopify/ejson v1.2.1
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
github.com/aws/aws-sdk-go v1.28.9 // indirect
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 // indirect
@@ -37,9 +31,13 @@ require (
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7
github.com/docker/go v1.5.1-1 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/gobuffalo/envy v1.9.0 // indirect
github.com/gobuffalo/logger v1.0.3 // indirect
github.com/gobuffalo/packd v1.0.0 // indirect
github.com/gobuffalo/packr/v2 v2.7.1
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.3.3 // indirect
github.com/google/uuid v1.1.1
github.com/gorilla/mux v1.7.3 // indirect
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
@@ -52,21 +50,24 @@ require (
github.com/mitchellh/go-homedir v1.1.0
github.com/morikuni/aec v1.0.0 // indirect
github.com/pkg/errors v0.8.1
github.com/qlik-oss/k-apis v0.0.4
github.com/sirupsen/logrus v1.4.2
github.com/qlik-oss/k-apis v0.0.8
github.com/rogpeppe/go-internal v1.5.2 // indirect
github.com/spf13/cobra v0.0.5
github.com/spf13/viper v1.6.1
github.com/theupdateframework/notary v0.6.1 // indirect
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 // indirect
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 // indirect
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a // indirect
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 // indirect
golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c // indirect
golang.org/x/tools v0.0.0-20200219054238-753a1d49df85 // indirect
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b // indirect
google.golang.org/grpc v1.27.0 // indirect
gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect
gopkg.in/fatih/pool.v2 v2.0.0 // indirect
gopkg.in/gorethink/gorethink.v3 v3.0.5 // indirect
gopkg.in/yaml.v2 v2.2.8
gopkg.in/yaml.v3 v3.0.0-20190924164351-c8b7dadae555
sigs.k8s.io/kustomize/api v0.3.2
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 // indirect
)

30
go.sum
View File

@@ -400,10 +400,16 @@ github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.7.1 h1:OQl5ys5MBea7OGCdvPbBJWRgnhC/fGona6QKfvFeau8=
github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
github.com/gobuffalo/envy v1.9.0 h1:eZR0DuEgVLfeIb1zIKt3bT4YovIMf9O9LXQeCZLXpqE=
github.com/gobuffalo/envy v1.9.0/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
github.com/gobuffalo/logger v1.0.1 h1:ZEgyRGgAm4ZAhAO45YXMs5Fp+bzGLESFewzAVBMKuTg=
github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc=
github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM=
github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM=
github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI=
github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o=
github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
@@ -901,16 +907,10 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/qlik-oss/k-apis v0.0.2 h1:7Sdz7528of+34NijkhxSc964FTOl+RV3IMMJxiScmd0=
github.com/qlik-oss/k-apis v0.0.2/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE=
github.com/qlik-oss/k-apis v0.0.3-0.20200204195649-ea2ee5c630f2 h1:OWdcKP73yCchNf02qJ+xYof7WSHDBeaEyiPRsgRMy3Y=
github.com/qlik-oss/k-apis v0.0.3-0.20200204195649-ea2ee5c630f2/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE=
github.com/qlik-oss/k-apis v0.0.3 h1:LJTQik87Rcsl+rphXfyPtxQc9UgQy+XGdT+epQqi+YY=
github.com/qlik-oss/k-apis v0.0.3/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE=
github.com/qlik-oss/k-apis v0.0.4 h1:fNX1LsGbNZtR7X/0o/2HAnQkuEJs6JcnedHzacBcbfM=
github.com/qlik-oss/k-apis v0.0.4/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200129153315-09eb26c762c8 h1:WLVkArXf58T+3SHDvSddE8P6OjkeeUtGEgHk8LDdfeo=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200129153315-09eb26c762c8/go.mod h1:OCt7FTrRVHj4kmR2xLJJUIqu00BTr6GeF09hSmM17Kw=
github.com/qlik-oss/k-apis v0.0.8 h1:8ZZefgFT+rI4PuE/sp3Wpc+d5LtZBKZuDb2G/YelTOs=
github.com/qlik-oss/k-apis v0.0.8/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9 h1:iqeqTS4zjp6rPEaxmFB7pemA2CMjOEN5dYSXZaQ82uw=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9/go.mod h1:OCt7FTrRVHj4kmR2xLJJUIqu00BTr6GeF09hSmM17Kw=
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=
@@ -923,6 +923,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.4.0 h1:LUa41nrWTQNGhzdsZ5lTnkwbNjj6rXTdazA1cSdjkOY=
github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w=
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -1114,8 +1116,11 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 h1:Sy5bstxEqwwbYs6n0/pBuxKENqOeZUgD45Gp3Q3pqLg=
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1148,6 +1153,7 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1272,8 +1278,8 @@ golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c h1:2EA2K0k9bcvvEDlqD8xdlOhCOqq+O/p9Voqi4x9W1YU=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200219054238-753a1d49df85 h1:XNHaQ2CZDl/SjEZlUXGh7+OQvfLuFgmk3oNWkCFfERE=
golang.org/x/tools v0.0.0-20200219054238-753a1d49df85/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -132,6 +132,15 @@ func (qc *QliksenseConfig) WriteCurrentContextCR(cr *QliksenseCR) error {
return qc.WriteCR(cr, qc.Spec.CurrentContext)
}
func (qc *QliksenseConfig) IsContextExist(ctxName string) bool {
for _, ct := range qc.Spec.Contexts {
if ct.Name == ctxName {
return true
}
}
return false
}
func (cr *QliksenseCR) AddLabelToCr(key, value string) error {
if cr.Metadata.Labels == nil {
cr.Metadata.Labels = make(map[string]string)
@@ -139,3 +148,12 @@ func (cr *QliksenseCR) AddLabelToCr(key, value string) error {
cr.Metadata.Labels[key] = value
return nil
}
func (cr *QliksenseCR) GetString() (string, error) {
out, err := yaml.Marshal(cr)
if err != nil {
fmt.Println("cannot unmarshal cr ", err)
return "", err
}
return string(out), nil
}

94
pkg/api/context_apis.go Normal file
View File

@@ -0,0 +1,94 @@
package api
import (
"fmt"
"io/ioutil"
"log"
"github.com/qlik-oss/k-apis/pkg/config"
"gopkg.in/yaml.v2"
)
const (
QliksenseConfigApiVersion = "config.qlik.com/v1"
QliksenseConfigKind = "QliksenseConfig"
QliksenseContextApiVersion = "qlik.com/v1"
QliksenseContextKind = "Qliksense"
QliksenseDefaultProfile = "docker-desktop"
DefaultRotateKeys = "yes"
QliksenseMetadataName = "QliksenseConfigMetadata"
)
// AddCommonConfig adds common configs into CRs
func (qliksenseCR *QliksenseCR) AddCommonConfig(contextName string) {
qliksenseCR.ApiVersion = QliksenseContextApiVersion
qliksenseCR.Kind = QliksenseContextKind
if qliksenseCR.Metadata == nil {
qliksenseCR.Metadata = &Metadata{}
}
if qliksenseCR.Metadata.Name == "" {
qliksenseCR.Metadata.Name = contextName
}
qliksenseCR.Spec = &config.CRSpec{}
qliksenseCR.Spec.Profile = QliksenseDefaultProfile
qliksenseCR.Spec.ReleaseName = contextName
qliksenseCR.Spec.RotateKeys = DefaultRotateKeys
}
// AddBaseQliksenseConfigs adds configs into config.yaml
func (qliksenseConfig *QliksenseConfig) AddBaseQliksenseConfigs(defaultQliksenseContext string) {
qliksenseConfig.ApiVersion = QliksenseConfigApiVersion
qliksenseConfig.Kind = QliksenseConfigKind
if qliksenseConfig.Metadata == nil {
qliksenseConfig.Metadata = &Metadata{}
}
qliksenseConfig.Metadata.Name = QliksenseMetadataName
if defaultQliksenseContext != "" {
if qliksenseConfig.Spec == nil {
qliksenseConfig.Spec = &ContextSpec{}
}
qliksenseConfig.Spec.CurrentContext = defaultQliksenseContext
}
}
// WriteToFile (content, targetFile) writes content into specified file
func WriteToFile(content interface{}, targetFile string) error {
if content == nil || targetFile == "" {
return nil
}
x, err := yaml.Marshal(content)
if err != nil {
err = fmt.Errorf("An error occurred during marshalling CR: %v", err)
log.Println(err)
return err
}
// Writing content
err = ioutil.WriteFile(targetFile, x, 0644)
if err != nil {
log.Println(err)
return err
}
LogDebugMessage("Wrote content into %s", targetFile)
return nil
}
// ReadFromFile (content, targetFile) reads content from specified sourcefile
func ReadFromFile(content interface{}, sourceFile string) error {
if content == nil || sourceFile == "" {
return nil
}
contents, err := ioutil.ReadFile(sourceFile)
if err != nil {
err = fmt.Errorf("There was an error reading from file: %s, %v", sourceFile, err)
log.Println(err)
return err
}
if err := yaml.Unmarshal(contents, content); err != nil {
err = fmt.Errorf("An error occurred during unmarshalling: %v", err)
log.Println(err)
return err
}
return nil
}

View File

@@ -0,0 +1,94 @@
package api
import (
"reflect"
"testing"
"github.com/qlik-oss/k-apis/pkg/config"
)
var (
testDir = "./tests"
)
func TestAddCommonConfig(t *testing.T) {
type args struct {
qliksenseCR *QliksenseCR
contextName string
}
tests := []struct {
name string
args args
want *QliksenseCR
}{
{
name: "valid case",
args: args{
qliksenseCR: &QliksenseCR{},
contextName: "myqliksense",
},
want: &QliksenseCR{
CommonConfig: CommonConfig{
ApiVersion: QliksenseContextApiVersion,
Kind: QliksenseContextKind,
Metadata: &Metadata{
Name: "myqliksense",
},
},
Spec: &config.CRSpec{
Profile: QliksenseDefaultProfile,
ReleaseName: "myqliksense",
RotateKeys: DefaultRotateKeys,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.args.qliksenseCR.AddCommonConfig(tt.args.contextName)
if !reflect.DeepEqual(tt.args.qliksenseCR, tt.want) {
t.Errorf("AddCommonConfig() = %+v, want %+v", tt.args.qliksenseCR, tt.want)
}
})
}
}
func TestAddBaseQliksenseConfigs(t *testing.T) {
type args struct {
qliksenseConfig *QliksenseConfig
defaultQliksenseContext string
}
tests := []struct {
name string
args args
want *QliksenseConfig
}{
{
name: "valid case",
args: args{
qliksenseConfig: &QliksenseConfig{},
defaultQliksenseContext: "qlik-default",
},
want: &QliksenseConfig{
CommonConfig: CommonConfig{
ApiVersion: QliksenseConfigApiVersion,
Kind: QliksenseConfigKind,
Metadata: &Metadata{
Name: QliksenseMetadataName,
},
},
Spec: &ContextSpec{
CurrentContext: "qlik-default",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.args.qliksenseConfig.AddBaseQliksenseConfigs(tt.args.defaultQliksenseContext)
if !reflect.DeepEqual(tt.args.qliksenseConfig, tt.want) {
t.Errorf("AddBaseQliksenseConfigs() = %+v, want %+v", tt.args.qliksenseConfig, tt.want)
}
})
}
}

171
pkg/api/encryption.go Normal file
View File

@@ -0,0 +1,171 @@
package api
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"log"
"path/filepath"
)
const (
RSA_KEY_LENGTH = 4096
privateKeyPath = "privKey"
publicKeyPath = "pubKey"
QliksensePublicKey = "qliksensePub"
QliksensePrivateKey = "qliksensePriv"
)
// GenerateAndStoreSecretKeypair generates and stores key pairs
func GenerateAndStoreSecretKeypair(secretsPath string) error {
LogDebugMessage("%s exists", secretsPath)
// creating contexts/qlik-default/secrets/qliksensePub and contexts/qlik-default/secrets/qliksensePriv files
publicKeyFilePath := filepath.Join(secretsPath, QliksensePublicKey)
privateKeyFilePath := filepath.Join(secretsPath, QliksensePrivateKey)
LogDebugMessage("Generating public-private key pair.....")
GenerateRSAEncryptionKeys(publicKeyFilePath, privateKeyFilePath)
LogDebugMessage("Generated public-private key pairs")
return nil
}
// GenerateRSAEncryptionKeys is used to generate a new public-private key pair
func GenerateRSAEncryptionKeys(publicKeyFilePath, privateKeyFilePath string) error {
LogDebugMessage("Generating new RSA key pair")
privateKey, err := rsa.GenerateKey(rand.Reader, RSA_KEY_LENGTH)
if err != nil {
log.Printf("error generating RSA private key: %v\n", err)
return err
}
privateKeyPEM := EncodePrivateKey(privateKey)
if err := writeContentToFile(privateKeyPEM, privateKeyFilePath); err != nil {
return err
}
pubKeyPEM, err2 := EncodePublicKey(&privateKey.PublicKey)
if err2 != nil {
log.Printf("error occurred when encoding public key: %v\n", err2)
return err2
}
if err := writeContentToFile(pubKeyPEM, publicKeyFilePath); err != nil {
return err
}
return nil
}
// writeContentToFile writes keys to a file
func writeContentToFile(keyData []byte, fileName string) error {
err := ioutil.WriteFile(fileName, keyData, 0600)
if err != nil {
log.Printf("error writing to file (%s): %v", fileName, err)
return err
}
LogDebugMessage("Key saved: %s", fileName)
return nil
}
// Encrypt encrypts data with public key
func Encrypt(pt []byte, pub *rsa.PublicKey) ([]byte, error) {
// hash := sha512.New()
// ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, pub, msg, nil)
ct, err := rsa.EncryptPKCS1v15(rand.Reader, pub, pt)
if err != nil {
log.Println(err)
return nil, err
}
return ct, nil
}
// Decrypt decrypts data with private key
func Decrypt(ct []byte, priv *rsa.PrivateKey) ([]byte, error) {
// hash := sha512.New()
// plaintext, err := rsa.DecryptOAEP(hash, rand.Reader, priv, ciphertext, nil)
pt, err := rsa.DecryptPKCS1v15(rand.Reader, priv, ct)
if err != nil {
log.Println(err)
return nil, err
}
return pt, nil
}
// EncodePrivateKey private key to bytes
func EncodePrivateKey(priv *rsa.PrivateKey) []byte {
privBytes := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(priv),
},
)
return privBytes
}
// EncodePublicKey public key to bytes
func EncodePublicKey(pub *rsa.PublicKey) ([]byte, error) {
pubASN1, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
log.Println(err)
return nil, err
}
pubBytes := pem.EncodeToMemory(&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: pubASN1,
})
return pubBytes, nil
}
// DecodeToPrivateKey bytes to private key
func DecodeToPrivateKey(priv []byte) (*rsa.PrivateKey, error) {
block, _ := pem.Decode(priv)
enc := x509.IsEncryptedPEMBlock(block)
b := block.Bytes
var err error
if enc {
log.Println("is encrypted pem block")
b, err = x509.DecryptPEMBlock(block, nil)
if err != nil {
log.Println(err)
return nil, err
}
}
key, err := x509.ParsePKCS1PrivateKey(b)
if err != nil {
log.Println(err)
return nil, err
}
return key, nil
}
// DecodeToPublicKey bytes to public key
func DecodeToPublicKey(pub []byte) (*rsa.PublicKey, error) {
block, _ := pem.Decode(pub)
enc := x509.IsEncryptedPEMBlock(block)
b := block.Bytes
var err error
if enc {
log.Println("is encrypted pem block")
b, err = x509.DecryptPEMBlock(block, nil)
if err != nil {
log.Println(err)
return nil, err
}
}
iface, err := x509.ParsePKIXPublicKey(b)
if err != nil {
log.Println(err)
return nil, err
}
key, ok := iface.(*rsa.PublicKey)
if !ok {
err := fmt.Errorf("Unable to decode public key")
log.Println(err)
return nil, err
}
return key, nil
}

129
pkg/api/encryption_test.go Normal file
View File

@@ -0,0 +1,129 @@
package api
import (
"encoding/base64"
"log"
"os"
"testing"
)
func Test_generateRSAEncryptionKeys(t *testing.T) {
tests := []struct {
name string
wantErr bool
}{
{
name: "valid case",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := GenerateAndStoreSecretKeypair(os.TempDir()); (err != nil) != tt.wantErr {
t.Errorf("generateRSAEncryptionKeys() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_encryption_decryption(t *testing.T) {
privKeyBytes := []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIJJwIBAAKCAgEAwUCimKCidbF3UxEHPy8K+hvhklRB9JYhj5sJy0if4lTVibkK
1MrYCykOnmC40pPU9GLY1b8HxAg9tvyRn0YHUxOra6vVQaVcOVJhTM8D18d+lSr3
Lp1yiX+UGT4nzWI9+R1CCbwXrqeQVoZs6QZKynEXMkFI9/wNMOwPOvQFOSTuoEoC
O+zyTyUWEkNbUq825ELUQdIsjgmlWUOONudxsAr7ESRXW9QTHVh6uWmr3VRKZHby
1JdU3I/wjdlGg5M2dDuXy5nQO9w/nYLjJXiw+zzOetZ/+t7/VOkOpNTeJQhwTM1W
F7Y2VLetbi9FHgyzHatrduh07+XEiTbgDf3GIx2bp2p6oh0G3N2zpiLcK/aZj8ro
uWWydfFfsU3MZ4FfJDP8I6b9awxjmKYqIr6hiPQCJaLBED8mwK+I5evIbnKv6E6u
K+BApWA/R7ElragoFYbqQ1VpvntVMtJt9Dy5ZrI+IQARdXD3bb34oh0IPBhClnvv
MUc1cWxDoXEX6oJ4I+LzxE87Zkwnan9qOwengolMVKFwPx1o37qrbmrXID21kKt7
FL6xN4HxHLkItr1fKzdyWDFRHgASTAWfx5BIwvPuUW0vZHkvO80VyV2L63whVhPn
PASmFkbviomrBttYfpr2aGQqF/qR1Nlxe834MFxk1pS9LMa/WnzvFr0gWakCAwEA
AQKCAgARSp9B2N2wejibDiL/3E23I1eDqFZedDB8kPrHXbAwqDaTJCN79spt9TaB
pVXkQaYEV/Pe7EDdoX8kKGU/QxzUqiXkdHOYdBtUZbKfFMbbP9ZrsnR7j0r4UpoF
yDH3hprU93E5PcNAtW2M0GpeT1nR01yn+n908PCdOAIE3GC7RDq1zOl2QzVLL55R
9ATv2Q2oTvJ/ETc7XlGVMx4+e2cIwXLFjeLjLI6pSYlxnarrGuetJZeEviWxto9n
odFVZI6yx8JFTXX8ZTCr/1IjwDDVyhMPmrHI2Lsv9cqBpSpbVe32cUkKxhsGaYjz
GvesQKamOPhco2ATNxPm0yopFlPsGKMfVl0BK0J6BqFh1BvU/SYJmXfnFuUNO3vV
4u2Saa0q1iddxV0rXDwIqUfn+S6rwzK0G7y8bH2yvpB2VwiG3TFPnULep4wsefNq
Fj92kqFBjacGpQLEEslUY0CMgeZ2+NuBQSUTscP3wBRsottMR6YXJtINdvfHBx+e
EcN71z8D00w3mYqIQ7qb4Ml6HOqknunn58g43L9sACMUMTlEBXa9pUnScNYgWBAz
W2q2mH37cIydM2JRZPpA8B4yTHt5ugJmChwyNFM7941arjKrebH+6AzLkofGedOP
zg+vZQuPEXWs+3MBBnkWoyJW3Y0fbQdjsuQTtnd+7iyoxoBroQKCAQEA4dIiFlIS
MDfRhQQWSiDvaw9aneDEJ3uo63ZRH5tm/IynLgtjYgEm/ZxlBCQgqRKLYELBxhu8
SaF0uPK8pmpFJt0mIwSlsdeVhuE2obQeKUCczaqrKeaHS3PdWLjTlwph81BGRkHy
qfqtNylyyMxrdEbnR51EtsWgFq6anTUAui1Q09JMuMNZRMOzDs1F4gExgD22rc0V
c9YQ+jHJRxBGtNKMpMEqc8cvaxBidbItrN9SMTSWog7uYPBuEuaJ6K9vpgyJMOzJ
SYcQEFGqgIqIDCg+ABE4d/4YROMKZ1DV/bJCind9brUHSx6XALsF0nC5c1Q9TnUL
qI2khOwts4KYKwKCAQEA2xRC6Az97Vkdzu7BjLJ1FKmx4S2nEEgVS12ds82U+5Xf
BHKAJnjqlqmmpzzJG+d77IYktz0+mey1QCNkqlm2fhuKs8LZMnpZRf0l8VcoBsUP
/xKz7wfiE7RRFZtLJhPp4hhe43GzX5/JFMWMnC6UykwQbj4t1E/GNM/Suqwvg12M
wktAJ6nqLgfhjQSO4xWo+nPzcbX+fNtrPCZVrBhYXihhcwRRNImWUCGJ6J4LMdPY
Y9Z59qhOvE9cReH/Xw1av46omyiSyAqlgPyZ/kzA2IJSqYCjiQR/2+RD/g13jpcJ
jatXLVZ8MJSL5OTS40G/HHTNNpNHbKKh0GOyxBA3ewKCAQBAn8UXhCcmW2L/YPsL
/b7mcX9qPP+FmRLvR23R0MQ5M/tH5wRq8I969n3GIJykJeVzB8eybQ+GNslTgEvS
iAkAJTubu+G7MkndTqg2wHf9MDtvdA8Fr646Po8yq7oJuHPtkKR7yLWsRUu6xIbP
xgheP0hCq1QVxhqZQyCGKrvpi7xc0gsYuPbcAfFFJCOCmPrUi1SzCkTAYJt9LjA+
wP6rErIjGBCRD4iXaBn1OqdtmH9KC5WsDP/VCBlIGWeQCly2NVIxiSHVg+xp7yUP
IhXq/L05gbQaSsIhPKQmivCiaJg4The8TdwneDqYf+0bmxzHT203/bD3bImPbJNr
ksz/AoIBAEwu4Y1cZzkQUmNRd5D7xecnk6ngfEYXKwCIT3zlMrfCSEl9n77BMaKu
4Dsr0iuX9eosQ7xM2eYhAG6LYEg05lc4MKWOToVVMpI6E+W3Dz47bPKgiF3I+f8s
Jz5CQIG/TwfGvciOE3hfUkec4ua09BzdEqGjkcBQ9XYMBxXPJr6h2379OBQS7FKR
fwfQ2/dv4tElXTTfut2kV8gU9Jnh5Wjo1epvR+XjKpg28YQo4W+0YX1magcyRB8L
4eSTUIC3XiVa8Jr0IwbZXPBb5xkdi7o+p4w2JahSHjxTRqmj+T1mnHXdbXVgq9Mg
9Pzl7cgFZvX4UBx4XtASRf73jITNtt0CggEADH9K+O7FrIOSQly0sMvsRCMtejp3
o+MDh1Q+vEg2kEgNXjS4ZFVljUpM2kg1OdUz7feS4dLXUJiIQ8ZWtZPedcq7wjHd
02he5+s06l0jPifN3tX1ADfXGpXg5R2fbkrIzakkPP5/RO/aDxIUo7qhklNsVTXO
VlGGfWLdk0ekA4upKm02Q1+YOlbIcAicEYYY8K7IffUwnohzKwL9yfuGi1VKTXpE
4fzdegsHI03FSqR7V+LvtBpIupQ7RO4kuBmCEyI4E9FVknchg4te4gO3qwd9y0rJ
Gu7HNIOrwOHzviI7J6Nd/l9MmeKqklHSgJvko/f5TmiXuQQ8xDZf84rcjQ==
-----END RSA PRIVATE KEY-----
`)
publicKeyBytes := []byte(`-----BEGIN RSA PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwUCimKCidbF3UxEHPy8K
+hvhklRB9JYhj5sJy0if4lTVibkK1MrYCykOnmC40pPU9GLY1b8HxAg9tvyRn0YH
UxOra6vVQaVcOVJhTM8D18d+lSr3Lp1yiX+UGT4nzWI9+R1CCbwXrqeQVoZs6QZK
ynEXMkFI9/wNMOwPOvQFOSTuoEoCO+zyTyUWEkNbUq825ELUQdIsjgmlWUOONudx
sAr7ESRXW9QTHVh6uWmr3VRKZHby1JdU3I/wjdlGg5M2dDuXy5nQO9w/nYLjJXiw
+zzOetZ/+t7/VOkOpNTeJQhwTM1WF7Y2VLetbi9FHgyzHatrduh07+XEiTbgDf3G
Ix2bp2p6oh0G3N2zpiLcK/aZj8rouWWydfFfsU3MZ4FfJDP8I6b9awxjmKYqIr6h
iPQCJaLBED8mwK+I5evIbnKv6E6uK+BApWA/R7ElragoFYbqQ1VpvntVMtJt9Dy5
ZrI+IQARdXD3bb34oh0IPBhClnvvMUc1cWxDoXEX6oJ4I+LzxE87Zkwnan9qOwen
golMVKFwPx1o37qrbmrXID21kKt7FL6xN4HxHLkItr1fKzdyWDFRHgASTAWfx5BI
wvPuUW0vZHkvO80VyV2L63whVhPnPASmFkbviomrBttYfpr2aGQqF/qR1Nlxe834
MFxk1pS9LMa/WnzvFr0gWakCAwEAAQ==
-----END RSA PUBLIC KEY-----
`)
origStr := "Value1234"
pubKey, err := DecodeToPublicKey(publicKeyBytes)
if err != nil {
t.Error(err)
t.FailNow()
}
privKey, err := DecodeToPrivateKey(privKeyBytes)
if err != nil {
t.Error(err)
t.FailNow()
}
encData, err := Encrypt([]byte(origStr), pubKey)
if err != nil {
t.Error(err)
t.FailNow()
}
encDataStr := base64.StdEncoding.EncodeToString(encData)
log.Printf("encrypted data: %s\n", encDataStr)
dec, _ := base64.StdEncoding.DecodeString(encDataStr)
data, err := Decrypt(dec, privKey)
if err != nil {
t.Error(err)
t.FailNow()
}
if string(data) != origStr {
t.Error("original string and decrypted string don't match")
t.FailNow()
}
log.Printf("decrypted data: %s\n", data)
}

View File

@@ -8,6 +8,14 @@ import (
)
func KubectlApply(manifests string) error {
return kubectlOperation(manifests, "apply")
}
func KubectlDelete(manifests string) error {
return kubectlOperation(manifests, "delete")
}
func kubectlOperation(manifests string, oprName string) error {
tempYaml, err := ioutil.TempFile("", "")
if err != nil {
fmt.Println("cannot create file ", err)
@@ -15,7 +23,13 @@ func KubectlApply(manifests string) error {
}
tempYaml.WriteString(manifests)
cmd := exec.Command("kubectl", "apply", "-f", tempYaml.Name(), "--validate=false")
var cmd *exec.Cmd
if oprName == "apply" {
cmd = exec.Command("kubectl", oprName, "-f", tempYaml.Name(), "--validate=false")
} else {
cmd = exec.Command("kubectl", oprName, "-f", tempYaml.Name())
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()

View File

@@ -39,3 +39,10 @@ type Metadata struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
}
// ServiceKeyValue holds the combination of service, key and value
type ServiceKeyValue struct {
SvcName string
Key string
Value string
}

88
pkg/api/utils.go Normal file
View File

@@ -0,0 +1,88 @@
package api
import (
"fmt"
"io/ioutil"
"log"
"os"
"regexp"
)
func checkExists(filename string, isFile bool) os.FileInfo {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
if isFile {
LogDebugMessage("File does not exist")
} else {
LogDebugMessage("Dir does not exist")
}
return nil
}
LogDebugMessage("File exists")
return info
}
// FileExists checks if a file exists
func FileExists(filename string) bool {
if fe := checkExists(filename, true); fe != nil && !fe.IsDir() {
return true
}
return false
}
// DirExists checks if a directory exists
func DirExists(dirname string) bool {
if fe := checkExists(dirname, false); fe != nil && fe.IsDir() {
return true
}
return false
}
// LogDebugMessage logs a debug message
func LogDebugMessage(strMessage string, args ...interface{}) {
if os.Getenv("QLIKSENSE_DEBUG") == "true" {
log.Printf(strMessage, args...)
}
}
// ReadKeys reads key file from disk
func ReadKeys(keyFile string) ([]byte, error) {
keybyteArray, err := ioutil.ReadFile(keyFile)
if err != nil {
err = fmt.Errorf("There was an error reading from file: %s, %v", keyFile, err)
log.Println(err)
return nil, err
} else {
LogDebugMessage("Read key as byte[]: %+v", keybyteArray)
}
return keybyteArray, nil
}
// ProcessConfigArgs processes args and returns an service, key, value slice
func ProcessConfigArgs(args []string) ([]*ServiceKeyValue, error) {
// prepare received args
// split args[0] into key and value
if len(args) == 0 {
err := fmt.Errorf("No args were provided. Please provide args to configure the current context")
log.Println(err)
return nil, err
}
resultSvcKV := make([]*ServiceKeyValue, len(args))
re1 := regexp.MustCompile(`(\w{1,})\[name=(\w{1,})\]=("*[\w\-_/:0-9]+"*)`)
for i, arg := range args {
LogDebugMessage("Arg received: %s", arg)
result := re1.FindStringSubmatch(arg)
// check if result array's length is == 4 (index 0 - is the full match & indices 1,2,3- are the fields we need)
if len(result) != 4 {
err := fmt.Errorf("Please provide valid args for this command")
log.Println(err)
return nil, err
}
resultSvcKV[i] = &ServiceKeyValue{
SvcName: result[1],
Key: result[2],
Value: result[3],
}
}
return resultSvcKV, nil
}

View File

@@ -2,6 +2,7 @@ package qliksense
import (
"bytes"
"errors"
"io"
"io/ioutil"
"os"
@@ -11,6 +12,7 @@ import (
"sort"
kapis_git "github.com/qlik-oss/k-apis/pkg/git"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
"gopkg.in/yaml.v2"
)
@@ -61,8 +63,8 @@ const (
defaultGitUrl = "https://github.com/qlik-oss/qliksense-k8s"
)
func (p *Qliksense) About(gitRef, profile string) (*VersionOutput, error) {
configDirectory, isTemporary, profile, err := getConfigDirectory(defaultGitUrl, gitRef, profile)
func (q *Qliksense) About(gitRef, profile string) (*VersionOutput, error) {
configDirectory, isTemporary, profile, err := q.getConfigDirectory(defaultGitUrl, gitRef, profile)
if err != nil {
return nil, err
}
@@ -70,6 +72,10 @@ func (p *Qliksense) About(gitRef, profile string) (*VersionOutput, error) {
defer os.RemoveAll(configDirectory)
}
return q.AboutDir(configDirectory, profile)
}
func (q *Qliksense) AboutDir(configDirectory, profile string) (*VersionOutput, error) {
chartVersion, err := getChartVersion(filepath.Join(configDirectory, "transformers", "qseokversion.yaml"), "qliksense")
if err != nil {
return nil, err
@@ -91,7 +97,7 @@ func (p *Qliksense) About(gitRef, profile string) (*VersionOutput, error) {
}, nil
}
func getConfigDirectory(gitUrl, gitRef, profileEntered string) (dir string, isTemporary bool, profile string, err error) {
func (q *Qliksense) getConfigDirectory(gitUrl, gitRef, profileEntered string) (dir string, isTemporary bool, profile string, err error) {
profile = profileEntered
if profile == "" {
profile = defaultProfile
@@ -113,13 +119,13 @@ func getConfigDirectory(gitUrl, gitRef, profileEntered string) (dir string, isTe
return dir, false, profile, nil
}
var profileFromCR string
exists, dir, profileFromCR, err = configExistsInCR()
var profileFromCurrentContext string
exists, dir, profileFromCurrentContext, err = q.ConfigExistsInCurrentContext()
if err != nil {
return "", false, "", err
} else if exists {
if profileEntered == "" {
profile = profileFromCR
profile = profileFromCurrentContext
}
return dir, false, profile, nil
}
@@ -164,8 +170,17 @@ func configExistsInCurrentDirectory(profile string) (exists bool, currentDirecto
return exists, currentDirectory, err
}
func configExistsInCR() (exists bool, directory string, profile string, err error) {
return exists, directory, profile, err
func (q *Qliksense) ConfigExistsInCurrentContext() (exists bool, directory string, profile string, err error) {
qConfig := qapi.NewQConfig(q.QliksenseHome)
if currentCr, err := qConfig.GetCurrentCR(); err != nil {
return false, "", "", err
} else if currentCr.Spec.ManifestsRoot == "" {
return false, "", "", nil
} else if path.Base(currentCr.Spec.ManifestsRoot) != "manifests" {
return false, "", "", errors.New("currentCr.Spec.ManifestsRoot path should terminate with manifests/")
} else {
return true, path.Join(currentCr.Spec.ManifestsRoot, "../"), currentCr.Spec.Profile, nil
}
}
func getImageList(yamlContent []byte) ([]string, error) {

View File

@@ -2,10 +2,13 @@ package qliksense
import (
"fmt"
"io/ioutil"
"os"
"path"
"reflect"
"testing"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func Test_About_getImageList(t *testing.T) {
@@ -260,13 +263,13 @@ func Test_About_getConfigDirectory(t *testing.T) {
var testCases = []struct {
name string
setup func(t *testing.T) (gitUrl, gitRef, profileEntered string)
verify func(configDir string, isTemporary bool, profile string) (ok bool, reason string, err error)
cleanup func(configDir string) error
setup func(t *testing.T) (q *Qliksense, gitUrl, gitRef, profileEntered string)
verify func(q *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error)
cleanup func(q *Qliksense, configDir string) error
}{
{
name: "config in current directory and default profile",
setup: func(t *testing.T) (gitUrl, gitRef, profileEntered string) {
setup: func(t *testing.T) (q *Qliksense, gitUrl, gitRef, profileEntered string) {
currentDirectory, err := os.Getwd()
if err != nil {
t.Fatalf("error obtaining current directory: %v\n", err)
@@ -277,9 +280,9 @@ func Test_About_getConfigDirectory(t *testing.T) {
if err != nil {
t.Fatalf("error making path: %v, err: %v\n", defaultProfilePath, err)
}
return "no-clone-for-you", "", ""
return &Qliksense{}, "no-clone-for-you", "", ""
},
verify: func(configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
verify: func(_ *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
currentDirectory, err := os.Getwd()
if err != nil {
return false, "", err
@@ -299,7 +302,7 @@ func Test_About_getConfigDirectory(t *testing.T) {
return true, "", nil
},
cleanup: func(configDir string) error {
cleanup: func(_ *Qliksense, configDir string) error {
if currentDirectory, err := os.Getwd(); err != nil {
return err
} else if err := os.RemoveAll(path.Join(currentDirectory, "manifests")); err != nil {
@@ -310,7 +313,7 @@ func Test_About_getConfigDirectory(t *testing.T) {
},
{
name: "config in current directory and profile specified",
setup: func(t *testing.T) (gitUrl, gitRef, profileEntered string) {
setup: func(t *testing.T) (q *Qliksense, gitUrl, gitRef, profileEntered string) {
currentDirectory, err := os.Getwd()
if err != nil {
t.Fatalf("error obtaining current directory: %v\n", err)
@@ -322,9 +325,9 @@ func Test_About_getConfigDirectory(t *testing.T) {
if err != nil {
t.Fatalf("error making path: %v, err: %v\n", defaultProfilePath, err)
}
return "no-clone-for-you", "", profileEntered
return &Qliksense{}, "no-clone-for-you", "", profileEntered
},
verify: func(configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
verify: func(_ *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
currentDirectory, err := os.Getwd()
if err != nil {
return false, "", err
@@ -344,7 +347,7 @@ func Test_About_getConfigDirectory(t *testing.T) {
return true, "", nil
},
cleanup: func(configDir string) error {
cleanup: func(_ *Qliksense, configDir string) error {
if currentDirectory, err := os.Getwd(); err != nil {
return err
} else if err := os.RemoveAll(path.Join(currentDirectory, "manifests")); err != nil {
@@ -355,10 +358,10 @@ func Test_About_getConfigDirectory(t *testing.T) {
},
{
name: "config downloaded from git based on specific git ref and default profile used",
setup: func(t *testing.T) (gitUrl, gitRef, profileEntered string) {
return "https://github.com/test/HelloWorld", "asd", ""
setup: func(t *testing.T) (q *Qliksense, gitUrl, gitRef, profileEntered string) {
return &Qliksense{}, "https://github.com/test/HelloWorld", "asd", ""
},
verify: func(configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
verify: func(_ *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
ok, reason = verifyAsdBranch(configDir)
if !ok {
return ok, reason, nil
@@ -374,7 +377,7 @@ func Test_About_getConfigDirectory(t *testing.T) {
return true, "", nil
},
cleanup: func(configDir string) error {
cleanup: func(_ *Qliksense, configDir string) error {
tmpDir := os.TempDir()
if path.Clean(path.Dir(path.Dir(configDir))) == path.Clean(tmpDir) && path.Base(configDir) == "repo" {
@@ -388,10 +391,10 @@ func Test_About_getConfigDirectory(t *testing.T) {
},
{
name: "config downloaded from git based on specific git ref and profile specified",
setup: func(t *testing.T) (gitUrl, gitRef, profileEntered string) {
return "https://github.com/test/HelloWorld", "asd", "foo"
setup: func(t *testing.T) (q *Qliksense, gitUrl, gitRef, profileEntered string) {
return &Qliksense{}, "https://github.com/test/HelloWorld", "asd", "foo"
},
verify: func(configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
verify: func(_ *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
ok, reason = verifyAsdBranch(configDir)
if !ok {
return ok, reason, nil
@@ -407,7 +410,7 @@ func Test_About_getConfigDirectory(t *testing.T) {
return true, "", nil
},
cleanup: func(configDir string) error {
cleanup: func(_ *Qliksense, configDir string) error {
tmpDir := os.TempDir()
if path.Clean(path.Dir(path.Dir(configDir))) == path.Clean(tmpDir) && path.Base(configDir) == "repo" {
@@ -421,10 +424,21 @@ func Test_About_getConfigDirectory(t *testing.T) {
},
{
name: "config downloaded from git from master branch and default profile used",
setup: func(t *testing.T) (gitUrl, gitRef, profileEntered string) {
return "https://github.com/test/HelloWorld", "", ""
setup: func(t *testing.T) (q *Qliksense, gitUrl, gitRef, profileEntered string) {
if qliksenseHome, err := ioutil.TempDir("", ""); err != nil {
t.Fatalf("error creating tmp qliksenseHome directory: %v\n", err)
return nil, "", "", ""
} else {
q := &Qliksense{QliksenseHome: qliksenseHome}
if err := q.SetUpQliksenseDefaultContext(); err != nil {
t.Fatalf("error setting up default context in the tmp dir: %v\n", err)
return nil, "", "", ""
} else {
return q, "https://github.com/test/HelloWorld", "", ""
}
}
},
verify: func(configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
verify: func(_ *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
ok, reason = verifyMasterBranch(configDir)
if !ok {
return ok, reason, nil
@@ -440,24 +454,37 @@ func Test_About_getConfigDirectory(t *testing.T) {
return true, "", nil
},
cleanup: func(configDir string) error {
cleanup: func(q *Qliksense, configDir string) error {
tmpDir := os.TempDir()
if path.Clean(path.Dir(path.Dir(configDir))) == path.Clean(tmpDir) && path.Base(configDir) == "repo" {
tmpTmpDir := path.Dir(configDir)
if err := os.RemoveAll(tmpTmpDir); err != nil {
return err
}
}
if err := os.RemoveAll(q.QliksenseHome); err != nil {
return err
}
return nil
},
},
{
name: "config downloaded from git from master branch and profile specified",
setup: func(t *testing.T) (gitUrl, gitRef, profileEntered string) {
return "https://github.com/test/HelloWorld", "", "foo"
setup: func(t *testing.T) (q *Qliksense, gitUrl, gitRef, profileEntered string) {
if qliksenseHome, err := ioutil.TempDir("", ""); err != nil {
t.Fatalf("error creating tmp qliksenseHome directory: %v\n", err)
return nil, "", "", ""
} else {
q := &Qliksense{QliksenseHome: qliksenseHome}
if err := q.SetUpQliksenseDefaultContext(); err != nil {
t.Fatalf("error setting up default context in the tmp dir: %v\n", err)
return nil, "", "", ""
} else {
return q, "https://github.com/test/HelloWorld", "", "foo"
}
}
},
verify: func(configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
verify: func(_ *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
ok, reason = verifyMasterBranch(configDir)
if !ok {
return ok, reason, nil
@@ -473,31 +500,78 @@ func Test_About_getConfigDirectory(t *testing.T) {
return true, "", nil
},
cleanup: func(configDir string) error {
cleanup: func(q *Qliksense, configDir string) error {
tmpDir := os.TempDir()
if path.Clean(path.Dir(path.Dir(configDir))) == path.Clean(tmpDir) && path.Base(configDir) == "repo" {
tmpTmpDir := path.Dir(configDir)
if err := os.RemoveAll(tmpTmpDir); err != nil {
return err
}
}
if err := os.RemoveAll(q.QliksenseHome); err != nil {
return err
}
return nil
},
},
{
name: "config loaded from current context",
setup: func(t *testing.T) (q *Qliksense, gitUrl, gitRef, profileEntered string) {
if qliksenseHome, err := ioutil.TempDir("", ""); err != nil {
t.Fatalf("error creating tmp qliksenseHome directory: %v\n", err)
return nil, "", "", ""
} else {
q := &Qliksense{QliksenseHome: qliksenseHome}
if err := q.SetUpQliksenseDefaultContext(); err != nil {
t.Fatalf("error setting up default context in the tmp dir: %v\n", err)
return nil, "", "", ""
} else if err := q.FetchQK8s("master"); err != nil {
t.Fatalf("error fetching master config to the tmp dir: %v\n", err)
return nil, "", "", ""
} else {
return q, "no-git-clone-for-you", "", ""
}
}
},
verify: func(q *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
qConfig := qapi.NewQConfig(q.QliksenseHome)
expectedConfigDir := qConfig.BuildRepoPath("master")
if configDir != expectedConfigDir {
return false, fmt.Sprintf("expected configDir to be %v", expectedConfigDir), nil
}
if isTemporary {
return false, "expected isTemporary to be false", nil
}
if profile != "docker-desktop" {
return false, fmt.Sprintf("expected profile to be: docker-desktop, but it was: %v", profile), nil
}
return true, "", nil
},
cleanup: func(q *Qliksense, configDir string) error {
if err := os.RemoveAll(q.QliksenseHome); err != nil {
return err
}
return nil
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
configDirectory, isTemporary, profile, err := getConfigDirectory(testCase.setup(t))
q, gitUrl, gitRef, profileEntered := testCase.setup(t)
configDirectory, isTemporary, profile, err := q.getConfigDirectory(gitUrl, gitRef, profileEntered)
if err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
if ok, reason, err := testCase.verify(configDirectory, isTemporary, profile); err != nil {
if ok, reason, err := testCase.verify(q, configDirectory, isTemporary, profile); err != nil {
t.Fatalf("unexpected verification error: %v\n", err)
} else if !ok {
t.Fatalf("verification failed: %v\n", reason)
} else if err := testCase.cleanup(configDirectory); err != nil {
} else if err := testCase.cleanup(q, configDirectory); err != nil {
t.Fatalf("unexpected cleanup error: %v\n", err)
}
})

View File

@@ -2,13 +2,14 @@ package qliksense
import (
"fmt"
"github.com/mitchellh/go-homedir"
"os"
"path"
"path/filepath"
"github.com/mitchellh/go-homedir"
"github.com/qlik-oss/k-apis/pkg/cr"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
"gopkg.in/yaml.v2"
)
const (
@@ -24,21 +25,33 @@ func (q *Qliksense) ConfigApplyQK8s() error {
fmt.Println("cannot get the current-context cr", err)
return err
}
return applyConfigToK8s(qcr)
return q.applyConfigToK8s(qcr, "")
}
func applyConfigToK8s(qcr *qapi.QliksenseCR) error {
func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR, cmd string) error {
// apply qliksense-init crd first
mroot := qcr.Spec.GetManifestsRoot()
qInitMsPath := filepath.Join(mroot, Q_INIT_CRD_PATH)
qInitByte, err := executeKustomizeBuild(qInitMsPath)
if err != nil {
fmt.Println("cannot generate crds for qliksense-init", err)
return err
if qcr.Spec.RotateKeys != "None" {
if err := os.Unsetenv("EJSON_KEY"); err != nil {
fmt.Printf("error unsetting EJSON_KEY environment variable: %v\n", err)
return err
}
if err := os.Setenv("EJSON_KEYDIR", q.QliksenseEjsonKeyDir); err != nil {
fmt.Printf("error setting EJSON_KEYDIR environment variable: %v\n", err)
return err
}
}
if err = qapi.KubectlApply(string(qInitByte)); err != nil {
return err
if cmd != "upgrade" {
qInitByte, err := executeKustomizeBuild(qInitMsPath)
if err != nil {
fmt.Println("cannot generate crds for qliksense-init", err)
return err
}
if err = qapi.KubectlApply(string(qInitByte)); err != nil {
return err
}
}
userHomeDir, err := homedir.Dir()
@@ -76,15 +89,15 @@ func (q *Qliksense) ConfigViewCR() error {
func (q *Qliksense) getCurrentCRString() (string, error) {
qConfig := qapi.NewQConfig(q.QliksenseHome)
qcr, err := qConfig.GetCurrentCR()
if err != nil {
fmt.Println("cannot get the current-context cr", err)
return "", err
}
out, err := yaml.Marshal(qcr)
if err != nil {
fmt.Println("cannot unmarshal cr ", err)
return "", err
}
return string(out), nil
return q.getCRString(qConfig.Spec.CurrentContext)
}
func (q *Qliksense) getCRString(contextName string) (string, error) {
qConfig := qapi.NewQConfig(q.QliksenseHome)
qcr, err := qConfig.GetCR(contextName)
if err != nil {
fmt.Println("cannot get the context cr", err)
return "", err
}
return qcr.GetString()
}

View File

@@ -1,318 +1,316 @@
package qliksense
import (
"io/ioutil"
"crypto/rsa"
"fmt"
"log"
"os"
"path/filepath"
"regexp"
"strings"
"text/tabwriter"
"github.com/qlik-oss/k-apis/pkg/config"
b64 "encoding/base64"
ansi "github.com/mattn/go-colorable"
"github.com/qlik-oss/sense-installer/pkg/api"
yaml "gopkg.in/yaml.v2"
"github.com/ttacon/chalk"
)
const (
// Below are some constants to support qliksense context setup
QliksenseConfigHome = "/.qliksense"
QliksenseConfigContextHome = "/.qliksense/contexts"
QliksenseConfigApiVersion = "config.qlik.com/v1"
QliksenseConfigKind = "QliksenseConfig"
QliksenseMetadataName = "QliksenseConfigMetadata"
QliksenseContextApiVersion = "qlik.com/v1"
QliksenseContextKind = "Qliksense"
QliksenseDefaultProfile = "docker-desktop"
QliksenseConfigFile = "config.yaml"
QliksenseContextsDir = "contexts"
DefaultQliksenseContext = "qliksense-default"
QliksenseConfigFile = "config.yaml"
QliksenseContextsDir = "contexts"
DefaultQliksenseContext = "qlik-default"
MaxContextNameLength = 17
QliksenseSecretsDir = "secrets"
)
// WriteToFile writes content into specified file
func WriteToFile(content interface{}, targetFile string) {
if content == nil || targetFile == "" {
return
}
file, err := os.OpenFile(targetFile, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
LogDebugMessage("There was an error creating the file: %s, %v", targetFile, err)
log.Fatal(err)
}
defer file.Close()
x, err := yaml.Marshal(content)
if err != nil {
log.Fatalf("An error occurred during marshalling CR: %v", err)
}
LogDebugMessage("Marshalled yaml:\n%s\nWriting to file...", x)
// truncating the file before we write new content
file.Truncate(0)
file.Seek(0, 0)
_, err = file.Write(x)
if err != nil {
log.Fatal(err)
}
LogDebugMessage("Wrote content into %s", targetFile)
}
// ReadFromFile reads content from specified sourcefile
func ReadFromFile(content interface{}, sourceFile string) {
if content == nil || sourceFile == "" {
return
}
contents, err := ioutil.ReadFile(sourceFile)
if err != nil {
LogDebugMessage("There was an error reading from file: %s, %v", sourceFile, err)
log.Fatal(err)
}
if err := yaml.Unmarshal(contents, content); err != nil {
log.Fatalf("An error occurred during unmarshalling: %v", err)
}
}
// AddCommonConfig adds common configs into CRs
func AddCommonConfig(qliksenseCR api.QliksenseCR, contextName string) api.QliksenseCR {
qliksenseCR.ApiVersion = QliksenseContextApiVersion
qliksenseCR.Kind = QliksenseContextKind
if qliksenseCR.Metadata == nil {
qliksenseCR.Metadata = &api.Metadata{}
}
if qliksenseCR.Metadata.Name == "" {
qliksenseCR.Metadata.Name = contextName
}
qliksenseCR.Spec = &config.CRSpec{}
qliksenseCR.Spec.Profile = QliksenseDefaultProfile
return qliksenseCR
}
// AddBaseQliksenseConfigs adds configs into config.yaml
func AddBaseQliksenseConfigs(qliksenseConfig api.QliksenseConfig, defaultQliksenseContext string) api.QliksenseConfig {
qliksenseConfig.ApiVersion = QliksenseConfigApiVersion
qliksenseConfig.Kind = QliksenseConfigKind
if qliksenseConfig.Metadata == nil {
qliksenseConfig.Metadata = &api.Metadata{}
}
qliksenseConfig.Metadata.Name = QliksenseMetadataName
if defaultQliksenseContext != "" {
if qliksenseConfig.Spec == nil {
qliksenseConfig.Spec = &api.ContextSpec{}
}
qliksenseConfig.Spec.CurrentContext = defaultQliksenseContext
}
return qliksenseConfig
}
func checkExists(filename string, isFile bool) os.FileInfo {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
if isFile {
LogDebugMessage("File does not exist")
} else {
LogDebugMessage("Dir does not exist")
}
return nil
}
LogDebugMessage("File exists")
return info
}
// FileExists checks if a file exists
func FileExists(filename string) bool {
if fe := checkExists(filename, true); fe != nil && !fe.IsDir() {
return true
}
return false
}
// DirExists checks if a directory exists
func DirExists(dirname string) bool {
if fe := checkExists(dirname, false); fe != nil && fe.IsDir() {
return true
}
return false
}
// LogDebugMessage logs a debug message
func LogDebugMessage(strMessage string, args ...interface{}) {
if os.Getenv("QLIKSENSE_DEBUG") == "true" {
log.Printf(strMessage, args...)
}
}
// SetSecrets - set-secrets <key>=<value> commands
func SetSecrets(q *Qliksense, args []string) error {
// retieve current context from config.yaml
qliksenseCR, qliksenseContextsFile := retrieveCurrentContextInfo(q)
func (q *Qliksense) SetSecrets(args []string, isK8sSecret bool) error {
api.LogDebugMessage("Args received: %v\n", args)
api.LogDebugMessage("isK8sSecret: %v\n", isK8sSecret)
processConfigArgs(args, qliksenseCR.Spec, qliksenseCR.Spec.AddToSecrets)
LogDebugMessage("CR now: %v", qliksenseCR.Spec)
// retieve current context from config.yaml
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q)
if err != nil {
return err
}
secretKeyPairLocation := filepath.Join(q.QliksenseHome, QliksenseSecretsDir, QliksenseContextsDir, qliksenseCR.Metadata.Name, QliksenseSecretsDir)
api.LogDebugMessage("SecretKeyLocation to store key pair: %s", secretKeyPairLocation)
if os.Getenv("QLIKSENSE_KEY_LOCATION") != "" {
api.LogDebugMessage("Env variable: QLIKSENSE_KEY_LOCATION= %s", os.Getenv("QLIKSENSE_KEY_LOCATION"))
secretKeyPairLocation = os.Getenv("QLIKSENSE_KEY_LOCATION")
}
// Env var: QLIKSENSE_KEY_LOCATION hasn't been set, so dropping key pair in the location:
// /.qliksense/secrets/contexts/<current-context>/secrets/
api.LogDebugMessage("Using default location to store keys: %s", secretKeyPairLocation)
publicKeyFilePath := filepath.Join(secretKeyPairLocation, api.QliksensePublicKey)
privateKeyFilePath := filepath.Join(secretKeyPairLocation, api.QliksensePrivateKey)
// try to create the dir if it doesn't exist
if !api.FileExists(publicKeyFilePath) || !api.FileExists(privateKeyFilePath) {
api.LogDebugMessage("Qliksense secretKeyLocation dir does not exist, creating it now: %s", secretKeyPairLocation)
if err := os.MkdirAll(secretKeyPairLocation, os.ModePerm); err != nil {
err = fmt.Errorf("Not able to create %s dir: %v", secretKeyPairLocation, err)
log.Println(err)
return err
}
// generating and storing key-pair
err1 := api.GenerateAndStoreSecretKeypair(secretKeyPairLocation)
if err1 != nil {
err1 = fmt.Errorf("Not able to generate and store key pair for encryption")
log.Println(err1)
return err1
}
}
var rsaPublicKey *rsa.PublicKey
var e1 error
// Read Public Key
publicKeybytes, err2 := api.ReadKeys(publicKeyFilePath)
if err2 != nil {
api.LogDebugMessage("Not able to read public key")
return err2
}
// api.LogDebugMessage("PublicKey: %+v", publicKeybytes)
// convert []byte into RSA public key object
rsaPublicKey, e1 = api.DecodeToPublicKey(publicKeybytes)
if e1 != nil {
return e1
}
resultArgs, err := api.ProcessConfigArgs(args)
if err != nil {
return err
}
for _, ra := range resultArgs {
// Metadata name in qliksense CR is the name of the current context
api.LogDebugMessage("Trying to retreive current context: %+v ----- %s", qliksenseCR.Metadata.Name, qliksenseContextsFile)
// encrypt value with RSA key pair
valueBytes := []byte(ra.Value)
cipherText, e2 := api.Encrypt(valueBytes, rsaPublicKey)
if e2 != nil {
return e2
}
api.LogDebugMessage("Returned cipher text: %s", b64.StdEncoding.EncodeToString(cipherText))
if isK8sSecret {
// TODO: store the key value in k8s as secret
api.LogDebugMessage("Need to create a Kubernetes secret")
}
// TODO: Extend AddToSecrets to support adding k8s key ref. (OR) create a separate method to support that
// store the encrypted value in the file (OR)
// TODO: k8s secret name in the file
qliksenseCR.Spec.AddToSecrets(ra.SvcName, ra.Key, string(cipherText))
}
// write modified content into context.yaml
WriteToFile(&qliksenseCR, qliksenseContextsFile)
api.WriteToFile(&qliksenseCR, qliksenseContextsFile)
return nil
}
// SetConfigs - set-configs <key>=<value> commands
func SetConfigs(q *Qliksense, args []string) error {
func (q *Qliksense) SetConfigs(args []string) error {
// retieve current context from config.yaml
qliksenseCR, qliksenseContextsFile := retrieveCurrentContextInfo(q)
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q)
if err != nil {
return err
}
processConfigArgs(args, qliksenseCR.Spec, qliksenseCR.Spec.AddToConfigs)
LogDebugMessage("CR now: %v", qliksenseCR.Spec)
resultArgs, err := api.ProcessConfigArgs(args)
if err != nil {
return err
}
for _, ra := range resultArgs {
qliksenseCR.Spec.AddToConfigs(ra.SvcName, ra.Key, ra.Value)
}
// write modified content into context.yaml
WriteToFile(&qliksenseCR, qliksenseContextsFile)
api.WriteToFile(&qliksenseCR, qliksenseContextsFile)
return nil
}
func retrieveCurrentContextInfo(q *Qliksense) (api.QliksenseCR, string) {
func retrieveCurrentContextInfo(q *Qliksense) (api.QliksenseCR, string, error) {
var qliksenseConfig api.QliksenseConfig
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
LogDebugMessage("qliksenseConfigFile: %s", qliksenseConfigFile)
ReadFromFile(&qliksenseConfig, qliksenseConfigFile)
api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile)
currentContext := qliksenseConfig.Spec.CurrentContext
LogDebugMessage("Current-context from config.yaml: %s", currentContext)
api.LogDebugMessage("Current-context from config.yaml: %s", currentContext)
if currentContext == "" {
// current-context is empty
log.Fatal(`Please run the "qliksense config set-context <context-name>" first before viewing the current context info`)
err := fmt.Errorf(`Please run the "qliksense config set-context <context-name>" first before viewing the current context info`)
log.Println(err)
return api.QliksenseCR{}, "", err
}
// read the context.yaml file
var qliksenseCR api.QliksenseCR
if currentContext == "" {
// current-context is empty
log.Fatal(`Please run the "qliksense config set-context <context-name>" first before viewing the current context info`)
err := fmt.Errorf(`Please run the "qliksense config set-context <context-name>" first before viewing the current context info`)
log.Println(err)
return api.QliksenseCR{}, "", err
}
qliksenseContextsFile := filepath.Join(q.QliksenseHome, QliksenseContextsDir, currentContext, currentContext+".yaml")
if !FileExists(qliksenseContextsFile) {
log.Fatalf("Context file does not exist.\nPlease try re-running `qliksense config set-context <context-name>` and then `qliksense config view` again")
if !api.FileExists(qliksenseContextsFile) {
err := fmt.Errorf("Context file does not exist.\nPlease try re-running `qliksense config set-context <context-name>` and then `qliksense config view` again")
log.Println(err)
return api.QliksenseCR{}, "", err
}
ReadFromFile(&qliksenseCR, qliksenseContextsFile)
api.ReadFromFile(&qliksenseCR, qliksenseContextsFile)
LogDebugMessage("Read QliksenseCR: %v", qliksenseCR)
LogDebugMessage("Read context file: %s", qliksenseContextsFile)
return qliksenseCR, qliksenseContextsFile
}
func processConfigArgs(args []string, cr *config.CRSpec, updateFn func(string, string, string)) {
// prepare received args
// split args[0] into key and value
if len(args) == 0 {
log.Fatalf("No args were provided. Please provide args to configure the current context")
}
re1 := regexp.MustCompile(`(\w{1,})\[name=(\w{1,})\]=("*[\w\-_/:0-9]+"*)`)
for _, arg := range args {
result := re1.FindStringSubmatch(arg)
LogDebugMessage("finding matches...\n")
LogDebugMessage("Results: %s, %s, %s", result[1], result[2], result[3])
// check if result array's length is == 4 (index 0 - is the full match & indices 1,2,3- are the fields we need)
if len(result) != 4 {
log.Fatal("Please provide valid args for this command")
}
updateFn(result[1], result[2], result[3])
}
api.LogDebugMessage("Read context file: %s, Read QliksenseCR: %v", qliksenseContextsFile, qliksenseCR)
return qliksenseCR, qliksenseContextsFile, nil
}
// SetOtherConfigs - set profile/namespace/storageclassname/git.repository commands
func SetOtherConfigs(q *Qliksense, args []string) error {
func (q *Qliksense) SetOtherConfigs(args []string) error {
// retieve current context from config.yaml
qliksenseCR, qliksenseContextsFile := retrieveCurrentContextInfo(q)
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q)
if err != nil {
return err
}
// modify appropriate fields
LogDebugMessage("Command: %s", args[0])
// split args[0] into key and value
if len(args) > 0 {
argsString := strings.Split(args[0], "=")
LogDebugMessage("Split string: %v", argsString)
if len(args) == 0 {
err := fmt.Errorf("No args were provided. Please provide args to configure the current context")
log.Println(err)
return err
}
for _, arg := range args {
argsString := strings.Split(arg, "=")
switch argsString[0] {
case "profile":
LogDebugMessage("Current profile: %s, Incoming profile: %s", qliksenseCR.Spec.Profile, argsString[1])
qliksenseCR.Spec.Profile = argsString[1]
LogDebugMessage("Current profile after modification: %s ", qliksenseCR.Spec.Profile)
api.LogDebugMessage("Current profile after modification: %s ", qliksenseCR.Spec.Profile)
case "namespace":
LogDebugMessage("Current namespace: %s, Incoming namespace: %s", qliksenseCR.Spec.NameSpace, argsString[1])
qliksenseCR.Spec.NameSpace = argsString[1]
LogDebugMessage("Current namespace after modification: %s ", qliksenseCR.Spec.NameSpace)
api.LogDebugMessage("Current namespace after modification: %s ", qliksenseCR.Spec.NameSpace)
case "git.repository":
LogDebugMessage("Current git.repository: %s, Incoming git.repository: %s", qliksenseCR.Spec.Git.Repository, argsString[1])
qliksenseCR.Spec.Git.Repository = argsString[1]
LogDebugMessage("Current git repository after modification: %s ", qliksenseCR.Spec.Git.Repository)
api.LogDebugMessage("Current git repository after modification: %s ", qliksenseCR.Spec.Git.Repository)
case "storageClassName":
LogDebugMessage("Current StorageClassName: %s, Incoming StorageClassName: %s", qliksenseCR.Spec.StorageClassName, argsString[1])
qliksenseCR.Spec.StorageClassName = argsString[1]
LogDebugMessage("Current StorageClassName after modification: %s ", qliksenseCR.Spec.StorageClassName)
api.LogDebugMessage("Current StorageClassName after modification: %s ", qliksenseCR.Spec.StorageClassName)
case "rotateKeys":
rotateKeys, err := validateInput(argsString[1])
if err != nil {
return err
}
qliksenseCR.Spec.RotateKeys = rotateKeys
api.LogDebugMessage("Current rotateKeys after modification: %s ", qliksenseCR.Spec.RotateKeys)
default:
log.Println("As part of the `qliksense config set` command, please enter one of: profile, namespace, storageClassName or git.repository arguments")
log.Println("As part of the `qliksense config set` command, please enter one of: profile, namespace, storageClassName,rotateKeys or git.repository arguments")
}
} else {
log.Fatalf("No args were provided. Please provide args to configure the current context")
}
// write modified content into context.yaml
WriteToFile(&qliksenseCR, qliksenseContextsFile)
api.WriteToFile(&qliksenseCR, qliksenseContextsFile)
return nil
}
// SetContextConfig - set the context for qliksense kubernetes resources to live in
func SetContextConfig(q *Qliksense, args []string) error {
func (q *Qliksense) SetContextConfig(args []string) error {
if len(args) == 1 {
LogDebugMessage("The command received: %s", args)
SetUpQliksenseContext(q.QliksenseHome, args[0], false)
q.SetUpQliksenseContext(args[0], false)
} else {
log.Fatalf("Please provide a name to configure the context with.")
err := fmt.Errorf("Please provide a name to configure the context with")
log.Println(err)
return err
}
return nil
}
func (q *Qliksense) ListContextConfigs() error {
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
var qliksenseConfig api.QliksenseConfig
api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile)
out := ansi.NewColorableStdout()
w := tabwriter.NewWriter(out, 5, 8, 0, '\t', 0)
fmt.Fprintln(w, chalk.Underline.TextStyle("Context Name"), "\t", chalk.Underline.TextStyle("CR File Location"))
w.Flush()
if len(qliksenseConfig.Spec.Contexts) > 0 {
for _, cont := range qliksenseConfig.Spec.Contexts {
fmt.Fprintln(w, cont.Name, "\t", cont.CrFile, "\t")
}
w.Flush()
fmt.Fprintln(out, "")
fmt.Fprintln(out, chalk.Bold.TextStyle("Current Context : "), qliksenseConfig.Spec.CurrentContext)
} else {
fmt.Fprintln(out, "No Contexts Available")
}
return nil
}
// SetUpQliksenseDefaultContext - to setup dir structure for default qliksense context
func SetUpQliksenseDefaultContext(qlikSenseHome string) {
SetUpQliksenseContext(qlikSenseHome, DefaultQliksenseContext, true)
func (q *Qliksense) SetUpQliksenseDefaultContext() error {
return q.SetUpQliksenseContext(DefaultQliksenseContext, true)
}
// SetUpQliksenseContext - to setup qliksense context
func SetUpQliksenseContext(qlikSenseHome, contextName string, isDefaultContext bool) {
qliksenseConfigFile := filepath.Join(qlikSenseHome, QliksenseConfigFile)
func (q *Qliksense) SetUpQliksenseContext(contextName string, isDefaultContext bool) error {
// check the length of the context name entered by the user, it should not exceed 17 chars
if len(contextName) > MaxContextNameLength {
err := fmt.Errorf("Please enter a context-name with utmost 17 characters")
log.Println(err)
return err
}
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
var qliksenseConfig api.QliksenseConfig
configFileTrack := false
if !FileExists(qliksenseConfigFile) {
qliksenseConfig = AddBaseQliksenseConfigs(qliksenseConfig, contextName)
if !api.FileExists(qliksenseConfigFile) {
qliksenseConfig.AddBaseQliksenseConfigs(contextName)
} else {
ReadFromFile(&qliksenseConfig, qliksenseConfigFile)
api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile)
if isDefaultContext { // if config file exits but a default context is requested, we want to prevent writing to config file
configFileTrack = true
}
}
// creating a file in the name of the context if it does not exist/ opening it to append/modify content if it already exists
qliksenseContextsDir1 := filepath.Join(qlikSenseHome, QliksenseContextsDir)
if !DirExists(qliksenseContextsDir1) {
qliksenseContextsDir1 := filepath.Join(q.QliksenseHome, QliksenseContextsDir)
if !api.DirExists(qliksenseContextsDir1) {
if err := os.Mkdir(qliksenseContextsDir1, os.ModePerm); err != nil {
log.Fatalf("Not able to create %s dir: %v", qliksenseContextsDir1, err)
err = fmt.Errorf("Not able to create %s dir: %v", qliksenseContextsDir1, err)
log.Println(err)
return err
}
}
LogDebugMessage("%s exists", qliksenseContextsDir1)
// creating contexts/qliksense-default.yaml file
api.LogDebugMessage("%s exists", qliksenseContextsDir1)
// creating contexts/qlik-default/qlik-default.yaml file
qliksenseContextFile := filepath.Join(qliksenseContextsDir1, contextName, contextName+".yaml")
var qliksenseCR api.QliksenseCR
defaultContextsDir := filepath.Join(qliksenseContextsDir1, contextName)
if !DirExists(defaultContextsDir) {
if !api.DirExists(defaultContextsDir) {
if err := os.Mkdir(defaultContextsDir, os.ModePerm); err != nil {
log.Fatalf("Not able to create %s: %v", defaultContextsDir, err)
err = fmt.Errorf("Not able to create %s: %v", defaultContextsDir, err)
log.Println(err)
return err
}
}
LogDebugMessage("%s exists", defaultContextsDir)
if !FileExists(qliksenseContextFile) {
qliksenseCR = AddCommonConfig(qliksenseCR, contextName)
LogDebugMessage("Added Context: %s", contextName)
api.LogDebugMessage("%s exists", defaultContextsDir)
if !api.FileExists(qliksenseContextFile) {
qliksenseCR.AddCommonConfig(contextName)
api.LogDebugMessage("Added Context: %s", contextName)
} else {
ReadFromFile(&qliksenseCR, qliksenseContextFile)
api.ReadFromFile(&qliksenseCR, qliksenseContextFile)
}
WriteToFile(&qliksenseCR, qliksenseContextFile)
api.WriteToFile(&qliksenseCR, qliksenseContextFile)
ctxTrack := false
if len(qliksenseConfig.Spec.Contexts) > 0 {
for _, ctx := range qliksenseConfig.Spec.Contexts {
@@ -331,6 +329,26 @@ func SetUpQliksenseContext(qlikSenseHome, contextName string, isDefaultContext b
}
qliksenseConfig.Spec.CurrentContext = contextName
if !configFileTrack {
WriteToFile(&qliksenseConfig, qliksenseConfigFile)
api.WriteToFile(&qliksenseConfig, qliksenseConfigFile)
}
return nil
}
func validateInput(input string) (string, error) {
var err error
validInputs := []string{"yes", "no", "None"}
isValid := false
for _, elem := range validInputs {
if input == elem {
isValid = true
break
}
}
if !isValid {
err = fmt.Errorf("Please enter one of: yes, no or None")
log.Println(err)
}
return input, err
}

View File

@@ -0,0 +1,221 @@
package qliksense
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"testing"
)
var (
testDir = "./tests"
)
func setup() func() {
// create tests dir
if err := os.Mkdir(testDir, 0777); err != nil {
log.Printf("\nError occurred: %v", err)
}
config :=
`
apiVersion: config.qlik.com/v1
kind: QliksenseConfig
metadata:
name: qliksenseConfig
spec:
contexts:
- name: qlik-default
crLocation: /root/.qliksense/contexts/qlik-default.yaml
currentContext: qlik-default
`
configFile := filepath.Join(testDir, "config.yaml")
// tests/config.yaml exists
ioutil.WriteFile(configFile, []byte(config), 0777)
contextYaml :=
`
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: qlik-default
spec:
profile: docker-desktop
rotateKeys: "yes"
releaseName: qlik-default
`
qlikDefaultContext := "qlik-default"
// create contexts/qlik-default/ under tests/
contexts := "contexts"
contextsDir := filepath.Join(testDir, contexts, qlikDefaultContext)
if err := os.MkdirAll(contextsDir, 0777); err != nil {
err = fmt.Errorf("Not able to create directories")
log.Fatal(err)
}
contextFile := filepath.Join(contextsDir, qlikDefaultContext+".yaml")
ioutil.WriteFile(contextFile, []byte(contextYaml), 0777)
tearDown := func() {
os.RemoveAll(testDir)
}
return tearDown
}
func Test_retrieveCurrentContextInfo(t *testing.T) {
tearDown := setup()
defer tearDown()
q := &Qliksense{
QliksenseHome: testDir,
}
_, _, err := retrieveCurrentContextInfo(q)
if err != nil {
t.FailNow()
}
}
func TestSetUpQliksenseContext(t *testing.T) {
type args struct {
qlikSenseHome string
contextName string
isDefaultContext bool
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "valid contextname",
args: args{
qlikSenseHome: testDir,
contextName: "testContext1",
isDefaultContext: false,
},
wantErr: false,
},
{
name: "invalid contextname",
args: args{
qlikSenseHome: testDir,
contextName: "testContext_abcdefgh",
isDefaultContext: false,
},
wantErr: true,
},
}
tearDown := setup()
defer tearDown()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
q, err := New(tt.args.qlikSenseHome)
if err != nil {
t.Errorf("unable to create a qliksense instance")
return
}
if err := q.SetUpQliksenseContext(tt.args.contextName, tt.args.isDefaultContext); (err != nil) != tt.wantErr {
t.Errorf("SetUpQliksenseContext() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestSetUpQliksenseDefaultContext(t *testing.T) {
type args struct {
qlikSenseHome string
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "valid case",
args: args{
qlikSenseHome: testDir,
},
wantErr: false,
},
}
tearDown := setup()
defer tearDown()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
q, err := New(tt.args.qlikSenseHome)
if err != nil {
t.Errorf("unable to create a qliksense instance")
return
}
if err := q.SetUpQliksenseDefaultContext(); (err != nil) != tt.wantErr {
t.Errorf("SetUpQliksenseDefaultContext() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestSetOtherConfigs(t *testing.T) {
type args struct {
q *Qliksense
args []string
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "valid case",
args: args{
q: &Qliksense{
QliksenseHome: testDir,
},
args: []string{"profile=minikube", "namespace=qliksense", "storageClassName=efs"},
},
wantErr: false,
},
}
tearDown := setup()
defer tearDown()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.args.q.SetOtherConfigs(tt.args.args); (err != nil) != tt.wantErr {
t.Errorf("SetOtherConfigs() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestSetConfigs(t *testing.T) {
type args struct {
q *Qliksense
args []string
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "valid case",
args: args{
q: &Qliksense{
QliksenseHome: testDir,
},
args: []string{"qliksense[name=acceptEULA]=\"yes\"", "qliksense[name=mongoDbUri]=\"mongo://mongo:3307\""},
},
wantErr: false,
},
}
tearDown := setup()
defer tearDown()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.args.q.SetConfigs(tt.args.args); (err != nil) != tt.wantErr {
t.Errorf("SetConfigs() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@@ -29,7 +29,7 @@ import (
)
// PullImages ...
func (p *Qliksense) PullImages(gitRef, profile string, engine bool) error {
func (q *Qliksense) PullImages(gitRef, profile string, engine bool) error {
var (
image, versionFile, imagesDir, homeDir string
err error
@@ -38,7 +38,7 @@ func (p *Qliksense) PullImages(gitRef, profile string, engine bool) error {
println("getting images list...")
// TODO: get getref and profile from config/cr for About function call
if versionOut, err = p.About(gitRef, profile); err != nil {
if versionOut, err = q.About(gitRef, profile); err != nil {
return err
}
@@ -61,7 +61,7 @@ func (p *Qliksense) PullImages(gitRef, profile string, engine bool) error {
}
}
for _, image = range versionOut.Images {
if _, err = p.PullImage(image, engine); err != nil {
if _, err = q.PullImage(image, engine); err != nil {
fmt.Print(err)
}
println("---")
@@ -71,14 +71,14 @@ func (p *Qliksense) PullImages(gitRef, profile string, engine bool) error {
}
// PullImage ...
func (p *Qliksense) PullImage(imageName string, engine bool) (map[string]string, error) {
func (q *Qliksense) PullImage(imageName string, engine bool) (map[string]string, error) {
if engine {
return p.pullDockerImage(imageName)
return q.pullDockerImage(imageName)
}
return p.pullImage(imageName)
return q.pullImage(imageName)
}
func (p *Qliksense) commandTimeoutContext(commandTimeout time.Duration) (context.Context, context.CancelFunc) {
func (q *Qliksense) commandTimeoutContext(commandTimeout time.Duration) (context.Context, context.CancelFunc) {
ctx := context.Background()
var cancel context.CancelFunc = func() {}
if commandTimeout > 0 {
@@ -87,7 +87,7 @@ func (p *Qliksense) commandTimeoutContext(commandTimeout time.Duration) (context
return ctx, cancel
}
func (p *Qliksense) pullImage(imageName string) (map[string]string, error) {
func (q *Qliksense) pullImage(imageName string) (map[string]string, error) {
var (
ctx context.Context
cancel context.CancelFunc
@@ -98,7 +98,7 @@ func (p *Qliksense) pullImage(imageName string) (map[string]string, error) {
err error
policyContext *signature.PolicyContext
)
ctx, cancel = p.commandTimeoutContext(0)
ctx, cancel = q.commandTimeoutContext(0)
defer cancel()
if srcRef, err = alltransports.ParseImageName("docker://" + imageName); err != nil {
@@ -141,7 +141,7 @@ func (p *Qliksense) pullImage(imageName string) (map[string]string, error) {
})
return nil, err
}
func (p *Qliksense) pullDockerImage(imageName string) (map[string]string, error) {
func (q *Qliksense) pullDockerImage(imageName string) (map[string]string, error) {
var (
cli *command.DockerCli
dockerOutput io.Writer
@@ -156,7 +156,7 @@ func (p *Qliksense) pullDockerImage(imageName string) (map[string]string, error)
termFd uintptr
err error
)
ctx, cancel = p.commandTimeoutContext(0)
ctx, cancel = q.commandTimeoutContext(0)
defer cancel()
if cli, err = command.NewDockerCli(); err != nil {
@@ -208,7 +208,7 @@ func (p *Qliksense) pullDockerImage(imageName string) (map[string]string, error)
}
//TagAndPushImages ...
func (p *Qliksense) TagAndPushImages(registry string, engine bool) error {
func (q *Qliksense) TagAndPushImages(registry string, engine bool) error {
var (
image string
err error
@@ -221,7 +221,7 @@ func (p *Qliksense) TagAndPushImages(registry string, engine bool) error {
}
for _, image = range images.Images {
if err = p.TagAndPush(image, registry, engine); err != nil {
if err = q.TagAndPush(image, registry, engine); err != nil {
fmt.Print(err)
}
println("---")
@@ -230,7 +230,7 @@ func (p *Qliksense) TagAndPushImages(registry string, engine bool) error {
return nil
}
func (p *Qliksense) directoryExists(path string) (exists bool, err error) {
func (q *Qliksense) directoryExists(path string) (exists bool, err error) {
if info, err := os.Stat(path); err != nil && os.IsNotExist(err) {
exists = false
err = nil
@@ -246,14 +246,14 @@ func (p *Qliksense) directoryExists(path string) (exists bool, err error) {
}
//TagAndPush ...
func (p *Qliksense) TagAndPush(image string, registryName string, engine bool) error {
func (q *Qliksense) TagAndPush(image string, registryName string, engine bool) error {
if engine {
return p.tagAndDockerPush(image, registryName)
return q.tagAndDockerPush(image, registryName)
}
return p.tagAndPush(image, registryName)
return q.tagAndPush(image, registryName)
}
func (p *Qliksense) tagAndPush(image string, registryName string) error {
func (q *Qliksense) tagAndPush(image string, registryName string) error {
var (
ctx context.Context
cancel context.CancelFunc
@@ -265,7 +265,7 @@ func (p *Qliksense) tagAndPush(image string, registryName string) error {
policyContext *signature.PolicyContext
srcExists bool
)
ctx, cancel = p.commandTimeoutContext(0)
ctx, cancel = q.commandTimeoutContext(0)
defer cancel()
segments = strings.Split(image, "/")
@@ -278,11 +278,11 @@ func (p *Qliksense) tagAndPush(image string, registryName string) error {
return err
}
srcDir = filepath.Join(homeDir, ".qliksense", "images", nameTag[0], nameTag[1])
if srcExists, err = p.directoryExists(srcDir); err != nil {
if srcExists, err = q.directoryExists(srcDir); err != nil {
return err
}
if !srcExists {
if _, err = p.PullImage(image, false); err != nil {
if _, err = q.PullImage(image, false); err != nil {
return err
}
}
@@ -323,7 +323,7 @@ func (p *Qliksense) tagAndPush(image string, registryName string) error {
}
// PullImage ...
func (p *Qliksense) tagAndDockerPush(image string, registryName string) error {
func (q *Qliksense) tagAndDockerPush(image string, registryName string) error {
var (
cli *command.DockerCli
dockerOutput io.Writer
@@ -344,7 +344,7 @@ func (p *Qliksense) tagAndDockerPush(image string, registryName string) error {
err error
)
// TODO: Create a real cli config context
ctx, cancel = p.commandTimeoutContext(0)
ctx, cancel = q.commandTimeoutContext(0)
defer cancel()
if cli, err = command.NewDockerCli(); err != nil {
return err

View File

@@ -2,6 +2,8 @@ package qliksense
import (
"fmt"
"github.com/google/uuid"
kapis_git "github.com/qlik-oss/k-apis/pkg/git"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
@@ -29,7 +31,7 @@ func fetchAndUpdateCR(qConfig *qapi.QliksenseConfig, version string) error {
if repo, err := kapis_git.CloneRepository(destDir, QLIK_GIT_REPO, nil); err != nil {
return err
} else if err = kapis_git.Checkout(repo, version, "", nil); err != nil {
} else if err = kapis_git.Checkout(repo, version, fmt.Sprintf("%v-by-operator-%v", version, uuid.New().String()), nil); err != nil {
return err
}
qcr.Spec.ManifestsRoot = qConfig.BuildCurrentManifestsRoot(version)

View File

@@ -2,6 +2,7 @@ package qliksense
import (
"fmt"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
@@ -9,6 +10,8 @@ type InstallCommandOptions struct {
AcceptEULA string
Namespace string
StorageClass string
MongoDbUri string
RotateKeys string
}
func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) error {
@@ -39,15 +42,20 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) err
if opts.AcceptEULA != "" {
qcr.Spec.AddToConfigs("qliksense", "acceptEULA", opts.AcceptEULA)
}
if opts.MongoDbUri != "" {
qcr.Spec.AddToSecrets("qliksense", "mongoDbUri", opts.MongoDbUri)
}
if opts.StorageClass != "" {
qcr.Spec.StorageClassName = opts.StorageClass
}
if opts.Namespace != "" {
qcr.Spec.NameSpace = opts.Namespace
}
if opts.RotateKeys != "" {
qcr.Spec.RotateKeys = opts.RotateKeys
}
qConfig.WriteCurrentContextCR(qcr)
if err := applyConfigToK8s(qcr); err != nil {
if err := q.applyConfigToK8s(qcr, "install"); err != nil {
fmt.Println("cannot do kubectl apply on manifests")
return err
}

View File

@@ -1,11 +1,22 @@
package qliksense
import (
"bytes"
"encoding/base64"
"io"
"io/ioutil"
"log"
"os"
"path"
"strings"
"testing"
"gopkg.in/yaml.v3"
"github.com/Shopify/ejson"
"github.com/qlik-oss/k-apis/pkg/config"
"github.com/qlik-oss/k-apis/pkg/qust"
kapis_git "github.com/qlik-oss/k-apis/pkg/git"
)
@@ -47,11 +58,7 @@ metadata:
}
}
func Test_executeKustomizeBuild_onQlikConfig_DISABLED(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
}
func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("unexpected error: %v\n", err)
@@ -65,7 +72,70 @@ func Test_executeKustomizeBuild_onQlikConfig_DISABLED(t *testing.T) {
t.Fatalf("unexpected error: %v\n", err)
}
if _, err := executeKustomizeBuild(path.Join(configPath, "manifests", "base")); err != nil {
cr := &config.CRSpec{
ManifestsRoot: configPath,
}
if err := os.Setenv("EJSON_KEYDIR", tmpDir); err != nil {
t.Fatalf("unexpected error setting EJSON_KEYDIR environment variable: %v\n", err)
}
if err := os.Unsetenv("EJSON_KEY"); err != nil {
t.Fatalf("unexpected error unsetting EJSON_KEY: %v\n", err)
}
generateKeys(cr, "won't-use")
yamlResources, err := executeKustomizeBuild(path.Join(configPath, "manifests", "base", "resources", "users"))
if err != nil {
t.Fatalf("unexpected kustomize error: %v\n", err)
}
decoder := yaml.NewDecoder(bytes.NewReader(yamlResources))
var resource map[string]interface{}
keyIdBase64 := ""
for {
err := decoder.Decode(&resource)
if err != nil {
if err != io.EOF {
t.Fatalf("unexpected yaml decode error: %v\n", err)
}
break
}
if resource["kind"].(string) == "Secret" && strings.Contains(resource["metadata"].(map[string]interface{})["name"].(string), "users-secrets-") {
keyIdBase64 = resource["data"].(map[string]interface{})["tokenAuthPrivateKeyId"].(string)
break
}
}
untransformedKeyId := `(( (ds "data").kid ))`
if keyIdBase64 == "" {
t.Fatalf("expected keyIdBase64 for users secret to be non empty:\n")
} else if keyId, err := base64.StdEncoding.DecodeString(keyIdBase64); err != nil {
t.Fatalf("unexpected base64 decode error: %v\n", err)
} else if string(keyId) == untransformedKeyId {
t.Fatalf("unexpected users keyId: %v\n", untransformedKeyId)
}
}
func generateKeys(cr *config.CRSpec, defaultKeyDir string) {
log.Println("rotating all keys")
keyDir := getEjsonKeyDir(defaultKeyDir)
if ejsonPublicKey, ejsonPrivateKey, err := ejson.GenerateKeypair(); err != nil {
log.Printf("error generating an ejson key pair: %v\n", err)
} else if err := qust.GenerateKeys(cr, ejsonPublicKey); err != nil {
log.Printf("error generating application keys: %v\n", err)
} else if err := os.MkdirAll(keyDir, os.ModePerm); err != nil {
log.Printf("error makeing sure private key storage directory: %v exists, error: %v\n", keyDir, err)
} else if err := ioutil.WriteFile(path.Join(keyDir, ejsonPublicKey), []byte(ejsonPrivateKey), os.ModePerm); err != nil {
log.Printf("error storing ejson private key: %v\n", err)
}
}
func getEjsonKeyDir(defaultKeyDir string) string {
ejsonKeyDir := os.Getenv("EJSON_KEYDIR")
if ejsonKeyDir == "" {
ejsonKeyDir = defaultKeyDir
}
return ejsonKeyDir
}

View File

@@ -2,6 +2,7 @@ package qliksense
import (
"fmt"
"path/filepath"
"io"
"os"
"strings"
@@ -32,7 +33,7 @@ func (q *Qliksense) getYamlFromPackrFile(packrFile string) string {
func (q *Qliksense) getFileList(resourceType string) []string {
var resList []string
for _, v := range q.CrdBox.List() {
if strings.Contains(v, resourceType+"/") {
if strings.Contains(v, filepath.Join(resourceType, "")) {
resList = append(resList, []string{v}...)
}
}

View File

@@ -2,19 +2,29 @@
package qliksense
import (
"os"
"path"
"github.com/gobuffalo/packr/v2"
)
// Qliksense is the logic behind the qliksense client
type Qliksense struct {
QliksenseHome string
CrdBox *packr.Box
QliksenseHome string
QliksenseEjsonKeyDir string
CrdBox *packr.Box ``
}
// New qliksense client, initialized with useful defaults.
func New(qliksenseHome string) *Qliksense {
return &Qliksense{
func New(qliksenseHome string) (*Qliksense, error) {
qliksenseClient := &Qliksense{
QliksenseHome: qliksenseHome,
CrdBox: packr.New("crds", "./crds"),
}
qliksenseClient.QliksenseEjsonKeyDir = path.Join(qliksenseHome, "ejson", "keys")
if err := os.MkdirAll(qliksenseClient.QliksenseEjsonKeyDir, os.ModePerm); err != nil {
return nil, err
}
return qliksenseClient, nil
}

View File

@@ -0,0 +1,21 @@
package qliksense
import (
"errors"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func (q *Qliksense) UninstallQK8s(contextName string) error {
qConfig := qapi.NewQConfig(q.QliksenseHome)
if contextName == "" {
contextName = qConfig.Spec.CurrentContext
} else if !qConfig.IsContextExist(contextName) {
return errors.New("context name [ " + contextName + " ] not found")
}
str, err := q.getCRString(contextName)
if err != nil {
return err
}
return qapi.KubectlDelete(str)
}

39
pkg/qliksense/upgrade.go Normal file
View File

@@ -0,0 +1,39 @@
package qliksense
import (
"fmt"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func (q *Qliksense) UpgradeQK8s() error {
// step1: get CR
// step2: run kustomize
// step3: run kubectl apply
// fetch the version
qConfig := qapi.NewQConfig(q.QliksenseHome)
qcr, err := qConfig.GetCurrentCR()
if err != nil {
fmt.Println("cannot get the current-context cr", err)
return err
}
qcr.Spec.RotateKeys = "no"
if err := q.applyConfigToK8s(qcr, "upgrade"); err != nil {
fmt.Println("cannot do kubectl apply on manifests")
return err
}
fmt.Println("Install operator CR into cluster")
r, err := qcr.GetString()
if err != nil {
return err
}
if err := qapi.KubectlApply(r); err != nil {
fmt.Println("cannot do kubectl apply on operator CR")
}
return nil
}