Compare commits

..

58 Commits

Author SHA1 Message Date
Sanat Nayar
ffc0e5c062 go.sum 2020-03-15 23:06:28 -04:00
Sanat Nayar
9b2fec9987 go.sum 2020-03-15 22:46:42 -04:00
Sanat Nayar
bbecb56586 mod. go.sum 2020-03-13 16:34:19 -04:00
Andriy Bulynko
0b2fdae015 Scoping ejson keys and their rotations to the current context/CR (#214) 2020-03-13 16:28:06 -04:00
Sanat Nayar
ef595b4b3f added GitOps to spec 2020-03-13 12:59:17 -04:00
Ilir Bekteshi
d38852398e Merge pull request #212 from qlik-oss/ibiqlik/archiveignore
Ignore files/dirs on export
2020-03-12 14:06:33 +01:00
Ilir Bekteshi
e85636822d Ignore files/dirs on export 2020-03-12 14:03:20 +01:00
Mo Kassem
b9a80f588d Merge pull request #204 from qlik-oss/initmkdocs
Init mkdocs and gh workflow for publishing docs
2020-03-11 16:44:43 -04:00
Ilir Bekteshi
b9074d9f3c Change branch to master 2020-03-11 21:33:49 +01:00
Ilir Bekteshi
f3a3e97618 Init mkdocs and gh workflow for publishing docs
Initialize mkdocs for serving documentation on GitHub pages
On push to ms-3 branch a workflow publishes the documentation to gh-pages which gets served by GitHub
The content is based on README.md
2020-03-11 21:32:34 +01:00
Sanat Nayar
5c56013a70 Merge pull request #208 from qlik-oss/context_delete
context-delete Unit Tests
2020-03-11 14:58:19 -04:00
Sanat Nayar
134dbd44ed fixed error handling 2020-03-11 14:54:30 -04:00
Sanat Nayar
9898d3b9ec added tests 2020-03-11 11:27:46 -04:00
Sanat Nayar
32fa0a6570 added tests 2020-03-10 14:42:34 -04:00
Sanat Nayar
0bf1f3ca3a added tests 2020-03-10 14:22:39 -04:00
Sanat Nayar
8f56872842 Merge branch 'ms-3' into context_delete 2020-03-10 10:31:08 -04:00
Andriy Bulynko
defdb899b7 Locking pack2 dependency to v2.7.1 (#203) 2020-03-10 10:06:46 -04:00
Sanat Nayar
c7478fb8c1 added tests 2020-03-09 14:59:21 -04:00
Sanat Nayar
34df4b3a5c added tests 2020-03-09 14:54:48 -04:00
Sanat Nayar
c7bac06533 Merge branch 'ms-3' into context_delete 2020-03-09 14:52:03 -04:00
Sanat Nayar
89d5e261ab added tests 2020-03-09 14:50:33 -04:00
Sanat Nayar
6cd70cb643 added tests 2020-03-09 14:48:16 -04:00
Andriy Bulynko
941bb76444 No need to fetch anything before executing "qliksense crds" commands (#195) 2020-03-09 11:32:28 -04:00
Foysal Iqbal
513daa54f4 Text from encrypted (#196)
* fix secret naming
* Fixed a bug with setting git.repo
* fixed code to get decrypted CR and corresponding test
2020-03-08 20:08:30 -04:00
Andriy Bulynko
46b40d6011 Optionally keep temporary patches in the config repo after each install/upgrade and use an explicit command to remove these patches later (#193) 2020-03-06 15:08:02 -05:00
Sanat Nayar
7893329ab7 Merge pull request #152 from qlik-oss/context_delete
qliksense delete-context
2020-03-06 13:07:29 -05:00
Sanat Nayar
a127127317 reverted tests 2020-03-06 11:50:26 -05:00
Sanat Nayar
d8f1ab4f30 deleted secrets 2020-03-06 11:46:45 -05:00
Ashwathi Shiva
37bf4eae2b Merge branch 'context_delete' of github.com:qlik-oss/sense-installer into context_delete 2020-03-05 15:29:41 -05:00
Ashwathi Shiva
376f6ae838 Merge branch 'ms-3' into context_delete 2020-03-05 15:15:55 -05:00
Foysal Iqbal
659db113d7 Fix cr (#191) 2020-03-05 14:26:04 -05:00
Foysal Iqbal
19e8eda3a3 Fix install withsecret (#190) 2020-03-05 14:17:57 -05:00
Sanat Nayar
12e511ab04 added tests 2020-03-05 14:17:07 -05:00
Andriy Bulynko
3fec90e50b Pull/push to private image registry include the operator image (#187) 2020-03-05 14:12:27 -05:00
Sanat Nayar
36c32d4ca6 added tests 2020-03-05 13:58:55 -05:00
Sanat Nayar
21d7e63588 added tests 2020-03-05 13:57:16 -05:00
Sanat Nayar
7397fb3b34 added tests 2020-03-05 13:35:29 -05:00
Sanat Nayar
8608a69406 added tests 2020-03-05 13:32:26 -05:00
Sanat Nayar
e530a6a79e added unit tests 2020-03-05 10:27:25 -05:00
Foysal Iqbal
096ba5062b fix secret naming (#189)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-05 09:57:03 -05:00
Ashwathi Shiva
2719da19a5 fixed base64 decode logic in one spot, updated tests (#188)
* fix install with secret
* fixed code and tests
2020-03-04 18:54:16 -05:00
Foysal Iqbal
0d3ba901ef make accept eula mandatory for a context (#186)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-04 16:54:32 -05:00
Sanat Nayar
9630453a24 added unit tests 2020-03-04 15:55:01 -05:00
Sanat Nayar
a6d81fa8a5 added unit tests 2020-03-04 15:47:20 -05:00
Ashwathi Shiva
758496cac7 Will not display error message twice (#185)
* fixed a bug that shows error twice
* updated Readme and command help
2020-03-04 12:36:39 -05:00
Ashwathi Shiva
7fadbb8392 Command qliksense config fixed, unit tests added (#184)
qliksense config working and added unit tests
2020-03-04 10:51:21 -05:00
Andriy Bulynko
1c8e4df00a Applying private registry pull secret to the cluster (#181) 2020-03-03 17:51:06 -05:00
Foysal Iqbal
27226568fb Fix namespace (#182) 2020-03-03 15:10:42 -05:00
Sanat Nayar
36008ab0dc Merge branch 'ms-3' into context_delete 2020-02-28 09:36:37 -05:00
Sanat Nayar
9758746361 Merge branch 'ms-3' into context_delete 2020-02-27 16:12:04 -05:00
Sanat Nayar
1bbf82a15a prevented default deletion 2020-02-27 15:50:07 -05:00
Sanat Nayar
c65fad8f5c updated docs 2020-02-27 14:37:01 -05:00
Sanat Nayar
b29c1ec193 updated docs 2020-02-27 14:31:50 -05:00
Sanat Nayar
287ff62507 updated docs 2020-02-27 14:31:00 -05:00
Sanat Nayar
74d6863acf delete context change 2020-02-25 15:42:53 -05:00
Sanat Nayar
d261be6c13 delete context change 2020-02-25 11:41:41 -05:00
Sanat Nayar
a3a6c47375 delete context change 2020-02-24 16:41:35 -05:00
Sanat Nayar
b413e1bca9 init 2020-02-20 14:39:52 -05:00
48 changed files with 1890 additions and 383 deletions

5
.gitattributes vendored Normal file
View File

@@ -0,0 +1,5 @@
# Ignore all files and folders that start with .; .circleci, .github, .git, etc.
# Warning! This will ignore files in subfolders as well.
# If you needs files starting with . then change condition below to be specific
# for each file and folder that needs to be ignored
.* export-ignore

21
.github/workflows/mkdocs.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Publish docs via GitHub Pages
on:
push:
branches:
- master
paths:
- docs/
- mkdocs.yml
jobs:
build:
name: Deploy docs
runs-on: ubuntu-latest
steps:
- name: Checkout master
uses: actions/checkout@v1
- name: Deploy docs
uses: mhausenblas/mkdocs-deploy-gh-pages@1.11
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@ pkg/qliksense/crds
pkg/qliksense/packrd pkg/qliksense/packrd
pkg/qliksense/qliksense-packr.go pkg/qliksense/qliksense-packr.go
pkg/qliksense/docker-registry pkg/qliksense/docker-registry
/pkg/qliksense/tests

23
MKDOCS.md Normal file
View File

@@ -0,0 +1,23 @@
# Qlik Sense installer documentation
## Local development of documentation
Documentation is built using [mkdocs](https://www.mkdocs.org/) and uses [Material for MKDocs theme](https://squidfunk.github.io/mkdocs-material/)
Requirements: Python and PIP or Docker
```console
pip install mkdocs
pip install mkdocs-material
```
View live changes locally at http://localhost:8000
```console
mkdocs serve
```
### Docker
```console
docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material
```

View File

@@ -44,14 +44,16 @@ build: clean generate
$(MAKE) clean $(MAKE) clean
.PHONY: test .PHONY: test
test: test: clean generate
ifeq ($(shell ${WHICH} docker-registry 2>${DEVNUL}),) ifeq ($(shell ${WHICH} docker-registry 2>${DEVNUL}),)
$(eval TMP := $(shell mktemp -d)) $(eval TMP := $(shell mktemp -d))
git clone https://github.com/docker/distribution.git $(TMP)/docker-distribution git clone https://github.com/docker/distribution.git $(TMP)/docker-distribution
cd $(TMP)/docker-distribution; git checkout -b v2.7.1; make cd $(TMP)/docker-distribution; git checkout -b v2.7.1; make
cp $(TMP)/docker-distribution/bin/registry pkg/qliksense/docker-registry cp $(TMP)/docker-distribution/bin/registry pkg/qliksense/docker-registry
-rm -rf $(TMP)/docker-distribution
endif endif
go test -short -count=1 -tags "$(BUILDTAGS)" -v ./... go test -short -count=1 -tags "$(BUILDTAGS)" -v ./...
$(MAKE) clean
xbuild-all: clean generate xbuild-all: clean generate
$(foreach OS, $(SUPPORTED_PLATFORMS), \ $(foreach OS, $(SUPPORTED_PLATFORMS), \
@@ -70,14 +72,12 @@ $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT):
generate: get-crds packr2 generate: get-crds packr2
go generate ./... go generate ./...
HAS_PACKR2 := $(shell packr2)
packr2: packr2:
ifndef HAS_PACKR2 ifeq ($(shell ${WHICH} packr2 2>${DEVNUL}),)
go get -u github.com/gobuffalo/packr/v2/packr2 go get -u github.com/gobuffalo/packr/v2/packr2@v2.7.1
endif endif
clean: clean-packr clean: clean-packr
-rm -rf /tmp/operator
-rm -fr pkg/qliksense/crds -rm -fr pkg/qliksense/crds
clean-packr: packr2 clean-packr: packr2
@@ -92,3 +92,4 @@ get-crds:
cp $(TMP)/operator/deploy/*.yaml 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/*_crd.yaml pkg/qliksense/crds/crd
cp $(TMP)/operator/deploy/crds/*_cr.yaml pkg/qliksense/crds/cr cp $(TMP)/operator/deploy/crds/*_cr.yaml pkg/qliksense/crds/cr
-rm -rf $(TMP)/operator

View File

@@ -14,23 +14,23 @@
## About ## About
The Qlik Sense installer CLI (qliksense) provides an imperitive interface to many of the configurations that need to be applied against the declaritive structure described in [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s). This cli faciliates to do The Qlik Sense installer CLI (qliksense) provides an imperative interface to many of the configurations that need to be applied against the declarative structure described in [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s). This cli facilitates:
- installation of QSEoK - installation of QSEoK
- installation of qliksense operator to manage QSEoK - installation of qliksense operator to manage QSEoK
- air gapped installation of QSEoK - air gapped installation of QSEoK
This is a technology preview that uses qlik modified [kustomize](https://github.com/qlik-oss/kustomize) to kubernetes manifests of the versions of the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository. This is a technology preview that uses Qlik modified [kustomize](https://github.com/qlik-oss/kustomize) to kubernetes manifests of the versions of the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository.
For each version of a qliksense sense edge build there should be a corresponding release in [qliksense-k8s] repository under [releases](https://github.com/qlik-oss/qliksense-k8s/releases) For each version of a qliksense edge build there should be a corresponding release in [qliksense-k8s] repository under [releases](https://github.com/qlik-oss/qliksense-k8s/releases)
### Future Direction ### Future Direction
- More operations: - More operations:
- Expanded preflight checks - Expand preflight checks
- backup/restore operations - backup/restore operations
- fully support airgap installation of QSEoK - fully support airgap installation of QSEoK
- restore unwanted deletion of kubernetes resoureces - restore unwanted deletion of kubernetes resources
## Getting Started ## Getting Started
@@ -97,7 +97,7 @@ spec:
The `qliksense` cli creates a default qliksense context (different from kubectl context) named `qlik-default` which will be the prefix for all kubernetes resources created by the cli under this context latter on. New context and configuration can be created by the cli. The `qliksense` cli creates a default qliksense context (different from kubectl context) named `qlik-default` which will be the prefix for all kubernetes resources created by the cli under this context latter on. New context and configuration can be created by the cli.
```console ```console
$ qliksense config $ qliksense config -h
do operations on/around CR do operations on/around CR
Usage: Usage:
@@ -107,9 +107,9 @@ Available Commands:
apply generate the patchs and apply manifests to k8s apply generate the patchs and apply manifests to k8s
list-contexts retrieves the contexts and lists them list-contexts retrieves the contexts and lists them
set configure a key value pair into the current context set configure a key value pair into the current context
set-configs set configurations into the qliksense context set-configs set configurations into the qliksense context as key-value pairs
set-context Sets the context in which the Kubernetes cluster and resources live in set-context Sets the context in which the Kubernetes cluster and resources live in
set-secrets set secrets configurations into the qliksense context set-secrets set secrets configurations into the qliksense context as key-value pairs
view view the qliksense operator CR view view the qliksense operator CR
Flags: Flags:

View File

@@ -7,10 +7,13 @@ it will support following commands:
- `qliksense config apply` - generate the patchs and apply manifests to k8s - `qliksense config apply` - generate the patchs and apply manifests to k8s
- `qliksense config list-contexts` - retrieves the contexts and lists them - `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` - configure a key value pair into the current context
- `qliksense config set-configs` - set configurations into the qliksense context - `qliksense config set-configs` - set configurations into the qliksense context as key-value pairs
- `qliksense config set-context` - sets the context in which the Kubernetes cluster and resources live in - `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 set-secrets <service_name>.<attribute>="<value>" --secret=false` - set secrets configurations into the qliksense context as key-value pairs and show encrypted value as part of CR
- `qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=true` - set secrets configurations into the qliksense context as key-value pairs and show a key reference to the created Kubernetes secret resource as part of the CR
- `qliksense config view` - view the qliksense operator CR - `qliksense config view` - view the qliksense operator CR
- `qliksense config delete-context` - deletes a specific context locally (not in-cluster). Deletes context in spec of `config.yaml` and locally deletes entire folder of specified context (does not delete in-cluster secrets)
the global file that abstracts all the contexts is `config.yaml`, located at: `~/.qliksense/config.yaml`: the global file that abstracts all the contexts is `config.yaml`, located at: `~/.qliksense/config.yaml`:
```yaml ```yaml

View File

@@ -5,10 +5,16 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var configCmd = &cobra.Command{ func configCmd(q *qliksense.Qliksense) *cobra.Command {
Use: "config", var configCmd = &cobra.Command{
Short: "do operations on/around CR", Use: "config",
Long: `do operations on/around CR`, Short: "do operations on/around CR",
Long: `do operations on/around CR`,
RunE: func(cmd *cobra.Command, args []string) error {
return q.ConfigViewCR()
},
}
return configCmd
} }
func configApplyCmd(q *qliksense.Qliksense) *cobra.Command { func configApplyCmd(q *qliksense.Qliksense) *cobra.Command {

View File

@@ -2,6 +2,9 @@ package main
import ( import (
"errors" "errors"
"fmt"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
"github.com/qlik-oss/sense-installer/pkg/qliksense" "github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -13,9 +16,12 @@ func setContextConfigCmd(q *qliksense.Qliksense) *cobra.Command {
) )
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "set-context", Use: "set-context",
Short: "Sets the context in which the Kubernetes cluster and resources live in", Short: "Sets the context in which the Kubernetes cluster and resources live in",
Example: `qliksense config set-context <context_name>`, Example: `
qliksense config set-context <context_name>
- The above configuration will be displayed in the CR
`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return q.SetContextConfig(args) return q.SetContextConfig(args)
}, },
@@ -45,9 +51,12 @@ func setOtherConfigsCmd(q *qliksense.Qliksense) *cobra.Command {
) )
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "set", Use: "set",
Short: "configure a key value pair into the current context", Short: "configure a key value pair into the current context",
Example: `qliksense config set <key>=<value>`, Example: `
qliksense config set <key>=<value>
- The above configuration will be displayed in the CR
`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return q.SetOtherConfigs(args) return q.SetOtherConfigs(args)
}, },
@@ -61,9 +70,12 @@ func setConfigsCmd(q *qliksense.Qliksense) *cobra.Command {
) )
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "set-configs", Use: "set-configs",
Short: "set configurations into the qliksense context", Short: "set configurations into the qliksense context as key-value pairs",
Example: `qliksense config set-configs <key>=<value>`, Example: `
qliksense config set-configs <service_name>.<attribute>="<value>"
- The above configuration will be displayed in the CR
`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return q.SetConfigs(args) return q.SetConfigs(args)
}, },
@@ -78,9 +90,18 @@ func setSecretsCmd(q *qliksense.Qliksense) *cobra.Command {
) )
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "set-secrets", Use: "set-secrets",
Short: "set secrets configurations into the qliksense context", Short: "set secrets configurations into the qliksense context as key-value pairs",
Example: `qliksense config set-secrets <key>=<value> --secret=true`, Example: `
qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=true
- Encrypt the secret value into a new Kubernetes secret resource
- The secret resource is placed in the location: <qliksense_home>/<contexts>/<context_name>/secrets/<service_name>.yaml
- Include it's key reference in the current context
qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=false
- Encrypt the secret value and display it in the current context
- No secret resource is created
- The above configuration will be displayed in the CR `,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return q.SetSecrets(args, secret) return q.SetSecrets(args, secret)
}, },
@@ -90,6 +111,22 @@ func setSecretsCmd(q *qliksense.Qliksense) *cobra.Command {
return cmd return cmd
} }
func deleteContextConfigCmd(q *qliksense.Qliksense) *cobra.Command {
var (
cmd *cobra.Command
)
cmd = &cobra.Command{
Use: "delete-context",
Short: "deletes a specific context locally (not in-cluster)",
Example: `qliksense config delete-contexts <context_name>`,
RunE: func(cmd *cobra.Command, args []string) error {
return q.DeleteContextConfig(args)
},
}
return cmd
}
func setImageRegistryCmd(q *qliksense.Qliksense) *cobra.Command { func setImageRegistryCmd(q *qliksense.Qliksense) *cobra.Command {
var ( var (
cmd *cobra.Command cmd *cobra.Command
@@ -140,3 +177,19 @@ qliksense config set-image-registry https://your.private.registry.example.com:50
f.StringVar(&password, "password", "", "Password used for both pushing and pulling images") f.StringVar(&password, "password", "", "Password used for both pushing and pulling images")
return cmd return cmd
} }
func cleanConfigRepoPatchesCmd(q *qliksense.Qliksense) *cobra.Command {
return &cobra.Command{
Use: "clean-config-repo-patches",
Short: "Clean config repo patch files",
Example: "qliksense config clean-config-repo-patches",
RunE: func(cmd *cobra.Command, args []string) error {
qConfig := qapi.NewQConfig(q.QliksenseHome)
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
return fmt.Errorf("error removing temporary changes to the config: %v\n", err)
}
fmt.Println("done")
return nil
},
}
}

View File

@@ -7,24 +7,25 @@ import (
func installCmd(q *qliksense.Qliksense) *cobra.Command { func installCmd(q *qliksense.Qliksense) *cobra.Command {
opts := &qliksense.InstallCommandOptions{} opts := &qliksense.InstallCommandOptions{}
keepPatchFiles := false
c := &cobra.Command{ c := &cobra.Command{
Use: "install", Use: "install",
Short: "install a qliksense release", Short: "install a qliksense release",
Long: `install a qliksense release`, Long: `install a qliksense release`,
Example: `qliksense install <version>`, Example: `qliksense install <version> #if no version provides, expect manifestsRoot is set somewhere in the file system`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 { if len(args) == 0 {
return q.InstallQK8s("", opts) return q.InstallQK8s("", opts, keepPatchFiles)
} }
return q.InstallQK8s(args[0], opts) return q.InstallQK8s(args[0], opts, keepPatchFiles)
}, },
} }
f := c.Flags() f := c.Flags()
f.StringVarP(&opts.AcceptEULA, "acceptEULA", "a", "", "AcceptEULA for qliksense") 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.StorageClass, "storageClass", "s", "", "Storage class for qliksense")
f.StringVarP(&opts.MongoDbUri, "mongoDbUri", "m", "", "mongoDbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)") f.StringVarP(&opts.MongoDbUri, "mongoDbUri", "m", "", "mongoDbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
f.StringVarP(&opts.RotateKeys, "rotateKeys", "r", "", "Rotate JWT keys for qliksense (yes:rotate keys/ no:use exising keys from cluster/ None: use default EJSON_KEY from env") f.StringVarP(&opts.RotateKeys, "rotateKeys", "r", "", "Rotate JWT keys for qliksense (yes:rotate keys/ no:use exising keys from cluster/ None: use default EJSON_KEY from env")
f.BoolVar(&keepPatchFiles, keepPatchFilesFlagName, keepPatchFiles, keepPatchFilesFlagUsage)
return c return c
} }

View File

@@ -21,13 +21,22 @@ func pullQliksenseImages(q *qliksense.Qliksense) *cobra.Command {
return err return err
} }
qConfig := qapi.NewQConfig(q.QliksenseHome)
if version == "" {
if qcr, err := qConfig.GetCurrentCR(); err != nil {
return err
} else {
version = qcr.GetLabelFromCr("version")
}
}
if version != "" { if version != "" {
qConfig := qapi.NewQConfig(q.QliksenseHome)
if !qConfig.IsRepoExistForCurrent(version) { if !qConfig.IsRepoExistForCurrent(version) {
if err := q.FetchQK8s(version); err != nil { if err := q.FetchQK8s(version); err != nil {
return err return err
} }
} else if err := qConfig.SwitchCurrentCRToVersionAndProfile(version, opts.Profile); err != nil { }
if err := qConfig.SwitchCurrentCRToVersionAndProfile(version, opts.Profile); err != nil {
return err return err
} }
} }

View File

@@ -2,6 +2,13 @@ package main
import ( import (
"fmt" "fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strings"
ansi "github.com/mattn/go-colorable" ansi "github.com/mattn/go-colorable"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"github.com/qlik-oss/sense-installer/pkg" "github.com/qlik-oss/sense-installer/pkg"
@@ -10,12 +17,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/ttacon/chalk" "github.com/ttacon/chalk"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strings"
) )
// To run this project in ddebug mode, run: // To run this project in ddebug mode, run:
@@ -23,8 +24,10 @@ import (
// qliksense <command> // qliksense <command>
const ( const (
qlikSenseHomeVar = "QLIKSENSE_HOME" qlikSenseHomeVar = "QLIKSENSE_HOME"
qlikSenseDirVar = ".qliksense" qlikSenseDirVar = ".qliksense"
keepPatchFilesFlagName = "keep-config-repo-patches"
keepPatchFilesFlagUsage = "Keep config repo patch files (for debugging)"
) )
func initAndExecute() error { func initAndExecute() error {
@@ -39,10 +42,7 @@ func initAndExecute() error {
// create dirs and appropriate files for setting up contexts // create dirs and appropriate files for setting up contexts
api.LogDebugMessage("QliksenseHomeDir: %s", qlikSenseHome) api.LogDebugMessage("QliksenseHomeDir: %s", qlikSenseHome)
qliksenseClient, err := qliksense.New(qlikSenseHome) qliksenseClient := qliksense.New(qlikSenseHome)
if err != nil {
return err
}
qliksenseClient.SetUpQliksenseDefaultContext() qliksenseClient.SetUpQliksenseDefaultContext()
cmd := rootCmd(qliksenseClient) cmd := rootCmd(qliksenseClient)
//levenstein checks //levenstein checks
@@ -123,6 +123,7 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
cmd.AddCommand(installCmd(p)) cmd.AddCommand(installCmd(p))
// add config command // add config command
configCmd := configCmd(p)
cmd.AddCommand(configCmd) cmd.AddCommand(configCmd)
configCmd.AddCommand(configApplyCmd(p)) configCmd.AddCommand(configApplyCmd(p))
configCmd.AddCommand(configViewCmd(p)) configCmd.AddCommand(configViewCmd(p))
@@ -147,9 +148,15 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
// add the list config command as a sub-command to the app config sub-command // add the list config command as a sub-command to the app config sub-command
configCmd.AddCommand(listContextConfigCmd(p)) configCmd.AddCommand(listContextConfigCmd(p))
// add the delete-context config command as a sub-command to the app config command
configCmd.AddCommand(deleteContextConfigCmd(p))
// add set-image-registry command as a sub-command to the app config sub-command // add set-image-registry command as a sub-command to the app config sub-command
configCmd.AddCommand(setImageRegistryCmd(p)) configCmd.AddCommand(setImageRegistryCmd(p))
// add clean-config-repo-patches command as a sub-command to the app config sub-command
configCmd.AddCommand(cleanConfigRepoPatchesCmd(p))
// add uninstall command // add uninstall command
cmd.AddCommand(uninstallCmd(p)) cmd.AddCommand(uninstallCmd(p))

View File

@@ -6,15 +6,18 @@ import (
) )
func upgradeCmd(q *qliksense.Qliksense) *cobra.Command { func upgradeCmd(q *qliksense.Qliksense) *cobra.Command {
keepPatchFiles := false
c := &cobra.Command{ c := &cobra.Command{
Use: "upgrade", Use: "upgrade",
Short: "upgrade qliksense release", Short: "upgrade qliksense release",
Long: `upgrade qliksense release`, Long: `upgrade qliksense release`,
Example: `qliksense upgrade <version>`, Example: `qliksense upgrade <version>`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return q.UpgradeQK8s() return q.UpgradeQK8s(keepPatchFiles)
}, },
} }
f := c.Flags()
f.BoolVar(&keepPatchFiles, keepPatchFilesFlagName, keepPatchFiles, keepPatchFilesFlagUsage)
return c return c
} }

0
docs/air_gap.md Normal file
View File

83
docs/concepts.md Normal file
View File

@@ -0,0 +1,83 @@
# How qliksense cli works
At the initialization `qliksense` cli create few files in the director `~/.qliksene` and it contains following files
```console
.qliksense
├── config.yaml
├── contexts
│   └── qlik-default
│   └── qlik-default.yaml
└── ejson
└── keys
```
`qlik-default.yaml` is a default CR has been created with some default values like this
```yaml
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: qlik-default
spec:
profile: docker-desktop
secrets:
qliksense:
- name: mongoDbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
rotateKeys: "yes"
releaseName: qlik-default
```
The `qliksense` cli creates a default qliksense context (different from kubectl context) named `qlik-default` which will be the prefix for all kubernetes resources created by the cli under this context latter on. New context and configuration can be created by the cli.
```console
$ qliksense config -h
do operations on/around CR
Usage:
qliksense config [command]
Available Commands:
apply generate the patchs and apply manifests to k8s
list-contexts retrieves the contexts and lists them
set configure a key value pair into the current context
set-configs set configurations into the qliksense context as key-value pairs
set-context Sets the context in which the Kubernetes cluster and resources live in
set-secrets set secrets configurations into the qliksense context as key-value pairs
view view the qliksense operator CR
Flags:
-h, --help help for config
Use "qliksense config [command] --help" for more information about a command.
```
`qliksense` cli works in two modes
- with a git repo fork/clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s)
- without git repo
## Without git repo
In this mode `qliksense` CLI download the specified version from [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and put it into folder `~/.qliksense/contexts/<context-name>/qlik-k8s`.
The qliksense cli create a CR for the qliksense operator and all the config operations are peformed to edit the CR. So when `qliksense install` or `qliksense config apply` both generate patches in local file system (i.e `~/.qliksense/contexts/<context-name>/qlik-k8s`) and install those manifests into the cluster and create a custom resoruce (CR) for the `qliksene operator` then the operator make association to the isntalled resoruces so that when `qliksenes uninstall` is performed the operator can delete all those kubernetes resources related to QSEoK for the current context.
## With a git repo
User has to create fork or clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and push it to their own git server. When user perform `qliksense install` or `qliksene config apply` the qliksense operator do these tasks
- downloads the corresponding version of manifests from the user's git repo.
- generate kustomize patches
- install kubernetes resoruces
- push those generated patches into a new branch in the provided git repo. so that user user can merge those patches into their master branch.
- spinup a cornjob to monitor master branch. If user modifies anything in the master branch those changes will be applied into the cluster. This is a light weight `git-ops` model
This is how repo info is provided into the CR
```console
qliksense config set git.repository="https://github.com/my-org/qliksense-k8s"
qliksense config set git.accessToken=blablalaala
```

30
docs/getting_started.md Normal file
View File

@@ -0,0 +1,30 @@
# Getting started
## Requirements
- `kubectl` need to be installed and configured properly so that `kubectl` can connect to the kubernetes cluser. The `qliksense` CLI uses `kubectl` under the hood to perform operations on cluster
- (Docker Desktop setup tested for these instructions)
## Download
- Download the appropriate executable for your platform from the [releases page](https://github.com/qlik-oss/sense-installer/releases) and rename it to `qliksense`. All the examplease down below uses `qliksense`.
## Quick start
- To download the version `v0.0.2` from qliksense-k8s [releases](https://github.com/qlik-oss/qliksense-k8s/releases).
```shell
$qliksense fetch v0.0.2
```
- To install CRDs for QSEoK and qliksense operator into the kubernetes cluster.
```shell
$qliksense crds install --all
```
- To install QSEoK into a namespace in the kubernetes cluster where `kubectl` is pointing to.
```shell
$qliksense install --acceptEULA="yes"
```

19
docs/index.md Normal file
View File

@@ -0,0 +1,19 @@
# Overview
The Qlik Sense installer CLI (qliksense) provides an imperative interface to many of the configurations that need to be applied against the declarative structure described in [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s). This cli facilitates:
- installation of QSEoK
- installation of qliksense operator to manage QSEoK
- air gapped installation of QSEoK
This is a technology preview that uses Qlik modified [kustomize](https://github.com/qlik-oss/kustomize) to kubernetes manifests of the versions of the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository.
For each version of a qliksense edge build there should be a corresponding release in [qliksense-k8s] repository under [releases](https://github.com/qlik-oss/qliksense-k8s/releases)
## Future Direction
- More operations:
- Expand preflight checks
- backup/restore operations
- fully support airgap installation of QSEoK
- restore unwanted deletion of kubernetes resources

11
go.mod
View File

@@ -33,23 +33,22 @@ require (
github.com/golang/protobuf v1.3.3 // indirect github.com/golang/protobuf v1.3.3 // indirect
github.com/google/uuid v1.1.1 github.com/google/uuid v1.1.1
github.com/gorilla/mux v1.7.3 // indirect github.com/gorilla/mux v1.7.3 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/mattn/go-colorable v0.1.4 github.com/mattn/go-colorable v0.1.4
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/qlik-oss/k-apis v0.0.11 github.com/qlik-oss/k-apis v0.0.19
github.com/rogpeppe/go-internal v1.5.2 // indirect github.com/rogpeppe/go-internal v1.5.2 // indirect
github.com/spf13/cobra v0.0.6 github.com/spf13/cobra v0.0.6
github.com/spf13/viper v1.6.1 github.com/spf13/viper v1.6.1
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31 github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d // indirect golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 // indirect
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a // indirect golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a // indirect
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae // indirect golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect
golang.org/x/tools v0.0.0-20200228135638-5c7c66ced534 // indirect golang.org/x/tools v0.0.0-20200309202150-20ab64c0d93f // indirect
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b // indirect google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b // indirect
google.golang.org/grpc v1.27.0 // indirect google.golang.org/grpc v1.27.0 // indirect
gopkg.in/yaml.v2 v2.2.8 gopkg.in/yaml.v2 v2.2.8

31
go.sum
View File

@@ -380,7 +380,6 @@ 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 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 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM=
github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI=
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o= 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/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
@@ -844,8 +843,11 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/qlik-oss/k-apis v0.0.11 h1:dhbcH1+8r14OMlTSrP2RAlr+707QXcrj1iVnA/y9r5I= github.com/qlik-oss/k-apis v0.0.16/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE=
github.com/qlik-oss/k-apis v0.0.11/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE= github.com/qlik-oss/k-apis v0.0.17 h1:tOdrEe9gfb9CXq0+uowFnXIsI781qz/zgeN8xqupXYw=
github.com/qlik-oss/k-apis v0.0.17/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE=
github.com/qlik-oss/k-apis v0.0.19 h1:yrMgALQ08vMDi5hN6fnvIfyNsEaXA5fZjB1YhyIdTfg=
github.com/qlik-oss/k-apis v0.0.19/go.mod h1:DNiWYqCqPIN216l7+1rccArNIYPb1Le7kYDcPSyNp+Q=
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 h1:iqeqTS4zjp6rPEaxmFB7pemA2CMjOEN5dYSXZaQ82uw=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9/go.mod h1:OCt7FTrRVHj4kmR2xLJJUIqu00BTr6GeF09hSmM17Kw= 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/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
@@ -1041,8 +1043,10 @@ golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/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 h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/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-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-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1077,6 +1081,8 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 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 h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1117,6 +1123,8 @@ golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCT
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1194,21 +1202,14 @@ golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 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 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-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2 h1:L/G4KZvrQn7FWLN/LlulBtBzrLUhqjiGfTWWDmrh+IQ= golang.org/x/tools v0.0.0-20200309202150-20ab64c0d93f h1:NbrfHxef+IfdI86qCgO/1Siq1BuMH2xG0NqgvCguRhQ=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200309202150-20ab64c0d93f/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200225230052-807dcd883420 h1:4RJNOV+2rLxMEfr6QIpC7GEv9MjD6ApGXTCLrNF9+eA=
golang.org/x/tools v0.0.0-20200225230052-807dcd883420/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200226205201-eb7c56241bdb h1:RXjcsi6scaPhM5uXm7JRqP2JibKvbgMqx9zDLDB9voM=
golang.org/x/tools v0.0.0-20200226205201-eb7c56241bdb/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d h1:7M9AXzLrJWWGdDYtBblPHBTnHtaN6KKQ98OYb35mLlY=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200228135638-5c7c66ced534 h1:XVzrScQUlfS6ssloilmEJdJhlMDtnculCx+0zmVHSA8=
golang.org/x/tools v0.0.0-20200228135638-5c7c66ced534/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 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=
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=

18
mkdocs.yml Normal file
View File

@@ -0,0 +1,18 @@
site_name: Qlik Sense installer
repo_url: 'https://github.com/qlik-oss/sense-installer'
strict: true
theme:
name: "material"
palette:
primary: 'green'
accent: 'indigo'
markdown_extensions:
- toc:
permalink: true
- admonition
nav:
- Overview: index.md
- getting_started.md
- concepts.md
- air_gap.md
- Releases ⧉: https://github.com/qlik-oss/sense-installer/releases

View File

@@ -8,9 +8,13 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/qlik-oss/k-apis/pkg/config"
b64 "encoding/base64"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
"gopkg.in/yaml.v2"
) )
const ( const (
@@ -18,18 +22,15 @@ const (
pullSecretFileName = "image-registry-pull-secret.yaml" pullSecretFileName = "image-registry-pull-secret.yaml"
qliksenseContextsDirName = "contexts" qliksenseContextsDirName = "contexts"
qliksenseSecretsDirName = "secrets" qliksenseSecretsDirName = "secrets"
qliksenseEjsonDirName = "ejson"
) )
// NewQConfig create QliksenseConfig object from file ~/.qliksense/config.yaml // NewQConfig create QliksenseConfig object from file ~/.qliksense/config.yaml
func NewQConfig(qsHome string) *QliksenseConfig { func NewQConfig(qsHome string) *QliksenseConfig {
configFile := filepath.Join(qsHome, "config.yaml") configFile := filepath.Join(qsHome, "config.yaml")
data, err := ioutil.ReadFile(configFile)
if err != nil {
fmt.Println("Cannot read config file from: "+configFile, err)
os.Exit(1)
}
qc := &QliksenseConfig{} qc := &QliksenseConfig{}
err = yaml.Unmarshal(data, qc)
err := ReadFromFile(qc, configFile)
if err != nil { if err != nil {
fmt.Println("yaml unmarshalling error ", err) fmt.Println("yaml unmarshalling error ", err)
os.Exit(1) os.Exit(1)
@@ -77,17 +78,13 @@ func (qc *QliksenseConfig) SetCrLocation(contextName, filepath string) (*Qliksen
} }
func getCRObject(crfile string) (*QliksenseCR, error) { func getCRObject(crfile string) (*QliksenseCR, error) {
data, err := ioutil.ReadFile(crfile)
if err != nil {
fmt.Println("Cannot read config file from: "+crfile, err)
return nil, err
}
cr := &QliksenseCR{} cr := &QliksenseCR{}
err = yaml.Unmarshal(data, cr) err := ReadFromFile(cr, crfile)
if err != nil { if err != nil {
fmt.Println("cannot unmarshal cr ", err) fmt.Println("cannot unmarshal cr ", err)
return nil, err return nil, err
} }
return cr, nil return cr, nil
} }
@@ -132,13 +129,7 @@ func (qc *QliksenseConfig) WriteCR(cr *QliksenseCR, contextName string) error {
if crf == "" { if crf == "" {
return errors.New("context name " + contextName + " not found") return errors.New("context name " + contextName + " not found")
} }
by, err := yaml.Marshal(cr) return WriteToFile(cr, crf)
if err != nil {
fmt.Println("cannot marshal cr ", err)
return err
}
ioutil.WriteFile(crf, by, 0644)
return nil
} }
func (qc *QliksenseConfig) WriteCurrentContextCR(cr *QliksenseCR) error { func (qc *QliksenseConfig) WriteCurrentContextCR(cr *QliksenseCR) error {
@@ -158,7 +149,7 @@ func (qc *QliksenseConfig) GetCurrentContextDir() (string, error) {
if qcr, err := qc.GetCurrentCR(); err != nil { if qcr, err := qc.GetCurrentCR(); err != nil {
return "", err return "", err
} else { } else {
return filepath.Join(qc.QliksenseHomePath, qliksenseContextsDirName, qcr.Metadata.Name), nil return filepath.Join(qc.QliksenseHomePath, qliksenseContextsDirName, qcr.GetObjectMeta().GetName()), nil
} }
} }
@@ -193,10 +184,30 @@ func (qc *QliksenseConfig) SetPullDockerConfigJsonSecret(dockerConfigJsonSecret
} }
func (qc *QliksenseConfig) GetPushDockerConfigJsonSecret() (*DockerConfigJsonSecret, error) { func (qc *QliksenseConfig) GetPushDockerConfigJsonSecret() (*DockerConfigJsonSecret, error) {
return qc.GetDockerConfigJsonSecret(pushSecretFileName) return qc.getDockerConfigJsonSecret(pushSecretFileName)
} }
func (qc *QliksenseConfig) GetDockerConfigJsonSecret(name string) (*DockerConfigJsonSecret, error) { func (qc *QliksenseConfig) GetPullDockerConfigJsonSecret() (*DockerConfigJsonSecret, error) {
return qc.getDockerConfigJsonSecret(pullSecretFileName)
}
func (qc *QliksenseConfig) DeletePushDockerConfigJsonSecret() error {
return qc.deleteDockerConfigJsonSecret(pushSecretFileName)
}
func (qc *QliksenseConfig) DeletePullDockerConfigJsonSecret() error {
return qc.deleteDockerConfigJsonSecret(pullSecretFileName)
}
func (qc *QliksenseConfig) deleteDockerConfigJsonSecret(name string) error {
if secretsDir, err := qc.GetCurrentContextSecretsDir(); err != nil {
return err
} else {
return os.Remove(filepath.Join(secretsDir, name))
}
}
func (qc *QliksenseConfig) getDockerConfigJsonSecret(name string) (*DockerConfigJsonSecret, error) {
dockerConfigJsonSecret := &DockerConfigJsonSecret{} dockerConfigJsonSecret := &DockerConfigJsonSecret{}
if secretsDir, err := qc.GetCurrentContextSecretsDir(); err != nil { if secretsDir, err := qc.GetCurrentContextSecretsDir(); err != nil {
return nil, err return nil, err
@@ -222,13 +233,25 @@ func (qc *QliksenseConfig) getCurrentContextEncryptionKeyPairLocation() (string,
if qcr, err := qc.GetCurrentCR(); err != nil { if qcr, err := qc.GetCurrentCR(); err != nil {
return "", err return "", err
} else { } else {
secretKeyPairLocation = filepath.Join(qc.QliksenseHomePath, qliksenseSecretsDirName, qliksenseContextsDirName, qcr.Metadata.Name, qliksenseSecretsDirName) secretKeyPairLocation = filepath.Join(qc.QliksenseHomePath, qliksenseSecretsDirName, qliksenseContextsDirName, qcr.GetObjectMeta().GetName(), qliksenseSecretsDirName)
} }
} }
LogDebugMessage("SecretKeyLocation to store key pair: %s", secretKeyPairLocation) LogDebugMessage("SecretKeyLocation to store key pair: %s", secretKeyPairLocation)
return secretKeyPairLocation, nil return secretKeyPairLocation, nil
} }
func (qc *QliksenseConfig) GetCurrentContextEjsonKeyDir() (string, error) {
if qcr, err := qc.GetCurrentCR(); err != nil {
return "", err
} else {
ejsonKeyDir := filepath.Join(qc.QliksenseHomePath, qliksenseSecretsDirName, qliksenseContextsDirName, qcr.GetObjectMeta().GetName(), qliksenseEjsonDirName)
if err := os.MkdirAll(ejsonKeyDir, os.ModePerm); err != nil {
return "", err
}
return ejsonKeyDir, nil
}
}
func (qc *QliksenseConfig) GetCurrentContextEncryptionKeyPair() (*rsa.PublicKey, *rsa.PrivateKey, error) { func (qc *QliksenseConfig) GetCurrentContextEncryptionKeyPair() (*rsa.PublicKey, *rsa.PrivateKey, error) {
secretKeyPairLocation, err := qc.getCurrentContextEncryptionKeyPairLocation() secretKeyPairLocation, err := qc.getCurrentContextEncryptionKeyPairLocation()
if err != nil { if err != nil {
@@ -237,7 +260,6 @@ func (qc *QliksenseConfig) GetCurrentContextEncryptionKeyPair() (*rsa.PublicKey,
publicKeyFilePath := filepath.Join(secretKeyPairLocation, QliksensePublicKey) publicKeyFilePath := filepath.Join(secretKeyPairLocation, QliksensePublicKey)
privateKeyFilePath := filepath.Join(secretKeyPairLocation, QliksensePrivateKey) privateKeyFilePath := filepath.Join(secretKeyPairLocation, QliksensePrivateKey)
// try to create the dir if it doesn't exist // try to create the dir if it doesn't exist
if !FileExists(publicKeyFilePath) || !FileExists(privateKeyFilePath) { if !FileExists(publicKeyFilePath) || !FileExists(privateKeyFilePath) {
LogDebugMessage("Qliksense secretKeyLocation dir does not exist, creating it now: %s", secretKeyPairLocation) LogDebugMessage("Qliksense secretKeyLocation dir does not exist, creating it now: %s", secretKeyPairLocation)
@@ -271,22 +293,20 @@ func (qc *QliksenseConfig) GetCurrentContextEncryptionKeyPair() (*rsa.PublicKey,
} }
func (cr *QliksenseCR) AddLabelToCr(key, value string) { func (cr *QliksenseCR) AddLabelToCr(key, value string) {
if cr.Metadata.Labels == nil { m := cr.GetObjectMeta().GetLabels()
cr.Metadata.Labels = make(map[string]string) if m == nil {
m = make(map[string]string)
} }
cr.Metadata.Labels[key] = value m[key] = value
cr.GetObjectMeta().SetLabels(m)
} }
func (cr *QliksenseCR) GetLabelFromCr(key string) string { func (cr *QliksenseCR) GetLabelFromCr(key string) string {
val := "" return cr.GetObjectMeta().GetLabels()[key]
if cr.Metadata.Labels != nil {
val = cr.Metadata.Labels[key]
}
return val
} }
func (cr *QliksenseCR) GetString() (string, error) { func (cr *QliksenseCR) GetString() (string, error) {
out, err := yaml.Marshal(cr) out, err := K8sToYaml(cr)
if err != nil { if err != nil {
fmt.Println("cannot unmarshal cr ", err) fmt.Println("cannot unmarshal cr ", err)
return "", err return "", err
@@ -304,3 +324,53 @@ func (cr *QliksenseCR) GetImageRegistry() string {
} }
return "" return ""
} }
func (cr *QliksenseCR) GetK8sSecretsFolder(qlikSenseHomeDir string) string {
return filepath.Join(qlikSenseHomeDir, qliksenseContextsDirName, cr.GetName(), qliksenseSecretsDirName)
}
func (cr *QliksenseCR) IsEULA() bool {
for k, nvs := range cr.Spec.Configs {
if k == "qliksense" {
for _, nv := range nvs {
if nv.Name == "acceptEULA" {
return nv.Value == "yes"
}
}
}
}
return false
}
// GetDecryptedCr it decrypts all the encrypted value and return a new CR
func (qc *QliksenseConfig) GetDecryptedCr(cr *QliksenseCR) (*QliksenseCR, error) {
newCr := &QliksenseCR{}
copier.Copy(newCr, cr)
_, rsaPrivateKey, err := qc.GetCurrentContextEncryptionKeyPair()
if err != nil {
return nil, err
}
finalSecrets := map[string]config.NameValues{}
for k, nvs := range newCr.Spec.Secrets {
newNvs := config.NameValues{}
for _, nv := range nvs {
if nv.Value != "" {
b, err := b64.StdEncoding.DecodeString(strings.TrimSpace(nv.Value))
if err != nil {
return nil, err
}
db, err := Decrypt(b, rsaPrivateKey)
if err != nil {
return nil, err
}
newNvs = append(newNvs, config.NameValue{
Name: nv.Name,
Value: string(db),
})
}
}
finalSecrets[k] = newNvs
}
newCr.Spec.Secrets = finalSecrets
return newCr, nil
}

View File

@@ -1,7 +1,9 @@
package api package api
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@@ -45,12 +47,17 @@ metadata:
spec: spec:
profile: docker-desktop profile: docker-desktop
manifestsRoot: /Users/mqb/.qliksense/contexts/contx1/qlik-k8s/v0.0.1/manifests manifestsRoot: /Users/mqb/.qliksense/contexts/contx1/qlik-k8s/v0.0.1/manifests
namespace: myqliksense
storageClassName: efs storageClassName: efs
configs: configs:
qliksense: qliksense:
- name: acceptEULA - name: acceptEULA
value: "yes" value: "yes"
secrets:
qliksense:
- name: mongoDbUri
# this is rsa encrypted value, the pub and pri keys are in setuPublicAndPrivateKey() method
# actual value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
value: n/pDi7Z/A3i16cAHFFwMp19/egNKc8WZxm6MKHLT/B1DMv3U6pDXWyXT5fYYDV1wDTO3Vk43yECST1UgZYmMpgUOwgSfGgqTVi2VqS0JQsnwI+Twwhnvha8RJANX8b/XIoSFVWaOgy7+RP35ZkvOqHdCfC2aT8JMIHgBQqqCbsNgimCuRSxi0klR000ic/Tp5PYSz5mD+WLrkPw2FbS0OVBsQ/hIp5GZrmVpvEOZdbT63Sz+n/G4Br6GTv2LkZcU7JBuKQm2wfB+mRjJmJnNrPawLfn2UZ89Rz0BLwIy+6b24/RoIUgoNowfGkJreGiwItGK8fjCcx11oavK/yAo6pYZXCcru46pmHbxxle1OlkdTKkG6EVtJuKjSZXtVmBHZYRFzsR7HnAiXnL7QzSEcS7ieZlQvTmNLfpidJhK199oSbyKREqXGl2S8DzPKM9RLccVbQTy6X8qWimP3MYCnO4K0KoQnNQAgfuV8ZxnvdDecByLDPIpmFMGy0Xm9pUZWxmSoDBq+p5WBI2HdCX2gCYVv5yxS2iBqO5SMKo8iOglHtPI9NIMvloERdN1vZtxSRkY5uDEfrU9ysYwfayEXxvXmdWv0HxlotcgUinP02j7k+OfIapTmY/jGfvF4euyCGRKuJ9JlSD9pIiRdAcekjL6hCxXLJLdajCV4sL/YDo=
` `
ctx1Dir := filepath.Join(homeDir, "contexts", "contx1") ctx1Dir := filepath.Join(homeDir, "contexts", "contx1")
crFile := filepath.Join(ctx1Dir, "contx1.yaml") crFile := filepath.Join(ctx1Dir, "contx1.yaml")
@@ -83,3 +90,136 @@ func TestGetCR(t *testing.T) {
} }
td() td()
} }
func TestGetDecryptedCr(t *testing.T) {
td, dir := setup()
qc := NewQConfig(dir)
if qc.Spec.CurrentContext != "contx1" {
t.Fail()
}
// create CR
createCRFile(dir)
crFile := filepath.Join(dir, "contexts", "contx1", "contx1.yaml")
qct, e := qc.SetCrLocation("contx1", crFile)
if e != nil {
t.Fail()
t.Log(e)
}
qcr, err := qct.GetCurrentCR()
if err != nil {
t.Fail()
t.Log(err)
}
setuPublicAndPrivateKey(dir)
newCr, err := qct.GetDecryptedCr(qcr)
if err != nil {
t.Fail()
t.Log(err)
}
decryptedValue := newCr.Spec.GetFromSecrets("qliksense", "mongoDbUri")
orignalValue := qcr.Spec.GetFromSecrets("qliksense", "mongoDbUri")
if decryptedValue != "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false" {
t.Fail()
b, _ := K8sToYaml(newCr)
t.Log(b)
}
if decryptedValue == orignalValue {
t.Fail()
}
td()
}
func setuPublicAndPrivateKey(homeDir string) ([]byte, []byte, error) {
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-----
`)
secretKeyPairDir := filepath.Join(homeDir, "secrets", "contexts", "contx1", "secrets")
if err := os.MkdirAll(secretKeyPairDir, 0777); err != nil {
err = fmt.Errorf("Not able to create directories")
log.Fatal(err)
}
os.Setenv("QLIKSENSE_KEY_LOCATION", secretKeyPairDir)
privKeyFile := filepath.Join(secretKeyPairDir, "qliksensePriv")
// construct and write priv key file into secretsDir location
err := ioutil.WriteFile(privKeyFile, privKeyBytes, 0777)
if err != nil {
log.Printf("Error while creating file: %v", err)
return nil, nil, err
}
pubKeyFile := filepath.Join(secretKeyPairDir, "qliksensePub")
// construct and write pub key file into secretsDir location
err = ioutil.WriteFile(pubKeyFile, publicKeyBytes, 0777)
if err != nil {
log.Printf("Error while creating file: %v", err)
return nil, nil, err
}
return publicKeyBytes, privKeyBytes, nil
}

View File

@@ -1,51 +1,54 @@
package api package api
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"github.com/qlik-oss/k-apis/pkg/config" "github.com/qlik-oss/k-apis/pkg/config"
"gopkg.in/yaml.v2" "k8s.io/apimachinery/pkg/runtime/schema"
machine_yaml "k8s.io/apimachinery/pkg/util/yaml"
) )
const ( const (
QliksenseConfigApiVersion = "config.qlik.com/v1" QliksenseConfigApiVersion = "v1"
QliksenseConfigKind = "QliksenseConfig" QliksenseConfigApiGroup = "config.qlik.com"
QliksenseContextApiVersion = "qlik.com/v1" QliksenseConfigKind = "QliksenseConfig"
QliksenseContextKind = "Qliksense"
QliksenseDefaultProfile = "docker-desktop" QliksenseApiVersion = "v1"
DefaultRotateKeys = "yes" QliksenseKind = "Qliksense"
QliksenseMetadataName = "QliksenseConfigMetadata" QliksenseGroup = "qlik.com"
DefaultMongoDbUri = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false" QliksenseDefaultProfile = "docker-desktop"
DefaultMongoDbUriKey = "mongoDbUri" DefaultRotateKeys = "yes"
QliksenseMetadataName = "QliksenseConfigMetadata"
DefaultMongoDbUri = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
DefaultMongoDbUriKey = "mongoDbUri"
) )
// AddCommonConfig adds common configs into CRs // AddCommonConfig adds common configs into CRs
func (qliksenseCR *QliksenseCR) AddCommonConfig(contextName string) { func (qliksenseCR *QliksenseCR) AddCommonConfig(contextName string) {
qliksenseCR.ApiVersion = QliksenseContextApiVersion qliksenseCR.SetGroupVersionKind(schema.GroupVersionKind{
qliksenseCR.Kind = QliksenseContextKind Group: QliksenseGroup,
if qliksenseCR.Metadata == nil { Kind: QliksenseKind,
qliksenseCR.Metadata = &Metadata{} Version: QliksenseApiVersion,
})
qliksenseCR.SetName(contextName)
qliksenseCR.Spec = &config.CRSpec{
Profile: QliksenseDefaultProfile,
RotateKeys: DefaultRotateKeys,
} }
if qliksenseCR.Metadata.Name == "" {
qliksenseCR.Metadata.Name = contextName
}
qliksenseCR.Spec = &config.CRSpec{}
qliksenseCR.Spec.Profile = QliksenseDefaultProfile
qliksenseCR.Spec.ReleaseName = contextName
qliksenseCR.Spec.RotateKeys = DefaultRotateKeys
qliksenseCR.Spec.AddToSecrets("qliksense", DefaultMongoDbUriKey, DefaultMongoDbUri, "") qliksenseCR.Spec.AddToSecrets("qliksense", DefaultMongoDbUriKey, DefaultMongoDbUri, "")
} }
// AddBaseQliksenseConfigs adds configs into config.yaml // AddBaseQliksenseConfigs adds configs into config.yaml
func (qliksenseConfig *QliksenseConfig) AddBaseQliksenseConfigs(defaultQliksenseContext string) { func (qliksenseConfig *QliksenseConfig) AddBaseQliksenseConfigs(defaultQliksenseContext string) {
qliksenseConfig.ApiVersion = QliksenseConfigApiVersion qliksenseConfig.SetGroupVersionKind(schema.GroupVersionKind{
qliksenseConfig.Kind = QliksenseConfigKind Group: QliksenseConfigApiGroup,
if qliksenseConfig.Metadata == nil { Kind: QliksenseConfigKind,
qliksenseConfig.Metadata = &Metadata{} Version: QliksenseConfigApiVersion,
} })
qliksenseConfig.Metadata.Name = QliksenseMetadataName qliksenseConfig.SetName(QliksenseMetadataName)
if defaultQliksenseContext != "" { if defaultQliksenseContext != "" {
if qliksenseConfig.Spec == nil { if qliksenseConfig.Spec == nil {
qliksenseConfig.Spec = &ContextSpec{} qliksenseConfig.Spec = &ContextSpec{}
@@ -79,13 +82,12 @@ func WriteToFile(content interface{}, targetFile string) error {
return nil return nil
} }
x, err := yaml.Marshal(content) x, err := K8sToYaml(content)
if err != nil { if err != nil {
err = fmt.Errorf("An error occurred during marshalling CR: %v", err) err = fmt.Errorf("An error occurred during marshalling CR: %v", err)
log.Println(err) log.Println(err)
return err return err
} }
// Writing content // Writing content
err = ioutil.WriteFile(targetFile, x, 0644) err = ioutil.WriteFile(targetFile, x, 0644)
if err != nil { if err != nil {
@@ -106,9 +108,9 @@ func ReadFromFile(content interface{}, sourceFile string) error {
err = fmt.Errorf("There was an error reading from file: %s, %v", sourceFile, err) err = fmt.Errorf("There was an error reading from file: %s, %v", sourceFile, err)
return err return err
} }
if err := yaml.Unmarshal(contents, content); err != nil { // reading k8s style object
err = fmt.Errorf("An error occurred during unmarshalling: %v", err) // https://stackoverflow.com/questions/44306554/how-to-deserialize-kubernetes-yaml-file
return err dec := machine_yaml.NewYAMLOrJSONDecoder(bytes.NewReader(contents), 10000)
} dec.Decode(content)
return nil return nil
} }

View File

@@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/qlik-oss/k-apis/pkg/config" "github.com/qlik-oss/k-apis/pkg/config"
"k8s.io/apimachinery/pkg/runtime/schema"
) )
var ( var (
@@ -12,6 +13,26 @@ var (
) )
func TestAddCommonConfig(t *testing.T) { func TestAddCommonConfig(t *testing.T) {
gvk := schema.GroupVersionKind{
Group: QliksenseGroup,
Kind: QliksenseKind,
Version: QliksenseApiVersion,
}
q := &QliksenseCR{}
q.SetName("myqliksense")
q.SetGroupVersionKind(gvk)
q.Spec = &config.CRSpec{
Profile: QliksenseDefaultProfile,
RotateKeys: DefaultRotateKeys,
Secrets: map[string]config.NameValues{
"qliksense": []config.NameValue{{
Name: DefaultMongoDbUriKey,
Value: DefaultMongoDbUri,
},
},
},
}
type args struct { type args struct {
qliksenseCR *QliksenseCR qliksenseCR *QliksenseCR
contextName string contextName string
@@ -27,27 +48,7 @@ func TestAddCommonConfig(t *testing.T) {
qliksenseCR: &QliksenseCR{}, qliksenseCR: &QliksenseCR{},
contextName: "myqliksense", contextName: "myqliksense",
}, },
want: &QliksenseCR{ want: q,
CommonConfig: CommonConfig{
ApiVersion: QliksenseContextApiVersion,
Kind: QliksenseContextKind,
Metadata: &Metadata{
Name: "myqliksense",
},
},
Spec: &config.CRSpec{
Profile: QliksenseDefaultProfile,
ReleaseName: "myqliksense",
RotateKeys: DefaultRotateKeys,
Secrets: map[string]config.NameValues{
"qliksense": []config.NameValue{{
Name: DefaultMongoDbUriKey,
Value: DefaultMongoDbUri,
},
},
},
},
},
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@@ -61,6 +62,18 @@ func TestAddCommonConfig(t *testing.T) {
} }
func TestAddBaseQliksenseConfigs(t *testing.T) { func TestAddBaseQliksenseConfigs(t *testing.T) {
gvk := schema.GroupVersionKind{
Group: QliksenseConfigApiGroup,
Kind: QliksenseConfigKind,
Version: QliksenseConfigApiVersion,
}
qc := &QliksenseConfig{}
qc.SetGroupVersionKind(gvk)
qc.SetName(QliksenseMetadataName)
qc.Spec = &ContextSpec{
CurrentContext: "qlik-default",
}
type args struct { type args struct {
qliksenseConfig *QliksenseConfig qliksenseConfig *QliksenseConfig
defaultQliksenseContext string defaultQliksenseContext string
@@ -76,18 +89,7 @@ func TestAddBaseQliksenseConfigs(t *testing.T) {
qliksenseConfig: &QliksenseConfig{}, qliksenseConfig: &QliksenseConfig{},
defaultQliksenseContext: "qlik-default", defaultQliksenseContext: "qlik-default",
}, },
want: &QliksenseConfig{ want: qc,
CommonConfig: CommonConfig{
ApiVersion: QliksenseConfigApiVersion,
Kind: QliksenseConfigKind,
Metadata: &Metadata{
Name: QliksenseMetadataName,
},
},
Spec: &ContextSpec{
CurrentContext: "qlik-default",
},
},
}, },
} }
for _, tt := range tests { for _, tt := range tests {

View File

@@ -28,7 +28,6 @@ func (kdcjt *k8sDockerConfigJsonType) GenerateAuth() {
type DockerConfigJsonSecret struct { type DockerConfigJsonSecret struct {
Name string Name string
Namespace string
Uri string Uri string
Username string Username string
Password string Password string
@@ -51,9 +50,13 @@ func (d *DockerConfigJsonSecret) ToYaml(encryptionKey *rsa.PublicKey) ([]byte, e
if err != nil { if err != nil {
return nil, err return nil, err
} }
k8sDockerConfigJsonMapEncryptedBytes, err := Encrypt(k8sDockerConfigJsonMapBytes, encryptionKey) var k8sDockerConfigJsonMapMaybeEncryptedBytes []byte
if err != nil { if encryptionKey != nil {
return nil, err if k8sDockerConfigJsonMapMaybeEncryptedBytes, err = Encrypt(k8sDockerConfigJsonMapBytes, encryptionKey); err != nil {
return nil, err
}
} else {
k8sDockerConfigJsonMapMaybeEncryptedBytes = k8sDockerConfigJsonMapBytes
} }
k8sSecret := v1.Secret{ k8sSecret := v1.Secret{
@@ -63,11 +66,10 @@ func (d *DockerConfigJsonSecret) ToYaml(encryptionKey *rsa.PublicKey) ([]byte, e
}, },
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: d.Name, Name: d.Name,
Namespace: d.Namespace,
}, },
Type: v1.SecretTypeDockerConfigJson, Type: v1.SecretTypeDockerConfigJson,
Data: map[string][]byte{ Data: map[string][]byte{
".dockerconfigjson": k8sDockerConfigJsonMapEncryptedBytes, ".dockerconfigjson": k8sDockerConfigJsonMapMaybeEncryptedBytes,
}, },
} }
@@ -90,7 +92,6 @@ func (d *DockerConfigJsonSecret) FromYaml(secretBytes []byte, decryptionKey *rsa
return err return err
} else { } else {
d.Name = k8sSecret.ObjectMeta.Name d.Name = k8sSecret.ObjectMeta.Name
d.Namespace = k8sSecret.ObjectMeta.Namespace
for registry, k8sDockerConfigJson := range k8sDockerConfigJsonMap.Auths { for registry, k8sDockerConfigJson := range k8sDockerConfigJsonMap.Auths {
d.Uri = registry d.Uri = registry
d.Username = k8sDockerConfigJson.Username d.Username = k8sDockerConfigJson.Username

View File

@@ -15,7 +15,6 @@ import (
func TestDockerConfigJsonSecret(t *testing.T) { func TestDockerConfigJsonSecret(t *testing.T) {
dockerConfigJsonSecret := DockerConfigJsonSecret{ dockerConfigJsonSecret := DockerConfigJsonSecret{
Name: "some-name", Name: "some-name",
Namespace: "some-namespace",
Uri: "some-uri", Uri: "some-uri",
Username: "some-username", Username: "some-username",
Password: "some-password", Password: "some-password",
@@ -38,7 +37,6 @@ func TestDockerConfigJsonSecret(t *testing.T) {
} else if validYamlMap["apiVersion"] != "v1" || } else if validYamlMap["apiVersion"] != "v1" ||
validYamlMap["kind"] != "Secret" || validYamlMap["kind"] != "Secret" ||
validYamlMap["metadata"].(map[string]interface{})["name"] != dockerConfigJsonSecret.Name || validYamlMap["metadata"].(map[string]interface{})["name"] != dockerConfigJsonSecret.Name ||
validYamlMap["metadata"].(map[string]interface{})["namespace"] != dockerConfigJsonSecret.Namespace ||
validYamlMap["type"] != "kubernetes.io/dockerconfigjson" { validYamlMap["type"] != "kubernetes.io/dockerconfigjson" {
t.Fatalf("error verifying validity of secret yaml: %v", string(dockerConfigJsonSecretYamlBytes)) t.Fatalf("error verifying validity of secret yaml: %v", string(dockerConfigJsonSecretYamlBytes))
} else if dockerConfigJsonBytesBase64, ok := validYamlMap["data"].(map[string]interface{})[".dockerconfigjson"]; !ok { } else if dockerConfigJsonBytesBase64, ok := validYamlMap["data"].(map[string]interface{})[".dockerconfigjson"]; !ok {

View File

@@ -67,8 +67,8 @@ func writeContentToFile(keyData []byte, fileName string) error {
// Encrypt encrypts data with public key // Encrypt encrypts data with public key
func Encrypt(pt []byte, pub *rsa.PublicKey) ([]byte, error) { func Encrypt(pt []byte, pub *rsa.PublicKey) ([]byte, error) {
// hash := sha512.New() //hash := sha512.New()
// ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, pub, msg, nil) //ct, err := rsa.EncryptOAEP(hash, rand.Reader, pub, pt, nil)
ct, err := rsa.EncryptPKCS1v15(rand.Reader, pub, pt) ct, err := rsa.EncryptPKCS1v15(rand.Reader, pub, pt)
if err != nil { if err != nil {
log.Println(err) log.Println(err)

View File

@@ -114,7 +114,7 @@ MFxk1pS9LMa/WnzvFr0gWakCAwEAAQ==
} }
encDataStr := base64.StdEncoding.EncodeToString(encData) encDataStr := base64.StdEncoding.EncodeToString(encData)
log.Printf("encrypted data: %s\n", encDataStr) log.Println("Encoded text:", encDataStr)
dec, _ := base64.StdEncoding.DecodeString(encDataStr) dec, _ := base64.StdEncoding.DecodeString(encDataStr)
data, err := Decrypt(dec, privKey) data, err := Decrypt(dec, privKey)
if err != nil { if err != nil {
@@ -125,5 +125,4 @@ MFxk1pS9LMa/WnzvFr0gWakCAwEAAQ==
t.Error("original string and decrypted string don't match") t.Error("original string and decrypted string don't match")
t.FailNow() t.FailNow()
} }
log.Printf("decrypted data: %s\n", data)
} }

View File

@@ -6,15 +6,7 @@ import (
) )
func K8sSecretToYaml(k8sSecret v1.Secret) ([]byte, error) { func K8sSecretToYaml(k8sSecret v1.Secret) ([]byte, error) {
k8sSecretYamlMap := map[string]interface{}{} return K8sToYaml(k8sSecret)
if k8sSecretYamlBytes, err := yaml.Marshal(k8sSecret); err != nil {
return nil, err
} else if err := yaml.Unmarshal(k8sSecretYamlBytes, &k8sSecretYamlMap); err != nil {
return nil, err
} else {
delete(k8sSecretYamlMap["metadata"].(map[string]interface{}), "creationTimestamp")
return yaml.Marshal(k8sSecretYamlMap)
}
} }
func K8sSecretFromYaml(k8sSecretBytes []byte) (v1.Secret, error) { func K8sSecretFromYaml(k8sSecretBytes []byte) (v1.Secret, error) {
@@ -24,3 +16,15 @@ func K8sSecretFromYaml(k8sSecretBytes []byte) (v1.Secret, error) {
} }
return k8sSecret, nil return k8sSecret, nil
} }
func K8sToYaml(k8sObj interface{}) ([]byte, error) {
k8sSecretYamlMap := map[string]interface{}{}
if k8sSecretYamlBytes, err := yaml.Marshal(k8sObj); err != nil {
return nil, err
} else if err := yaml.Unmarshal(k8sSecretYamlBytes, &k8sSecretYamlMap); err != nil {
return nil, err
} else {
delete(k8sSecretYamlMap["metadata"].(map[string]interface{}), "creationTimestamp")
return yaml.Marshal(k8sSecretYamlMap)
}
}

View File

@@ -1,20 +1,65 @@
package api package api
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"strings"
) )
// KubectlApply create resoruces in the provided namespace,
// if namespace="" then use whatever the kubectl default is
func KubectlApply(manifests, namespace string) error { func KubectlApply(manifests, namespace string) error {
return kubectlOperation(manifests, "apply", namespace) return kubectlOperation(manifests, "apply", namespace)
} }
// KubectlDelete delete resoruces in the provided namespace,
// if namespace="" then use whatever the kubectl default is
func KubectlDelete(manifests, namespace string) error { func KubectlDelete(manifests, namespace string) error {
return kubectlOperation(manifests, "delete", namespace) return kubectlOperation(manifests, "delete", namespace)
} }
func GetKubectlNamespace() string {
namespace := ""
cmd := exec.Command("kubectl", "config", "current-context")
var out, out2 bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
fmt.Printf("kubectl config current-context %q\n", err)
return namespace
}
if out.String() == "" {
fmt.Println("kubectl config current-context does not return anything")
return namespace
}
cmd = exec.Command("kubectl", "config", "view", "-o", `jsonpath={.contexts[?(@.name == "`+strings.TrimSpace(out.String())+`")].context.namespace}`)
cmd.Stdout = &out2
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
fmt.Printf("kubectl config view failed with %q\n", err)
return namespace
}
namespace = out2.String()
return namespace
}
func SetKubectlNamespace(ns string) {
cmd := exec.Command("kubectl", "config", "set-context", "--namespace="+ns, "--current")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
fmt.Printf("kubectl config set-context --namespace failed with %q\n", err)
}
}
func kubectlOperation(manifests string, oprName string, namespace string) error { func kubectlOperation(manifests string, oprName string, namespace string) error {
tempYaml, err := ioutil.TempFile("", "") tempYaml, err := ioutil.TempFile("", "")
if err != nil { if err != nil {
@@ -42,13 +87,12 @@ func kubectlOperation(manifests string, oprName string, namespace string) error
cmd = exec.Command("kubectl", arguments...) cmd = exec.Command("kubectl", arguments...)
} }
sterrBuffer := &bytes.Buffer{}
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = sterrBuffer
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
fmt.Printf("kubectl apply failed with %s\n", err) return fmt.Errorf("kubectl %v failed with: %v, %v, temp k8s yaml file:%v\n", oprName, err, sterrBuffer.String(), tempYaml.Name())
fmt.Println("temp CRD file: " + tempYaml.Name())
return err
} }
os.Remove(tempYaml.Name()) os.Remove(tempYaml.Name())
return nil return nil

17
pkg/api/kubectl_test.go Normal file
View File

@@ -0,0 +1,17 @@
package api
import (
"testing"
)
func TestGetKubectlNamespace(t *testing.T) {
t.Skip()
ns := GetKubectlNamespace()
SetKubectlNamespace("tada")
got := GetKubectlNamespace()
if got != "tada" {
t.Log(got)
t.Fail()
}
SetKubectlNamespace(ns)
}

View File

@@ -1,25 +1,26 @@
package api package api
import "github.com/qlik-oss/k-apis/pkg/config" import (
kapi_config "github.com/qlik-oss/k-apis/pkg/config"
// CommonConfig is exported metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type CommonConfig struct { )
ApiVersion string `json:"apiVersion" yaml:"apiVersion"`
Kind string `json:"kind" yaml:"kind"`
Metadata *Metadata `json:"metadata" yaml:"metadata"`
}
// QliksenseConfig is exported // QliksenseConfig is exported
type QliksenseConfig struct { type QliksenseConfig struct {
CommonConfig `json:",inline" yaml:",inline"` metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
Spec *ContextSpec `json:"spec" yaml:"spec"` Spec *ContextSpec `json:"spec" yaml:"spec"`
QliksenseHomePath string `json:"-" yaml:"-"` QliksenseHomePath string `json:"-" yaml:"-"`
} }
/*type CommonConfig struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
*/
// QliksenseCR is exported // QliksenseCR is exported
type QliksenseCR struct { type QliksenseCR struct {
CommonConfig `json:",inline" yaml:",inline"` kapi_config.KApiCr `json:",inline" yaml:",inline"`
Spec *config.CRSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
} }
// ContextSpec is exported // ContextSpec is exported

View File

@@ -59,24 +59,22 @@ func ProcessConfigArgs(args []string) ([]*ServiceKeyValue, error) {
// split args[0] into key and value // split args[0] into key and value
if len(args) == 0 { if len(args) == 0 {
err := fmt.Errorf("No args were provided. Please provide args to configure the current context") err := fmt.Errorf("No args were provided. Please provide args to configure the current context")
log.Println(err)
return nil, err return nil, err
} }
resultSvcKV := make([]*ServiceKeyValue, len(args)) resultSvcKV := make([]*ServiceKeyValue, len(args))
re1 := regexp.MustCompile(`(\w{1,}).(\w{1,})=("*[\w\-_/:0-9]+"*)`) re1 := regexp.MustCompile(`(\w{1,}).(\w{1,})=("*[\w\-?=_/:0-9]+"*)`)
for i, arg := range args { for i, arg := range args {
LogDebugMessage("Arg received: %s", arg) LogDebugMessage("Arg received: %s", arg)
result := re1.FindStringSubmatch(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) // 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 { if len(result) != 4 {
err := fmt.Errorf("Please provide valid args for this command") err := fmt.Errorf("Please provide valid args for this command")
log.Println(err)
return nil, err return nil, err
} }
resultSvcKV[i] = &ServiceKeyValue{ resultSvcKV[i] = &ServiceKeyValue{
SvcName: result[1], SvcName: result[1],
Key: result[2], Key: result[2],
Value: result[3], Value: strings.ReplaceAll(result[3], `"`, ""),
} }
} }
return resultSvcKV, nil return resultSvcKV, nil

View File

@@ -58,42 +58,33 @@ func (nw *nullWriter) Write(p []byte) (n int, err error) {
} }
const ( const (
defaultProfile = "docker-desktop" defaultProfile = "docker-desktop"
defaultGitUrl = "https://github.com/qlik-oss/qliksense-k8s" defaultConfigRepoGitUrl = "https://github.com/qlik-oss/qliksense-k8s"
) )
func (q *Qliksense) About(gitRef, profile string) (*VersionOutput, error) { func (q *Qliksense) About(gitRef, profile string) (*VersionOutput, error) {
configDirectory, isTemporary, profile, err := q.getConfigDirectory(defaultGitUrl, gitRef, profile) configDirectory, isTemporary, profile, err := q.getConfigDirectory(defaultConfigRepoGitUrl, gitRef, profile)
if err != nil { if err != nil {
return nil, err return nil, err
} } else if isTemporary {
if isTemporary {
defer os.RemoveAll(configDirectory) defer os.RemoveAll(configDirectory)
} }
return q.AboutDir(configDirectory, profile) return q.AboutDir(configDirectory, profile)
} }
func (q *Qliksense) AboutDir(configDirectory, profile string) (*VersionOutput, error) { func (q *Qliksense) AboutDir(configDirectory, profile string) (*VersionOutput, error) {
chartVersion, err := getChartVersion(filepath.Join(configDirectory, "transformers", "qseokversion.yaml"), "qliksense") if chartVersion, err := getChartVersion(filepath.Join(configDirectory, "transformers", "qseokversion.yaml"), "qliksense"); err != nil {
if err != nil {
return nil, err return nil, err
} } else if kuzManifest, err := executeKustomizeBuildWithStdoutProgress(filepath.Join(configDirectory, "manifests", profile)); err != nil {
kuzManifest, err := executeKustomizeBuildWithStdoutProgress(filepath.Join(configDirectory, "manifests", profile))
if err != nil {
return nil, err return nil, err
} } else if images, err := getImageList(kuzManifest); err != nil {
images, err := getImageList(kuzManifest)
if err != nil {
return nil, err return nil, err
} else {
return &VersionOutput{
QliksenseVersion: chartVersion,
Images: images,
}, nil
} }
return &VersionOutput{
QliksenseVersion: chartVersion,
Images: images,
}, nil
} }
func (q *Qliksense) 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) {

View File

@@ -1,6 +1,7 @@
package qliksense package qliksense
import ( import (
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@@ -9,7 +10,6 @@ import (
"strings" "strings"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"gopkg.in/yaml.v2"
"github.com/qlik-oss/k-apis/pkg/cr" "github.com/qlik-oss/k-apis/pkg/cr"
"github.com/qlik-oss/sense-installer/pkg/api" "github.com/qlik-oss/sense-installer/pkg/api"
@@ -17,7 +17,11 @@ import (
) )
const ( const (
Q_INIT_CRD_PATH = "manifests/base/manifests/qliksense-init" Q_INIT_CRD_PATH = "manifests/base/manifests/qliksense-init"
agreementTempalte = `
Please read the agreement at https://www.qlik.com/us/legal/license-terms
Accept the end user license agreement by providing acceptEULA=yes
`
) )
func (q *Qliksense) ConfigApplyQK8s() error { func (q *Qliksense) ConfigApplyQK8s() error {
@@ -29,22 +33,47 @@ func (q *Qliksense) ConfigApplyQK8s() error {
fmt.Println("cannot get the current-context cr", err) fmt.Println("cannot get the current-context cr", err)
return err return err
} }
// check if acceptEULA is yes or not
if !qcr.IsEULA() {
return errors.New(agreementTempalte + "\nPlease do $ qliksense config set-configs qliksense.acceptEULA=yes\n")
}
// create patch dependent resoruces
fmt.Println("Installing resoruces used kuztomize patch")
if err := q.createK8sResoruceBeforePatch(qcr); err != nil {
return err
}
if qcr.Spec.Git.Repository != "" { if qcr.Spec.Git.Repository != "" {
// fetching and applying manifest will be in the operator controller // fetching and applying manifest will be in the operator controller
return q.applyCR(qcr.Spec.NameSpace) if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
return err
} else {
return q.applyCR(dcr)
}
} }
return q.applyConfigToK8s(qcr) if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
return err
} else {
return q.applyConfigToK8s(dcr)
}
}
func (q *Qliksense) configEjson() error {
qConfig := qapi.NewQConfig(q.QliksenseHome)
if ejsonKeyDir, err := qConfig.GetCurrentContextEjsonKeyDir(); err != nil {
return err
} else if err := os.Unsetenv("EJSON_KEY"); err != nil {
return err
} else if err := os.Setenv("EJSON_KEYDIR", ejsonKeyDir); err != nil {
return err
}
return nil
} }
func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error { func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error {
if qcr.Spec.RotateKeys != "None" { if qcr.Spec.RotateKeys != "None" {
if err := os.Unsetenv("EJSON_KEY"); err != nil { if err := q.configEjson(); 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 return err
} }
} }
@@ -54,8 +83,9 @@ func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error {
return err return err
} }
fmt.Println("Manifests root: " + qcr.Spec.GetManifestsRoot()) fmt.Println("Manifests root: " + qcr.Spec.GetManifestsRoot())
qcr.SetNamespace(qapi.GetKubectlNamespace())
// generate patches // generate patches
cr.GeneratePatches(qcr.Spec, path.Join(userHomeDir, ".kube", "config")) cr.GeneratePatches(&qcr.KApiCr, path.Join(userHomeDir, ".kube", "config"))
// apply generated manifests // apply generated manifests
profilePath := filepath.Join(qcr.Spec.GetManifestsRoot(), qcr.Spec.GetProfileDir()) profilePath := filepath.Join(qcr.Spec.GetManifestsRoot(), qcr.Spec.GetProfileDir())
mByte, err := executeKustomizeBuild(profilePath) mByte, err := executeKustomizeBuild(profilePath)
@@ -63,7 +93,7 @@ func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error {
fmt.Println("cannot generate manifests for "+profilePath, err) fmt.Println("cannot generate manifests for "+profilePath, err)
return err return err
} }
if err = qapi.KubectlApply(string(mByte), qcr.Spec.NameSpace); err != nil { if err = qapi.KubectlApply(string(mByte), qcr.GetNamespace()); err != nil {
return err return err
} }
@@ -77,6 +107,11 @@ func (q *Qliksense) ConfigViewCR() error {
return err return err
} }
fmt.Println(r) fmt.Println(r)
oth, err := q.getCurrentCrDependentResourceAsString()
if err != nil {
return err
}
fmt.Println(r + "\n" + oth)
return nil return nil
} }
@@ -92,18 +127,28 @@ func (q *Qliksense) getCRString(contextName string) (string, error) {
fmt.Println("cannot get the context cr", err) fmt.Println("cannot get the context cr", err)
return "", err return "", err
} }
out, err := yaml.Marshal(qcr) out, err := qapi.K8sToYaml(qcr)
if err != nil { if err != nil {
fmt.Println("cannot unmarshal cr ", err) fmt.Println("cannot unmarshal cr ", err)
return "", err return "", err
} }
return string(out), nil
}
func (q *Qliksense) getCurrentCrDependentResourceAsString() (string, error) {
qConfig := qapi.NewQConfig(q.QliksenseHome)
qcr, err := qConfig.GetCR(qConfig.Spec.CurrentContext)
if err != nil {
fmt.Println("cannot get the context cr", err)
return "", err
}
var crString strings.Builder var crString strings.Builder
crString.Write(out)
for svcName, v := range qcr.Spec.Secrets { for svcName, v := range qcr.Spec.Secrets {
for _, item := range v { for _, item := range v {
if item.ValueFrom != nil && item.ValueFrom.SecretKeyRef != nil { if item.ValueFrom != nil && item.ValueFrom.SecretKeyRef != nil {
secretFilePath := filepath.Join(q.QliksenseHome, QliksenseContextsDir, qcr.Metadata.Name, QliksenseSecretsDir, svcName+".yaml") secretFilePath := filepath.Join(q.QliksenseHome, QliksenseContextsDir, qcr.GetName(), QliksenseSecretsDir, svcName+".yaml")
if api.FileExists(secretFilePath) { if api.FileExists(secretFilePath) {
secretFile, err := ioutil.ReadFile(secretFilePath) secretFile, err := ioutil.ReadFile(secretFilePath)
@@ -116,5 +161,6 @@ func (q *Qliksense) getCRString(contextName string) (string, error) {
} }
} }
} }
crString.WriteString("\n---\n")
return crString.String(), nil return crString.String(), nil
} }

View File

@@ -10,25 +10,24 @@ import (
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"encoding/base64" "github.com/qlik-oss/k-apis/pkg/config"
b64 "encoding/base64"
b64 "encoding/base64"
ansi "github.com/mattn/go-colorable" ansi "github.com/mattn/go-colorable"
"github.com/qlik-oss/sense-installer/pkg/api" "github.com/qlik-oss/sense-installer/pkg/api"
"github.com/ttacon/chalk" "github.com/ttacon/chalk"
_ "gopkg.in/yaml.v2"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
const ( const (
// Below are some constants to support qliksense context setup // Below are some constants to support qliksense context setup
QliksenseConfigHome = "/.qliksense" QliksenseConfigFile = "config.yaml"
QliksenseConfigContextHome = "/.qliksense/contexts" QliksenseContextsDir = "contexts"
QliksenseConfigFile = "config.yaml" DefaultQliksenseContext = "qlik-default"
QliksenseContextsDir = "contexts" MaxContextNameLength = 17
DefaultQliksenseContext = "qlik-default" QliksenseSecretsDir = "secrets"
MaxContextNameLength = 17
QliksenseSecretsDir = "secrets"
imageRegistryConfigKey = "imageRegistry" imageRegistryConfigKey = "imageRegistry"
pullSecretName = "artifactory-docker-secret" pullSecretName = "artifactory-docker-secret"
@@ -43,17 +42,17 @@ func (q *Qliksense) SetSecrets(args []string, isSecretSet bool) error {
} }
// Metadata name in qliksense CR is the name of the current context // Metadata name in qliksense CR is the name of the current context
api.LogDebugMessage("Current context: %s", qliksenseCR.Metadata.Name) api.LogDebugMessage("Current context: %s", qliksenseCR.GetName())
rsaPublicKey, _, err := qConfig.GetCurrentContextEncryptionKeyPair() rsaPublicKey, _, err := qConfig.GetCurrentContextEncryptionKeyPair()
if err != nil { if err != nil {
return err return err
} }
resultArgs, err := api.ProcessConfigArgs(args) resultArgs, err := api.ProcessConfigArgs(args)
if err != nil { if err != nil {
return err return err
} }
for _, ra := range resultArgs { for _, ra := range resultArgs {
api.LogDebugMessage("value args to be encrypted: %s", ra.Value)
if err := q.processSecret(ra, rsaPublicKey, qliksenseCR, isSecretSet); err != nil { if err := q.processSecret(ra, rsaPublicKey, qliksenseCR, isSecretSet); err != nil {
return err return err
} }
@@ -64,7 +63,7 @@ func (q *Qliksense) SetSecrets(args []string, isSecretSet bool) error {
return nil return nil
} }
func (q *Qliksense) processSecret(ra *api.ServiceKeyValue, rsaPublicKey *rsa.PublicKey, qliksenseCR api.QliksenseCR, isSecretSet bool) error { func (q *Qliksense) processSecret(ra *api.ServiceKeyValue, rsaPublicKey *rsa.PublicKey, qliksenseCR *api.QliksenseCR, isSecretSet bool) error {
// encrypt value with RSA key pair // encrypt value with RSA key pair
valueBytes := []byte(ra.Value) valueBytes := []byte(ra.Value)
cipherText, e2 := api.Encrypt(valueBytes, rsaPublicKey) cipherText, e2 := api.Encrypt(valueBytes, rsaPublicKey)
@@ -74,10 +73,10 @@ func (q *Qliksense) processSecret(ra *api.ServiceKeyValue, rsaPublicKey *rsa.Pub
base64EncodedSecret := b64.StdEncoding.EncodeToString(cipherText) base64EncodedSecret := b64.StdEncoding.EncodeToString(cipherText)
secretName := "" secretName := ""
if isSecretSet { if isSecretSet {
secretFolder := filepath.Join(q.QliksenseHome, QliksenseContextsDir, qliksenseCR.Metadata.Name, QliksenseSecretsDir) secretFolder := qliksenseCR.GetK8sSecretsFolder(q.QliksenseHome)
secretFileName := filepath.Join(secretFolder, ra.SvcName+".yaml") secretFileName := filepath.Join(secretFolder, ra.SvcName+".yaml")
secretName = fmt.Sprintf("%s-%s-%s", qliksenseCR.Metadata.Name, ra.SvcName, "sense_installer") secretName = fmt.Sprintf("%s-%s-%s", qliksenseCR.GetName(), ra.SvcName, "senseinstaller")
api.LogDebugMessage("Constructed secret name: %s", secretName) api.LogDebugMessage("Constructed secret name: %s", secretName)
k8sSecret := v1.Secret{ k8sSecret := v1.Secret{
@@ -115,7 +114,6 @@ func (q *Qliksense) processSecret(ra *api.ServiceKeyValue, rsaPublicKey *rsa.Pub
api.LogDebugMessage("Error while writing K8s secret to file") api.LogDebugMessage("Error while writing K8s secret to file")
return err return err
} }
// api.WriteToFile(&k8sSecret, secretFileName)
api.LogDebugMessage("Created a Kubernetes secret") api.LogDebugMessage("Created a Kubernetes secret")
// Prepare args to update CR in the next step // Prepare args to update CR in the next step
@@ -148,13 +146,13 @@ func (q *Qliksense) SetConfigs(args []string) error {
return nil return nil
} }
func retrieveCurrentContextInfo(q *Qliksense) (api.QliksenseCR, string, error) { func retrieveCurrentContextInfo(q *Qliksense) (*api.QliksenseCR, string, error) {
var qliksenseConfig api.QliksenseConfig var qliksenseConfig api.QliksenseConfig
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile) qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
if err := api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile); err != nil { if err := api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile); err != nil {
log.Println(err) log.Println(err)
return api.QliksenseCR{}, "", err return nil, "", err
} }
currentContext := qliksenseConfig.Spec.CurrentContext currentContext := qliksenseConfig.Spec.CurrentContext
api.LogDebugMessage("Current-context from config.yaml: %s", currentContext) api.LogDebugMessage("Current-context from config.yaml: %s", currentContext)
@@ -162,32 +160,33 @@ func retrieveCurrentContextInfo(q *Qliksense) (api.QliksenseCR, string, error) {
// current-context is empty // current-context is empty
err := fmt.Errorf(`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) log.Println(err)
return api.QliksenseCR{}, "", err return nil, "", err
} }
// read the context.yaml file // read the context.yaml file
var qliksenseCR api.QliksenseCR qliksenseCR := &api.QliksenseCR{}
if currentContext == "" { if currentContext == "" {
// current-context is empty // current-context is empty
err := fmt.Errorf(`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) log.Println(err)
return api.QliksenseCR{}, "", err return nil, "", err
} }
qliksenseContextsFile := filepath.Join(q.QliksenseHome, QliksenseContextsDir, currentContext, currentContext+".yaml") qliksenseContextsFile := filepath.Join(q.QliksenseHome, QliksenseContextsDir, currentContext, currentContext+".yaml")
if !api.FileExists(qliksenseContextsFile) { 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") 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) log.Println(err)
return api.QliksenseCR{}, "", err return nil, "", err
} }
if err := api.ReadFromFile(&qliksenseCR, qliksenseContextsFile); err != nil { if err := api.ReadFromFile(qliksenseCR, qliksenseContextsFile); err != nil {
log.Println(err) log.Println(err)
return api.QliksenseCR{}, "", err return nil, "", err
} }
api.LogDebugMessage("Read context file: %s, Read QliksenseCR: %v", qliksenseContextsFile, qliksenseCR) api.LogDebugMessage("Read context file: %s, Read QliksenseCR: %v", qliksenseContextsFile, qliksenseCR)
return qliksenseCR, qliksenseContextsFile, nil return qliksenseCR, qliksenseContextsFile, nil
} }
// SetOtherConfigs - set profile/namespace/storageclassname/git.repository commands // SetOtherConfigs - set profile/storageclassname/git.repository/manifestRoot commands
func (q *Qliksense) SetOtherConfigs(args []string) error { func (q *Qliksense) SetOtherConfigs(args []string) error {
// retieve current context from config.yaml // retieve current context from config.yaml
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q) qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q)
@@ -208,10 +207,10 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
case "profile": case "profile":
qliksenseCR.Spec.Profile = argsString[1] qliksenseCR.Spec.Profile = argsString[1]
api.LogDebugMessage("Current profile after modification: %s ", qliksenseCR.Spec.Profile) api.LogDebugMessage("Current profile after modification: %s ", qliksenseCR.Spec.Profile)
case "namespace":
qliksenseCR.Spec.NameSpace = argsString[1]
api.LogDebugMessage("Current namespace after modification: %s ", qliksenseCR.Spec.NameSpace)
case "git.repository": case "git.repository":
if qliksenseCR.Spec.Git == nil {
qliksenseCR.Spec.Git = &config.Repo{}
}
qliksenseCR.Spec.Git.Repository = argsString[1] qliksenseCR.Spec.Git.Repository = argsString[1]
api.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": case "storageClassName":
@@ -226,8 +225,42 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
} }
qliksenseCR.Spec.RotateKeys = rotateKeys qliksenseCR.Spec.RotateKeys = rotateKeys
api.LogDebugMessage("Current rotateKeys after modification: %s ", qliksenseCR.Spec.RotateKeys) api.LogDebugMessage("Current rotateKeys after modification: %s ", qliksenseCR.Spec.RotateKeys)
case "gitops.enabled":
if qliksenseCR.Spec.GitOps == nil {
qliksenseCR.Spec.GitOps = &config.GitOps{}
}
if strings.EqualFold(argsString[1], "false") {
qliksenseCR.Spec.GitOps.Enabled = false
} else if strings.EqualFold(argsString[1], "true") {
qliksenseCR.Spec.GitOps.Enabled = true
} else {
err := fmt.Errorf("Please use a boolean value")
log.Println(err)
return err
}
api.LogDebugMessage("Current gitOps enabled status : %s ", qliksenseCR.Spec.GitOps.Enabled)
case "gitops.schedule":
if qliksenseCR.Spec.GitOps == nil {
qliksenseCR.Spec.GitOps = &config.GitOps{}
}
qliksenseCR.Spec.GitOps.Schedule = argsString[1]
api.LogDebugMessage("Current gitOps schedule is : %s ", qliksenseCR.Spec.GitOps.Schedule)
case "gitops.watchbranch":
if qliksenseCR.Spec.GitOps == nil {
qliksenseCR.Spec.GitOps = &config.GitOps{}
}
qliksenseCR.Spec.GitOps.WatchBranch = argsString[1]
api.LogDebugMessage("Current gitOps watchbranch is : %s ", qliksenseCR.Spec.GitOps.WatchBranch)
case "gitops.image":
if qliksenseCR.Spec.GitOps == nil {
qliksenseCR.Spec.GitOps = &config.GitOps{}
}
qliksenseCR.Spec.GitOps.Image = argsString[1]
api.LogDebugMessage("Current gitOps watchbranch is : %s ", qliksenseCR.Spec.GitOps.Image)
default: default:
log.Println("As part of the `qliksense config set` command, please enter one of: profile, namespace, storageClassName,rotateKeys or git.repository arguments") err := fmt.Errorf("Please enter one of: profile, storageClassName,rotateKeys, manifestRoot or git.repository arguments to configure the current context")
log.Println(err)
return err
} }
} }
// write modified content into context.yaml // write modified content into context.yaml
@@ -239,7 +272,10 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
// SetContextConfig - set the context for qliksense kubernetes resources to live in // SetContextConfig - set the context for qliksense kubernetes resources to live in
func (q *Qliksense) SetContextConfig(args []string) error { func (q *Qliksense) SetContextConfig(args []string) error {
if len(args) == 1 { if len(args) == 1 {
q.SetUpQliksenseContext(args[0], false) err := q.SetUpQliksenseContext(args[0], false)
if err != nil {
return err
}
} else { } else {
err := fmt.Errorf("Please provide a name to configure the context with") err := fmt.Errorf("Please provide a name to configure the context with")
log.Println(err) log.Println(err)
@@ -272,6 +308,66 @@ func (q *Qliksense) ListContextConfigs() error {
return nil return nil
} }
func (q *Qliksense) DeleteContextConfig(args []string) error {
if len(args) == 1 {
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
var qliksenseConfig api.QliksenseConfig
api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile)
out := ansi.NewColorableStdout()
switch args[0] {
case qliksenseConfig.Spec.CurrentContext:
fmt.Fprintln(out, chalk.Yellow.Color("Please switch contexts to be able to delete this context."))
err := fmt.Errorf(chalk.Red.Color("Cannot delete current context - %s"), chalk.White.Color(chalk.Bold.TextStyle(qliksenseConfig.Spec.CurrentContext)))
return err
case DefaultQliksenseContext:
err := fmt.Errorf(chalk.Red.Color("Cannot delete default qliksense context"))
return err
default:
qliksenseContextsDir1 := filepath.Join(q.QliksenseHome, QliksenseContextsDir)
qliksenseContextFile := filepath.Join(qliksenseContextsDir1, args[0])
qliksenseSecretsDir1 := filepath.Join(q.QliksenseHome, QliksenseSecretsDir, QliksenseContextsDir)
qliksenseSecretsFile := filepath.Join(qliksenseSecretsDir1, args[0])
if err := os.RemoveAll(qliksenseContextFile); err != nil {
err = fmt.Errorf("Not able to delete %s dir: %v", qliksenseContextsDir1, err)
log.Println(err)
return err
} else if err := os.RemoveAll(qliksenseSecretsFile); err != nil {
err = fmt.Errorf("No Secrets Folder Detected")
log.Println(err)
return err
} else {
currentLength := len(qliksenseConfig.Spec.Contexts)
if currentLength > 0 {
temp := qliksenseConfig.Spec.Contexts
qliksenseConfig.Spec.Contexts = nil
for _, ctx := range temp {
if ctx.Name != args[0] {
qliksenseConfig.Spec.Contexts = append(qliksenseConfig.Spec.Contexts, api.Context{
Name: ctx.Name,
CrFile: ctx.CrFile,
})
}
}
newLength := len(qliksenseConfig.Spec.Contexts)
if currentLength != newLength {
api.WriteToFile(&qliksenseConfig, qliksenseConfigFile)
fmt.Fprintln(out, chalk.Yellow.Color(chalk.Underline.TextStyle("Warning: Active resources may still be running in-cluster")))
fmt.Fprintln(out, chalk.Green.Color("Successfully deleted context: "), chalk.Bold.TextStyle(args[0]))
} else {
err := fmt.Errorf(chalk.Red.Color("Context not found"))
return err
}
}
}
}
} else {
err := fmt.Errorf("Please provide a context as an argument to delete")
log.Println(err)
return err
}
return nil
}
// SetUpQliksenseDefaultContext - to setup dir structure for default qliksense context // SetUpQliksenseDefaultContext - to setup dir structure for default qliksense context
func (q *Qliksense) SetUpQliksenseDefaultContext() error { func (q *Qliksense) SetUpQliksenseDefaultContext() error {
return q.SetUpQliksenseContext(DefaultQliksenseContext, true) return q.SetUpQliksenseContext(DefaultQliksenseContext, true)
@@ -279,6 +375,11 @@ func (q *Qliksense) SetUpQliksenseDefaultContext() error {
// SetUpQliksenseContext - to setup qliksense context // SetUpQliksenseContext - to setup qliksense context
func (q *Qliksense) SetUpQliksenseContext(contextName string, isDefaultContext bool) error { func (q *Qliksense) SetUpQliksenseContext(contextName string, isDefaultContext bool) error {
if contextName == "" {
err := fmt.Errorf("Please enter a non-empty context-name")
log.Println(err)
return err
}
// check the length of the context name entered by the user, it should not exceed 17 chars // check the length of the context name entered by the user, it should not exceed 17 chars
if len(contextName) > MaxContextNameLength { if len(contextName) > MaxContextNameLength {
err := fmt.Errorf("Please enter a context-name with utmost 17 characters") err := fmt.Errorf("Please enter a context-name with utmost 17 characters")
@@ -315,7 +416,7 @@ func (q *Qliksense) SetUpQliksenseContext(contextName string, isDefaultContext b
// creating contexts/qlik-default/qlik-default.yaml file // creating contexts/qlik-default/qlik-default.yaml file
qliksenseContextFile := filepath.Join(qliksenseContextsDir1, contextName, contextName+".yaml") qliksenseContextFile := filepath.Join(qliksenseContextsDir1, contextName, contextName+".yaml")
var qliksenseCR api.QliksenseCR //var qliksenseCR api.QliksenseCR
defaultContextsDir := filepath.Join(qliksenseContextsDir1, contextName) defaultContextsDir := filepath.Join(qliksenseContextsDir1, contextName)
if !api.DirExists(defaultContextsDir) { if !api.DirExists(defaultContextsDir) {
@@ -327,16 +428,19 @@ func (q *Qliksense) SetUpQliksenseContext(contextName string, isDefaultContext b
} }
api.LogDebugMessage("%s exists", defaultContextsDir) api.LogDebugMessage("%s exists", defaultContextsDir)
if !api.FileExists(qliksenseContextFile) { if !api.FileExists(qliksenseContextFile) {
qliksenseCR := &api.QliksenseCR{}
qliksenseCR.AddCommonConfig(contextName) qliksenseCR.AddCommonConfig(contextName)
api.WriteToFile(&qliksenseCR, qliksenseContextFile)
api.LogDebugMessage("Added Context: %s", contextName) api.LogDebugMessage("Added Context: %s", contextName)
} else {
if err := api.ReadFromFile(&qliksenseCR, qliksenseContextFile); err != nil {
log.Println(err)
return err
}
} }
// else {
// if err := api.ReadFromFile(&qliksenseCR, qliksenseContextFile); err != nil {
// log.Println(err)
// return err
// }
// }
api.WriteToFile(&qliksenseCR, qliksenseContextFile) //api.WriteToFile(&qliksenseCR, qliksenseContextFile)
ctxTrack := false ctxTrack := false
if len(qliksenseConfig.Spec.Contexts) > 0 { if len(qliksenseConfig.Spec.Contexts) > 0 {
for _, ctx := range qliksenseConfig.Spec.Contexts { for _, ctx := range qliksenseConfig.Spec.Contexts {
@@ -357,6 +461,8 @@ func (q *Qliksense) SetUpQliksenseContext(contextName string, isDefaultContext b
if !configFileTrack { if !configFileTrack {
api.WriteToFile(&qliksenseConfig, qliksenseConfigFile) api.WriteToFile(&qliksenseConfig, qliksenseConfigFile)
} }
// set the encrypted default mongo
q.SetSecrets([]string{`qliksense.mongoDbUri="mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"`}, false)
return nil return nil
} }
@@ -380,19 +486,19 @@ func validateInput(input string) (string, error) {
} }
// PrepareK8sSecret decodes and decrypts the secret value in the secret.yaml file and returns a B64encoded string // PrepareK8sSecret decodes and decrypts the secret value in the secret.yaml file and returns a B64encoded string
func (q *Qliksense) PrepareK8sSecret(qliksenseCR api.QliksenseCR, targetFile string) (string, error) { func (q *Qliksense) PrepareK8sSecret(targetFile string) (string, error) {
qConfig := api.NewQConfig(q.QliksenseHome)
_, rsaPrivateKey, err := qConfig.GetCurrentContextEncryptionKeyPair()
if err != nil {
return "", err
}
// check if targetFile exists // check if targetFile exists
if !api.FileExists(targetFile) { if !api.FileExists(targetFile) {
err := fmt.Errorf("Target file does not exist in the path provided") err := fmt.Errorf("Target file does not exist in the path provided")
log.Println(err) log.Println(err)
return "", err return "", err
} }
qConfig := api.NewQConfig(q.QliksenseHome)
_, rsaPrivateKey, err := qConfig.GetCurrentContextEncryptionKeyPair()
if err != nil {
return "", err
}
// read the target file // read the target file
k8sSecret, err := readTargetfile(targetFile) k8sSecret, err := readTargetfile(targetFile)
if err != nil { if err != nil {
@@ -403,34 +509,28 @@ func (q *Qliksense) PrepareK8sSecret(qliksenseCR api.QliksenseCR, targetFile str
if err != nil { if err != nil {
return "", err return "", err
} }
dataMap := k8sSecret1.Data dataMap := k8sSecret1.Data
var base64EncodedSecret string
var resultMap = make(map[string][]byte) var resultMap = make(map[string][]byte)
for k, v := range dataMap { for k, v := range dataMap {
// base64 decode every value ba, err := b64.StdEncoding.DecodeString(string(v))
decodedStr, _ := base64.StdEncoding.DecodeString(string(v))
decryptedString, err := api.Decrypt(decodedStr, rsaPrivateKey)
if err != nil { if err != nil {
err := fmt.Errorf("Not able to decrypt message") err := fmt.Errorf("Not able to decode message: %v", err)
return "", err return "", err
} }
decryptedString, err := api.Decrypt(ba, rsaPrivateKey)
// base64 encode the values if err != nil {
base64EncodedSecret = b64.StdEncoding.EncodeToString(decryptedString) err := fmt.Errorf("Not able to decrypt message: %v", err)
resultMap[k] = []byte(base64EncodedSecret) return "", err
}
resultMap[k] = decryptedString
} }
api.LogDebugMessage("B64 encoded Map: %v\n", resultMap)
// putting the above map back into the k8sSecret struct // putting the above map back into the k8sSecret struct
k8sSecret1.Data = resultMap k8sSecret1.Data = resultMap
k8sSecretBytes, err := api.K8sSecretToYaml(k8sSecret1) k8sSecretBytes, err := api.K8sSecretToYaml(k8sSecret1)
api.LogDebugMessage("Final Yaml: %v\n", string(k8sSecretBytes))
if err != nil { if err != nil {
return "", err return "", err
} }
return string(k8sSecretBytes), nil return string(k8sSecretBytes), nil
} }
@@ -458,16 +558,20 @@ func (q *Qliksense) SetImageRegistry(registry, pushUsername, pushPassword, pullU
}); err != nil { }); err != nil {
return err return err
} else if err := qConfig.SetPullDockerConfigJsonSecret(&api.DockerConfigJsonSecret{ } else if err := qConfig.SetPullDockerConfigJsonSecret(&api.DockerConfigJsonSecret{
Name: pullSecretName, Name: pullSecretName,
Namespace: qliksenseCR.Spec.NameSpace, Uri: registry,
Uri: registry, Username: pullUsername,
Username: pullUsername, Password: pullPassword,
Password: pullPassword, Email: pullUsername,
Email: pullUsername,
}); err != nil { }); err != nil {
return err return err
} }
} else if err := qConfig.DeletePushDockerConfigJsonSecret(); err != nil && !os.IsNotExist(err) {
return err
} else if err := qConfig.DeletePullDockerConfigJsonSecret(); err != nil && !os.IsNotExist(err) {
return err
} }
qliksenseCR.Spec.AddToConfigs("qliksense", imageRegistryConfigKey, registry) qliksenseCR.Spec.AddToConfigs("qliksense", imageRegistryConfigKey, registry)
return api.WriteToFile(&qliksenseCR, qliksenseContextsFile) return api.WriteToFile(&qliksenseCR, qliksenseContextsFile)
} }

View File

@@ -1,21 +1,155 @@
package qliksense package qliksense
import ( import (
"encoding/base64"
b64 "encoding/base64"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"reflect"
"strings"
"testing" "testing"
"github.com/qlik-oss/k-apis/pkg/config"
"github.com/qlik-oss/sense-installer/pkg/api" "github.com/qlik-oss/sense-installer/pkg/api"
"gopkg.in/yaml.v2"
) )
var ( const (
testDir = "./tests" testDir = "./tests"
qlikDefaultContext = "qlik-default"
secrets = "secrets"
contexts = "contexts"
) )
var targetFileStringTemplate = `
apiVersion: v1
data:
mongoDbUri: %s
kind: Secret
metadata:
name: testctx-qliksense-senseinstaller
type: Opaque
`
var encText = "SFpVZ2t5SGsrN2lLQjlTYm9rbFUxSDFRcmVYdUxhTW9MUHlQOGtGditxMEcwZTlIZDl1dVRrV0tEYm5qUURSWFp3dStuNklueGk3anI2c1djSVdsbWlKTHdWQUJwdUg0a1NXd3llMUlMa2oxK3FRSFlMM2dQUExvN1pBYkVDeDROMUVvam12M0t0NmQwbkdhSXlWWEpmWWJUVVFDM1Y4L0ZTVXBVN0JUb0l4OVZWdmlPam5HTHk4RlF2a3RUaHJxWTUvZEh2N3pVUmhiOTc2Q2YwbEovZ3I2L2NwRk9RMUFXVXdodVhrTG9lYjVzNFdtTEZzNldqT3k0bWlKM1J6VllLaWVUSFJ2SE85eDB6dUthanRwSGEzWEZkaE5QNnpySVJJNTRFalUyblVYYUNlYXVnWnZEOUxjdWluOFhFcjExbkFINURCUDAycXhoZk5BejVoMlV2eFNWVmR0aW1QTDBhMVBJTUxGQTgyWUkrQkFOQkhkSUNnZGU5SkxIRFBoTzR6c0llaE1LRmhVQkNoOUhQa3kyRnhTeDJ3YWp3M1UycEsvcFJVZUxDazRUbkhmL25LN3h5ekdpV3dSUFFFZHdsWE5JbUhjVlVPV3gvNWh4WlJCUTZtb3pGYk1HbXR1Mkh5Z3RVV2gzNFYzd1BhS01TNFRsa0hyODFjRjVCWVpxenBFK1pKWnVyLy8zbzJsU0tFMjMxTG1pcGk1K0FqbXZvUVcyWHBocjFNVWJQY1pXUkJFRkkyQXBCM0FhQXFPa0k1MkRqNG43Mko5bCtaMzdydTk1aHk5K1lzY0FxMjZVbExYRlc0S3RUUkRLSjlMNnVmdlIrUUNudER3em5UTFRHUnEwZU5COWt6S0Q4MFlUdXozeHNXK3cxdjlHbDJaMnBZMTZWTCtEV1k9"
var decText = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
func setupTargetFileAndPrivateKey() ([]byte, []byte, string, error) {
targetFileString := fmt.Sprintf(targetFileStringTemplate, encText)
privKeyBytes := []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIJJwIBAAKCAgEApFf3qCQhAr2QLRRZdhLyB8exLjrQiXLr8hwDe0xHSLJX3w7v
5z4ujJWrHUulQ2/hvS8uffxMVrp2YqeA4sjy/ku1KqZVQv/WNTdL2v9Z1ewbRnBj
DQvmkWDKZ+cP8VPdHGzQ4iM2z6BZ4RQTkdQMKqsVwUsLO9amI2TOny/M696eFRW0
pk4+W3QZZRawT0HqJPvKKXKqoO2+62W54rOV8glJi29Do06e4S6CZUl1hBUy0VlL
trLlRSOHTois0dF2a9f7+GGgU11MHO6w1k1NesSlfZ0vnrrkW8WFLqewk+Jj+w1x
eQnYHOeHjx6zi9f9DC96eSylSB3iJ71NmXcMc0IEZ2LiQIqTL83BLOgMBCsK3FSl
GMakQUR2GJ+M0I/selYkRMhid6eOmhlsTNMPbpcTHxZ+ReIzS+5B1X0FZ7RIL+jS
L9mFcxmD3dxurrrt/DkLpXcuWdi1s1bpVn3jIQhU0+bgA6hT0k8Kj2f3Q9QnvkHS
Eff+XyLvLwQeSsSAcnM+1I7fNSPEo2cq0au6ZtjHcirXmMminAQ6cKW1XrEvJBef
HHibtjJjIM4bHH7MKRA5H3km/J4CCwI1VogSTcE05Z6ypAFU2TCrnec9c9VXkRDP
94h0GuoG8sdhmQudqvghr/8T7CV+sGRQbdeqrXwanfnGPrjcMVIO/dSOxOUCAwEA
AQKCAgA5b2TmJnpC8u0IVCxPz582iNurRHLNFpTPMGsnFCl1hp6fHiFJt7mc+FGt
E1rWjqtd6rdc4Gfth40IPXIV0BTcOqk+FpOFrtO2FXU1PDixQqrlmzGCxb324NTc
KyyvMpf77yuxXI0zUt8WgmW0eV8nKlOYEhoC96lohTqQ96uuY0bsJ4HS/VVdsN2P
Lra/fFHQSw8EHUb0pyIqMoscZ5bn18cUK/Z/hGKSYCbCL0Iavy3bbFHBsBPgbeJD
2BBN4953Iiy1Sak2eUy4b9LtkmaZmVAc7mpOFxLn38gD3icgB+bZPoGBw6b7sw71
Pc2R+hI9x/oNj0TUR11adhZApBJ9RhBbnSCUt8OUt9U5prNj+9qs8cHJGywtz1da
ZT1M6mn2MFSsaOyOlJPzGUzSf4AhI7HiouDpLHtHDqLmc8Vv4rZUqrcFw6kZTCY5
564yE8hh/UimOgQr7467/hADHZ0kBsupFEDWRqQ3qTIikHmGhTYZehDrSGL/3BMG
rvsFdv0krUHyW4FfHqPN09jfP3LTqd5vVbzRhxcGsoGmP/1kXIDtO8Cp1s/K6Mse
tInRCRla8ttZ3CZZ+Vf8HLi/n8XSRfbnMGYi7lVVxnp6kNsTEBgosbdU/r1zbMRJ
8mMMHygyugaRLHmOD/8fkWLfyR88cxPH8u9NufTfvJgiFpZboQKCAQEA0uSU6IGZ
pXIVZdmDWt4mxpS5T2UYarw3V9z/Isd3kkUU5YrC2XrvPRwmx5Jh9GXl9WENYJAR
wH7PaJT0HpBwpxJa1RqHHDSka7DnDcy44oRXyM7e2AmcW8QvcDty/0HPo4oZq4IT
m/+ot1R+bIpmJOweGRhVauzxJEUlQyt+kiH/ad8GiOS6LwqFPq42alnUxPQ106wF
EZZ2WQdzkyV6tF9aMG18AT1fJsGwNjCLRxJ52t/aEUP5mYwlL2UTT5Acn8KbtrTO
fFLAxGuB9LDdT1tGgIpzsXmxAaaeuPvSK4TDFdQyLAUdQJdz0GD9j9ciMPQH3UPe
Vjt6qtpfY6QK2wKCAQEAx36Vys5BlQI0TG6qORI0fiOYpLG1GqmdbCNRgBUsMj5T
LFe7uSd4qnDvGmns4MdkSSOlpF17bQiWhWKbjKRQpT0U/46zcIT4pWyajXJe+i9H
M/DpSRkMq2kGkx6KX2u9L66QBzcxJjtS17amdSpDAfsrvJgOWkxxInzw9n1u6aTe
ZjRDXdVX0KjPebEPOaoJToxne21Od3t+47TnDsQPsO1dvvrXX76IfH8cAlD5+0C/
b2YvDqWDmh9ICjKShwuDWgi4KjCV5PMHCIxH0FQ2L6mSbwIb9YgGin3wjN3KbWqz
dgEu7MeDxEwxZSSg4OstYVLQVgM39G/2ZA4YVJEbPwKCAQAo9FjymhBzb6c2Izp+
D/wpvkIKaBCI0cpRlso5P9E5p466UOsr/tKs5GWnhgbdxlgVAebuJKw93KJ8pciO
kvA9kbPwBHnOgW6Ytz73kBUrcBX4GixueddSftPTkMfxSB+Bm9UGWHlkZw6lo5P1
kh7p9qyVpQMZg7AEoiTtWWn4CQAn2DbVqM17Syi7Fmvc1VsbcG1vkM1fMAAFpAvO
vI2Kr6W9F9XoC7oJtb15mI3DnJPrbGNVzQSQzAWAoblRTyQv5kQFBDHBNPTYcCRJ
l3sy6P/VAI4dHgvAzVGvjL+w0dRszct8fvXCUGceRWeYYmfyZ8GLN53a0ywsN8Ik
gHvXAoIBACee5HEa9bt6bJihgf1DuFk1CKPtB2L8PN+1RAKEMfrolexAoG/tfvGa
7GH6l6ks8KX2BnfWeST2h66GHw6Xs8ydjQYUeV7nidqQ70EYbfSSXznZpvt1liaU
/VFKx4CcDT7jFIfaVlCZh6KADB9I/XXvRIh4SqF0fSO0XMcXsmeE7watapPAQ2iV
nl804yk4tBB9oi/JTcQ9Kr5et2UfW15wRiYf+5ZwaPsQ46cyHfPgsCSXztDB3plF
jTE5ShC4IKZJBQqcC6kk+0ifU8P0da6RpxuU96iUE3h9+sB/bCy+/FV7dq5gEbNy
znygAbOqAaFKqUXr7bkGY5ELm5lwGFECggEACcyaF9mMqLGghR55ew+cMmdeYdK3
meMLi5nrgtbQpVLlz+IV7Vdmrv7lZjeTr4nvU/5miU+p+If14CCFBiSucGq3Kmyp
OSM5cNCjDhw8uIDfY2qWCrZ2NSMR3qaAoBAQyQ2ER1IL98TDF/Qui0ZatbPiM4Ns
GErhkBZh3MCDSt24yiVKcUB79BxatWB4K7h7y8wqpX4Rj7rpfJMF7wz/I1cgyuCE
7XFpRwj7F1B2MmXnvV3KAgAD0EqrJDLeM0vIlDhpOUEaFUkuqmQyeB8qQkWfyXbD
jzloS3cNq0MBijB8oixwD2b4dVhBM7z8vQMX6OntN+97luWgO8OIukoYAg==
-----END RSA PRIVATE KEY-----
`)
publicKeyBytes := []byte(`-----BEGIN RSA PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApFf3qCQhAr2QLRRZdhLy
B8exLjrQiXLr8hwDe0xHSLJX3w7v5z4ujJWrHUulQ2/hvS8uffxMVrp2YqeA4sjy
/ku1KqZVQv/WNTdL2v9Z1ewbRnBjDQvmkWDKZ+cP8VPdHGzQ4iM2z6BZ4RQTkdQM
KqsVwUsLO9amI2TOny/M696eFRW0pk4+W3QZZRawT0HqJPvKKXKqoO2+62W54rOV
8glJi29Do06e4S6CZUl1hBUy0VlLtrLlRSOHTois0dF2a9f7+GGgU11MHO6w1k1N
esSlfZ0vnrrkW8WFLqewk+Jj+w1xeQnYHOeHjx6zi9f9DC96eSylSB3iJ71NmXcM
c0IEZ2LiQIqTL83BLOgMBCsK3FSlGMakQUR2GJ+M0I/selYkRMhid6eOmhlsTNMP
bpcTHxZ+ReIzS+5B1X0FZ7RIL+jSL9mFcxmD3dxurrrt/DkLpXcuWdi1s1bpVn3j
IQhU0+bgA6hT0k8Kj2f3Q9QnvkHSEff+XyLvLwQeSsSAcnM+1I7fNSPEo2cq0au6
ZtjHcirXmMminAQ6cKW1XrEvJBefHHibtjJjIM4bHH7MKRA5H3km/J4CCwI1VogS
TcE05Z6ypAFU2TCrnec9c9VXkRDP94h0GuoG8sdhmQudqvghr/8T7CV+sGRQbdeq
rXwanfnGPrjcMVIO/dSOxOUCAwEAAQ==
-----END RSA PUBLIC KEY-----
`)
targetFile := filepath.Join(testDir, "targetfile.yaml")
// tests/config.yaml exists
err := ioutil.WriteFile(targetFile, []byte(targetFileString), 0777)
if err != nil {
log.Printf("Error while creating file: %v", err)
return nil, nil, "", err
}
secretKeyPairDir := filepath.Join(testDir, secrets, contexts, qlikDefaultContext, secrets)
if err := os.MkdirAll(secretKeyPairDir, 0777); err != nil {
err = fmt.Errorf("Not able to create directories")
log.Fatal(err)
}
os.Setenv("QLIKSENSE_KEY_LOCATION", secretKeyPairDir)
privKeyFile := filepath.Join(secretKeyPairDir, "qliksensePriv")
// construct and write priv key file into secretsDir location
err = ioutil.WriteFile(privKeyFile, privKeyBytes, 0777)
if err != nil {
log.Printf("Error while creating file: %v", err)
return nil, nil, "", err
}
pubKeyFile := filepath.Join(secretKeyPairDir, "qliksensePub")
api.LogDebugMessage("Test setup - \npub key path: %s\n, priv key path: %s\n", pubKeyFile, privKeyFile)
// construct and write pub key file into secretsDir location
err = ioutil.WriteFile(pubKeyFile, publicKeyBytes, 0777)
if err != nil {
log.Printf("Error while creating file: %v", err)
return nil, nil, "", err
}
return publicKeyBytes, privKeyBytes, targetFile, nil
}
func removePrivateKey() {
err := os.Remove(filepath.Join(testDir, secrets, contexts, qlikDefaultContext, secrets, "qliksensePriv"))
if err != nil {
log.Fatalf("Could not delete private key %v", err)
}
return
}
func setup() func() { func setup() func() {
// create tests dir // create tests dir
if err := os.Mkdir(testDir, 0777); err != nil { if err := os.Mkdir(testDir, 0777); err != nil {
@@ -30,7 +164,7 @@ metadata:
spec: spec:
contexts: contexts:
- name: qlik-default - name: qlik-default
crLocation: /root/.qliksense/contexts/qlik-default.yaml crFile: /root/.qliksense/contexts/qlik-default.yaml
currentContext: qlik-default currentContext: qlik-default
` `
configFile := filepath.Join(testDir, "config.yaml") configFile := filepath.Join(testDir, "config.yaml")
@@ -65,6 +199,22 @@ spec:
return tearDown return tearDown
} }
func readCRFile() (*api.QliksenseCR, error) {
qlikDefaultContext := "qlik-default"
qliksenseCR := &api.QliksenseCR{}
contextFileContents, err := ioutil.ReadFile(filepath.Join(testDir, contexts, qlikDefaultContext, qlikDefaultContext+".yaml"))
if err != nil {
log.Println(err)
err = fmt.Errorf("Not able to read current context info")
return nil, err
}
if err := yaml.Unmarshal(contextFileContents, qliksenseCR); err != nil {
err = fmt.Errorf("An error occurred during unmarshalling: %v", err)
return nil, err
}
return qliksenseCR, nil
}
func Test_retrieveCurrentContextInfo(t *testing.T) { func Test_retrieveCurrentContextInfo(t *testing.T) {
tearDown := setup() tearDown := setup()
@@ -108,17 +258,22 @@ func TestSetUpQliksenseContext(t *testing.T) {
}, },
wantErr: true, wantErr: true,
}, },
{
name: "empty contextname",
args: args{
qlikSenseHome: testDir,
contextName: "",
isDefaultContext: false,
},
wantErr: true,
},
} }
tearDown := setup() tearDown := setup()
defer tearDown() defer tearDown()
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
q, err := New(tt.args.qlikSenseHome) q := 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 { if err := q.SetUpQliksenseContext(tt.args.contextName, tt.args.isDefaultContext); (err != nil) != tt.wantErr {
t.Errorf("SetUpQliksenseContext() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("SetUpQliksenseContext() error = %v, wantErr %v", err, tt.wantErr)
} }
@@ -147,11 +302,7 @@ func TestSetUpQliksenseDefaultContext(t *testing.T) {
defer tearDown() defer tearDown()
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
q, err := New(tt.args.qlikSenseHome) q := New(tt.args.qlikSenseHome)
if err != nil {
t.Errorf("unable to create a qliksense instance")
return
}
if err := q.SetUpQliksenseDefaultContext(); (err != nil) != tt.wantErr { if err := q.SetUpQliksenseDefaultContext(); (err != nil) != tt.wantErr {
t.Errorf("SetUpQliksenseDefaultContext() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("SetUpQliksenseDefaultContext() error = %v, wantErr %v", err, tt.wantErr)
} }
@@ -175,10 +326,30 @@ func TestSetOtherConfigs(t *testing.T) {
q: &Qliksense{ q: &Qliksense{
QliksenseHome: testDir, QliksenseHome: testDir,
}, },
args: []string{"profile=minikube", "namespace=qliksense", "storageClassName=efs"}, args: []string{"profile=minikube", "rotateKeys=yes", "storageClassName=efs"},
}, },
wantErr: false, wantErr: false,
}, },
{
name: "invalid configs",
args: args{
q: &Qliksense{
QliksenseHome: testDir,
},
args: []string{"someconfig=somevalue"},
},
wantErr: true,
},
{
name: "empty configs",
args: args{
q: &Qliksense{
QliksenseHome: testDir,
},
args: []string{},
},
wantErr: true,
},
} }
tearDown := setup() tearDown := setup()
defer tearDown() defer tearDown()
@@ -207,7 +378,7 @@ func TestSetConfigs(t *testing.T) {
q: &Qliksense{ q: &Qliksense{
QliksenseHome: testDir, QliksenseHome: testDir,
}, },
args: []string{"qliksense[name=acceptEULA]=\"yes\"", "qliksense[name=mongoDbUri]=\"mongo://mongo:3307\""}, args: []string{"qliksense.acceptEULA=\"yes\"", "qliksense.mongoDbUri=\"mongo://mongo:3307\""},
}, },
wantErr: false, wantErr: false,
}, },
@@ -318,20 +489,448 @@ spec:
pushSecret.Username != testCase.pushUsername || pushSecret.Password != testCase.pushPassword { pushSecret.Username != testCase.pushUsername || pushSecret.Password != testCase.pushPassword {
t.Fatalf("unexpected push secret content: %v", pushSecret) t.Fatalf("unexpected push secret content: %v", pushSecret)
} }
if pullSecret, err := qConfig.GetDockerConfigJsonSecret("image-registry-pull-secret.yaml"); err != nil { if pullSecret, err := qConfig.GetPullDockerConfigJsonSecret(); err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} else if pullSecret.Uri != testCase.registry || } else if pullSecret.Uri != testCase.registry ||
pullSecret.Name != "artifactory-docker-secret" || pullSecret.Namespace != "some-namespace" || pullSecret.Name != "artifactory-docker-secret" ||
pullSecret.Username != testCase.pullUsername || pullSecret.Password != testCase.pullPassword { pullSecret.Username != testCase.pullUsername || pullSecret.Password != testCase.pullPassword {
t.Fatalf("unexpected pull secret content: %v", pullSecret) t.Fatalf("unexpected pull secret content: %v", pullSecret)
} }
} else { } else {
if _, err := qConfig.GetPushDockerConfigJsonSecret(); err == nil { if _, err := qConfig.GetPushDockerConfigJsonSecret(); err == nil {
t.Fatal("unexpected image-registry-push-secret.yaml") t.Fatal("unexpected image-registry-push-secret.yaml")
} else if _, err := qConfig.GetDockerConfigJsonSecret("image-registry-pull-secret.yaml"); err == nil { } else if _, err := qConfig.GetPullDockerConfigJsonSecret(); err == nil {
t.Fatal("unexpected image-registry-pull-secret.yaml") t.Fatal("unexpected image-registry-pull-secret.yaml")
} }
} }
}) })
} }
} }
func Test_PrepareK8sSecret(t *testing.T) {
type fields struct {
QliksenseHome string
}
tests := []struct {
name string
fields fields
want string
wantErr bool
setup func() (string, func())
}{
{
name: "valid case",
fields: fields{
QliksenseHome: testDir,
},
want: fmt.Sprintf(targetFileStringTemplate, base64.StdEncoding.EncodeToString([]byte(decText))),
wantErr: false,
setup: func() (string, func()) {
tearDown := setup()
_, _, targetFile, _ := setupTargetFileAndPrivateKey()
return targetFile, tearDown
},
},
{
name: "private key not supplied should result in decryption error",
fields: fields{
QliksenseHome: testDir,
},
want: "",
wantErr: true,
setup: func() (string, func()) {
tearDown := setup()
_, _, targetFile, _ := setupTargetFileAndPrivateKey()
removePrivateKey()
return targetFile, tearDown
},
},
{
name: "target file not supplied",
fields: fields{
QliksenseHome: testDir,
},
want: "",
wantErr: true,
setup: func() (string, func()) {
tearDown := setup()
_, _, _, _ = setupTargetFileAndPrivateKey()
removePrivateKey()
return "", tearDown
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
targetFile, tearDown := tt.setup()
q := &Qliksense{
QliksenseHome: tt.fields.QliksenseHome,
}
got, err := q.PrepareK8sSecret(targetFile)
if (err != nil) != tt.wantErr {
t.Errorf("Qliksense.PrepareK8sSecret() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(strings.TrimSpace(got), strings.TrimSpace(tt.want)) {
t.Errorf("Qliksense.PrepareK8sSecret() = %v, want %v", got, tt.want)
}
tearDown()
})
}
}
func Test_ListContextConfigs(t *testing.T) {
type fields struct {
QliksenseHome string
}
tests := []struct {
name string
fields fields
wantErr bool
setup func() (string, func())
}{
{
name: "valid case",
fields: fields{
QliksenseHome: testDir,
},
wantErr: false,
setup: func() (string, func()) {
tearDown := setup()
return "", tearDown
},
},
{
name: "config yaml does not exist",
fields: fields{
QliksenseHome: testDir,
},
wantErr: true,
setup: func() (string, func()) {
return "", func() {}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, tearDown := tt.setup()
q := &Qliksense{
QliksenseHome: tt.fields.QliksenseHome,
}
if err := q.ListContextConfigs(); (err != nil) != tt.wantErr {
t.Errorf("ListContextConfigs() error = %v, wantErr %v", err, tt.wantErr)
}
tearDown()
})
}
}
func Test_SetSecrets(t *testing.T) {
type fields struct {
QliksenseHome string
}
type args struct {
args []string
isSecretSet bool
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "valid secret secrets=false",
fields: fields{
QliksenseHome: testDir,
},
args: args{
args: []string{"qliksense.mongoDbUri=\"mongodb://qlik-default-mongodb:27017/qliksense?ssl=false\""},
isSecretSet: false,
},
wantErr: false,
},
{
name: "test1 valid secret secrets=true",
fields: fields{
QliksenseHome: testDir,
},
args: args{
args: []string{"qliksense.mongoDbUri=\"mongo://mongo:3307\""},
isSecretSet: true,
},
wantErr: false,
},
{
name: "test2 valid secret secrets=true",
fields: fields{
QliksenseHome: testDir,
},
args: args{
args: []string{"qliksense.mongoDbUri=\"mongodb://qlik-default-mongodb:27017/qliksense?ssl=false\""},
isSecretSet: true,
},
wantErr: false,
},
{
name: "invalid secret secrets=false",
fields: fields{
QliksenseHome: testDir,
},
args: args{
args: []string{},
isSecretSet: false,
},
wantErr: true,
},
}
tearDown := setup()
_, privateKeyBytes, _, err := setupTargetFileAndPrivateKey()
if err != nil {
t.FailNow()
}
defer tearDown()
privKey, err := api.DecodeToPrivateKey(privateKeyBytes)
if err != nil {
t.FailNow()
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
q := &Qliksense{
QliksenseHome: tt.fields.QliksenseHome,
}
if err := q.SetSecrets(tt.args.args, tt.args.isSecretSet); (err != nil) != tt.wantErr {
t.Errorf("SetSecrets() error = %v, wantErr %v", err, tt.wantErr)
t.FailNow()
}
if tt.wantErr || len(tt.args.args) == 0 {
return
}
// VERIFICATION PART BELOW
// extract the value for testing
testValueArr := strings.SplitN(tt.args.args[0], "=", 2)
testValue := strings.ReplaceAll(testValueArr[1], "\"", "")
qliksenseCR, err := readCRFile()
if err != nil {
err = fmt.Errorf("Not able to read from context file: %v", err)
log.Println(err)
t.FailNow()
}
for svcName := range qliksenseCR.Spec.Secrets { // we are sure we only have one service
for _, v := range qliksenseCR.Spec.Secrets {
for _, item := range v { // we are sure we only have one entry
valToBeEncrypted, err := getValueToBeDecodedForSetSecrets(item, qliksenseCR, svcName)
if err != nil {
err := fmt.Errorf("Error occurred while decoding: %v", err)
log.Printf("decode error: %v", err)
t.FailNow()
}
decodedValue, err := b64.StdEncoding.DecodeString(valToBeEncrypted)
if err != nil {
err := fmt.Errorf("Error occurred while decoding: %v", err)
log.Printf("decode error: %v", err)
t.FailNow()
}
decryptedVal, err := api.Decrypt(decodedValue, privKey)
if err != nil {
err := fmt.Errorf("Error occurred while testing decryption: %v", err)
log.Printf("No Data in Secret: %v", err)
t.FailNow()
}
if string(decryptedVal) != testValue {
t.FailNow()
}
}
}
}
})
}
}
func getValueToBeDecodedForSetSecrets(item config.NameValue, qliksenseCR *api.QliksenseCR, svcName string) (string, error) {
if item.ValueFrom != nil && item.ValueFrom.SecretKeyRef != nil {
// secret=true
secretFilePath := filepath.Join(testDir, contexts, qliksenseCR.GetName(), QliksenseSecretsDir, svcName+".yaml")
if api.FileExists(secretFilePath) {
secretFileContents, err := ioutil.ReadFile(secretFilePath)
if err != nil {
err = fmt.Errorf("An error occurred during unmarshalling: %v", err)
return "", err
}
k8sSecret, err := api.K8sSecretFromYaml(secretFileContents)
if err != nil {
err = fmt.Errorf("An error occurred during unmarshalling: %v", err)
return "", err
}
if k8sSecret.Data == nil {
err = fmt.Errorf("No Data in Secret: %v", err)
return "", err
}
return string(k8sSecret.Data[item.ValueFrom.SecretKeyRef.Key]), nil
}
}
// secret=false
if item.Value != "" {
return item.Value, nil
}
err := fmt.Errorf("Both Value and ValueFrom are empty")
return "", err
}
func setupDeleteContext() func() {
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
crFile: /root/.qliksense/contexts/qlik-default.yaml
- name: qlik1
crFile: /root/.qliksense/contexts/qlik1.yaml
- name: qlik2
crFile: /root/.qliksense/contexts/qlik2.yaml
currentContext: qlik1
`
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"
contextsDir1 := filepath.Join(testDir, contexts, qlikDefaultContext)
if err := os.MkdirAll(contextsDir1, 0777); err != nil {
err = fmt.Errorf("Not able to create directories")
log.Fatal(err)
}
contextYaml1 :=
`
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: qlik1
spec:
profile: docker-desktop
rotateKeys: "yes"
releaseName: qlik1`
contextYaml2 :=
`
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: qlik2
spec:
profile: docker-desktop
rotateKeys: "yes"
releaseName: qlik2`
contextsDir := filepath.Join(testDir, contexts, "qlik1")
if err := os.MkdirAll(contextsDir, 0777); err != nil {
err = fmt.Errorf("Not able to create directories")
log.Fatal(err)
}
contextsDir2 := filepath.Join(testDir, contexts, "qlik2")
if err := os.MkdirAll(contextsDir2, 0777); err != nil {
err = fmt.Errorf("Not able to create directories")
log.Fatal(err)
}
contextFile := filepath.Join(contextsDir, "qlik1.yaml")
ioutil.WriteFile(contextFile, []byte(contextYaml1), 0777)
contextFile2 := filepath.Join(contextsDir2, "qlik2.yaml")
ioutil.WriteFile(contextFile2, []byte(contextYaml2), 0777)
contextFile1 := filepath.Join(contextsDir1, "qlik-default.yaml")
ioutil.WriteFile(contextFile1, []byte(contextYaml), 0777)
tearDown := func() {
os.RemoveAll(testDir)
}
return tearDown
}
func TestDeleteContexts(t *testing.T) {
type args struct {
qlikSenseHome string
contextName string
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "valid context",
args: args{
qlikSenseHome: testDir,
contextName: "qlik2",
},
wantErr: false,
},
{
name: "default context",
args: args{
qlikSenseHome: testDir,
contextName: "qlik-default",
},
wantErr: true,
},
{
name: "non-existent context",
args: args{
qlikSenseHome: testDir,
contextName: "qlik3",
},
wantErr: true,
},
{
name: "current context",
args: args{
qlikSenseHome: testDir,
contextName: "qlik1",
},
wantErr: true,
},
}
tearDown := setupDeleteContext()
defer tearDown()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
q := New(tt.args.qlikSenseHome)
var arg []string
arg = append(arg, tt.args.contextName)
if err := q.DeleteContextConfig(arg); (err != nil) != tt.wantErr {
t.Errorf("DeleteContext() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@@ -1,10 +1,10 @@
package qliksense package qliksense
import ( import (
"errors"
"fmt" "fmt"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
"path/filepath" "path/filepath"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
) )
type CrdCommandOptions struct { type CrdCommandOptions struct {
@@ -40,11 +40,11 @@ func (q *Qliksense) InstallCrds(opts *CrdCommandOptions) error {
if engineCRD, err := getQliksenseInitCrd(qcr); err != nil { if engineCRD, err := getQliksenseInitCrd(qcr); err != nil {
return err return err
} else if err = qapi.KubectlApply(engineCRD, qcr.Spec.NameSpace); err != nil { } else if err = qapi.KubectlApply(engineCRD, ""); err != nil {
return err return err
} }
if opts.All { // install opeartor crd if opts.All { // install opeartor crd
if err := qapi.KubectlApply(q.GetOperatorCRDString(), qcr.Spec.NameSpace); err != nil { if err := qapi.KubectlApply(q.GetOperatorCRDString(), ""); err != nil {
fmt.Println("cannot do kubectl apply on opeartor CRD", err) fmt.Println("cannot do kubectl apply on opeartor CRD", err)
return err return err
} }
@@ -53,13 +53,18 @@ func (q *Qliksense) InstallCrds(opts *CrdCommandOptions) error {
} }
func getQliksenseInitCrd(qcr *qapi.QliksenseCR) (string, error) { func getQliksenseInitCrd(qcr *qapi.QliksenseCR) (string, error) {
var repoPath string
var err error
if qcr.Spec.GetManifestsRoot() == "" { if qcr.Spec.GetManifestsRoot() != "" {
return "", errors.New("Cannot find manifests root. Please use `qliksense fetch <version>`") repoPath = qcr.Spec.GetManifestsRoot()
} else {
if repoPath, err = downloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master"); err != nil {
return "", err
}
} }
qInitMsPath := filepath.Join(qcr.Spec.GetManifestsRoot(), Q_INIT_CRD_PATH) qInitMsPath := filepath.Join(repoPath, Q_INIT_CRD_PATH)
qInitByte, err := executeKustomizeBuild(qInitMsPath) qInitByte, err := executeKustomizeBuild(qInitMsPath)
if err != nil { if err != nil {
fmt.Println("cannot generate crds for qliksense-init", err) fmt.Println("cannot generate crds for qliksense-init", err)

View File

@@ -0,0 +1,41 @@
package qliksense
import (
"testing"
kapi_config "github.com/qlik-oss/k-apis/pkg/config"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func TestGetQliksenseInitCrd(t *testing.T) {
someTmpRepoPath, err := downloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
crdFromContextConfig, err := getQliksenseInitCrd(&qapi.QliksenseCR{
KApiCr: kapi_config.KApiCr{
Spec: &kapi_config.CRSpec{
ManifestsRoot: someTmpRepoPath,
},
},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
crdFromDownloadedConfig, err := getQliksenseInitCrd(&qapi.QliksenseCR{
KApiCr: kapi_config.KApiCr{
Spec: &kapi_config.CRSpec{
ManifestsRoot: "",
},
},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if crdFromContextConfig != crdFromDownloadedConfig {
t.Fatalf("expected %v to equal %v, but they didn't", crdFromContextConfig, crdFromDownloadedConfig)
}
}

View File

@@ -48,7 +48,12 @@ func (q *Qliksense) PullImagesForCurrentCR() error {
return err return err
} }
for _, image := range versionOut.Images { images := versionOut.Images
if err := q.appendOperatorImages(&images); err != nil {
return err
}
for _, image := range images {
if err := pullImage(image, imagesDir); err != nil { if err := pullImage(image, imagesDir); err != nil {
fmt.Printf("%v\n", err) fmt.Printf("%v\n", err)
return err return err
@@ -64,6 +69,15 @@ func (q *Qliksense) PullImagesForCurrentCR() error {
return nil return nil
} }
func (q *Qliksense) appendOperatorImages(images *[]string) error {
if operatorImages, err := getImageList([]byte(q.GetOperatorControllerString())); err != nil {
return err
} else {
*images = append(*images, operatorImages...)
return nil
}
}
func pullImage(image, imagesDir string) error { func pullImage(image, imagesDir string) error {
srcRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%v", image)) srcRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%v", image))
if err != nil { if err != nil {
@@ -134,7 +148,12 @@ func (q *Qliksense) PushImagesForCurrentCR() error {
return err return err
} }
for _, image := range versionOut.Images { images := versionOut.Images
if err := q.appendOperatorImages(&images); err != nil {
return err
}
for _, image := range images {
if err = pushImage(image, imagesDir, dockerConfigJsonSecret); err != nil { if err = pushImage(image, imagesDir, dockerConfigJsonSecret); err != nil {
fmt.Printf("%v\n", err) fmt.Printf("%v\n", err)
return err return err

View File

@@ -22,6 +22,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/gobuffalo/packr/v2"
"github.com/containers/image/v5/copy" "github.com/containers/image/v5/copy"
"github.com/containers/image/v5/signature" "github.com/containers/image/v5/signature"
"github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/transports/alltransports"
@@ -47,7 +49,9 @@ func Test_locateDockerRegistryBinary(t *testing.T) {
} }
func Test_getSelfSignedCertAndKey(t *testing.T) { func Test_getSelfSignedCertAndKey(t *testing.T) {
selfSignedCert, key, err := getSelfSignedCertAndKey() host := "andriy.registry.com"
validity := time.Hour * 24 * 365
selfSignedCert, key, err := getSelfSignedCertAndKey(host, validity)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@@ -125,7 +129,10 @@ func Test_Pull_Push_ImagesForCurrentCR(t *testing.T) {
if err := setupQlikSenseHome(t, tmpQlikSenseHome, registry, testCase.clientAuth); err != nil { if err := setupQlikSenseHome(t, tmpQlikSenseHome, registry, testCase.clientAuth); err != nil {
t.Fatalf("unexpected error setting up qliksense home: %v", err) t.Fatalf("unexpected error setting up qliksense home: %v", err)
} }
q := &Qliksense{QliksenseHome: tmpQlikSenseHome} q := &Qliksense{
QliksenseHome: tmpQlikSenseHome,
CrdBox: packr.New("crds", "./crds"),
}
var versionOut VersionOutput var versionOut VersionOutput
if err := q.PullImagesForCurrentCR(); err != nil { if err := q.PullImagesForCurrentCR(); err != nil {
@@ -135,7 +142,7 @@ func Test_Pull_Push_ImagesForCurrentCR(t *testing.T) {
} else if err = yaml.Unmarshal(versionOutBytes, &versionOut); err != nil { } else if err = yaml.Unmarshal(versionOutBytes, &versionOut); err != nil {
t.Fatalf("unexpected error unmarshalling version file: %v", err) t.Fatalf("unexpected error unmarshalling version file: %v", err)
} else if len(versionOut.Images) != 1 || versionOut.Images[0] != "alpine:latest" { } else if len(versionOut.Images) != 1 || versionOut.Images[0] != "alpine:latest" {
t.Fatal("did not find alpine:latest in the version file") t.Fatal(`did not find "alpine:latest"" in the version file`)
} else if infos, err := ioutil.ReadDir(path.Join(tmpQlikSenseHome, "images", "index", "alpine", "latest")); err != nil || len(infos) == 0 { } else if infos, err := ioutil.ReadDir(path.Join(tmpQlikSenseHome, "images", "index", "alpine", "latest")); err != nil || len(infos) == 0 {
t.Fatal("expected images/index/alpine/latest directory to be non-empty") t.Fatal("expected images/index/alpine/latest directory to be non-empty")
} else if blobInfos, err := ioutil.ReadDir(path.Join(tmpQlikSenseHome, "images", "blobs", "sha256")); err != nil || len(blobInfos) == 0 { } else if blobInfos, err := ioutil.ReadDir(path.Join(tmpQlikSenseHome, "images", "blobs", "sha256")); err != nil || len(blobInfos) == 0 {
@@ -345,7 +352,7 @@ http:
) )
var env []string var env []string
if auth { if auth {
if certificate, key, err := getSelfSignedCertAndKey(); err != nil { if certificate, key, err := getSelfSignedCertAndKey("localhost", time.Hour*24); err != nil {
return nil, err return nil, err
} else { } else {
certPath := filepath.Join(tmp, "domain.crt") certPath := filepath.Join(tmp, "domain.crt")
@@ -464,7 +471,7 @@ func consumeAndLogOutputs(id string, cmd *exec.Cmd) (*bytes.Buffer, *bytes.Buffe
return consumeAndLogOutputStream(id+" stdout", stdout), consumeAndLogOutputStream(id+" stderr", stderr), nil return consumeAndLogOutputStream(id+" stdout", stdout), consumeAndLogOutputStream(id+" stderr", stderr), nil
} }
func getSelfSignedCertAndKey() (certificate, key []byte, err error) { func getSelfSignedCertAndKey(hostname string, validity time.Duration) (certificate, key []byte, err error) {
priv, err := rsa.GenerateKey(rand.Reader, 4096) priv, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@@ -477,13 +484,14 @@ func getSelfSignedCertAndKey() (certificate, key []byte, err error) {
template := x509.Certificate{ template := x509.Certificate{
SerialNumber: serialNumber, SerialNumber: serialNumber,
Subject: pkix.Name{ Subject: pkix.Name{
Organization: []string{"Acme Co"}, Organization: []string{"self-signed"},
}, },
NotBefore: time.Now(), NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24), NotAfter: time.Now().Add(validity),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true, BasicConstraintsValid: true,
DNSNames: []string{hostname},
} }
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)

View File

@@ -3,19 +3,22 @@ package qliksense
import ( import (
"errors" "errors"
"fmt" "fmt"
"path/filepath"
"github.com/qlik-oss/k-apis/pkg/config"
"sigs.k8s.io/kustomize/api/filesys"
qapi "github.com/qlik-oss/sense-installer/pkg/api" qapi "github.com/qlik-oss/sense-installer/pkg/api"
) )
type InstallCommandOptions struct { type InstallCommandOptions struct {
AcceptEULA string AcceptEULA string
Namespace string
StorageClass string StorageClass string
MongoDbUri string MongoDbUri string
RotateKeys string RotateKeys string
} }
func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) error { func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, keepPatchFiles bool) error {
// step1: fetch 1.0.0 # pull down qliksense-k8s@1.0.0 // step1: fetch 1.0.0 # pull down qliksense-k8s@1.0.0
// step2: operator view | kubectl apply -f # operator manifest (CRD) // step2: operator view | kubectl apply -f # operator manifest (CRD)
@@ -24,6 +27,13 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) err
// fetch the version // fetch the version
qConfig := qapi.NewQConfig(q.QliksenseHome) qConfig := qapi.NewQConfig(q.QliksenseHome)
if !keepPatchFiles {
defer func() {
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
fmt.Printf("error removing temporary changes to the config: %v\n", err)
}
}()
}
qcr, err := qConfig.GetCurrentCR() qcr, err := qConfig.GetCurrentCR()
if err != nil { if err != nil {
@@ -40,25 +50,52 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) err
if opts.StorageClass != "" { if opts.StorageClass != "" {
qcr.Spec.StorageClassName = opts.StorageClass qcr.Spec.StorageClassName = opts.StorageClass
} }
if opts.Namespace != "" {
qcr.Spec.NameSpace = opts.Namespace
}
if opts.RotateKeys != "" { if opts.RotateKeys != "" {
qcr.Spec.RotateKeys = opts.RotateKeys qcr.Spec.RotateKeys = opts.RotateKeys
} }
qConfig.WriteCurrentContextCR(qcr) qConfig.WriteCurrentContextCR(qcr)
//if the docker pull secret exists on disk, install it in the cluster
//if it doesn't exist on disk, remove it in the cluster
if err := installOrRemoveImagePullSecret(qConfig); err != nil {
return err
}
// check if acceptEULA is yes or not
if !qcr.IsEULA() {
return errors.New(agreementTempalte + "\n Please do $ qliksense install --acceptEULA=yes\n")
}
//CRD will be installed outside of operator //CRD will be installed outside of operator
//install operator controller into the namespace //install operator controller into the namespace
fmt.Println("Installing operator controller") fmt.Println("Installing operator controller")
if err := qapi.KubectlApply(q.GetOperatorControllerString(), qcr.Spec.NameSpace); err != nil { operatorControllerString := q.GetOperatorControllerString()
if imageRegistry := qcr.GetImageRegistry(); imageRegistry != "" {
operatorControllerString, err = kustomizeForImageRegistry(operatorControllerString, pullSecretName,
"qlik/qliksense-operator", fmt.Sprintf("%v/qliksense-operator", imageRegistry))
if err != nil {
return err
}
}
if err := qapi.KubectlApply(operatorControllerString, ""); err != nil {
fmt.Println("cannot do kubectl apply on opeartor controller", err) fmt.Println("cannot do kubectl apply on opeartor controller", err)
return err return err
} }
if qcr.Spec.Git.Repository != "" { // create patch dependent resoruces
fmt.Println("Installing resoruces used kuztomize patch")
if err := q.createK8sResoruceBeforePatch(qcr); err != nil {
return err
}
if qcr.Spec.Git != nil && qcr.Spec.Git.Repository != "" {
// fetching and applying manifest will be in the operator controller // fetching and applying manifest will be in the operator controller
return q.applyCR(qcr.Spec.NameSpace) // get decrypted cr
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
return err
} else {
return q.applyCR(dcr)
}
} }
if version != "" { // no need to fetch manifest root already set by some other way if version != "" { // no need to fetch manifest root already set by some other way
if err := fetchAndUpdateCR(qConfig, version); err != nil { if err := fetchAndUpdateCR(qConfig, version); err != nil {
@@ -76,25 +113,100 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) err
// install generated manifests into cluster // install generated manifests into cluster
fmt.Println("Installing generated manifests into cluster") fmt.Println("Installing generated manifests into cluster")
if err := q.applyConfigToK8s(qcr); err != nil {
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
return err
} else if err := q.applyConfigToK8s(dcr); err != nil {
fmt.Println("cannot do kubectl apply on manifests") fmt.Println("cannot do kubectl apply on manifests")
return err return err
} else {
return q.applyCR(dcr)
} }
return q.applyCR(qcr.Spec.NameSpace)
} }
func (q *Qliksense) applyCR(ns string) error { func installOrRemoveImagePullSecret(qConfig *qapi.QliksenseConfig) error {
if pullDockerConfigJsonSecret, err := qConfig.GetPullDockerConfigJsonSecret(); err == nil {
if dockerConfigJsonSecretYaml, err := pullDockerConfigJsonSecret.ToYaml(nil); err != nil {
return err
} else if err := qapi.KubectlApply(string(dockerConfigJsonSecretYaml), ""); err != nil {
return err
}
} else {
deleteDockerConfigJsonSecret := qapi.DockerConfigJsonSecret{
Name: pullSecretName,
}
if deleteDockerConfigJsonSecretYaml, err := deleteDockerConfigJsonSecret.ToYaml(nil); err != nil {
return err
} else if err := qapi.KubectlDelete(string(deleteDockerConfigJsonSecretYaml), ""); err != nil {
qapi.LogDebugMessage("failed deleting %v, error: %v\n", pullSecretName, err)
}
}
return nil
}
func kustomizeForImageRegistry(resources, dockerConfigJsonSecretName, name, newName string) (string, error) {
fSys := filesys.MakeFsInMemory()
if err := fSys.WriteFile("/resources.yaml", []byte(resources)); err != nil {
return "", err
} else if err := fSys.WriteFile("/addImagePullSecrets.yaml", []byte(fmt.Sprintf(`
apiVersion: builtin
kind: PatchTransformer
metadata:
name: notImportantHere
patch: '[{"op": "add", "path": "/spec/template/spec/imagePullSecrets", "value": [{"name": "%v"}]}]'
target:
name: .*-operator
kind: Deployment
`, dockerConfigJsonSecretName))); err != nil {
return "", err
} else if err := fSys.WriteFile("/kustomization.yaml", []byte(fmt.Sprintf(`
resources:
- resources.yaml
transformers:
- addImagePullSecrets.yaml
images:
- name: %s
newName: %s
`, name, newName))); err != nil {
return "", err
} else if out, err := executeKustomizeBuildForFileSystem("/", fSys); err != nil {
return "", err
} else {
return string(out), nil
}
}
func (q *Qliksense) applyCR(cr *qapi.QliksenseCR) error {
// install operator cr into cluster // install operator cr into cluster
//get the current context cr //get the current context cr
fmt.Println("Install operator CR into cluster") fmt.Println("Install operator CR into cluster")
r, err := q.getCurrentCRString() r, err := cr.GetString()
if err != nil { if err != nil {
return err return err
} }
if err := qapi.KubectlApply(r, ns); err != nil { if err := qapi.KubectlApply(r, ""); err != nil {
fmt.Println("cannot do kubectl apply on operator CR") fmt.Println("cannot do kubectl apply on operator CR")
return err return err
} }
return nil return nil
} }
func (q *Qliksense) createK8sResoruceBeforePatch(qcr *qapi.QliksenseCR) error {
for svc, nvs := range qcr.Spec.Secrets {
for _, nv := range nvs {
if isK8sSecretNeedToCreate(nv) {
fmt.Println(filepath.Join(qcr.GetK8sSecretsFolder(q.QliksenseHome), svc+".yaml"))
if secS, err := q.PrepareK8sSecret(filepath.Join(qcr.GetK8sSecretsFolder(q.QliksenseHome), svc+".yaml")); err != nil {
return err
} else {
return qapi.KubectlApply(secS, "")
}
}
}
}
return nil
}
func isK8sSecretNeedToCreate(nv config.NameValue) bool {
return nv.ValueFrom != nil
}

View File

@@ -13,12 +13,15 @@ import (
) )
func executeKustomizeBuild(directory string) ([]byte, error) { func executeKustomizeBuild(directory string) ([]byte, error) {
return executeKustomizeBuildForFileSystem(directory, filesys.MakeFsOnDisk())
}
func executeKustomizeBuildForFileSystem(directory string, fSys filesys.FileSystem) ([]byte, error) {
log.SetOutput(&nullWriter{}) log.SetOutput(&nullWriter{})
defer func() { defer func() {
log.SetOutput(os.Stderr) log.SetOutput(os.Stderr)
}() }()
fSys := filesys.MakeFsOnDisk()
options := &krusty.Options{ options := &krusty.Options{
DoLegacyResourceSort: false, DoLegacyResourceSort: false,
LoadRestrictions: types.LoadRestrictionsNone, LoadRestrictions: types.LoadRestrictionsNone,

View File

@@ -66,7 +66,7 @@ func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
configPath := path.Join(tmpDir, "config") configPath := path.Join(tmpDir, "config")
if repo, err := kapis_git.CloneRepository(configPath, defaultGitUrl, nil); err != nil { if repo, err := kapis_git.CloneRepository(configPath, defaultConfigRepoGitUrl, nil); err != nil {
t.Fatalf("unexpected error: %v\n", err) t.Fatalf("unexpected error: %v\n", err)
} else if err := kapis_git.Checkout(repo, "v1.21.23-edge", "", nil); err != nil { } else if err := kapis_git.Checkout(repo, "v1.21.23-edge", "", nil); err != nil {
t.Fatalf("unexpected error: %v\n", err) t.Fatalf("unexpected error: %v\n", err)

View File

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

23
pkg/qliksense/repo.go Normal file
View File

@@ -0,0 +1,23 @@
package qliksense
import (
"errors"
kapis_git "github.com/qlik-oss/k-apis/pkg/git"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func (q *Qliksense) DiscardAllUnstagedChangesFromGitRepo(qConfig *qapi.QliksenseConfig) error {
if qcr, err := qConfig.GetCurrentCR(); err != nil {
return err
} else if version := qcr.GetLabelFromCr("version"); version == "" {
return errors.New("version label is not set in CR")
} else if qcr.Spec.ManifestsRoot == qConfig.BuildRepoPath(version) {
if repo, err := kapis_git.OpenRepository(qcr.Spec.ManifestsRoot); err != nil {
return err
} else if err = kapis_git.DiscardAllUnstagedChanges(repo); err != nil {
return err
}
}
return nil
}

View File

@@ -13,13 +13,9 @@ func (q *Qliksense) UninstallQK8s(contextName string) error {
} else if !qConfig.IsContextExist(contextName) { } else if !qConfig.IsContextExist(contextName) {
return errors.New("context name [ " + contextName + " ] not found") return errors.New("context name [ " + contextName + " ] not found")
} }
cr, err := qConfig.GetCurrentCR()
if err != nil {
return err
}
str, err := q.getCRString(contextName) str, err := q.getCRString(contextName)
if err != nil { if err != nil {
return err return err
} }
return qapi.KubectlDelete(str, cr.Spec.NameSpace) return qapi.KubectlDelete(str, "")
} }

View File

@@ -6,7 +6,7 @@ import (
qapi "github.com/qlik-oss/sense-installer/pkg/api" qapi "github.com/qlik-oss/sense-installer/pkg/api"
) )
func (q *Qliksense) UpgradeQK8s() error { func (q *Qliksense) UpgradeQK8s(keepPatchFiles bool) error {
// step1: get CR // step1: get CR
// step2: run kustomize // step2: run kustomize
@@ -14,6 +14,13 @@ func (q *Qliksense) UpgradeQK8s() error {
// fetch the version // fetch the version
qConfig := qapi.NewQConfig(q.QliksenseHome) qConfig := qapi.NewQConfig(q.QliksenseHome)
if !keepPatchFiles {
defer func() {
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
fmt.Printf("error removing temporary changes to the config: %v\n", err)
}
}()
}
qcr, err := qConfig.GetCurrentCR() qcr, err := qConfig.GetCurrentCR()
if err != nil { if err != nil {
@@ -21,7 +28,9 @@ func (q *Qliksense) UpgradeQK8s() error {
return err return err
} }
qcr.Spec.RotateKeys = "no" qcr.Spec.RotateKeys = "no"
if err := q.applyConfigToK8s(qcr); err != nil { if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
return err
} else if err := q.applyConfigToK8s(dcr); err != nil {
fmt.Println("cannot do kubectl apply on manifests") fmt.Println("cannot do kubectl apply on manifests")
return err return err
} }
@@ -31,7 +40,7 @@ func (q *Qliksense) UpgradeQK8s() error {
if err != nil { if err != nil {
return err return err
} }
if err := qapi.KubectlApply(r, qcr.Spec.NameSpace); err != nil { if err := qapi.KubectlApply(r, ""); err != nil {
fmt.Println("cannot do kubectl apply on operator CR") fmt.Println("cannot do kubectl apply on operator CR")
} }
return nil return nil