Compare commits

..

94 Commits

Author SHA1 Message Date
Foysal Iqbal
77a3bf4581 Merge pull request #401 from qlik-oss/upgrade-kapi
upgrade k-api and remove config-apply
2020-06-09 22:45:52 -04:00
Foysal Iqbal
a670b6c750 update to dev kapi v0.1.7
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-09 18:17:36 -04:00
Foysal Iqbal
492e4a1baa upgrade k-api and remove config-apply
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-09 17:21:09 -04:00
Foysal Iqbal
3b54a7f0b2 Merge pull request #400 from qlik-oss/fix-default-mongo
fix default mongo
2020-06-09 16:20:40 -04:00
Foysal Iqbal
55cfc42257 fix default mongo
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-09 15:41:52 -04:00
Foysal Iqbal
12e2bec618 fix default mongo
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-09 15:23:59 -04:00
Ashwathi Shiva
2ed59321e4 Preflight checks unit tests and bug fixes (#399)
* - committing changes lost during conflict resolution
- adding more tests
- marking tests to run in parallel to speed things up
- changed case of mongoDbUrl to mongodbUrl
- modified preflight -all output
2020-06-09 13:00:35 -04:00
Andriy Bulynko
98198a3c8b Renaming "keep-config-repo-patches" flag to "clean" and fixing a typo (#396) 2020-06-05 01:01:35 -04:00
Boris Kuschel
afab3e2939 Fix qliksense about (#397) 2020-06-04 17:19:23 -04:00
Andriy Bulynko
62fda8f2c6 Upgrading kustomize API to version qlik/v0.0.27 (#398) 2020-06-04 16:33:50 -04:00
Andriy Bulynko
6af87ab00a Upgrading kustomize API to version qlik/v0.0.26 (#395) 2020-06-04 10:41:31 -04:00
Foysal Iqbal
0b60838b52 add [] for special case (#392)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-04 09:48:10 -04:00
Foysal Iqbal
bb2974fe66 Merge branch 'master' into fix-regex 2020-06-04 09:20:19 -04:00
Andriy Bulynko
97b2239c2e crds view/install commands have option --all set to true by default (#391) 2020-06-04 04:00:23 -04:00
Foysal Iqbal
9eff54d9ec remove git functionality (#394)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-04 01:37:25 -04:00
Foysal Iqbal
2d0a2a32bf Merge branch 'master' into fix-regex 2020-06-03 16:19:48 -04:00
Foysal Iqbal
d7238e2b3c add [] for special case
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-06-03 16:18:51 -04:00
Ashwathi Shiva
1c0ded7f3d Operator Postflight check- db migration check (#382)
* postflight db-migration-check implemented, docs updated
* fixed typo: changed kube-version to k8s-version
2020-06-03 13:51:41 -04:00
Andriy Bulynko
ec8a9376e7 Checking if CRDs are installed before allowing install to proceed (#387) 2020-06-03 10:38:11 -04:00
Foysal Iqbal
bcc321e180 Fix fetch (#386) 2020-06-02 14:28:15 -04:00
Andriy Bulynko
0aabf63715 Fixing --acceptEULA=no behaviour (#385) 2020-06-02 12:45:47 -04:00
Foysal Iqbal
c9ca5c8be0 Dry run and fix mongo (#384) 2020-06-02 11:56:15 -04:00
Foysal Iqbal
9d0ac0290f add unset command (#381) 2020-06-01 00:08:08 -04:00
Andriy Bulynko
dd8a48b2b8 Fixing a test expecting a "v" at the start of the operator docker image version (#379) 2020-05-27 16:26:36 -04:00
Andriy Bulynko
9fb6800993 Upgrading kustomize to qlik/v0.0.25 (#378) 2020-05-27 15:19:20 -04:00
Ashwathi Shiva
bbb811a879 Preflight mongo mutual tls (#365)
* mongo check working when ca cert and client cert put in same file
* updated code to use image from bintray
2020-05-13 20:36:07 -04:00
Foysal Iqbal
8156b884ce Fix cipher (#363) 2020-05-12 09:54:04 -04:00
Ashwathi Shiva
7525c2e698 Preflight mongo mutual tls (#357)
* preflight mongo mutual tls working when cert is specified in CR
2020-05-04 09:42:43 -04:00
Andriy Bulynko
60763e034a Not deleting docker pull secret (#356) 2020-04-29 09:33:41 -04:00
Ashwathi Shiva
ce4081a422 Preflight mongo version check (#353)
* mongo minimum version check working
2020-04-28 16:51:02 -04:00
Sanat Nayar
dd503a40c1 Merge pull request #347 from qlik-oss/upgrade_libraries
upgrade/standardize yaml and semver
2020-04-28 09:02:10 -04:00
Sanat Nayar
b790419fc2 Merge branch 'master' into upgrade_libraries 2020-04-27 16:03:25 -04:00
Ashwathi Shiva
55f9c07c21 Preflight clean cmd (#345)
* Preflight clean as a command and cleanup before and after individual preflight checks
2020-04-27 10:26:50 -04:00
Sanat Nayar
ef77ea3a5f init 2020-04-27 08:49:42 -04:00
Sanat Nayar
7f70bfc7de init 2020-04-27 08:04:13 -04:00
Andriy Bulynko
8255fe0971 Adding a concurrency test for a version of qlik-oss config (#343) 2020-04-24 11:35:52 -04:00
Sanat Nayar
d9bea9b0fd Merge pull request #342 from qlik-oss/chalk_replacement
Chalk Library Replaced with Aurora
2020-04-23 10:47:53 -04:00
Sanat Nayar
0dd136f07b changed chalk in preflight 2020-04-22 22:44:42 -04:00
Sanat Nayar
9ec9801bea deleted files 2020-04-22 17:06:55 -04:00
Sanat Nayar
d48212ec8f Merge branch 'master' into chalk_replacement 2020-04-22 16:50:32 -04:00
Sanat Nayar
546a2caf3e deleted files 2020-04-22 16:49:28 -04:00
Sanat Nayar
0c26d8bb46 deleted files 2020-04-22 16:43:29 -04:00
Sanat Nayar
92cdcf2b84 changed context display to aurora 2020-04-22 16:34:46 -04:00
Andriy Bulynko
2eae380287 Upgrading kustomize api to version qlik/v0.0.21 (#341) 2020-04-22 16:23:46 -04:00
Sanat Nayar
5dd6a08e65 changed levensthein to aurora 2020-04-22 15:48:23 -04:00
Ashwathi Shiva
ed7e97332b Preflight fix output to remove symbols (#340)
* removed symbols and have all preflight checks working
2020-04-22 09:01:07 -04:00
Sanat Nayar
4b7674261c added functionality to add multiple folders 2020-04-21 17:56:31 -04:00
Sanat Nayar
2ea4366653 added recursive folder addition 2020-04-21 17:22:42 -04:00
Ashwathi Shiva
eb7517f88d Preflight beautify output (#332)
* kubernetes version check fixed with new output style
2020-04-20 09:02:55 -04:00
Foysal Iqbal
ea048f1b5f Unify install apply (#328) 2020-04-16 17:04:21 -04:00
Ilir Bekteshi
0992286e23 [action] test on supported platforms (#319) (#321)
* [action] test on supported platforms (#319)

* [action] install make on windows

* [action] go@beta to clean WFs from workarounds

* [action] Remove MacOS from matrix
2020-04-16 17:16:40 +02:00
Foysal Iqbal
6d87fadae0 Install latest (#326) 2020-04-16 10:29:56 -04:00
Andriy Bulynko
04e2cc5b22 install and apply commands can pull/push images (#322) 2020-04-16 09:58:01 -04:00
Andriy Bulynko
7b7cd7b4bf Fixing windows tests (#324) 2020-04-15 16:41:38 -04:00
Ashwathi Shiva
7a0bbcd5d8 Preflight mongo (#325)
* preflight mongo check working with ca cert
2020-04-15 16:17:21 -04:00
Sanat Nayar
6b6ef14fb1 Merge pull request #318 from qlik-oss/uninstall_confirm
Uninstall Confirmation
2020-04-15 16:03:16 -04:00
Sanat Nayar
fa0c6528e4 Merge pull request #317 from qlik-oss/context_delete
Delete Context Confirmation
2020-04-15 16:02:09 -04:00
Sanat Nayar
e6070a33c2 changed to bool 2020-04-15 10:04:53 -04:00
Sanat Nayar
0c9f264ed2 changed to bool 2020-04-15 10:01:09 -04:00
Sanat Nayar
22b9b902a9 modified tests 2020-04-15 09:32:36 -04:00
Ilir Bekteshi
3a49745622 Merge pull request #269 from qlik-oss/docs2
Update docs
2020-04-14 20:16:14 +02:00
Sanat Nayar
5795988d01 added flags 2020-04-14 13:46:40 -04:00
Sanat Nayar
e9b359c1bd changes 2020-04-14 13:31:52 -04:00
Foysal Iqbal
9ab2478aba fix go git version (#320) 2020-04-14 10:16:26 -04:00
Ilir Bekteshi
044e00afc5 [docs] reorder cmds, cleanup about cmd 2020-04-14 14:07:12 +02:00
Ilir Bekteshi
2f718649f4 [docs] preflight cleanup 2020-04-14 13:42:03 +02:00
Ilir Bekteshi
c6fe7084f5 remove preflight page 2020-04-14 13:17:39 +02:00
Ilir Bekteshi
e60ce7d62d Update docs 2020-04-14 13:17:34 +02:00
Andriy Bulynko
0d2e436639 Accounting for imageRegistry CR setting when executing preflight checks (#314) 2020-04-14 06:39:48 -04:00
Sanat Nayar
6093552ba9 changes 2020-04-13 17:33:44 -04:00
Sanat Nayar
449642e6f4 changes 2020-04-13 17:32:57 -04:00
Sanat Nayar
97b6cf21a7 changes 2020-04-13 17:14:58 -04:00
Sanat Nayar
14b6447154 changes 2020-04-13 17:09:10 -04:00
Sanat Nayar
1c60ce4cc0 changes 2020-04-13 16:57:04 -04:00
Sanat Nayar
7a8926773f changes 2020-04-13 16:50:33 -04:00
Sanat Nayar
0b868732a7 changes 2020-04-13 16:48:35 -04:00
Sanat Nayar
4f2581cde2 changes 2020-04-13 15:52:12 -04:00
Foysal Iqbal
ca15145499 update k-api (#315)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-04-13 14:03:03 -04:00
Foysal Iqbal
3274ebd12a fix access token encrypt (#313) 2020-04-13 09:43:35 -04:00
Foysal Iqbal
505b4ef4ce add base64 flag and input pipe (#312)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-04-13 09:21:28 -04:00
Foysal Iqbal
a4e2b0dfe6 Change encryption (#307) 2020-04-09 22:56:05 -04:00
Foysal Iqbal
7cf2b00f0b fix windows copy error (#306) 2020-04-09 13:49:44 -04:00
Ilir Bekteshi
d94454b832 Merge pull request #302 from qlik-oss/buildall
[action] Build for all platforms on PR
2020-04-09 16:21:37 +02:00
Ashwathi Shiva
f4d0bd87f6 Preflight- Provide command into container (#301)
* Supply command as a script into container working
2020-04-09 08:05:43 -04:00
Ilir Bekteshi
645d1496d4 [action] Build for all platforms on PR 2020-04-09 14:04:48 +02:00
Foysal Iqbal
65ce074981 move crds to base/crds folder (#298) 2020-04-08 23:10:12 -04:00
Andriy Bulynko
323014d137 Fixing operator deployment's image renaming if the imageRegistry is set in the CR (#297) 2020-04-08 20:59:33 -04:00
Foysal Iqbal
31262df504 add struct for fetch repo (#287) 2020-04-08 13:37:08 -04:00
Andriy Bulynko
a15fe75b6c Pulling/pushing GitOps and Preflight images (#286) 2020-04-07 19:32:00 -04:00
Ashwathi Shiva
a59bf2d015 mongo image included for preflight check (#288) 2020-04-07 18:40:38 -04:00
Foysal Iqbal
b36b8917da add prefix preflight (#282)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-04-06 02:24:25 -04:00
Ashwathi Shiva
eed4d49665 Removing namespaces from role, rolebinding and sa checks (#280)
* remove namespace from role, rolebinding and SA checks
2020-04-06 00:23:40 -04:00
Foysal Iqbal
34d35909a4 remove regex again (#279)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-04-05 22:13:32 -04:00
Sanat Nayar
cb78b4da9f added confirmation for context-delete 2020-03-27 16:26:31 -04:00
89 changed files with 6693 additions and 2371 deletions

View File

@@ -4,24 +4,43 @@ on: [pull_request]
jobs:
test:
name: Test
env:
CGO_ENABLED: 0
strategy:
matrix:
go: [1.13.x]
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Set up Go ${{ matrix.go }}
uses: actions/setup-go@v2-beta
with:
go-version: ${{ matrix.go }}
- uses: actions/checkout@v2
- name: setup make (Windows)
if: matrix.os == 'windows-latest'
run: choco install make -y
- run: make test
build:
name: Build
runs-on: ubuntu-latest
needs: test
steps:
- name: Set up Go 1.13
uses: actions/setup-go@v1
uses: actions/setup-go@v2-beta
with:
go-version: 1.13
- uses: actions/checkout@v2
- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
- name: Set GOPATH
# temporary fix
# see https://github.com/actions/setup-go/issues/14
run: |
echo "##[set-env name=GOPATH;]$(dirname $GITHUB_WORKSPACE)"
echo "##[add-path]$(dirname $GITHUB_WORKSPACE)/bin"
shell: bash
- run: make test
- run: make build
- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
- run: make xbuild-all

View File

@@ -12,19 +12,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.13
uses: actions/setup-go@v1
uses: actions/setup-go@v2-beta
with:
go-version: 1.13
- uses: actions/checkout@v2
- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* # Needed in makefile for versioning
- name: Set GOPATH
# temporary fix
# see https://github.com/actions/setup-go/issues/14
run: |
echo "##[set-env name=GOPATH;]$(dirname $GITHUB_WORKSPACE)"
echo "##[add-path]$(dirname $GITHUB_WORKSPACE)/bin"
shell: bash
- run: make test
- run: make xbuild-all

View File

@@ -39,21 +39,29 @@ endif
.PHONY: build
build: clean generate
mkdir -p $(BINDIR)
go run _make_support/mkdir_all/do.go $(BINDIR)
go build -ldflags '$(LDFLAGS)' -tags "$(BUILDTAGS)" -o $(BINDIR)/$(MIXIN)$(FILE_EXT) ./cmd/$(MIXIN)
$(MAKE) clean
.PHONY: test
test: clean generate
.PHONY: test-setup
test-setup: clean generate
ifeq ($(shell ${WHICH} docker-registry 2>${DEVNUL}),)
$(eval TMP-docker-distribution := $(shell mktemp -d))
git clone https://github.com/docker/distribution.git $(TMP-docker-distribution)/docker-distribution
cd $(TMP-docker-distribution)/docker-distribution; git checkout -b v2.7.1; make
cp $(TMP-docker-distribution)/docker-distribution/bin/registry pkg/qliksense/docker-registry
-rm -rf $(TMP-docker-distribution)
$(eval TMP-docker-distribution := $(shell go run _make_support/get_tmp_dir/do.go))
git clone https://github.com/docker/distribution.git "$(TMP-docker-distribution)/docker-distribution"
cd "$(TMP-docker-distribution)/docker-distribution" && git checkout -b v2.7.1 && "$(MAKE)"
go run _make_support/copy/do.go --src "$(TMP-docker-distribution)/docker-distribution/bin/registry" --dst pkg/qliksense/docker-registry$(FILE_EXT)
go run _make_support/remove_all/do.go "$(TMP-docker-distribution)"
endif
go test -short -count=1 -tags "$(BUILDTAGS)" -v ./...
$(MAKE) clean
.PHONY: test-short
test-short: test-setup
go test -count 1 -p 1 -tags "$(BUILDTAGS)" -v -short ./...
"$(MAKE)" clean
.PHONY: test
test: test-setup
go test -count 1 -p 1 -tags "$(BUILDTAGS)" -v ./...
"$(MAKE)" clean
xbuild-all: clean generate
$(foreach OS, $(SUPPORTED_PLATFORMS), \
@@ -78,6 +86,7 @@ endif
generate: get-crds packr2
go generate ./...
go run _make_support/remove_all/do.go pkg/qliksense/crds
packr2:
ifeq ($(shell ${WHICH} packr2 2>${DEVNUL}),)
@@ -85,22 +94,22 @@ ifeq ($(shell ${WHICH} packr2 2>${DEVNUL}),)
endif
clean: clean-packr
-rm -fr pkg/qliksense/crds
go run _make_support/remove_all/do.go pkg/qliksense/crds
clean-packr: packr2
cd pkg/qliksense && packr2 clean
get-crds:
ifeq ($(QLIKSENSE_OPERATOR_DIR),)
$(eval TMP-operator := $(shell mktemp -d))
$(eval TMP-operator := $(shell go run _make_support/get_tmp_dir/do.go))
git clone https://github.com/qlik-oss/qliksense-operator.git -b master $(TMP-operator)/operator
$(MAKE) QLIKSENSE_OPERATOR_DIR=$(TMP-operator)/operator get-crds
-rm -rf $(TMP-operator)
"$(MAKE)" QLIKSENSE_OPERATOR_DIR="$(TMP-operator)/operator" get-crds
go run _make_support/remove_all/do.go "$(TMP-operator)"
else
mkdir -p pkg/qliksense/crds/cr
mkdir -p pkg/qliksense/crds/crd
mkdir -p pkg/qliksense/crds/crd-deploy
cp $(QLIKSENSE_OPERATOR_DIR)/deploy/*.yaml pkg/qliksense/crds/crd-deploy
cp $(QLIKSENSE_OPERATOR_DIR)/deploy/crds/*_crd.yaml pkg/qliksense/crds/crd
cp $(QLIKSENSE_OPERATOR_DIR)/deploy/crds/*_cr.yaml pkg/qliksense/crds/cr
go run _make_support/mkdir_all/do.go pkg/qliksense/crds/cr
go run _make_support/mkdir_all/do.go pkg/qliksense/crds/crd
go run _make_support/mkdir_all/do.go pkg/qliksense/crds/crd-deploy
go run _make_support/copy/do.go --src-pattern "$(QLIKSENSE_OPERATOR_DIR)/deploy/*.yaml" --dst pkg/qliksense/crds/crd-deploy
go run _make_support/copy/do.go --src-pattern "$(QLIKSENSE_OPERATOR_DIR)/deploy/crds/*_crd.yaml" --dst pkg/qliksense/crds/crd
go run _make_support/copy/do.go --src-pattern "$(QLIKSENSE_OPERATOR_DIR)/deploy/crds/*_cr.yaml" --dst pkg/qliksense/crds/cr
endif

View File

@@ -1,16 +1,12 @@
# (WIP) Qlik Sense installation and operations CLI
# (WIP) Qlik Sense on Kubernetes installation and operations CLI
## Documentation
To learn more about Qlik Sense installer go to https://qlik-oss.github.io/sense-installer/
To learn more about Qlik Sense on Kubernetes CLI go to https://qlik-oss.github.io/sense-installer/
## About
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
The QSEoK 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 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.

109
_make_support/copy/do.go Normal file
View File

@@ -0,0 +1,109 @@
package main
import (
"flag"
"fmt"
"io"
"os"
"path/filepath"
"github.com/otiai10/copy"
)
func main() {
srcPattern := flag.String("src-pattern", "", "Source file pattern")
src := flag.String("src", "", "Source file or directory")
dst := flag.String("dst", "", "Destination file or directory")
flag.Parse()
if *srcPattern != "" {
if dstInfo, err := os.Lstat(*dst); err != nil {
panic(err)
} else if !dstInfo.IsDir() {
panic(fmt.Errorf("%v must be a directory", *dst))
}
if matches, err := filepath.Glob(*srcPattern); err != nil {
panic(err)
} else {
for _, match := range matches {
srcInfo, err := os.Lstat(match)
if err != nil {
panic(err)
}
if srcInfo.IsDir() {
if err := copy.Copy(match, *dst, copy.Options{
OnSymlink: func(p string) copy.SymlinkAction {
return copy.Skip
},
}); err != nil {
panic(err)
}
} else if srcInfo.Mode().IsRegular() {
if err := fcopy(match, filepath.Join(*dst, filepath.Base(match)), srcInfo); err != nil {
panic(err)
}
}
}
}
} else if *src != "" {
srcInfo, err := os.Lstat(*src)
if err != nil {
panic(err)
}
if srcInfo.IsDir() {
if err := copy.Copy(*src, *dst, copy.Options{
OnSymlink: func(p string) copy.SymlinkAction {
return copy.Skip
},
}); err != nil {
panic(err)
}
} else if srcInfo.Mode().IsRegular() {
finalDestination := *dst
if dstInfo, err := os.Lstat(*dst); err != nil {
if !os.IsNotExist(err) {
panic(err)
}
} else if dstInfo.IsDir() {
finalDestination = filepath.Join(*dst, filepath.Base(*src))
} else if dstInfo.Mode().IsRegular() {
fmt.Println("WARNING: over-writing existing file: ", *dst)
if err := os.Remove(*dst); err != nil {
panic(err)
}
} else {
panic(fmt.Errorf("not sure how to copy to this dst: %v", *dst))
}
if err := fcopy(*src, finalDestination, srcInfo); err != nil {
panic(err)
}
}
}
}
func fcopy(src, dest string, info os.FileInfo) (err error) {
if err := os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
return err
}
f, err := os.Create(dest)
if err != nil {
return err
}
defer f.Close()
if err = os.Chmod(f.Name(), info.Mode()); err != nil {
return err
}
s, err := os.Open(src)
if err != nil {
return err
}
defer s.Close()
_, err = io.Copy(f, s)
return err
}

View File

@@ -0,0 +1,5 @@
module github.com/qlik-oss/sense-installer/_make_support/copy
go 1.13
require github.com/otiai10/copy v1.1.1

View File

@@ -0,0 +1,8 @@
github.com/otiai10/copy v1.1.1 h1:PH7IFlRQ6Fv9vYmuXbDRLdgTHoP1w483kPNUP2bskpo=
github.com/otiai10/copy v1.1.1/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.1 h1:BCmzIS3n71sGfHB5NMNDB3lHYPz8fWSkCAErHed//qc=
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=

View File

@@ -0,0 +1,14 @@
package main
import (
"fmt"
"io/ioutil"
)
func main() {
if tmpDir, err := ioutil.TempDir("", ""); err != nil {
panic(err)
} else {
fmt.Print(tmpDir)
}
}

View File

@@ -0,0 +1,3 @@
module github.com/qlik-oss/sense-installer/_make_support/get_tmp_dir
go 1.13

View File

@@ -0,0 +1,11 @@
package main
import (
"os"
)
func main() {
if err := os.MkdirAll(os.Args[1], os.ModePerm); err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,3 @@
module github.com/qlik-oss/sense-installer/_make_support/mkdir_all
go 1.13

View File

@@ -0,0 +1,11 @@
package main
import (
"os"
)
func main() {
if err := os.RemoveAll(os.Args[1]); err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,3 @@
module github.com/qlik-oss/sense-installer/_make_support/remove_all
go 1.13

View File

@@ -39,7 +39,7 @@ qliksense about --profile=test
then get version information based on the default profile in the qliksense-k8s repo master
`,
RunE: func(cmd *cobra.Command, args []string) error {
if gitRef, err := getAboutCommandGitRef(args); err != nil {
if gitRef, err := getSingleArg(args); err != nil {
return err
} else if vout, err := q.About(gitRef, opts.Profile); err != nil {
return err
@@ -56,7 +56,7 @@ qliksense about --profile=test
return c
}
func getAboutCommandGitRef(args []string) (string, error) {
func getSingleArg(args []string) (string, error) {
if len(args) > 1 {
return "", errors.New("too many arguments, only 1 expected")
} else if len(args) == 1 {

View File

@@ -1,7 +1,11 @@
package main
import (
"io"
"bytes"
"errors"
"fmt"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
@@ -10,15 +14,21 @@ import (
func applyCmd(q *qliksense.Qliksense) *cobra.Command {
opts := &qliksense.InstallCommandOptions{}
filePath := ""
keepPatchFiles := false
cleanPatchFiles, pull, push := true, false, false
c := &cobra.Command{
Use: "apply",
Short: "install qliksense based on provided cr file",
Long: `install qliksense based on provided cr file`,
Example: `qliksense apply -f file_name or cat cr_file | qliksense apply -f -`,
RunE: func(cmd *cobra.Command, args []string) error {
return runLoadOrApplyCommandE(cmd, func(reader io.Reader) error {
return q.ApplyCRFromReader(reader, opts, keepPatchFiles, true)
return runLoadOrApplyCommandE(cmd, func(crBytes []byte) error {
if cr, crBytesWithEula, err := getCrWithEulaInserted(crBytes); err != nil {
return err
} else if err := validatePullPushFlagsOnApply(cr, pull, push); err != nil {
return err
} else {
return q.ApplyCRFromReader(bytes.NewReader(crBytesWithEula), opts, cleanPatchFiles, true, pull, push)
}
})
},
}
@@ -27,11 +37,38 @@ func applyCmd(q *qliksense.Qliksense) *cobra.Command {
f.StringVarP(&filePath, "file", "f", "", "Install from a CR file")
c.MarkFlagRequired("file")
f.StringVarP(&opts.StorageClass, "storageClass", "s", "", "Storage class for qliksense")
f.StringVarP(&opts.MongoDbUri, "mongoDbUri", "m", "", "mongoDbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
f.StringVarP(&opts.MongodbUri, "mongodbUri", "m", "", "mongodbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
f.StringVarP(&opts.RotateKeys, "rotateKeys", "r", "", "Rotate JWT keys for qliksense (yes:rotate keys/ no:use exising keys from cluster/ None: use default EJSON_KEY from env")
f.BoolVar(&keepPatchFiles, keepPatchFilesFlagName, keepPatchFiles, keepPatchFilesFlagUsage)
f.BoolVar(&cleanPatchFiles, cleanPatchFilesFlagName, cleanPatchFiles, cleanPatchFilesFlagUsage)
f.BoolVarP(&pull, pullFlagName, pullFlagShorthand, pull, pullFlagUsage)
f.BoolVarP(&push, pushFlagName, pushFlagShorthand, push, pushFlagUsage)
eulaPreRunHooks.addValidator(c.Name(), loadOrApplyCommandEulaPreRunHook)
eulaPreRunHooks.addValidator(fmt.Sprintf("%v %v", rootCommandName, c.Name()), loadOrApplyCommandEulaPreRunHook)
return c
}
func validatePullPushFlagsOnApply(cr *qapi.QliksenseCR, pull, push bool) error {
if pull && !push {
fmt.Printf("WARNING: pulling images without pushing them")
}
if push {
if registry := cr.Spec.GetImageRegistry(); registry == "" {
return errors.New("no image registry set in the CR; to set it use: qliksense config set-image-registry")
}
}
return nil
}
func getCrWithEulaInserted(crBytes []byte) (*qapi.QliksenseCR, []byte, error) {
if cr, err := qapi.CreateCRObjectFromString(string(crBytes)); err != nil {
return nil, nil, err
} else {
cr.SetEULA("yes")
if crBytesWithEula, err := qapi.K8sToYaml(cr); err != nil {
return nil, nil, err
} else {
return cr, crBytesWithEula, nil
}
}
}

View File

@@ -3,6 +3,7 @@ package main
import (
"errors"
"fmt"
"os"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
@@ -68,18 +69,30 @@ func setConfigsCmd(q *qliksense.Qliksense) *cobra.Command {
var (
cmd *cobra.Command
)
base64Encoded := false
cmd = &cobra.Command{
Use: "set-configs",
Short: "set configurations into the qliksense context as key-value pairs",
Example: `
qliksense config set-configs <service_name>.<attribute>="<value>"
- The above configuration will be displayed in the CR
- The above configuration will be displayed in the CR
qliksense config set-configs <service_name>.<attribute>="<value" --base64
- if the value is base64 encoded
echo "something" | base64 | qliksense config set-configs <service_name>.<attribute> --base64
- value is coming from input pipe as base64 encoded
echo "something" | qliksense config set-configs <service_name>.<attribute>
- value is coming from input pipe
`,
RunE: func(cmd *cobra.Command, args []string) error {
return q.SetConfigs(args)
if isInputFromPipe() && len(args) == 1 {
return q.SetConfigFromReader(args[0], os.Stdin, base64Encoded)
}
return q.SetConfigs(args, base64Encoded)
},
}
f := cmd.Flags()
f.BoolVarP(&base64Encoded, "base64", "", false, "if the arguments value is base64 encoded")
return cmd
}
@@ -88,7 +101,7 @@ func setSecretsCmd(q *qliksense.Qliksense) *cobra.Command {
cmd *cobra.Command
secret bool
)
base64Encoded := false
cmd = &cobra.Command{
Use: "set-secrets",
Short: "set secrets configurations into the qliksense context as key-value pairs",
@@ -101,13 +114,24 @@ qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=true
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 `,
- The above configuration will be displayed in the CR
qliksense config set-secrets <service_name>.<attribute>="<value>" --base64
- the <value> is base64 encoded
echo "something" | base64 | qliksense config set-secrets <service_name>.<attribute> --base64
- value coming from input pipe as base64 encoded
echo "something" | qliksense config set-secrets <service_name>.<attribute>
- value coming from input pipe`,
RunE: func(cmd *cobra.Command, args []string) error {
return q.SetSecrets(args, secret)
if isInputFromPipe() && len(args) == 1 {
return q.SetSecretsFromReader(args[0], os.Stdin, secret, base64Encoded)
}
return q.SetSecrets(args, secret, base64Encoded)
},
}
f := cmd.Flags()
f.BoolVar(&secret, "secret", false, "Whether secrets should be encrypted as a Kubernetes Secret resource")
f.BoolVarP(&base64Encoded, "base64", "", false, "if the arguments value is base64 encoded")
return cmd
}
@@ -115,15 +139,18 @@ func deleteContextConfigCmd(q *qliksense.Qliksense) *cobra.Command {
var (
cmd *cobra.Command
)
skipConfirmation := false
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 q.DeleteContextConfig(args, skipConfirmation)
},
}
f := cmd.Flags()
f.BoolVar(&skipConfirmation, "yes", skipConfirmation, "skips confirmation")
return cmd
}
@@ -193,3 +220,27 @@ func cleanConfigRepoPatchesCmd(q *qliksense.Qliksense) *cobra.Command {
},
}
}
func unsetCmd(q *qliksense.Qliksense) *cobra.Command {
cmd := &cobra.Command{
Use: "unset",
Short: "remove a key from a context or a secrets or a configs from the context",
Example: `
# remove the key from CR
qliksense config unset <key>
# remove the key from service inside configs/secrets of CR
qliksense config unset <service>.<key>
# remove the service from inside configs/secrets of CR
qliksense config usnet <servcie>
all of the above supports space separated multiple arguments
`,
RunE: func(cmd *cobra.Command, args []string) error {
return q.UnsetCmd(args)
},
Args: cobra.MinimumNArgs(1),
}
return cmd
}

View File

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

View File

@@ -41,20 +41,24 @@ func (e *eulaPreRunHooksT) getPostValidationArtifact(artifactName string) interf
var eulaEnforced = os.Getenv("QLIKSENSE_EULA_ENFORCE") == "true"
var eulaText = "Please read the end user license agreement at: https://www.qlik.com/us/legal/license-terms"
var eulaPrompt = "Do you accept our EULA? (y/n): "
var eulaErrorInstruction = `You must enter "y" to continue`
var eulaErrorInstruction = `You must enter "y" to continue or execute the command with the acceptEULA flag set to "yes"`
var eulaPreRunHooks = eulaPreRunHooksT{
validators: make(map[string]func(cmd *cobra.Command, q *qliksense.Qliksense) (bool, error)),
postValidationArtifacts: make(map[string]interface{}),
}
func commandAlwaysRequiresEulaAcceptance(commandName string) bool {
return commandName == "install" || commandName == "upgrade" || commandName == "apply"
return commandName == fmt.Sprintf("%v install", rootCommandName) ||
commandName == fmt.Sprintf("%v apply", rootCommandName)
}
func globalEulaPreRun(cmd *cobra.Command, q *qliksense.Qliksense) {
if isEulaEnforced(cmd.Name()) {
if strings.TrimSpace(strings.ToLower(cmd.Flag("acceptEULA").Value.String())) != "yes" {
if eulaPreRunHook := eulaPreRunHooks.getValidator(cmd.Name()); eulaPreRunHook != nil {
if isEulaEnforced(cmd.CommandPath()) {
eulaFlagValue := strings.TrimSpace(strings.ToLower(cmd.Flag("acceptEULA").Value.String()))
if eulaFlagValue != "" && eulaFlagValue != "yes" {
doEnforceEula()
} else if eulaFlagValue == "" {
if eulaPreRunHook := eulaPreRunHooks.getValidator(cmd.CommandPath()); eulaPreRunHook != nil {
if eulaAccepted, err := eulaPreRunHook(cmd, q); err != nil {
panic(err)
} else if !eulaAccepted {
@@ -70,7 +74,7 @@ func globalEulaPreRun(cmd *cobra.Command, q *qliksense.Qliksense) {
}
func globalEulaPostRun(cmd *cobra.Command, q *qliksense.Qliksense) {
if isEulaEnforced(cmd.Name()) {
if isEulaEnforced(cmd.CommandPath()) {
if err := q.SetEulaAccepted(); err != nil {
panic(err)
}

View File

@@ -1,26 +1,30 @@
package main
import (
"errors"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
func fetchCmd(q *qliksense.Qliksense) *cobra.Command {
opts := &qliksense.FetchCommandOptions{}
c := &cobra.Command{
Use: "fetch",
Short: "fetch a release from qliksense-k8s repo",
Long: `fetch a release from qliksense-k8s repo`,
Example: `qliksense fetch <version>`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("requires a version (i.e. v1.0.0)")
}
return nil
},
Short: "fetch a release from qliksense-k8s repo, if version not supplied, will use from context",
Long: `fetch a release from qliksense-k8s repo, if version not supplied, will use from context`,
Example: `qliksense fetch [version]`,
RunE: func(cmd *cobra.Command, args []string) error {
return q.FetchQK8s(args[0])
if len(args) == 1 {
opts.Version = args[0]
}
return q.FetchK8sWithOpts(opts)
},
}
f := c.Flags()
f.StringVarP(&opts.GitUrl, "url", "", "", "git url from where configuration will be pulled")
f.StringVarP(&opts.AccessToken, "accessToken", "", "", "access token for git url")
f.StringVarP(&opts.SecretName, "secretName", "", "", "kubernetes secret name where a key name accessToken exist")
f.BoolVarP(&opts.Overwrite, "overwrite", "", false, "Ovewrite previously fetched veersion as well as local chagnes")
return c
}

View File

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

View File

@@ -2,7 +2,7 @@ package main
import (
"bytes"
"io"
"fmt"
"io/ioutil"
"os"
@@ -20,8 +20,8 @@ func loadCrFile(q *qliksense.Qliksense) *cobra.Command {
Long: `load a CR a file and create necessary structure for future use`,
Example: `qliksense load -f file_name or cat cr_file | qliksense load -f -`,
RunE: func(cmd *cobra.Command, args []string) error {
return runLoadOrApplyCommandE(cmd, func(reader io.Reader) error {
return q.LoadCr(reader, overwriteExistingContext)
return runLoadOrApplyCommandE(cmd, func(buffer []byte) error {
return q.LoadCr(bytes.NewReader(buffer), overwriteExistingContext)
})
},
}
@@ -30,7 +30,7 @@ func loadCrFile(q *qliksense.Qliksense) *cobra.Command {
c.MarkFlagRequired("file")
f.BoolVarP(&overwriteExistingContext, "overwrite", "o", overwriteExistingContext, "Overwrite any existing contexts with the same name")
eulaPreRunHooks.addValidator(c.Name(), loadOrApplyCommandEulaPreRunHook)
eulaPreRunHooks.addValidator(fmt.Sprintf("%v %v", rootCommandName, c.Name()), loadOrApplyCommandEulaPreRunHook)
return c
}
@@ -70,15 +70,19 @@ func loadOrApplyCommandEulaPreRunHook(cmd *cobra.Command, q *qliksense.Qliksense
}
}
func runLoadOrApplyCommandE(cmd *cobra.Command, callBack func(io.Reader) error) error {
func runLoadOrApplyCommandE(cmd *cobra.Command, callBack func(buffer []byte) error) error {
if crBytes := eulaPreRunHooks.getPostValidationArtifact("CR"); crBytes != nil {
return callBack(bytes.NewBuffer(crBytes.([]byte)))
return callBack(crBytes.([]byte))
} else {
file, err := getCrFileFromFlag(cmd, "file")
if err != nil {
return err
}
defer file.Close()
return callBack(file)
if crBytes, err := ioutil.ReadAll(file); err != nil {
return err
} else {
return callBack(crBytes)
}
}
}

View File

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

View File

@@ -2,8 +2,10 @@ package main
import (
"fmt"
"log"
. "github.com/logrusorgru/aurora"
ansi "github.com/mattn/go-colorable"
"github.com/qlik-oss/sense-installer/pkg/api"
"github.com/qlik-oss/sense-installer/pkg/preflight"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
@@ -11,340 +13,462 @@ import (
)
func preflightCmd(q *qliksense.Qliksense) *cobra.Command {
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var preflightCmd = &cobra.Command{
Use: "preflight",
Short: "perform preflight checks on the cluster",
Long: `perform preflight checks on the cluster`,
Example: `qliksense preflight <preflight_check_to_run>`,
}
f := preflightCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return preflightCmd
}
func pfDnsCheckCmd(q *qliksense.Qliksense) *cobra.Command {
out := ansi.NewColorableStdout()
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var preflightDnsCmd = &cobra.Command{
Use: "dns",
Short: "perform preflight dns check",
Long: `perform preflight dns check to check DNS connectivity status in the cluster`,
Example: `qliksense preflight dns`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
// Preflight DNS check
fmt.Printf("Preflight DNS check\n")
fmt.Println("---------------------")
namespace, kubeConfigContents, err := preflight.InitPreflight()
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Printf("Preflight DNS check FAILED\n")
log.Fatal(err)
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if namespace == "" {
namespace = "default"
}
if err = qp.CheckDns(namespace, kubeConfigContents); err != nil {
fmt.Println(err)
fmt.Print("Preflight DNS check FAILED\n")
log.Fatal()
if err = qp.CheckDns(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
return nil
},
}
f := preflightDnsCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return preflightDnsCmd
}
func pfK8sVersionCheckCmd(q *qliksense.Qliksense) *cobra.Command {
out := ansi.NewColorableStdout()
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var preflightCheckK8sVersionCmd = &cobra.Command{
Use: "kube-version",
Use: "k8s-version",
Short: "check kubernetes version",
Long: `check minimum valid kubernetes version on the cluster`,
Example: `qliksense preflight kube-version`,
Example: `qliksense preflight k8s-version`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
// Preflight Kubernetes minimum version check
fmt.Printf("Preflight kubernetes minimum version check\n")
fmt.Println("------------------------------------------")
namespace, kubeConfigContents, err := preflight.InitPreflight()
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Printf("Preflight kubernetes minimum version check FAILED\n")
log.Fatal(err)
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if err = qp.CheckK8sVersion(namespace, kubeConfigContents); err != nil {
fmt.Println(err)
fmt.Printf("Preflight kubernetes minimum version check FAILED\n")
log.Fatal()
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
return nil
},
}
f := preflightCheckK8sVersionCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return preflightCheckK8sVersionCmd
}
func pfAllChecksCmd(q *qliksense.Qliksense) *cobra.Command {
var mongodbUrl string
out := ansi.NewColorableStdout()
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var preflightAllChecksCmd = &cobra.Command{
Use: "all",
Short: "perform all checks",
Long: `perform all preflight checks on the target cluster`,
Example: `qliksense preflight all`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
// Preflight run all checks
fmt.Printf("Running all preflight checks\n")
namespace, kubeConfigContents, err := preflight.InitPreflight()
fmt.Printf("Running all preflight checks...\n\n")
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Println(err)
fmt.Printf("Running preflight check suite has FAILED...\n")
log.Fatal()
fmt.Fprintf(out, "%s\n", Red("Unable to run the preflight checks suite"))
fmt.Printf("Error: %v\n", err)
return nil
}
if namespace == "" {
namespace = "default"
}
qp.RunAllPreflightChecks(namespace, kubeConfigContents, mongodbUrl)
if err = qp.RunAllPreflightChecks(kubeConfigContents, namespace, preflightOpts); err != nil {
fmt.Fprintf(out, "%s\n", Red("1 or more preflight checks have FAILED"))
fmt.Println("Completed running all preflight checks")
return nil
}
fmt.Fprintf(out, "%s\n\n", Green("All preflight checks have PASSED"))
return nil
},
}
f := preflightAllChecksCmd.Flags()
f.StringVarP(&mongodbUrl, "mongodb-url", "", "", "mongodbUrl to try connecting to")
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
f.StringVarP(&preflightOpts.MongoOptions.MongodbUrl, "mongodb-url", "", "", "mongodbUrl to try connecting to")
f.StringVarP(&preflightOpts.MongoOptions.CaCertFile, "mongodb-ca-cert", "", "", "certificate to use for mongodb check")
return preflightAllChecksCmd
}
func pfDeploymentCheckCmd(q *qliksense.Qliksense) *cobra.Command {
out := ansi.NewColorableStdout()
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var pfDeploymentCheckCmd = &cobra.Command{
Use: "deployment",
Short: "perform preflight deploymwnt check",
Short: "perform preflight deployment check",
Long: `perform preflight deployment check to ensure that we can create deployments in the cluster`,
Example: `qliksense preflight deployment`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
// Preflight deployments check
fmt.Printf("Preflight deployment check\n")
fmt.Println("--------------------------")
namespace, kubeConfigContents, err := preflight.InitPreflight()
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Printf("Preflight deployment check FAILED\n")
log.Fatal(err)
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if namespace == "" {
namespace = "default"
}
if err = qp.CheckDeployment(namespace, kubeConfigContents); err != nil {
fmt.Println(err)
fmt.Print("Preflight deploy check FAILED\n")
log.Fatal()
if err = qp.CheckDeployment(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
return nil
},
}
f := pfDeploymentCheckCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return pfDeploymentCheckCmd
}
func pfServiceCheckCmd(q *qliksense.Qliksense) *cobra.Command {
out := ansi.NewColorableStdout()
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var pfServiceCheckCmd = &cobra.Command{
Use: "service",
Short: "perform preflight service check",
Long: `perform preflight service check to ensure that we are able to create services in the cluster`,
Example: `qliksense preflight service`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
// Preflight service check
fmt.Printf("Preflight service check\n")
fmt.Println("-----------------------")
namespace, kubeConfigContents, err := preflight.InitPreflight()
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Printf("Preflight service check FAILED\n")
log.Fatal(err)
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if namespace == "" {
namespace = "default"
}
if err = qp.CheckService(namespace, kubeConfigContents); err != nil {
fmt.Println(err)
fmt.Print("Preflight service check FAILED\n")
log.Fatal()
if err = qp.CheckService(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
return nil
},
}
f := pfServiceCheckCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return pfServiceCheckCmd
}
func pfPodCheckCmd(q *qliksense.Qliksense) *cobra.Command {
out := ansi.NewColorableStdout()
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var pfPodCheckCmd = &cobra.Command{
Use: "pod",
Short: "perform preflight pod check",
Long: `perform preflight pod check to ensure we can create pods in the cluster`,
Example: `qliksense preflight pod`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
// Preflight pod check
fmt.Printf("Preflight pod check\n")
fmt.Println("--------------------")
namespace, kubeConfigContents, err := preflight.InitPreflight()
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Printf("Preflight pod check FAILED\n")
log.Fatal(err)
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if namespace == "" {
namespace = "default"
}
if err = qp.CheckPod(namespace, kubeConfigContents); err != nil {
fmt.Println(err)
fmt.Print("Preflight pod check FAILED\n")
log.Fatal()
if err = qp.CheckPod(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
return nil
},
}
f := pfPodCheckCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return pfPodCheckCmd
}
func pfCreateRoleCheckCmd(q *qliksense.Qliksense) *cobra.Command {
out := ansi.NewColorableStdout()
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var preflightRoleCmd = &cobra.Command{
Use: "role",
Short: "preflight create role check",
Long: `perform preflight role check to ensure we are able to create a role in the cluster`,
Example: `qliksense preflight createRole`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
// Preflight role check
fmt.Printf("Preflight role check\n")
fmt.Println("---------------------------")
namespace, _, err := preflight.InitPreflight()
namespace, _, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Printf("Preflight role check FAILED\n")
log.Fatal(err)
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if err = qp.CheckCreateRole(namespace); err != nil {
fmt.Println(err)
fmt.Print("Preflight role FAILED\n")
log.Fatal()
if err = qp.CheckCreateRole(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
return nil
},
}
f := preflightRoleCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return preflightRoleCmd
}
func pfCreateRoleBindingCheckCmd(q *qliksense.Qliksense) *cobra.Command {
out := ansi.NewColorableStdout()
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var preflightRoleBindingCmd = &cobra.Command{
Use: "rolebinding",
Short: "preflight create rolebinding check",
Long: `perform preflight rolebinding check to ensure we are able to create a rolebinding in the cluster`,
Example: `qliksense preflight rolebinding`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
// Preflight createRoleBinding check
fmt.Printf("Preflight rolebinding check\n")
fmt.Println("---------------------------")
namespace, _, err := preflight.InitPreflight()
namespace, _, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Printf("Preflight rolebinding check FAILED\n")
log.Fatal(err)
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if err = qp.CheckCreateRoleBinding(namespace); err != nil {
fmt.Println(err)
fmt.Print("Preflight rolebinding check FAILED\n")
log.Fatal()
if err = qp.CheckCreateRoleBinding(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
return nil
},
}
f := preflightRoleBindingCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return preflightRoleBindingCmd
}
func pfCreateServiceAccountCheckCmd(q *qliksense.Qliksense) *cobra.Command {
out := ansi.NewColorableStdout()
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var preflightServiceAccountCmd = &cobra.Command{
Use: "serviceaccount",
Short: "preflight create ServiceAccount check",
Short: "preflight create serviceaccount check",
Long: `perform preflight serviceaccount check to ensure we are able to create a service account in the cluster`,
Example: `qliksense preflight serviceaccount`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
// Preflight createServiceAccount check
fmt.Printf("Preflight ServiceAccount check\n")
fmt.Println("-------------------------------------")
namespace, _, err := preflight.InitPreflight()
namespace, _, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Printf("Preflight serviceaccount check FAILED\n")
log.Fatal(err)
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if err = qp.CheckCreateServiceAccount(namespace); err != nil {
fmt.Println(err)
fmt.Print("Preflight serviceaccount check FAILED\n")
log.Fatal()
if err = qp.CheckCreateServiceAccount(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
return nil
},
}
f := preflightServiceAccountCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return preflightServiceAccountCmd
}
func pfCreateAuthCheckCmd(q *qliksense.Qliksense) *cobra.Command {
out := ansi.NewColorableStdout()
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var preflightCreateAuthCmd = &cobra.Command{
Use: "authcheck",
Short: "preflight authcheck",
Long: `perform preflight authcheck that combines the role, rolebinding and serviceaccount checks`,
Example: `qliksense preflight authcheck`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
// Preflight authcheck
fmt.Printf("Preflight authcheck\n")
fmt.Println("------------------------")
namespace, kubeConfigContents, err := preflight.InitPreflight()
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Printf("Preflight authcheck FAILED\n")
log.Fatal(err)
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if err = qp.CheckCreateRB(namespace, kubeConfigContents); err != nil {
fmt.Println(err)
fmt.Print("Preflight authcheck FAILED\n")
log.Fatal()
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
return nil
},
}
f := preflightCreateAuthCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return preflightCreateAuthCmd
}
func pfMongoCheckCmd(q *qliksense.Qliksense) *cobra.Command {
var mongodbUrl string
out := ansi.NewColorableStdout()
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var preflightMongoCmd = &cobra.Command{
Use: "mongo",
Short: "preflight mongo OR preflight mongo --url=<url>",
Long: `perform preflight mongo check to ensure we are able to connect to a mongodb instance in the cluster`,
Example: `qliksense preflight mongo OR preflight mongo --url=<url>`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q}
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
// Preflight mongo check
fmt.Printf("Preflight mongo check\n")
fmt.Println("-------------------------------------")
namespace, kubeConfigContents, err := preflight.InitPreflight()
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Printf("Preflight mongo check FAILED\n")
log.Fatal(err)
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if namespace == "" {
namespace = "default"
}
if err = qp.CheckMongo(kubeConfigContents, namespace, mongodbUrl); err != nil {
fmt.Println(err)
fmt.Print("Preflight mongo check FAILED\n")
log.Fatal()
if err = qp.CheckMongo(kubeConfigContents, namespace, preflightOpts, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
return nil
},
}
f := preflightMongoCmd.Flags()
f.StringVarP(&mongodbUrl, "url", "", "", "mongodbUrl to try connecting to")
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
f.StringVarP(&preflightOpts.MongoOptions.MongodbUrl, "url", "", "", "mongodbUrl to try connecting to")
f.StringVarP(&preflightOpts.MongoOptions.CaCertFile, "ca-cert", "", "", "ca certificate to use for mongodb check")
return preflightMongoCmd
}
func pfCleanupCmd(q *qliksense.Qliksense) *cobra.Command {
out := ansi.NewColorableStdout()
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var pfCleanCmd = &cobra.Command{
Use: "clean",
Short: "perform preflight clean",
Long: `perform preflight clean to ensure that all resources are cleared up in the cluster`,
Example: `qliksense preflight clean`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
// Preflight clean
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight cleanup FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if namespace == "" {
namespace = "default"
}
if err = qp.Cleanup(namespace, kubeConfigContents); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight cleanup FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("Preflight cleanup complete"))
return nil
},
}
f := pfCleanCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return pfCleanCmd
}

View File

@@ -16,7 +16,7 @@ func pullQliksenseImages(q *qliksense.Qliksense) *cobra.Command {
Short: "Pull docker images for offline install",
Example: `qliksense pull`,
RunE: func(cmd *cobra.Command, args []string) error {
version, err := getAboutCommandGitRef(args)
version, err := getSingleArg(args)
if err != nil {
return err
}
@@ -34,11 +34,8 @@ func pushQliksenseImages(q *qliksense.Qliksense) *cobra.Command {
Short: "Push docker images for offline install",
Example: `qliksense push`,
RunE: func(cmd *cobra.Command, args []string) error {
qConfig := qapi.NewQConfig(q.QliksenseHome)
if qcr, err := qConfig.GetCurrentCR(); err != nil {
if err := ensureImageRegistrySetInCR(q); err != nil {
return err
} else if registry := qcr.GetImageRegistry(); registry == "" {
return errors.New("no image registry in config")
} else {
return q.PushImagesForCurrentCR()
}
@@ -46,3 +43,13 @@ func pushQliksenseImages(q *qliksense.Qliksense) *cobra.Command {
}
return cmd
}
func ensureImageRegistrySetInCR(q *qliksense.Qliksense) error {
qConfig := qapi.NewQConfig(q.QliksenseHome)
if qcr, err := qConfig.GetCurrentCR(); err != nil {
return err
} else if registry := qcr.Spec.GetImageRegistry(); registry == "" {
return errors.New("no image registry set in the CR; to set it use: qliksense config set-image-registry")
}
return nil
}

View File

@@ -8,15 +8,14 @@ import (
"path/filepath"
"strings"
. "github.com/logrusorgru/aurora"
ansi "github.com/mattn/go-colorable"
"github.com/mitchellh/go-homedir"
"github.com/qlik-oss/sense-installer/pkg"
"github.com/qlik-oss/sense-installer/pkg/api"
"github.com/qlik-oss/sense-installer/pkg/preflight"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/ttacon/chalk"
)
// To run this project in debug mode, run:
@@ -24,10 +23,17 @@ import (
// qliksense <command>
const (
qlikSenseHomeVar = "QLIKSENSE_HOME"
qlikSenseDirVar = ".qliksense"
keepPatchFilesFlagName = "keep-config-repo-patches"
keepPatchFilesFlagUsage = "Keep config repo patch files (for debugging)"
qlikSenseHomeVar = "QLIKSENSE_HOME"
qlikSenseDirVar = ".qliksense"
cleanPatchFilesFlagName = "clean"
cleanPatchFilesFlagUsage = "Set --clean=false to keep any prior config repo file changes on install (for debugging)"
pullFlagName = "pull"
pullFlagShorthand = "d"
pullFlagUsage = "If using private docker registry, pull (download) all required Qliksense images before install"
pushFlagName = "push"
pushFlagShorthand = "u"
pushFlagUsage = "If using private docker registry, push (upload) all downloaded Qliksense images to that registry before install"
rootCommandName = "qliksense"
)
func initAndExecute() error {
@@ -40,7 +46,7 @@ func initAndExecute() error {
log.Fatal(err)
}
// create dirs and appropriate files for setting up contexts
api.LogDebugMessage("QliksenseHomeDir: %s", qlikSenseHome)
api.LogDebugMessage("QliksenseHomeDir: %s\n", qlikSenseHome)
qliksenseClient := qliksense.New(qlikSenseHome)
cmd := rootCmd(qliksenseClient)
@@ -86,22 +92,25 @@ var versionCmd = &cobra.Command{
}
func commandUsesContext(commandName string) bool {
return commandName != "" && commandName != "qliksense" && commandName != "help" && commandName != "version"
return commandName != "" &&
commandName != rootCommandName &&
commandName != fmt.Sprintf("%v help", rootCommandName) &&
commandName != fmt.Sprintf("%v version", rootCommandName)
}
func getRootCmd(p *qliksense.Qliksense) *cobra.Command {
cmd := &cobra.Command{
Use: "qliksense",
Use: rootCommandName,
Short: "Qliksense cli tool",
Long: `qliksense cli tool provides functionality to perform operations on qliksense-k8s, qliksense operator, and kubernetes cluster`,
Args: cobra.ArbitraryArgs,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if commandUsesContext(cmd.Name()) {
if commandUsesContext(cmd.CommandPath()) {
globalEulaPreRun(cmd, p)
if err := p.SetUpQliksenseDefaultContext(); err != nil {
panic(err)
}
pf := preflight.NewPreflightConfig(p.QliksenseHome)
pf := api.NewPreflightConfig(p.QliksenseHome)
if err := pf.Initialize(); err != nil {
panic(err)
}
@@ -109,14 +118,15 @@ func getRootCmd(p *qliksense.Qliksense) *cobra.Command {
}
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
if commandUsesContext(cmd.Name()) {
if commandUsesContext(cmd.CommandPath()) {
globalEulaPostRun(cmd, p)
}
},
SilenceUsage: true,
}
origHelpFunc := cmd.HelpFunc()
cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
if !commandUsesContext(cmd.Name()) {
if !commandUsesContext(cmd.CommandPath()) {
cmd.Flags().MarkHidden("acceptEULA")
}
origHelpFunc(cmd, args)
@@ -158,14 +168,13 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
// add config command
configCmd := configCmd(p)
cmd.AddCommand(configCmd)
/** disabling for now
configCmd.AddCommand(configApplyCmd(p))
**/
configCmd.AddCommand(configViewCmd(p))
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
//add upgrade command
cmd.AddCommand(upgradeCmd(p))
// add the set-context config command as a sub-command to the app config command
configCmd.AddCommand(setContextConfigCmd(p))
@@ -192,6 +201,10 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
// open editor for config
configCmd.AddCommand(configEditCmd(p))
// add unset for config
configCmd.AddCommand((unsetCmd(p)))
// add uninstall command
cmd.AddCommand(uninstallCmd(p))
@@ -200,7 +213,7 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
crdsCmd.AddCommand(crdsViewCmd(p))
crdsCmd.AddCommand(crdsInstallCmd(p))
// add preflight command
// add preflight commands
preflightCmd := preflightCmd(p)
preflightCmd.AddCommand(pfDnsCheckCmd(p))
preflightCmd.AddCommand(pfK8sVersionCheckCmd(p))
@@ -213,10 +226,17 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
preflightCmd.AddCommand(pfCreateRoleBindingCheckCmd(p))
preflightCmd.AddCommand(pfCreateServiceAccountCheckCmd(p))
preflightCmd.AddCommand(pfCreateAuthCheckCmd(p))
preflightCmd.AddCommand(pfCleanupCmd(p))
cmd.AddCommand(preflightCmd)
cmd.AddCommand(loadCrFile(p))
cmd.AddCommand((applyCmd(p)))
// add postflight command
postflightCmd := postflightCmd(p)
postflightCmd.AddCommand(pfMigrationCheck(p))
cmd.AddCommand(postflightCmd)
return cmd
}
@@ -261,7 +281,7 @@ func levenstein(cmd *cobra.Command) {
if !strings.EqualFold(arg[1], suggest[0]) {
arg[1] = suggest[0]
out := ansi.NewColorableStdout()
fmt.Fprintln(out, chalk.Green.Color("Did you mean: "), chalk.Bold.TextStyle(strings.Join(arg, " ")), "?")
fmt.Fprintln(out, Green("Did you mean: "), Bold(strings.Join(arg, " ")), "?")
}
}
}

View File

@@ -6,6 +6,7 @@ import (
)
func uninstallCmd(q *qliksense.Qliksense) *cobra.Command {
skipConfirmation := false
c := &cobra.Command{
Use: "uninstall",
Short: "Uninstall the deployed qliksense.",
@@ -13,10 +14,15 @@ func uninstallCmd(q *qliksense.Qliksense) *cobra.Command {
Example: `qliksense uninstall <context-name>`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
return q.UninstallQK8s(args[0])
return q.UninstallQK8s(args[0], skipConfirmation)
}
return q.UninstallQK8s("")
return q.UninstallQK8s("", skipConfirmation)
},
}
f := c.Flags()
f.BoolVar(&skipConfirmation, "yes", skipConfirmation, "skips confirmation")
return c
}

View File

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

View File

@@ -1,13 +1,61 @@
# qliksense command reference
# CLI reference
## qliksense apply
### qliksense preflight
`qliksense apply` command takes input a cr file or input from pipe
Preflight checks provide pre-installation cluster conformance testing and validation before we install qliksense on the cluster. We gather a suite of conformance tests that can be easily written and run on the target cluster to verify that cluster-specific requirements are met.
We support a couple of tests at the moment as part of preflight checks, and the range of the suite will be expanded in future.
Run the following command to view help about the commands supported by preflight at any moment:
```
qliksense preflight
```
#### Running all checks
Run the following command to execute all preflight checks
```
qliksense preflight all --mongodb-url=<mongo-server url> --mongodb-ca-cert=<path to ca-cert file>
```
#### Running specific check
Run the following command to execute a specific check
```
qliksense preflight dns
```
#### Running cleanup
Run the following command to cleanup entities created for preflight checks that were left behind on the cluster.
```
qliksense preflight clean
```
### qliksense postflight
Postflight checks are performed after qliksense is installed on the cluster and during normal operating mode of the product. Such checks can range from validating certain conditions to checking the status of certain operations or entities.
Run the following command to view help about the commands supported by postflight at any moment:
```
qliksense postflight
```
### qliksense load
`qliksense load` command takes input from a file or from pipe
- `qliksense load -f cr-file.yaml`
- `cat cr-file.yaml | qliksense load -f -`
This will load the Custom Resource (CR) into `${QLIKSENSE_HOME}` folder, create context structure and set the current context to that CR.
This will also encrypt the secrets from CR while writing the CR into the disk.
### qliksense apply
`qliksense apply` command takes input from a file or from pipe
- `qliksense apply -f cr-file.yaml`
- `cat cr-file.yaml | qliksense apply -f -`
the content of `cr-file.yaml` should be something similar
The content of `cr-file.yaml` should be something like the following:
```yaml
apiVersion: qlik.com/v1
@@ -23,37 +71,30 @@ spec:
value: "yes"
secrets:
qliksense:
- name: mongoDbUri
- name: mongodbUri
value: mongodb://qlik-test-mongodb:27017/qliksense?ssl=false
profile: docker-desktop
rotateKeys: "yes"
```
```
This will do everything `qliksense load` does and install the qliksense into the cluster.
`qliksense apply` does everything `qliksense load` does but will install Qlik Sense into the cluster as well
## qliksense load
### qliksense about
`qliksense load` command takes input a cr file or input from pipe.
`qliksense about` command will display information about [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) release.
- `qliksense load -f cr-file.yaml`
- `cat cr-file.yaml | qliksense load -f -`
For example, running the following command will show information about default profile for `1.0.0` tag
This will load the CR into `${QLIKSENSE_HOME}` folder, create context structure and set the current context to that CR.
This will also encrypt the secrets from CR while writing the CR into the disk.
```
qliksense about 1.0.0
```
## qliksense about
Run the following command to view options for `about` command:
```
qliksense about --help
```
About action will display inside information regarding [qliksense-k8](https://github.com/qlik-oss/qliksense-k8s) release.
it will support following flags
- `qliksense about 1.0.0` display default profile for tag `1.0.0`.
- `qliksense about 1.0.0 --profile=docker-desktop`
- `qliksense about`
- assuming current directory has `manifests/docker-desktop`
- or get version information from pull of `qliksense-k8s` `master`
using other supported commands user might have built the CR into the location `~/.qliksense/myqliksense.yaml`
Using other supported commands user might have built the CR into the location `~/.qliksense/myqliksense.yaml`
```yaml
apiVersion: qlik.com/v1
@@ -62,7 +103,7 @@ metadata:
name: myqliksense
spec:
profile: docker-desktop
manifestsRoot: /Usr/ddd/my-k8-repo/manifests
manifestsRoot: /Usr/xyz/my-k8-repo/manifests
namespace: myqliksense
storageClassName: efs
configs:
@@ -71,37 +112,35 @@ spec:
value: "yes"
secrets:
qliksense:
- name: mongoDbUri
- name: mongodbUri
value: "mongo://mongo:3307"
- name: messagingPassword
valueFromKey: messagingPassword
```
In that case the command would be
In this case, the result of `qliksense about` command would display information from:
- `qliksense about`
- display from `/Usr/ddd/my-k8-repo/manifests/docker-desktop` location
- pull from `master` if directory invalid/empty
- `/Usr/xyz/my-k8-repo/manifests/docker-desktop` location, or
- Pull and show information from `master` branch if the directory is invalid or empty
## qliksense config
### qliksense config
Config action will perform operations on configurations and contexts regarding the [qliksense-k8](https://github.com/qlik-oss/qliksense-k8s) release.
`qliksense config` will perform operations on configurations and contexts regarding the [qliksense-k8](https://github.com/qlik-oss/qliksense-k8s) release.
it will support following commands:
It supports the following flags:
- `qliksense config apply` - generate the patchs and apply manifests to k8s
- `qliksense config list-contexts` - retrieves the contexts and lists them
- `qliksense config set` - configure a key value pair into the current context
- `qliksense config set-configs` - set configurations into the qliksense context as key-value pairs
- `qliksense config set-context` - sets the context in which the Kubernetes cluster and resources live in
- `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 list-contexts` - get and list contexts
- `qliksense config set` - configure a key-value pair into the current context
- `qliksense config set-configs` - set configurations into qliksense context as key-value pairs
- `qliksense config set-context` - sets the Kubernetes context where resources are located
- `qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=false` - set secrets configurations into 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 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 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)
- `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 secrets from cluster)
the global file that abstracts all the contexts is `config.yaml`, located at: `~/.qliksense/config.yaml`:
The global file which abstracts all contexts is `~/.qliksense/config.yaml`
```yaml
apiVersion: config.qlik.com/v1
kind: QliksenseConfig
@@ -110,10 +149,10 @@ metadata:
spec:
contexts:
- name: qlik-default
crFile: /Users/fff/.qliksense/contexts/qlik-default/qlik-default.yaml
crFile: /Users/xyz/.qliksense/contexts/qlik-default/qlik-default.yaml
- name: myqliksense
crFile: /Users/fff/.qliksense/contexts/myqliksense/myqliksense.yaml
crFile: /Users/xyz/.qliksense/contexts/myqliksense/myqliksense.yaml
- name: hello
crFile: /Users/fff/.qliksense/contexts/hello/hello.yaml
crFile: /Users/xyz/.qliksense/contexts/hello/hello.yaml
currentContext: hello
```
```

View File

@@ -1,4 +1,4 @@
# How qliksense cli works
# How CLI works
At the initialization, `qliksense` cli creates few files in the director `~/.qliksene` and it contains following files:
@@ -23,7 +23,7 @@ spec:
profile: docker-desktop
secrets:
qliksense:
- name: mongoDbUri
- name: mongodbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
rotateKeys: "yes"
releaseName: qlik-default
@@ -48,14 +48,14 @@ qliksense config -h
In this mode `qliksense` CLI downloads the specified version from [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and places it in `~/.qliksense/contexts/<context-name>/qlik-k8s` folder.
The qliksense cli creates a CR for the QlikSense operator and all config operations are peformed to edit the CR.
The qliksense cli creates a CR for the QlikSense operator and all config operations are performed to edit the CR.
`qliksense install` or `qliksense config apply` will generate patches in local file system (i.e `~/.qliksense/contexts/<context-name>/qlik-k8s`) and
`qliksense install` will generate patches in local file system (i.e `~/.qliksense/contexts/<context-name>/qlik-k8s`) and
- Install those manifests into the cluster
- Create a custom resoruce (CR) for the `qliksene operator`.
- Create a custom resource (CR) for the `qliksene operator`.
The operator makes the association to the installed resoruces so that when `qliksense uninstall` is performed the operator can delete all kubernetes resources related to QSEoK for the current context.
The operator makes the association to the installed resources so that when `qliksense uninstall` is performed the operator can delete all kubernetes resources related to QSEoK for the current context.
## With a git repo
@@ -68,7 +68,7 @@ qliksense config set git.repository="https://github.com/my-org/qliksense-k8s"
qliksense config set git.accessToken="<mySecretToken>"
```
When you perform `qliksense install` or `qliksene config apply`, qliksense operator performs these tasks:
When you perform `qliksense install`, qliksense operator performs these tasks:
- Download corresponding version of manifests from the your git repo
- Generate kustomize patches
@@ -94,4 +94,3 @@ spec:
image: qlik-docker-oss.bintray.io/qliksense-repo-watcher
....
```

View File

@@ -5,7 +5,7 @@
- Kubernetes cluster (Docker Desktop with enabled Kubernetes)
- `kubectl` installed, configured and able to communicate with kubernetes cluster. _`qliksense` CLI uses `kubectl` under the hood to perform operations on cluster_
## Installing Sense installer
## Installing `qliksense` CLI
Download the executable for your platform from [releases page](https://github.com/qlik-oss/sense-installer/releases) and rename it to `qliksense`

View File

@@ -1,22 +1,15 @@
# 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:
The Qlik Sense on Kubernetes CLI (`qliksense`) provides an imperative interface to many of the configurations that need to be applied against the declarative structure described in [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s).
The CLI facilitates:
- Installation of QSEoK
- Installation of qliksense operator to manage QSEoK
- Air gapped installation of QSEoK
!!! info ""
This is a technology preview that uses Qlik modified [kustomize](https://github.com/qlik-oss/kustomize) for kubernetes manifests on [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository
This is a technology preview that uses Qlik modified [kustomize](https://github.com/qlik-oss/kustomize) for Kubernetes manifests on [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository
!!! info ""
See QlikSense [edge releases on qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s/releases) repository
## Future Direction
Operations:
- Expand preflight checks
- Backup/restore operations
- Fully support airgap installation of QSEoK
- Restore unwanted deletion of kubernetes resources

33
docs/postflight_checks.md Normal file
View File

@@ -0,0 +1,33 @@
# Postflight checks
Postflight checks are performed after qliksense is installed on the cluster and during normal operating mode of the product. Such checks can range from validating certain conditions to checking the status of certain operations or entities on the kubernetes cluster.
Run the following command to view help about the commands supported by postflight at any moment:
```
$ qliksense postflight
perform postflight checks on the cluster
Usage:
qliksense postflight [command]
Examples:
qliksense postflight <postflight_check_to_run>
Available Commands:
db-migration-check check mongodb migration status on the cluster
Flags:
-h, --help help for postflight
-v, --verbose verbose mode
```
### DB migration check
This command checks init containers for successful database migrarion completions, and reports failure, if any to the user.
An example run of this check produces an output as shown below:
```shell
$ qliksense postflight db-migration-check
Logs from pod: qliksense-users-6977cb7788-cxxwh
{"caller":"main.go:39","environment":"qseok","error":"error parsing uri: scheme must be \"mongodb\" or \"mongodb+srv\"","level":"error","message":"failed to connect to ","timestamp":"2020-06-01T01:07:18.4170507Z","version":""}
Postflight db_migration_check completed
```

View File

@@ -1,10 +1,11 @@
##Preflight checks
# Preflight checks
Preflight checks provide pre-installation cluster conformance testing and validation before we install qliksense on the cluster. We gather a suite of conformance tests that can be easily written and run on the target cluster to verify that cluster-specific requirements are met.
The suite consists of a set of `collectors` which run the specifications of every test and `analyzers` which analyze the results of every test run by the collector.
We support the following tests at the moment as part of preflight checks, and the range of the suite will be expanded in future.
We support a couple of tests at the moment as part of preflight checks, and the range of the suite will be expanded in future.
Run the following command to view help about the commands supported by preflight at any moment:
```console
```shell
$ qliksense preflight
perform preflight checks on the cluster
@@ -15,19 +16,29 @@ Examples:
qliksense preflight <preflight_check_to_run>
Available Commands:
all perform all checks
dns perform preflight dns check
k8s-version check k8s version
all perform all checks
authcheck preflight authcheck
clean perform preflight clean
deployment perform preflight deployment check
dns perform preflight dns check
k8s-version check kubernetes version
mongo preflight mongo OR preflight mongo --url=<url>
pod perform preflight pod check
role preflight create role check
rolebinding preflight create rolebinding check
service perform preflight service check
serviceaccount preflight create ServiceAccount check
Flags:
-h, --help help for preflight
-v, --verbose verbose mode
```
### DNS check
Run the following command to perform preflight DNS check. We setup a kubernetes deployment and try to reach it as part of establishing DNS connectivity in this check.
The expected output should be similar to the one shown below.
```console
$ qliksense preflight dns
```shell
$ qliksense preflight dns -v
Preflight DNS check
---------------------
@@ -49,8 +60,8 @@ Deleted deployment: dep-dns-preflight-check
### Kubernetes version check
We check the version of the target kubernetes cluster and ensure that it falls in the valid range of kubernetes versions that are supported by qliksense.
The command to run this check and the expected similar output are as shown below:
```console
$ qliksense preflight k8s-version
```shell
$ qliksense preflight k8s-version -v
Preflight kubernetes minimum version check
------------------------------------------
@@ -64,8 +75,8 @@ Completed Preflight kubernetes minimum version check
### Service check
We use the commmand below to test if we are able to create a service in the cluster.
```console
$ qliksense preflight service
```shell
$ qliksense preflight service -v
Preflight service check
-----------------------
@@ -80,8 +91,8 @@ Completed preflight service check
### Deployment check
We use the commmand below to test if we are able to create a deployment in the cluster. After the test exexutes, we wait until the created deployment terminates before we exit the command.
```console
$ qliksense preflight deployment
```shell
$ qliksense preflight deployment -v
Preflight deployment check
-----------------------
@@ -95,8 +106,8 @@ Completed preflight deployment check
### Pod check
We use the commmand below to test if we are able to create a pod in the cluster.
```console
$ qliksense preflight pod
```shell
$ qliksense preflight pod -v
Preflight pod check
--------------------
@@ -109,61 +120,61 @@ Deleted pod: pod-pf-check
Completed preflight pod check
```
### Create-Role check
### Role check
We use the command below to test if we are able to create a role in the cluster
```shell
$ qliksense preflight create-role
Preflight create-role check
$ qliksense preflight role -v
Preflight role check
---------------------------
Preflight create-role check:
Preflight role check:
Created role: role-preflight-check
Preflight create-role check: PASSED
Preflight role check: PASSED
Cleaning up resources...
Deleted role: role-preflight-check
Completed preflight create-role check
Completed preflight role check
```
### Create-RoleBinding check
### RoleBinding check
We use the command below to test if we are able to create a role binding in the cluster
```shell
$ qliksense preflight createRoleBinding
$ qliksense preflight rolebinding -v
Preflight create roleBinding check
Preflight rolebinding check
---------------------------
Preflight createRoleBinding check:
Preflight rolebinding check:
Created RoleBinding: role-binding-preflight-check
Preflight createRoleBinding check: PASSED
Preflight rolebinding check: PASSED
Cleaning up resources...
Deleting RoleBinding: role-binding-preflight-check
Deleted RoleBinding: role-binding-preflight-check
Completed preflight createRoleBinding check
Completed preflight rolebinding check
```
### Create-ServiceAccount check
We use the command below to test if we are able to create a service account in the cluster
```shell
$ qliksense preflight createServiceAccount
$ qliksense preflight serviceaccount -v
Preflight create ServiceAccount check
Preflight ServiceAccount check
-------------------------------------
Preflight createServiceAccount check:
Preflight serviceaccount check:
Created Service Account: preflight-check-test-serviceaccount
Preflight createServiceAccount check: PASSED
Preflight serviceaccount check: PASSED
Cleaning up resources...
Deleting ServiceAccount: preflight-check-test-serviceaccount
Deleted ServiceAccount: preflight-check-test-serviceaccount
Completed preflight createServiceAccount check
Completed preflight serviceaccount check
```
### CreateRB check
### Auth check
We use the command below to combine creation of role, role binding, and service account tests
```shell
$ qliksense preflight createRB
$ qliksense preflight authcheck -v
Preflight createRB check
Preflight auth check
-------------------------------------
Preflight create-role check:
Created role: role-preflight-check
@@ -188,23 +199,25 @@ Cleaning up resources...
Deleted ServiceAccount: preflight-check-test-serviceaccount
Completed preflight createServiceAccount check
Completed preflight CreateRB check
Completed preflight auth check
```
### Mongodb check
We can check if we are able to connect to an instance of mongodb on the cluster by either supplying the mongodbUri as part of the command or infer it from the current context.
```shell
qliksense preflight mongo --url=<url> OR
qliksense preflight mongo
qliksense preflight mongo --url=<url> -v OR
qliksense preflight mongo -v
qliksense preflight mongo --url=<mongo-server url> --ca-cert=<path to ca-cert file> -v
```
```shell
Preflight mongo check
---------------------
Preflight mongodb check:
Created pod: pf-mongo-pod
stdout: MongoDB shell version v4.2.5
connecting to: <url>/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("64f639d3-2c93-4894-80f6-ee14acaf56a5") }
Implicit session: session { "id" : UUID("...") }
MongoDB server version: 4.2.5
bye
stderr:
@@ -213,13 +226,35 @@ Deleted pod: pf-mongo-pod
Completed preflight mongodb check
```
#### Mongodb check with mutual tls
In order to perform mutual tls with mongo we need to:
- append client certificate to the beginning/end of CA certificate. Make sure to include the beginning and end tags on each certificate.
The CA certificate file should look like this in the end:
```shell
<existing contents of CA cert>
...
-----BEGIN RSA PRIVATE KEY-----
<private key>
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
<public key>
-----END CERTIFICATE-----
```
- Run the command below to set the ca certificate into the CR
```shell
cat <path_to_ca.crt> | base64 | qliksense config set-secrets qliksense.caCertificates --base64
```
Next, run:
```shell
qliksense preflight mongo -v
```
### Running all checks
Run the command shown below to execute all preflight checks.
```console
$ qliksense preflight all --mongodb-url=<url> OR
$ qliksense preflight all
```shell
$ qliksense preflight all --mongodb-url=<url> -v OR
$ qliksense preflight all --mongodb-url=<mongo-server url> --mongodb-ca-cert=<path to ca-cert file> -v
Running all preflight checks
@@ -250,4 +285,23 @@ Completed Preflight kubernetes minimum version check
All preflight checks have PASSED
Completed running all preflight checks
```
```
### Clean
Run the command below to cleanup entities that were created for the purpose of running preflight checks and left behind in the cluster.
```shell
$ qliksense preflight clean -v
Preflight clean
----------------
Removing deployment...
Removing service...
Removing pod...
Removing role...
Removing rolebinding...
Removing serviceaccount...
Removing DNS check components...
Removing mongo check components...
Preflight cleanup complete
```

36
go.mod
View File

@@ -6,61 +6,55 @@ replace (
github.com/docker/docker => github.com/moby/moby v0.7.3-0.20190826074503-38ab9da00309
golang.org/x/sys => golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8
k8s.io/client-go => k8s.io/client-go v0.0.0-20191016111102-bec269661e48
k8s.io/kubectl => k8s.io/kubectl v0.0.0-20191016120415-2ed914427d51
k8s.io/apimachinery => k8s.io/apimachinery v0.17.0
k8s.io/client-go => k8s.io/client-go v0.17.0
k8s.io/kubectl => k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd
sigs.k8s.io/kustomize/api => github.com/qlik-oss/kustomize/api v0.3.3-0.20200402170547-2e8140160c36
sigs.k8s.io/kustomize/api => github.com/qlik-oss/kustomize/api v0.3.3-0.20200604192606-17370c1af57b
)
require (
cloud.google.com/go v0.52.0 // indirect
cloud.google.com/go/storage v1.5.0 // indirect
github.com/Masterminds/semver/v3 v3.0.3
github.com/Masterminds/semver/v3 v3.1.0
github.com/Shopify/ejson v1.2.1
github.com/aws/aws-sdk-go v1.28.9 // indirect
github.com/bugsnag/bugsnag-go v1.5.3 // indirect
github.com/containerd/containerd v1.3.2 // indirect
github.com/containerd/continuity v0.0.0-20191214063359-1097c8bae83b // indirect
github.com/containers/image/v5 v5.1.0
github.com/docker/cli v0.0.0-20191212191748-ebca1413117a // indirect
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/go-git/go-git/v5 v5.0.0
github.com/gobuffalo/envy v1.9.0 // indirect
github.com/gobuffalo/logger v1.0.3 // indirect
github.com/gobuffalo/packd v1.0.0 // indirect
github.com/gobuffalo/packr/v2 v2.7.1
github.com/gofrs/uuid v3.2.0+incompatible // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.3.3 // indirect
github.com/google/uuid v1.1.1
github.com/gorilla/mux v1.7.3 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381
github.com/mattn/go-colorable v0.1.4
github.com/mattn/go-tty v0.0.3
github.com/mitchellh/go-homedir v1.1.0
github.com/morikuni/aec v1.0.0 // indirect
github.com/pkg/errors v0.8.1
github.com/qlik-oss/k-apis v0.0.34
github.com/otiai10/copy v1.1.1
github.com/pkg/errors v0.9.1
github.com/qlik-oss/k-apis v0.1.7
github.com/robfig/cron/v3 v3.0.1
github.com/rogpeppe/go-internal v1.5.2 // indirect
github.com/spf13/cobra v0.0.6
github.com/spf13/viper v1.6.1
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 // indirect
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a // indirect
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b
golang.org/x/tools v0.0.0-20200312194400-c312e98713c2 // indirect
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b // indirect
google.golang.org/grpc v1.27.0 // indirect
gopkg.in/yaml.v2 v2.2.8
gopkg.in/yaml.v3 v3.0.0-20190924164351-c8b7dadae555
k8s.io/api v0.17.0
k8s.io/apimachinery v0.17.0
k8s.io/api v0.17.2
k8s.io/apiextensions-apiserver v0.17.2
k8s.io/apimachinery v0.17.2
k8s.io/client-go v11.0.0+incompatible
k8s.io/kubectl v0.0.0-20191016120415-2ed914427d51
sigs.k8s.io/kustomize/api v0.3.2
sigs.k8s.io/yaml v1.1.0
)

304
go.sum
View File

@@ -35,6 +35,7 @@ github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774/go.mod h1:6/0dY
github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuEd+YuKoUiazDC/N96FiDEU=
github.com/Azure/azure-pipeline-go v0.2.1 h1:OLBdZJ3yvOn2MezlWvbrBMTEUQC72zAftRZOMdj5HYo=
github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v30.1.0+incompatible h1:HyYPft8wXpxMd0kfLtXo6etWcO+XuPbLkcgx9g2cqxU=
github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
@@ -43,6 +44,7 @@ github.com/Azure/azure-storage-blob-go v0.8.0 h1:53qhf0Oxa0nOjgbDeeYPUeyiNmafAFE
github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest v12.1.0+incompatible h1:x0sVyfVo0Qw9jcgVHuKIAiTHGRvQ9PsJP+43TVPV/DM=
github.com/Azure/go-autorest v12.1.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs=
@@ -64,21 +66,19 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.4.0/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20190605020000-c4ba1fdf4d36/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo=
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU=
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e h1:eb0Pzkt15Bm7f2FFYv7sjY7NPFi3cPkS3tv1CcrFBWA=
github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.0.3 h1:znjIyLfpXEDQjOIEWh+ehwpTU14UzUPub3c3sm36u14=
github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/sprig/v3 v3.0.2 h1:wz22D0CiSctrliXiI9ZO3HoNApweeRGftyDN+BQa3B8=
github.com/Masterminds/sprig/v3 v3.0.2/go.mod h1:oesJ8kPONMONaZgtiHNzUShJbksypC5kWczhZAf6+aU=
github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/Microsoft/hcsshim v0.8.7 h1:ptnOoufxGSzauVTsdE+wMYnCWA301PdoN4xg5oRdZpg=
github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
@@ -98,12 +98,12 @@ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:H
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM=
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6 h1:bZ28Hqta7TFAK3Q08CMvv8y3/8ATaEqv2nGoc6yff6c=
github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
@@ -119,6 +119,9 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0=
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
github.com/aws/aws-sdk-go v1.17.4/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
@@ -127,6 +130,7 @@ github.com/aws/aws-sdk-go v1.25.36 h1:4+TL/Y2G5hsR1zdfHmjNG1ou1WEqsSWk8v7m1GaDKy
github.com/aws/aws-sdk-go v1.25.36/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.28.9 h1:grIuBQc+p3dTRXerh5+2OxSuWFi0iXuxbFdTSg0jaW0=
github.com/aws/aws-sdk-go v1.28.9/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -145,17 +149,17 @@ github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx2
github.com/bombsimon/wsl v1.2.5/go.mod h1:43lEF/i0kpXbLCeDXL9LMT8c92HyBywXb0AsgMHYngM=
github.com/bshuster-repo/logrus-logstash-hook v0.4.1 h1:pgAtgj+A31JBVtEHu2uHuEx0n+2ukqUJnS2vVe5pQNA=
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/bugsnag/bugsnag-go v1.5.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/bugsnag-go v1.5.3 h1:yeRUT3mUE13jL1tGwvoQsKdVbAsQx9AJ+fqahKveP04=
github.com/bugsnag/bugsnag-go v1.5.3/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/panicwrap v1.2.0 h1:OzrKrRvXis8qEvOkfcxNcYbOd2O7xXS2nnKMEMABFQA=
github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ=
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o=
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
@@ -163,20 +167,18 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f h1:tSNMc+rJDfmYntojat8lljbt1mgKNpTxUZJsSzJ9Y1s=
github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.3.0-beta.2.0.20190823190603-4a2f61c4f2b4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.3.2 h1:ForxmXkA6tPIvffbrDAcPUIB32QgXkt2XFj+F0UxetA=
github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/continuity v0.0.0-20180216233310-d8fb8589b0e8/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20191214063359-1097c8bae83b h1:pik3LX++5O3UiNWv45wfP/WT81l7ukBJzd3uUiifbSU=
github.com/containerd/continuity v0.0.0-20191214063359-1097c8bae83b/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY=
github.com/containerd/continuity v0.0.0-20200107194136-26c1120b8d41 h1:kIFnQBO7rQ0XkMe6xEwbybYHBEaWmh/f++laI6Emt7M=
github.com/containerd/continuity v0.0.0-20200107194136-26c1120b8d41/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY=
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
@@ -190,10 +192,8 @@ github.com/containers/ocicrypt v0.0.0-20190930154801-b87a4a69c741 h1:8tQkOcednLJ
github.com/containers/ocicrypt v0.0.0-20190930154801-b87a4a69c741/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc=
github.com/containers/storage v1.15.3 h1:+lFSQZnnKUFyUEtguIgdoQLJfWSuYz+j/wg5GxLtsN4=
github.com/containers/storage v1.15.3/go.mod h1:v0lq/3f+cXH3Y/HiDaFYRR0zilwDve7I4W7U5xQxvF8=
github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@@ -201,12 +201,14 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -214,27 +216,28 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=
github.com/deislabs/oras v0.7.0 h1:RnDoFd3tQYODMiUqxgQ8JxlrlWL0/VMKIKRD01MmNYk=
github.com/deislabs/oras v0.7.0/go.mod h1:sqMKPG3tMyIX9xwXUBRLhZ24o+uT4y6jgBD2RzUTKDM=
github.com/deislabs/oras v0.8.1 h1:If674KraJVpujYR00rzdi0QAmW4BxzMJPVAZJKuhQ0c=
github.com/deislabs/oras v0.8.1/go.mod h1:Mx0rMSbBNaNfY9hjpccEnxkOqJL6KGjtxNHPLC4G4As=
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/docker/cli v0.0.0-20190506213505-d88565df0c2d/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v0.0.0-20191212191748-ebca1413117a h1:rgpgmLocRiSIM3zdtVgJcyvH7S2cSiIPtL7LvFY8K/0=
github.com/docker/cli v0.0.0-20191212191748-ebca1413117a/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/cli v0.0.0-20200130152716-5d0cf8839492 h1:FwssHbCDJD025h+BchanCwE1Q8fyMgqDr2mOQAWOLGw=
github.com/docker/cli v0.0.0-20200130152716-5d0cf8839492/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v0.0.0-20170817175659-5f6282db7d65/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v0.0.0-20191216044856-a8371794149d/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker-credential-helpers v0.6.1/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ=
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
github.com/docker/go-connections v0.0.0-20180212134524-7beb39f0b969/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
@@ -242,25 +245,24 @@ github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libkv v0.2.1 h1:PNXYaftMVCFS5CmnDtDWTg3wbBO61Q/cEo3KX1oKxto=
github.com/docker/libkv v0.2.1/go.mod h1:r5hEwHwW8dr0TFBYGCarMNbrQOiwL1xoqDYZ/JqoTK0=
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s=
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad h1:Qk76DOWdOp+GlyDKBAG3Klr9cn7N+LcYc82AZ2S7+cA=
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad/go.mod h1:mPKfmRa823oBIgl2r20LeMSpTAteW5j7FLkc0vjmzyQ=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e h1:p1yVGRW3nmb85p1Sh1ZJSDm4A4iKLS5QNbvUHMgGu/M=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.11.1+incompatible h1:CjKsv3uWcCMvySPQYKxO8XX3f9zD4FeZRsW4G0B4ffE=
github.com/emicklei/go-restful v2.11.1+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -287,23 +289,30 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa h1:RDBNVkRviHZtvDvId8XSGPu3rmpmSe+wKRcEWNgsfWU=
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 h1:LofdAjjjqCSXMwLGgOgnE+rdPuvX9DxCqaHwKy7i/ko=
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v0.0.0-20161207003320-04f313413ffd/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc=
github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
github.com/go-git/go-git/v5 v5.0.0 h1:k5RWPm4iJwYtfWoxIJy4wJX9ON7ihPeZZYC1fLYDnpg=
github.com/go-git/go-git/v5 v5.0.0/go.mod h1:oYD8y9kWsGINPFJoLdaScGCN6dlKg23blmClfZwtUVA=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
@@ -314,6 +323,7 @@ github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70t
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
@@ -333,17 +343,21 @@ github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf
github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk=
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
@@ -352,8 +366,10 @@ github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tF
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
@@ -388,6 +404,7 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/flock v0.7.1 h1:DP+LD/t0njgoPBvT5MJLeliUIVQR03hiKR6vezdwHlc=
github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
@@ -406,12 +423,10 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@@ -491,36 +506,35 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.3.0 h1:CcQijm0XKekKjP/YCz28LXVSpgguuB+nCxaSjCe09y0=
github.com/googleapis/gnostic v0.3.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA=
github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33 h1:893HsJqtxp9z1SF76gg6hY70hRY1wVlTSnC/h1yUDCo=
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v0.0.0-20170217192616-94e7d24fd285/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gosimple/slug v1.9.0 h1:r5vDcYrFz9BmfIAMC829un9hq7hKM4cHUrsv36LbEqs=
github.com/gosimple/slug v1.9.0/go.mod h1:AMZ+sOVe65uByN3kgEyf9WEBKBCSS+dJjMX9x4vDJbg=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/gosuri/uitable v0.0.1 h1:M9sMNgSZPyAu1FJZJLpJ16ofL8q5ko2EDUkICsynvlY=
github.com/gosuri/uitable v0.0.1/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f h1:ShTPMJQes6tubcjzGMODIVG5hlrCeImaBnZzKF2N8SM=
github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hairyhenderson/gomplate/v3 v3.6.0 h1:EryWG7cCxvZ2awoZ957B3AMAd20Zy0uRXeZ7TXXMIp0=
github.com/hairyhenderson/gomplate/v3 v3.6.0/go.mod h1:RbEC6Y14nNTHCtNWpBAkwqDP4ICFUrAH0S8PUFa0qT4=
github.com/hairyhenderson/toml v0.3.1-0.20191004034452-2a4f3b6160f2 h1:Dc4YWWuY02jqhCnErAH++juCTwEPLstAOOVhyPXeE7Q=
@@ -618,6 +632,7 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/johannesboyne/gofakes3 v0.0.0-20191029185751-e238f04965fe h1:9kkgzfTjcHQqS6wGlEhJBJmAMI75lKyHX69w/ii+5So=
@@ -629,13 +644,14 @@ github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBv
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
@@ -667,13 +683,18 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e h1:9MlwzLdW7QSDrhDjFlsEYmxpFyIoXmYRon3dt0io31k=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@@ -684,6 +705,7 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@@ -698,14 +720,13 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k=
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-shellwords v1.0.5/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI=
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-shellwords v1.0.9 h1:eaB5JspOwiKKcHdqcjbfe5lA9cNn/4NRRtddXJCimqk=
github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI=
github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
@@ -713,7 +734,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mholt/archiver/v3 v3.3.0 h1:vWjhY8SQp5yzM9P6OJ/eZEkmi3UAbRrxCq48MxjAzig=
github.com/mholt/archiver/v3 v3.3.0/go.mod h1:YnQtqsp+94Rwd0D/rk5cnLrxusUBUXg+08Ebtr1Mqao=
github.com/miekg/dns v0.0.0-20181005163659-0d29b283ac0f/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mistifyio/go-zfs v2.1.1+incompatible h1:gAMO1HM9xBRONLHHYnu5iFsOJUiJdNZo6oqSENd4eW8=
@@ -739,6 +759,7 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/moby v0.7.3-0.20190826074503-38ab9da00309 h1:cvy4lBOYN3gKfKj8Lzz5Q9TfviP+L7koMHY7SvkyTKs=
@@ -750,33 +771,37 @@ github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lN
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
github.com/mtrmac/gpgme v0.0.0-20170102180018-b2432428689c h1:xa+eQWKuJ9MbB9FBL/eoNvDFvveAkz2LQoz8PzX7Q/4=
github.com/mtrmac/gpgme v0.0.0-20170102180018-b2432428689c/go.mod h1:GhAqVMEWnTcW2dxoD/SO3n2enrgWl3y6Dnx4m59GvcA=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs=
github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 h1:yN8BPXVwMBAm3Cuvh1L5XE8XpvYRMdsVLd82ILprhUU=
@@ -792,6 +817,14 @@ github.com/opencontainers/selinux v1.3.0 h1:xsI95WzPZu5exzA6JzkLSfdr/DilzOhCJOqG
github.com/opencontainers/selinux v1.3.0/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913 h1:TnbXhKzrTOyuvWrjI8W6pcoI9XPbLHFXCdN2dtUw7Rw=
github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913/go.mod h1:J6OG6YJVEWopen4avK3VNQSnALmmjvniMmni/YFYAwc=
github.com/otiai10/copy v1.1.1 h1:PH7IFlRQ6Fv9vYmuXbDRLdgTHoP1w483kPNUP2bskpo=
github.com/otiai10/copy v1.1.1/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.1 h1:BCmzIS3n71sGfHB5NMNDB3lHYPz8fWSkCAErHed//qc=
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@@ -801,8 +834,8 @@ github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/phayes/freeport v0.0.0-20171002181615-b8543db493a5 h1:rZQtoozkfsiNs36c7Tdv/gyGNzD1X1XWKO8rptVNZuM=
github.com/phayes/freeport v0.0.0-20171002181615-b8543db493a5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4 v2.3.0+incompatible h1:CZzRn4Ut9GbUkHlQ7jqBXeZQV41ZSKWFc302ZU6lUTk=
github.com/pierrec/lz4 v2.3.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
@@ -812,6 +845,8 @@ github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7 h1:RcqIXZDN7Vz5lgK7+0
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -820,31 +855,29 @@ github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prY
github.com/pquerna/ffjson v0.0.0-20181028064349-e517b90714f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M=
github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9 h1:kyf9snWXHvQc+yxE9imhdI8YAm4oKeZISlaAR+x73zs=
github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M=
github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI=
github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190129233650-316cf8ccfec5/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE=
@@ -852,12 +885,14 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/qlik-oss/k-apis v0.0.34 h1:lOC21wz/nNZNmSfTXZSJCOm1BulaZfdg7tAuYb7knAE=
github.com/qlik-oss/k-apis v0.0.34/go.mod h1:DNiWYqCqPIN216l7+1rccArNIYPb1Le7kYDcPSyNp+Q=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200401055330-fa528324112a h1:Vzod5XB+e25ENy5Lse0pXNmSYSDFxSEYhH/6Sj7twPg=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200401055330-fa528324112a/go.mod h1:tSQaDZ4Jt9KwYvD7LlMUPi5nkiGOno3PAKl5/XqEfxs=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200402170547-2e8140160c36 h1:BuT+cnXPQ6mcOWTDS1S8GXy65LAEMdPuNQCC36rMq28=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200402170547-2e8140160c36/go.mod h1:tSQaDZ4Jt9KwYvD7LlMUPi5nkiGOno3PAKl5/XqEfxs=
github.com/qlik-oss/k-apis v0.1.5 h1:IeqHuF1IIQCsuSmsUhL7GjdfkOFsNgh3z2UyX59GTsk=
github.com/qlik-oss/k-apis v0.1.5/go.mod h1:yoYGgPJ/H0t9H3NSq64dWfyQY6QWi2L9c+hCJoVO03U=
github.com/qlik-oss/k-apis v0.1.6 h1:4rTRwgIN1GtkmLa0VBjvpW2oyVC3h1fOcFHEg3NieNA=
github.com/qlik-oss/k-apis v0.1.6/go.mod h1:r5hXo1mrHOzIdI0Ri9TI4SKjEXft1TZnAyJzOSm9pi0=
github.com/qlik-oss/k-apis v0.7.1 h1:7XHTcIVTSgBM8s9LglWbOeWlnKm84cgHIm7Slrjxb9Y=
github.com/qlik-oss/k-apis v0.7.1/go.mod h1:r5hXo1mrHOzIdI0Ri9TI4SKjEXft1TZnAyJzOSm9pi0=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200604192606-17370c1af57b h1:UqyMR9WA/MbUxCOaaDFk8D2VwM3fG98foDU5nG2HPoM=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200604192606-17370c1af57b/go.mod h1:zh3yFgE5zFk1kreqzVyyj1eXyIxQJT53l4zSg8Wt4SA=
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
@@ -882,6 +917,7 @@ github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkB
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/securego/gosec v0.0.0-20191002120514-e680875ea14d/go.mod h1:w5+eXa0mYznDkHaMCXA4XYffjlH+cy1oyKbfzJXa2Do=
@@ -906,10 +942,10 @@ github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@@ -931,6 +967,7 @@ github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@@ -956,11 +993,10 @@ github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 h1:b6uOv7YOFK0
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/tchap/go-patricia v2.3.0+incompatible h1:GkY4dP3cEfEASBPPkWd+AmjYxhmDkqO9/zg7R0lSQRs=
github.com/tchap/go-patricia v2.3.0+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31 h1:OXcKh35JaYsGMRzpvFkLv/MEyPuL49CThT1pZ8aSml4=
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
@@ -974,6 +1010,7 @@ github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4A
github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/uudashr/gocognit v0.0.0-20190926065955-1655d0de0517/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
@@ -984,6 +1021,7 @@ github.com/vbatts/tar-split v0.11.1 h1:0Odu65rhcZ3JZaPHxl7tCI3V/C/Q9Zf82UFravl02
github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g=
github.com/vbauerster/mpb/v4 v4.11.1 h1:ZOYQSVHgmeanXsbyC44aDg76tBGCS/54Rk8VkL8dJGA=
github.com/vbauerster/mpb/v4 v4.11.1/go.mod h1:vMLa1J/ZKC83G2lB/52XpqT+ZZtFG4aZOdKhmpRL1uM=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
@@ -995,20 +1033,17 @@ github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf
github.com/xeipuuv/gojsonschema v0.0.0-20190816131739-be0936907f66/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xeipuuv/gojsonschema v1.1.0 h1:ngVtJC9TY/lg0AA/1k48FYhBrhRoFlEmWzsehpNAaZg=
github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
github.com/xenolf/lego v0.0.0-20160613233155-a9d8cec0e656/go.mod h1:fwiGnfsIjG7OHPfOvgK7Y/Qo6+2Ox0iozjNTkZICKbY=
github.com/xenolf/lego v0.3.2-0.20160613233155-a9d8cec0e656 h1:BTvU+npm3/yjuBd53EvgiFLl5+YLikf2WvHsjRQ4KrY=
github.com/xenolf/lego v0.3.2-0.20160613233155-a9d8cec0e656/go.mod h1:fwiGnfsIjG7OHPfOvgK7Y/Qo6+2Ox0iozjNTkZICKbY=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yujunz/go-getter v1.4.1-lite h1:FhvNc94AXMZkfqUwfMKhnQEC9phkphSGdPTL7tIdhOM=
github.com/yujunz/go-getter v1.4.1-lite/go.mod h1:sbmqxXjyLunH1PkF3n7zSlnVeMvmYUuIl9ZVs/7NyCc=
github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 h1:p7OofyZ509h8DmPLh8Hn+EIIZm/xYhdZHJ9GnXHdr6U=
github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
github.com/yvasiyarov/gorelic v0.0.6 h1:qMJQYPNdtJ7UNYHjX38KXZtltKTqimMuoQjNnSVIuJg=
github.com/yvasiyarov/gorelic v0.0.6/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI=
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE=
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY=
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
github.com/zealic/xignore v0.3.3 h1:EpLXUgZY/JEzFkTc+Y/VYypzXtNz+MSOMVCGW5Q4CKQ=
@@ -1017,17 +1052,20 @@ go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.2 h1:jxcFYjlkl8xaERsgLo+RNquI0epW6zuy/ZRQs6jnrFA=
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
gocloud.dev v0.18.0 h1:HX6uFZYZs9tUP87jzoWgB8dl4ihsRpiAsBDKTthiApY=
gocloud.dev v0.18.0/go.mod h1:lhLOb91+9tKB8RnNlsx+weJGEd0AHM94huK1bmrhPwM=
@@ -1043,19 +1081,21 @@ golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA=
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1073,7 +1113,6 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -1101,7 +1140,6 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1123,10 +1161,10 @@ golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -1136,6 +1174,10 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjut
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b h1:IYiJPiJfzktmDAO1HQiwjMjwjlYKHAL7KzeD544RJPs=
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1165,6 +1207,7 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1172,13 +1215,13 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqG
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190308174544-00c44ba9c14f/go.mod h1:25r3+/G6/xytQM8iWZKq3Hn0kr0rgFKPUNVEL/dr3z4=
@@ -1198,6 +1241,7 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
@@ -1207,6 +1251,7 @@ golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDq
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -1230,6 +1275,7 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
@@ -1252,9 +1298,9 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190128161407-8ac453e89fca/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -1268,8 +1314,6 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191028173616-919d9bdd9fe6 h1:UXl+Zk3jqqcbEVV7ace5lrt4YdA4tXiz3f/KbmD29Vo=
google.golang.org/genproto v0.0.0-20191028173616-919d9bdd9fe6/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
@@ -1277,8 +1321,8 @@ google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b h1:c8OBoXP3kTbDWWB/oVE3FkR851p4iZ3MPadz7zXEIPU=
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
@@ -1286,6 +1330,7 @@ google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s=
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
@@ -1295,15 +1340,18 @@ gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
@@ -1311,8 +1359,6 @@ gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v1 v1.1.2 h1:/5jmADZB+RiKtZGr4HxsEFOEfbfsjTKsVnqpThUpE30=
gopkg.in/square/go-jose.v1 v1.1.2/go.mod h1:QpYS+a4WhS+DTlyQIi6Ka7MS3SuR9a055rgXNEe6EiA=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
@@ -1340,9 +1386,8 @@ gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.0 h1:d+tVGRu6X0ZBQ+kyAR8JKi6AXhTP2gmQaoIYaGFz634=
gotest.tools/v3 v3.0.0/go.mod h1:TUP+/YtXl/dp++T+SZ5v2zUmLVBHmptSb/ajDLCJ+3c=
helm.sh/helm/v3 v3.0.2 h1:BggvLisIMrAc+Is5oAHVrlVxgwOOrMN8nddfQbm5gKo=
helm.sh/helm/v3 v3.0.2/go.mod h1:KBxE6XWO57XSNA1PA9CvVLYRY0zWqYQTad84bNXp1lw=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
helm.sh/helm/v3 v3.1.2 h1:VpNzaNv2DX4aRnOCcV7v5Of+XT2SZrJ8iOQ25AGKOos=
helm.sh/helm/v3 v3.1.2/go.mod h1:WYsFJuMASa/4XUqLyv54s0U/f3mlAaRErGmyy4z921g=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1350,38 +1395,40 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
k8s.io/api v0.0.0-20191016110408-35e52d86657a/go.mod h1:/L5qH+AD540e7Cetbui1tuJeXdmNhO8jM6VkXeDdDhQ=
k8s.io/api v0.0.0-20191214185829-ca1d04f8b0d3/go.mod h1:itOjKREfmUTvcjantxOsyYU5mbFsU7qUnyUuRfF5+5M=
k8s.io/api v0.17.0 h1:H9d/lw+VkZKEVIUc8F3wgiQ+FUXTTr21M87jXLU7yqM=
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
k8s.io/apiextensions-apiserver v0.0.0-20191016113550-5357c4baaf65 h1:kThoiqgMsSwBdMK/lPgjtYTsEjbUU9nXCA9DyU3feok=
k8s.io/apiextensions-apiserver v0.0.0-20191016113550-5357c4baaf65/go.mod h1:5BINdGqggRXXKnDgpwoJ7PyQH8f+Ypp02fvVNcIFy9s=
k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8 h1:Iieh/ZEgT3BWwbLD5qEKcY06jKuPEl6zC7gPSehoLw4=
k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ=
k8s.io/apiserver v0.0.0-20191016112112-5190913f932d/go.mod h1:7OqfAolfWxUM/jJ/HBLyE+cdaWFBUoo5Q5pHgJVj2ws=
k8s.io/cli-runtime v0.0.0-20191016114015-74ad18325ed5 h1:8ZfMjkMBzcXEawLsYHg9lDM7aLEVso3NiVKfUTnN56A=
k8s.io/cli-runtime v0.0.0-20191016114015-74ad18325ed5/go.mod h1:sDl6WKSQkDM6zS1u9F49a0VooQ3ycYFBFLqd2jf2Xfo=
k8s.io/client-go v0.0.0-20191016111102-bec269661e48 h1:C2XVy2z0dV94q9hSSoCuTPp1KOG7IegvbdXuz9VGxoU=
k8s.io/client-go v0.0.0-20191016111102-bec269661e48/go.mod h1:hrwktSwYGI4JK+TJA3dMaFyyvHVi/aLarVHpbs8bgCU=
k8s.io/code-generator v0.0.0-20191004115455-8e001e5d1894/go.mod h1:mJUgkl06XV4kstAnLHAIzJPVCOzVR+ZcfPIv4fUsFCY=
k8s.io/component-base v0.0.0-20191016111319-039242c015a9/go.mod h1:SuWowIgd/dtU/m/iv8OD9eOxp3QZBBhTIiWMsBQvKjI=
k8s.io/api v0.17.2 h1:NF1UFXcKN7/OOv1uxdRz3qfra8AHsPav5M93hlV9+Dc=
k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4=
k8s.io/apiextensions-apiserver v0.17.2 h1:cP579D2hSZNuO/rZj9XFRzwJNYb41DbNANJb6Kolpss=
k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs=
k8s.io/apimachinery v0.17.0 h1:xRBnuie9rXcPxUkDizUsGvPf1cnlZCFu210op7J7LJo=
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo=
k8s.io/cli-runtime v0.0.0-20191214191754-e6dc6d5c8724/go.mod h1:wzlq80lvjgHW9if6MlE4OIGC86MDKsy5jtl9nxz/IYY=
k8s.io/cli-runtime v0.17.2 h1:YH4txSplyGudvxjhAJeHEtXc7Tr/16clKGfN076ydGk=
k8s.io/cli-runtime v0.17.2/go.mod h1:aa8t9ziyQdbkuizkNLAw3qe3srSyWh9zlSB7zTqRNPI=
k8s.io/client-go v0.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg=
k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
k8s.io/code-generator v0.0.0-20191214185510-0b9b3c99f9f2/go.mod h1:BjGKcoq1MRUmcssvHiSxodCco1T6nVIt4YeCT5CMSao=
k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
k8s.io/component-base v0.0.0-20191214190519-d868452632e2/go.mod h1:wupxkh1T/oUDqyTtcIjiEfpbmIHGm8By/vqpSKC6z8c=
k8s.io/component-base v0.17.2 h1:0XHf+cerTvL9I5Xwn9v+0jmqzGAZI7zNydv4tL6Cw6A=
k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/kube-openapi v0.0.0-20190918143330-0270cf2f1c1d/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/kubectl v0.0.0-20191016120415-2ed914427d51 h1:RBkTKVMF+xsNsSOVc0+HdC0B5gD1sr6s6Cu5w9qNbuQ=
k8s.io/kubectl v0.0.0-20191016120415-2ed914427d51/go.mod h1:gL826ZTIfD4vXTGlmzgTbliCAT9NGiqpCqK2aNYv5MQ=
k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd h1:nZX5+wEqTu/EBIYjrZlFOA63z4+Zcy96lDkCZPU9a9c=
k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd/go.mod h1:9ehGcuUGjXVZh0qbYSB0vvofQw2JQe6c6cO0k4wu/Oo=
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
k8s.io/metrics v0.0.0-20191016113814-3b1a734dba6e/go.mod h1:ve7/vMWeY5lEBkZf6Bt5TTbGS3b8wAxwGbdXAsufjRs=
k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20191010214722-8d271d903fe4 h1:Gi+/O1saihwDqnlmC8Vhv1M5Sp4+rbOmK9TbsLn8ZEA=
k8s.io/utils v0.0.0-20191010214722-8d271d903fe4/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/metrics v0.0.0-20191214191643-6b1944c9f765/go.mod h1:5V7rewilItwK0cz4nomU0b3XCcees2Ka5EBYWS1HBeM=
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
@@ -1392,12 +1439,13 @@ mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jC
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
pack.ag/amqp v0.11.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/letsencrypt v0.0.1 h1:DV0d09Ne9E7UUa9ZqWktZ9L2VmybgTgfq7xlfFR/bbU=
rsc.io/letsencrypt v0.0.1/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY=
rsc.io/letsencrypt v0.0.3 h1:H7xDfhkaFFSYEJlKeq38RwX2jYcnTeHuDQyT+mMNMwM=
rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY=
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=

View File

@@ -1,4 +1,4 @@
site_name: Qlik Sense installer
site_name: Qlik Sense on Kubernetes CLI
repo_url: 'https://github.com/qlik-oss/sense-installer'
strict: true
theme:
@@ -19,6 +19,5 @@ nav:
- getting_started.md
- command_reference.md
- concepts.md
- preflight_checks.md
- air_gap.md
- Releases ⧉: https://github.com/qlik-oss/sense-installer/releases

View File

@@ -1,11 +1,9 @@
package api
import (
"crypto/rsa"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
@@ -23,6 +21,7 @@ const (
qliksenseContextsDirName = "contexts"
qliksenseSecretsDirName = "secrets"
qliksenseEjsonDirName = "ejson"
QLIK_GIT_REPO = "https://github.com/qlik-oss/qliksense-k8s"
)
// NewQConfig create QliksenseConfig object from file ~/.qliksense/config.yaml
@@ -144,6 +143,68 @@ func (cr *QliksenseCR) IsRepoExist() bool {
return true
}
func (cr *QliksenseCR) GetFetchUrl() string {
if cr.Spec.FetchSource == nil || cr.Spec.FetchSource.Repository == "" {
return QLIK_GIT_REPO
}
return cr.Spec.FetchSource.Repository
}
func (cr *QliksenseCR) GetFetchAccessToken(encryptionKey string) string {
if cr.Spec.FetchSource == nil {
return ""
}
if tok, err := cr.Spec.FetchSource.GetAccessToken(); err != nil {
fmt.Println(err)
return ""
} else if tok == "" {
return tok
} else {
by, _ := b64.StdEncoding.DecodeString(tok)
res, err := DecryptData(by, encryptionKey)
if err != nil {
fmt.Println(err)
return ""
}
return string(res)
}
}
func (cr *QliksenseCR) SetFetchUrl(url string) {
if cr.Spec.FetchSource == nil {
cr.Spec.FetchSource = &config.Repo{}
}
cr.Spec.FetchSource.Repository = url
}
func (cr *QliksenseCR) SetFetchAccessToken(token, encryptionKey string) error {
if cr.Spec.FetchSource == nil {
cr.Spec.FetchSource = &config.Repo{}
}
res, err := EncryptData([]byte(token), encryptionKey)
if err != nil {
return err
}
cr.Spec.FetchSource.AccessToken = b64.StdEncoding.EncodeToString(res)
return nil
}
func (cr *QliksenseCR) SetFetchAccessSecretName(sec string) {
if cr.Spec.FetchSource == nil {
cr.Spec.FetchSource = &config.Repo{}
}
cr.Spec.FetchSource.SecretName = sec
}
//DeleteRepo delete the manifest repo and unset manifestsRoot
func (cr *QliksenseCR) DeleteRepo() error {
if err := os.RemoveAll(cr.Spec.ManifestsRoot); err != nil {
return err
}
cr.Spec.ManifestsRoot = ""
return nil
}
func (qc *QliksenseConfig) IsRepoExist(contextName, version string) bool {
if _, err := os.Lstat(qc.BuildRepoPathForContext(contextName, version)); err != nil {
return false
@@ -158,6 +219,11 @@ func (qc *QliksenseConfig) IsRepoExistForCurrent(version string) bool {
return true
}
func (qc *QliksenseConfig) DeleteRepoForCurrent(version string) error {
path := qc.BuildRepoPath(version)
return os.RemoveAll(path)
}
func (qc *QliksenseConfig) BuildRepoPath(version string) string {
return qc.BuildRepoPathForContext(qc.Spec.CurrentContext, version)
}
@@ -258,9 +324,9 @@ func (qc *QliksenseConfig) GetCurrentContextSecretsDir() (string, error) {
func (qc *QliksenseConfig) setDockerConfigJsonSecret(filename string, dockerConfigJsonSecret *DockerConfigJsonSecret) error {
if secretsDir, err := qc.GetCurrentContextSecretsDir(); err != nil {
return err
} else if publicKey, _, err := qc.GetCurrentContextEncryptionKeyPair(); err != nil {
} else if encryptionKey, err := qc.GetEncryptionKeyForCurrent(); err != nil {
return err
} else if dockerConfigJsonSecretYaml, err := dockerConfigJsonSecret.ToYaml(publicKey); err != nil {
} else if dockerConfigJsonSecretYaml, err := dockerConfigJsonSecret.ToYaml(encryptionKey); err != nil {
return err
} else if err := os.MkdirAll(secretsDir, os.ModePerm); err != nil {
return err
@@ -307,9 +373,9 @@ func (qc *QliksenseConfig) getDockerConfigJsonSecret(name string) (*DockerConfig
return nil, err
} else if dockerConfigJsonSecretYaml, err := ioutil.ReadFile(filepath.Join(secretsDir, name)); err != nil {
return nil, err
} else if _, privateKey, err := qc.GetCurrentContextEncryptionKeyPair(); err != nil {
} else if encryptionKey, err := qc.GetEncryptionKeyForCurrent(); err != nil {
return nil, err
} else if err := dockerConfigJsonSecret.FromYaml(dockerConfigJsonSecretYaml, privateKey); err != nil {
} else if err := dockerConfigJsonSecret.FromYaml(dockerConfigJsonSecretYaml, encryptionKey); err != nil {
return nil, err
}
return dockerConfigJsonSecret, nil
@@ -320,11 +386,11 @@ func (qc *QliksenseConfig) getCurrentContextEncryptionKeyPairLocation() (string,
if qcr, err := qc.GetCurrentCR(); err != nil {
return "", err
} else {
return qc.getContextEncryptionKeyPairLocation(qcr.GetName())
return qc.getContextEncryptionKeyLocation(qcr.GetName())
}
}
func (qc *QliksenseConfig) getContextEncryptionKeyPairLocation(contextName string) (string, error) {
func (qc *QliksenseConfig) getContextEncryptionKeyLocation(contextName string) (string, error) {
// Check env var: QLIKSENSE_KEY_LOCATION to determine location to store keypair
var secretKeyPairLocation string
if os.Getenv("QLIKSENSE_KEY_LOCATION") != "" {
@@ -334,9 +400,9 @@ func (qc *QliksenseConfig) getContextEncryptionKeyPairLocation(contextName strin
// QLIKSENSE_KEY_LOCATION has not been set, hence storing key pair in default location:
// /.qliksense/secrets/contexts/<current-context>/secrets/
secretKeyPairLocation = filepath.Join(qc.QliksenseHomePath, qliksenseSecretsDirName, qliksenseContextsDirName, contextName, qliksenseSecretsDirName)
}
return secretKeyPairLocation, nil
return secretKeyPairLocation, os.MkdirAll(secretKeyPairLocation, os.ModePerm)
}
func (qc *QliksenseConfig) GetCurrentContextEjsonKeyDir() (string, error) {
@@ -351,52 +417,25 @@ func (qc *QliksenseConfig) GetCurrentContextEjsonKeyDir() (string, error) {
}
}
func (qc *QliksenseConfig) GetCurrentContextEncryptionKeyPair() (*rsa.PublicKey, *rsa.PrivateKey, error) {
func (qc *QliksenseConfig) GetEncryptionKeyForCurrent() (string, error) {
if qcr, err := qc.GetCurrentCR(); err != nil {
return nil, nil, err
return "", err
} else {
return qc.GetContextEncryptionKeyPair(qcr.GetName())
return qc.GetEncryptionKeyFor(qcr.GetName())
}
}
func (qc *QliksenseConfig) GetContextEncryptionKeyPair(contextName string) (*rsa.PublicKey, *rsa.PrivateKey, error) {
secretKeyPairLocation, err := qc.getContextEncryptionKeyPairLocation(contextName)
func (qc *QliksenseConfig) GetEncryptionKeyFor(contextName string) (string, error) {
secretKeyLocation, err := qc.getContextEncryptionKeyLocation(contextName)
if err != nil {
return nil, nil, err
return "", err
}
publicKeyFilePath := filepath.Join(secretKeyPairLocation, QliksensePublicKey)
privateKeyFilePath := filepath.Join(secretKeyPairLocation, QliksensePrivateKey)
// try to create the dir if it doesn't exist
if !FileExists(publicKeyFilePath) || !FileExists(privateKeyFilePath) {
LogDebugMessage("Qliksense secretKeyLocation dir does not exist, creating it now: %s", secretKeyPairLocation)
if err := os.MkdirAll(secretKeyPairLocation, os.ModePerm); err != nil {
err = fmt.Errorf("Not able to create %s dir: %v", secretKeyPairLocation, err)
log.Println(err)
return nil, nil, err
}
// generating and storing key-pair
err1 := GenerateAndStoreSecretKeypair(secretKeyPairLocation)
if err1 != nil {
err1 = fmt.Errorf("Not able to generate and store key pair for encryption")
log.Println(err1)
return nil, nil, err1
}
}
if publicKeyBytes, err := ReadKeys(publicKeyFilePath); err != nil {
LogDebugMessage("Not able to read public key")
return nil, nil, err
} else if privateKeyBytes, err := ReadKeys(privateKeyFilePath); err != nil {
LogDebugMessage("Not able to read private key")
return nil, nil, err
} else if rsaPublicKey, err := DecodeToPublicKey(publicKeyBytes); err != nil {
return nil, nil, err
} else if rsaPrivateKey, err := DecodeToPrivateKey(privateKeyBytes); err != nil {
return nil, nil, err
} else {
return rsaPublicKey, rsaPrivateKey, nil
key, err := LoadSecretKey(secretKeyLocation)
if key != "" {
return key, nil
}
fmt.Println("Generating new encryption key for the context: " + contextName)
return GenerateAndStoreSecretKey(secretKeyLocation)
}
func (cr *QliksenseCR) AddLabelToCr(key, value string) {
@@ -421,17 +460,6 @@ func (cr *QliksenseCR) GetString() (string, error) {
return string(out), nil
}
func (cr *QliksenseCR) GetImageRegistry() string {
for _, nameValues := range cr.Spec.Configs {
for _, nameValue := range nameValues {
if nameValue.Name == "imageRegistry" {
return nameValue.Value
}
}
}
return ""
}
func (cr *QliksenseCR) GetK8sSecretsFolder(qlikSenseHomeDir string) string {
return filepath.Join(qlikSenseHomeDir, qliksenseContextsDirName, cr.GetName(), qliksenseSecretsDirName)
}
@@ -469,7 +497,7 @@ func (cr *QliksenseCR) GetCustomCrdsPath() string {
func (qc *QliksenseConfig) GetDecryptedCr(cr *QliksenseCR) (*QliksenseCR, error) {
newCr := &QliksenseCR{}
copier.Copy(newCr, cr)
_, rsaPrivateKey, err := qc.GetCurrentContextEncryptionKeyPair()
encryptionKey, err := qc.GetEncryptionKeyFor(cr.GetName())
if err != nil {
return nil, err
}
@@ -482,7 +510,7 @@ func (qc *QliksenseConfig) GetDecryptedCr(cr *QliksenseCR) (*QliksenseCR, error)
if err != nil {
return nil, err
}
db, err := Decrypt(b, rsaPrivateKey)
db, err := DecryptData(b, encryptionKey)
if err != nil {
return nil, err
}
@@ -495,6 +523,11 @@ func (qc *QliksenseConfig) GetDecryptedCr(cr *QliksenseCR) (*QliksenseCR, error)
finalSecrets[k] = newNvs
}
newCr.Spec.Secrets = finalSecrets
if newCr.Spec.FetchSource != nil && newCr.Spec.FetchSource.AccessToken != "" {
decData := cr.GetFetchAccessToken(encryptionKey)
newCr.Spec.FetchSource.AccessToken = decData
}
return newCr, nil
}

View File

@@ -1,6 +1,7 @@
package api
import (
b64 "encoding/base64"
"fmt"
"io/ioutil"
"log"
@@ -52,12 +53,6 @@ spec:
qliksense:
- name: acceptEULA
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")
crFile := filepath.Join(ctx1Dir, "contx1.yaml")
@@ -106,21 +101,24 @@ func TestGetDecryptedCr(t *testing.T) {
t.Fail()
t.Log(e)
}
qcr, err := qct.GetCurrentCR()
if err != nil {
t.Fail()
t.Log(err)
}
setuPublicAndPrivateKey(dir)
qcr, err := qct.GetCurrentCR()
key, _ := setupGenerateKey(dir)
ecn, _ := EncryptData([]byte("mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"), key)
b := b64.StdEncoding.EncodeToString(ecn)
qcr.Spec.AddToSecrets("qliksense", "mongodbUri", b, "")
qcr.SetFetchAccessToken("mytoken", key)
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")
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)
@@ -129,77 +127,12 @@ func TestGetDecryptedCr(t *testing.T) {
if decryptedValue == orignalValue {
t.Fail()
}
if newCr.Spec.FetchSource.AccessToken != "mytoken" {
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-----
`)
func setupGenerateKey(homeDir string) (string, error) {
secretKeyPairDir := filepath.Join(homeDir, "secrets", "contexts", "contx1", "secrets")
if err := os.MkdirAll(secretKeyPairDir, 0777); err != nil {
err = fmt.Errorf("Not able to create directories")
@@ -207,19 +140,33 @@ MFxk1pS9LMa/WnzvFr0gWakCAwEAAQ==
}
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
key, _ := LoadSecretKey(secretKeyPairDir)
if key == "" {
return GenerateAndStoreSecretKey(secretKeyPairDir)
}
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
return key, nil
}
func Test_set_and_get_fetch_access_token(t *testing.T) {
td, homeDir := setup()
defer td()
createCRFile(homeDir)
crFile := filepath.Join("contexts", "contx1", "contx1.yaml")
qConfig := NewQConfig(homeDir)
newQ, _ := qConfig.SetCrLocation("contx1", crFile)
newQ.Write()
qConfig = NewQConfig(homeDir)
qcr, _ := qConfig.GetCurrentCR()
key, _ := qConfig.GetEncryptionKeyFor(qcr.GetName())
if err := qcr.SetFetchAccessToken("mytokenbeforeencryption", key); err != nil {
t.Log(err)
t.FailNow()
}
tok := qcr.GetFetchAccessToken(key)
if tok != "mytokenbeforeencryption" {
t.Log("Expected: mytokenbeforeencryption, got: " + tok)
t.Fail()
}
}

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

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

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,7 @@ import (
"io/ioutil"
"log"
"os"
"strings"
"github.com/qlik-oss/k-apis/pkg/config"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -24,8 +25,8 @@ const (
QliksenseDefaultProfile = "docker-desktop"
DefaultRotateKeys = "yes"
QliksenseMetadataName = "QliksenseConfigMetadata"
DefaultMongoDbUri = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
DefaultMongoDbUriKey = "mongoDbUri"
DefaultMongodbUri = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
DefaultMongodbUriKey = "mongodbUri"
)
// AddCommonConfig adds common configs into CRs
@@ -40,7 +41,7 @@ func (qliksenseCR *QliksenseCR) AddCommonConfig(contextName string) {
Profile: QliksenseDefaultProfile,
RotateKeys: DefaultRotateKeys,
}
qliksenseCR.Spec.AddToSecrets("qliksense", DefaultMongoDbUriKey, DefaultMongoDbUri, "")
qliksenseCR.Spec.AddToSecrets("qliksense", DefaultMongodbUriKey, strings.Replace(DefaultMongodbUri, "qlik-default", contextName, 1), "")
}
// AddBaseQliksenseConfigs adds configs into config.yaml
@@ -96,7 +97,7 @@ func WriteToFile(content interface{}, targetFile string) error {
log.Println(err)
return err
}
LogDebugMessage("Wrote content into %s", targetFile)
LogDebugMessage("Wrote content into %s\n", targetFile)
return nil
}
@@ -109,6 +110,7 @@ func ReadFromFile(content interface{}, sourceFile string) error {
if e != nil {
return e
}
defer file.Close()
return ReadFromStream(content, file)
}

View File

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

8
pkg/api/copy.go Normal file
View File

@@ -0,0 +1,8 @@
package api
import "github.com/otiai10/copy"
//copy source directory to destination
func CopyDirectory(source string, dest string) error {
return copy.Copy(source, dest)
}

101
pkg/api/copy_test.go Normal file
View File

@@ -0,0 +1,101 @@
package api
import (
"io/ioutil"
"os"
"path"
"path/filepath"
"testing"
kapis_git "github.com/qlik-oss/k-apis/pkg/git"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/krusty"
"sigs.k8s.io/kustomize/api/types"
)
func TestCopyDirectory(t *testing.T) {
src, _ := ioutil.TempDir("", "")
f1, _ := ioutil.TempFile(src, "")
ioutil.TempFile(src, "")
dest, _ := ioutil.TempDir("", "")
CopyDirectory(src, dest)
if _, err := os.Lstat(filepath.Join(dest, filepath.Base(f1.Name()))); err != nil {
t.Log(err)
t.Fail()
}
}
func TestCopyDirectory_withGit_withKuz(t *testing.T) {
if testing.Short() {
t.Skip("Skipping in short test mode")
}
tmpDir1, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer os.RemoveAll(tmpDir1)
tmpDir2, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer os.RemoveAll(tmpDir2)
repoPath1 := path.Join(tmpDir1, "repo")
repo1, err := kapis_git.CloneRepository(repoPath1, "https://github.com/qlik-oss/qliksense-k8s", nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := CopyDirectory(repoPath1, tmpDir2); err != nil {
t.Fatalf("unexpected error: %v", err)
}
repoPath2 := tmpDir2
repo2, err := kapis_git.OpenRepository(repoPath2)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := kapis_git.Checkout(repo2, "v0.0.2", "", nil); err != nil {
t.Fatalf("unexpected error: %v", err)
}
repo2Manifest, err := kuz(path.Join(repoPath2, "manifests", "docker-desktop"))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := kapis_git.Checkout(repo1, "v0.0.2", "", nil); err != nil {
t.Fatalf("unexpected error: %v", err)
}
repo1Manifest, err := kuz(path.Join(repoPath1, "manifests", "docker-desktop"))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(repo2Manifest) != string(repo1Manifest) {
t.Logf("manifest generated on the original config:\n%v", string(repo1Manifest))
t.Logf("manifest generated on the copied config:\n%v", string(repo2Manifest))
t.Fatal("expected manifests to be equal, but they were not")
}
}
func kuz(directory string) ([]byte, error) {
options := &krusty.Options{
DoLegacyResourceSort: false,
LoadRestrictions: types.LoadRestrictionsNone,
DoPrune: false,
PluginConfig: konfig.DisabledPluginConfig(),
}
k := krusty.MakeKustomizer(filesys.MakeFsOnDisk(), options)
resMap, err := k.Run(directory)
if err != nil {
return nil, err
}
return resMap.AsYaml()
}

View File

@@ -1,7 +1,6 @@
package api
import (
"crypto/rsa"
"encoding/base64"
"encoding/json"
"errors"
@@ -27,14 +26,14 @@ func (kdcjt *k8sDockerConfigJsonType) GenerateAuth() {
}
type DockerConfigJsonSecret struct {
Name string
Uri string
Username string
Password string
Email string
Name string
Uri string
Username string
Password string
Email string
}
func (d *DockerConfigJsonSecret) ToYaml(encryptionKey *rsa.PublicKey) ([]byte, error) {
func (d *DockerConfigJsonSecret) ToYaml(encryptionKey string) ([]byte, error) {
k8sDockerConfigJson := k8sDockerConfigJsonType{
Username: d.Username,
Password: d.Password,
@@ -51,8 +50,8 @@ func (d *DockerConfigJsonSecret) ToYaml(encryptionKey *rsa.PublicKey) ([]byte, e
return nil, err
}
var k8sDockerConfigJsonMapMaybeEncryptedBytes []byte
if encryptionKey != nil {
if k8sDockerConfigJsonMapMaybeEncryptedBytes, err = Encrypt(k8sDockerConfigJsonMapBytes, encryptionKey); err != nil {
if encryptionKey != "" {
if k8sDockerConfigJsonMapMaybeEncryptedBytes, err = EncryptData(k8sDockerConfigJsonMapBytes, encryptionKey); err != nil {
return nil, err
}
} else {
@@ -65,7 +64,7 @@ func (d *DockerConfigJsonSecret) ToYaml(encryptionKey *rsa.PublicKey) ([]byte, e
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: d.Name,
Name: d.Name,
},
Type: v1.SecretTypeDockerConfigJson,
Data: map[string][]byte{
@@ -76,7 +75,7 @@ func (d *DockerConfigJsonSecret) ToYaml(encryptionKey *rsa.PublicKey) ([]byte, e
return K8sSecretToYaml(k8sSecret)
}
func (d *DockerConfigJsonSecret) FromYaml(secretBytes []byte, decryptionKey *rsa.PrivateKey) error {
func (d *DockerConfigJsonSecret) FromYaml(secretBytes []byte, decryptionKey string) error {
k8sDockerConfigJsonMap := k8sDockerConfigJsonMapType{}
if k8sSecret, err := K8sSecretFromYaml(secretBytes); err != nil {
return err
@@ -86,7 +85,7 @@ func (d *DockerConfigJsonSecret) FromYaml(secretBytes []byte, decryptionKey *rsa
return errors.New("not a kubernetes.io/dockerconfigjson type")
} else if k8sDockerConfigJsonMapEncryptedBytes, ok := k8sSecret.Data[".dockerconfigjson"]; !ok {
return errors.New("secret data is missing a value for the .dockerconfigjson key")
} else if k8sDockerConfigJsonMapBytes, err := Decrypt(k8sDockerConfigJsonMapEncryptedBytes, decryptionKey); err != nil {
} else if k8sDockerConfigJsonMapBytes, err := DecryptData(k8sDockerConfigJsonMapEncryptedBytes, decryptionKey); err != nil {
return errors.New("secret data is missing a value for the .dockerconfigjson key")
} else if err := json.Unmarshal(k8sDockerConfigJsonMapBytes, &k8sDockerConfigJsonMap); err != nil {
return err

View File

@@ -1,34 +1,32 @@
package api
import (
"crypto/rand"
"crypto/rsa"
"encoding/base64"
"encoding/json"
"fmt"
"reflect"
"testing"
"gopkg.in/yaml.v3"
"gopkg.in/yaml.v2"
)
func TestDockerConfigJsonSecret(t *testing.T) {
dockerConfigJsonSecret := DockerConfigJsonSecret{
Name: "some-name",
Uri: "some-uri",
Username: "some-username",
Password: "some-password",
Email: "some-email",
Name: "some-name",
Uri: "some-uri",
Username: "some-username",
Password: "some-password",
Email: "some-email",
}
dockerConfigJsonSecretFromYaml := DockerConfigJsonSecret{}
validYamlMap := map[string]interface{}{}
privateKey, err := rsa.GenerateKey(rand.Reader, RSA_KEY_LENGTH)
encryptionKey, err := GenerateKey()
if err != nil {
t.Fatalf("error generating RSA private key: %v\n", err)
}
dockerConfigJsonSecretYamlBytes, err := dockerConfigJsonSecret.ToYaml(&privateKey.PublicKey)
dockerConfigJsonSecretYamlBytes, err := dockerConfigJsonSecret.ToYaml(encryptionKey)
dockerConfigJsonMap := map[string]interface{}{}
if err != nil {
t.Fatalf("error converting secret to yaml: %v", err)
@@ -36,25 +34,25 @@ func TestDockerConfigJsonSecret(t *testing.T) {
t.Fatalf("error unmarshalling yaml string: %v, error: %v", string(dockerConfigJsonSecretYamlBytes), err)
} else if validYamlMap["apiVersion"] != "v1" ||
validYamlMap["kind"] != "Secret" ||
validYamlMap["metadata"].(map[string]interface{})["name"] != dockerConfigJsonSecret.Name ||
validYamlMap["metadata"].(map[interface {}]interface {})["name"] != dockerConfigJsonSecret.Name ||
validYamlMap["type"] != "kubernetes.io/dockerconfigjson" {
t.Fatalf("error verifying validity of secret yaml: %v", string(dockerConfigJsonSecretYamlBytes))
} else if dockerConfigJsonBytesBase64, ok := validYamlMap["data"].(map[string]interface{})[".dockerconfigjson"]; !ok {
} else if dockerConfigJsonBytesBase64, ok := validYamlMap["data"].(map[interface {}]interface {})[".dockerconfigjson"]; !ok {
t.Fatalf("no .dockerconfigjson data key in the secret yaml: %v", string(dockerConfigJsonSecretYamlBytes))
} else if dockerConfigJsonEncryptedBytes, err := base64.StdEncoding.DecodeString(dockerConfigJsonBytesBase64.(string)); err != nil {
t.Fatalf("error decoding dockerConfigJsonBytes from base64: %v", err)
} else if dockerConfigJsonBytes, err := Decrypt(dockerConfigJsonEncryptedBytes, privateKey); err != nil {
} else if dockerConfigJsonBytes, err := DecryptData(dockerConfigJsonEncryptedBytes, encryptionKey); err != nil {
t.Fatalf("error decrypting dockerConfigJsonBytes: %v", err)
} else if err := json.Unmarshal(dockerConfigJsonBytes, &dockerConfigJsonMap); err != nil {
t.Fatalf("error unmarshalling dockerConfigJson from json: %v", err)
} else if dockerConfigJson, ok := dockerConfigJsonMap["auths"].(map[string]interface{})[dockerConfigJsonSecret.Uri]; !ok {
} else if dockerConfigJson, ok := dockerConfigJsonMap["auths"].(map[string]interface {})[dockerConfigJsonSecret.Uri]; !ok {
t.Fatalf("dockerConfigJson map does not contain data for the registry: %v", dockerConfigJsonSecret.Uri)
} else if dockerConfigJson.(map[string]interface{})["username"] != dockerConfigJsonSecret.Username ||
dockerConfigJson.(map[string]interface{})["password"] != dockerConfigJsonSecret.Password ||
dockerConfigJson.(map[string]interface{})["email"] != dockerConfigJsonSecret.Email {
} else if dockerConfigJson.(map[string]interface {})["username"] != dockerConfigJsonSecret.Username ||
dockerConfigJson.(map[string]interface {})["password"] != dockerConfigJsonSecret.Password ||
dockerConfigJson.(map[string]interface {})["email"] != dockerConfigJsonSecret.Email {
t.Fatal("dockerConfigJson map does not contain expected values")
} else {
authBase64 := dockerConfigJson.(map[string]interface{})["auth"]
authBase64 := dockerConfigJson.(map[string]interface {})["auth"]
if auth, err := base64.StdEncoding.DecodeString(authBase64.(string)); err != nil {
t.Fatal("error base64 decoding auth value")
} else if string(auth) != fmt.Sprintf("%s:%s", dockerConfigJsonSecret.Username, dockerConfigJsonSecret.Password) {
@@ -63,7 +61,7 @@ func TestDockerConfigJsonSecret(t *testing.T) {
}
t.Logf("dockerConfigJsonSecretYaml: \n%v\n", string(dockerConfigJsonSecretYamlBytes))
if err := dockerConfigJsonSecretFromYaml.FromYaml(dockerConfigJsonSecretYamlBytes, privateKey); err != nil {
if err := dockerConfigJsonSecretFromYaml.FromYaml(dockerConfigJsonSecretYamlBytes, encryptionKey); err != nil {
t.Fatalf("error reading secret in from yaml: %v", err)
} else if !reflect.DeepEqual(dockerConfigJsonSecret, dockerConfigJsonSecretFromYaml) {
t.Fatalf("secret: %v does not equal secret: %v", dockerConfigJsonSecret, dockerConfigJsonSecretFromYaml)

View File

@@ -1,58 +1,42 @@
package api
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"encoding/hex"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"path/filepath"
)
const (
RSA_KEY_LENGTH = 4096
QliksensePublicKey = "qliksensePub"
QliksensePrivateKey = "qliksensePriv"
key_file_name = "user_secret_key"
)
// GenerateAndStoreSecretKeypair generates and stores key pairs
func GenerateAndStoreSecretKeypair(secretsPath string) error {
LogDebugMessage("%s exists", secretsPath)
// creating contexts/qlik-default/secrets/qliksensePub and contexts/qlik-default/secrets/qliksensePriv files
publicKeyFilePath := filepath.Join(secretsPath, QliksensePublicKey)
privateKeyFilePath := filepath.Join(secretsPath, QliksensePrivateKey)
LogDebugMessage("Generating public-private key pair.....")
GenerateRSAEncryptionKeys(publicKeyFilePath, privateKeyFilePath)
LogDebugMessage("Generated public-private key pairs")
return nil
}
// GenerateRSAEncryptionKeys is used to generate a new public-private key pair
func GenerateRSAEncryptionKeys(publicKeyFilePath, privateKeyFilePath string) error {
LogDebugMessage("Generating new RSA key pair")
privateKey, err := rsa.GenerateKey(rand.Reader, RSA_KEY_LENGTH)
// GenerateAndStoreSecretKey generates and stores key
func GenerateAndStoreSecretKey(secretsDir string) (string, error) {
// creating contexts/qlik-default/secrets/user_secret_key
keyFile := filepath.Join(secretsDir, key_file_name)
key, err := GenerateKey()
if err != nil {
log.Printf("error generating RSA private key: %v\n", err)
return err
return "", err
}
privateKeyPEM := EncodePrivateKey(privateKey)
if err := writeContentToFile(privateKeyPEM, privateKeyFilePath); err != nil {
return err
if err := writeContentToFile([]byte(key), keyFile); err != nil {
return "", err
}
pubKeyPEM, err2 := EncodePublicKey(&privateKey.PublicKey)
if err2 != nil {
log.Printf("error occurred when encoding public key: %v\n", err2)
return err2
return key, nil
}
func LoadSecretKey(secretsDir string) (string, error) {
keyFile := filepath.Join(secretsDir, key_file_name)
by, err := ioutil.ReadFile(keyFile)
if err != nil {
return "", err
}
if err := writeContentToFile(pubKeyPEM, publicKeyFilePath); err != nil {
return err
}
return nil
return string(by), nil
}
// writeContentToFile writes keys to a file
@@ -65,104 +49,54 @@ func writeContentToFile(keyData []byte, fileName string) error {
return nil
}
// Encrypt encrypts data with public key
func Encrypt(pt []byte, pub *rsa.PublicKey) ([]byte, error) {
//hash := sha512.New()
//ct, err := rsa.EncryptOAEP(hash, rand.Reader, pub, pt, nil)
ct, err := rsa.EncryptPKCS1v15(rand.Reader, pub, pt)
func GenerateKey() (string, error) {
salt := make([]byte, 32)
if _, err := rand.Read(salt); err != nil {
return "", err
}
s := fmt.Sprintf("%x", salt)
return s, nil
}
func EncryptData(plaintext []byte, userKey string) ([]byte, error) {
key, _ := hex.DecodeString(userKey)
block, err := aes.NewCipher(key)
if err != nil {
log.Println(err)
return nil, err
}
return ct, nil
}
// Decrypt decrypts data with private key
func Decrypt(ct []byte, priv *rsa.PrivateKey) ([]byte, error) {
// hash := sha512.New()
// plaintext, err := rsa.DecryptOAEP(hash, rand.Reader, priv, ciphertext, nil)
pt, err := rsa.DecryptPKCS1v15(rand.Reader, priv, ct)
aesgcm, err := cipher.NewGCM(block)
if err != nil {
log.Println(err)
return nil, err
}
return pt, nil
nonce := make([]byte, aesgcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
return aesgcm.Seal(nonce, nonce, plaintext, nil), nil
}
// EncodePrivateKey private key to bytes
func EncodePrivateKey(priv *rsa.PrivateKey) []byte {
privBytes := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(priv),
},
)
return privBytes
}
// EncodePublicKey public key to bytes
func EncodePublicKey(pub *rsa.PublicKey) ([]byte, error) {
pubASN1, err := x509.MarshalPKIXPublicKey(pub)
func DecryptData(ciphertext []byte, userKey string) ([]byte, error) {
key, _ := hex.DecodeString(userKey)
block, err := aes.NewCipher(key)
if err != nil {
log.Println(err)
return nil, err
}
pubBytes := pem.EncodeToMemory(&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: pubASN1,
})
return pubBytes, nil
}
// DecodeToPrivateKey bytes to private key
func DecodeToPrivateKey(priv []byte) (*rsa.PrivateKey, error) {
block, _ := pem.Decode(priv)
enc := x509.IsEncryptedPEMBlock(block)
b := block.Bytes
var err error
if enc {
log.Println("is encrypted pem block")
b, err = x509.DecryptPEMBlock(block, nil)
if err != nil {
log.Println(err)
return nil, err
}
}
key, err := x509.ParsePKCS1PrivateKey(b)
aesgcm, err := cipher.NewGCM(block)
if err != nil {
log.Println(err)
return nil, err
}
return key, nil
}
// DecodeToPublicKey bytes to public key
func DecodeToPublicKey(pub []byte) (*rsa.PublicKey, error) {
block, _ := pem.Decode(pub)
enc := x509.IsEncryptedPEMBlock(block)
b := block.Bytes
var err error
if enc {
log.Println("is encrypted pem block")
b, err = x509.DecryptPEMBlock(block, nil)
if err != nil {
log.Println(err)
return nil, err
}
nonceSize := aesgcm.NonceSize()
if len(ciphertext) < nonceSize {
return nil, errors.New("ciphertext too short")
}
iface, err := x509.ParsePKIXPublicKey(b)
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
log.Println(err)
return nil, err
}
key, ok := iface.(*rsa.PublicKey)
if !ok {
err := fmt.Errorf("Unable to decode public key")
log.Println(err)
return nil, err
}
return key, nil
return plaintext, nil
}

View File

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

View File

@@ -9,18 +9,26 @@ import (
"strings"
)
// KubectlApply create resoruces in the provided namespace,
// KubectlApply create resources in the provided namespace,
// if namespace="" then use whatever the kubectl default is
func KubectlApply(manifests, namespace string) error {
return kubectlOperation(manifests, "apply", namespace)
}
// KubectlDelete delete resoruces in the provided namespace,
func KubectlApplyVerbose(manifests, namespace string, verbose bool) error {
return kubectlOperationVerbose(manifests, "apply", namespace, verbose)
}
// KubectlDelete delete resources in the provided namespace,
// if namespace="" then use whatever the kubectl default is
func KubectlDelete(manifests, namespace string) error {
return kubectlOperation(manifests, "delete", namespace)
}
func KubectlDeleteVerbose(manifests, namespace string, verbose bool) error {
return kubectlOperationVerbose(manifests, "delete", namespace, verbose)
}
func GetKubectlNamespace() string {
namespace := ""
cmd := exec.Command("kubectl", "config", "current-context")
@@ -61,6 +69,10 @@ func SetKubectlNamespace(ns string) {
}
func kubectlOperation(manifests string, oprName string, namespace string) error {
return kubectlOperationVerbose(manifests, oprName, namespace, true)
}
func kubectlOperationVerbose(manifests string, oprName string, namespace string, verbose bool) error {
tempYaml, err := ioutil.TempFile("", "")
if err != nil {
fmt.Println("cannot create file ", err)
@@ -88,12 +100,16 @@ func kubectlOperation(manifests string, oprName string, namespace string) error
}
sterrBuffer := &bytes.Buffer{}
cmd.Stdout = os.Stdout
stoutBuffer := &bytes.Buffer{}
cmd.Stdout = stoutBuffer
cmd.Stderr = sterrBuffer
err = cmd.Run()
if err != nil {
return fmt.Errorf("kubectl %v failed with: %v, %v, temp k8s yaml file:%v\n", oprName, err, sterrBuffer.String(), tempYaml.Name())
}
if verbose {
fmt.Println(stoutBuffer.String())
}
os.Remove(tempYaml.Name())
return nil
}

View File

@@ -1,10 +1,11 @@
package preflight
package api
import (
"os"
"path"
"path/filepath"
"strings"
api "github.com/qlik-oss/sense-installer/pkg/api"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -16,8 +17,9 @@ type PreflightConfig struct {
}
type PreflightSpec struct {
MinK8sVersion string `json:"minK8sVersion,omitempty" yaml:"minK8sVersion,omitempty"`
Images map[string]string `json:"images,omitempty" yaml:"images,omitempty"`
MinK8sVersion string `json:"minK8sVersion,omitempty" yaml:"minK8sVersion,omitempty"`
MinMongoVersion string `json:"minMongoVersion,omitempty" yaml:"minMongoVersion,omitempty"`
Images map[string]string `json:"images,omitempty" yaml:"images,omitempty"`
}
//NewPreflightConfigEmpty create empty PreflightConfig object
@@ -43,8 +45,10 @@ func NewPreflightConfig(qHome string) *PreflightConfig {
if _, err := os.Lstat(conFile); err != nil {
return p
}
p = &PreflightConfig{}
if err := api.ReadFromFile(p, conFile); err != nil {
p = &PreflightConfig{
QliksenseHomePath: qHome,
}
if err := ReadFromFile(p, conFile); err != nil {
return nil
}
return p
@@ -61,7 +65,7 @@ func (p *PreflightConfig) Write() error {
if err := os.MkdirAll(pDir, os.ModePerm); err != nil {
return err
}
return api.WriteToFile(p, p.GetConfigFilePath())
return WriteToFile(p, p.GetConfigFilePath())
}
func (p *PreflightConfig) AddMinK8sV(version string) {
@@ -71,6 +75,13 @@ func (p *PreflightConfig) AddMinK8sV(version string) {
p.Spec.MinK8sVersion = version
}
func (p *PreflightConfig) AddMinMongoV(version string) {
if p.Spec == nil {
p.Spec = &PreflightSpec{}
}
p.Spec.MinMongoVersion = version
}
func (p *PreflightConfig) AddImage(imageFor, imageName string) {
if p.Spec.Images == nil {
p.Spec.Images = make(map[string]string)
@@ -78,15 +89,31 @@ func (p *PreflightConfig) AddImage(imageFor, imageName string) {
p.Spec.Images[imageFor] = imageName
}
func (p *PreflightConfig) GetImageName(imageFor string) string {
func (p *PreflightConfig) GetImageName(imageFor string, accountForImageRegistry bool) (string, error) {
if p.Spec.Images == nil {
return ""
return "", nil
}
return p.Spec.Images[imageFor]
image := p.Spec.Images[imageFor]
if accountForImageRegistry {
qConfig := NewQConfig(p.QliksenseHomePath)
if currentCR, err := qConfig.GetCurrentCR(); err != nil {
return "", err
} else if imageRegistry := currentCR.Spec.GetImageRegistry(); imageRegistry != "" {
imageSegments := strings.Split(image, "/")
imageNameAndTag := imageSegments[len(imageSegments)-1]
return path.Join(imageRegistry, imageNameAndTag), nil
}
}
return image, nil
}
func (p *PreflightConfig) GetMinK8sVersion() string {
return p.Spec.MinK8sVersion
}
func (p *PreflightConfig) GetMinMongoVersion() string {
return p.Spec.MinMongoVersion
}
func (p *PreflightConfig) IsExistOnDisk() bool {
if _, err := os.Lstat(p.GetConfigFilePath()); err != nil {
return false
@@ -103,7 +130,9 @@ func (p *PreflightConfig) Initialize() error {
return nil
}
p.AddMinK8sV("1.15")
p.AddMinMongoV("3.6")
p.AddImage("nginx", "nginx")
p.AddImage("netcat", "subfuzion/netcat")
p.AddImage("preflight-mongo", "qlik-docker-oss.bintray.io/preflight-mongo")
return p.Write()
}

View File

@@ -0,0 +1,138 @@
package api
import (
"fmt"
"io/ioutil"
"os"
"path"
"testing"
)
func Test_Initalize(t *testing.T) {
testCases := []struct {
name string
validate func(t *testing.T, tempDir string)
}{
{
name: "without account for imageRegistry",
validate: func(t *testing.T, tempDir string) {
preflightConfig := NewPreflightConfig(tempDir)
imageName, err := preflightConfig.GetImageName("test", false)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if imageName != "testimage" {
t.Fatalf("expected image name: testimage, got: %v", imageName)
}
},
},
{
name: "with account for configured imageRegistry",
validate: func(t *testing.T, tempDir string) {
registry := "registryFoo"
setupQliksenseTestDefaultContext(t, tempDir, fmt.Sprintf(`
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: qlik-default
spec:
configs:
qliksense:
- name: imageRegistry
value: %v
`, registry))
preflightConfig := NewPreflightConfig(tempDir)
imageName, err := preflightConfig.GetImageName("test", true)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expectedImageName := fmt.Sprintf("%v/testimage", registry)
if imageName != expectedImageName {
t.Fatalf("expected image name: %v, got: %v", expectedImageName, imageName)
}
},
},
{
name: "with account for un-configured imageRegistry",
validate: func(t *testing.T, tempDir string) {
setupQliksenseTestDefaultContext(t, tempDir, `
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: qlik-default
spec:
configs:
qliksense:
- name: something
value: other
`)
preflightConfig := NewPreflightConfig(tempDir)
imageName, err := preflightConfig.GetImageName("test", true)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expectedImageName := "testimage"
if imageName != expectedImageName {
t.Fatalf("expected image name: %v, got: %v", expectedImageName, imageName)
}
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
tempDir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDir)
setupPreflightConfig(t, tempDir)
testCase.validate(t, tempDir)
})
}
}
func setupPreflightConfig(t *testing.T, tempDir string) {
pf := NewPreflightConfig(tempDir)
if err := pf.Initialize(); err != nil {
t.Fatal(err)
}
p := &PreflightConfig{
QliksenseHomePath: tempDir,
}
if err := ReadFromFile(p, pf.GetConfigFilePath()); err != nil {
t.Fatal(err)
}
if p.GetMinK8sVersion() != "1.15" {
t.Fatalf("expected k8 version: 1.15, but got " + p.GetMinK8sVersion())
}
p.AddImage("test", "testimage")
if err := p.Write(); err != nil {
t.Fatal(err)
}
}
func setupQliksenseTestDefaultContext(t *testing.T, tmpQlikSenseHome, CR string) {
if err := ioutil.WriteFile(path.Join(tmpQlikSenseHome, "config.yaml"), []byte(`
apiVersion: config.qlik.com/v1
kind: QliksenseConfig
metadata:
name: QliksenseConfigMetadata
spec:
contexts:
- name: qlik-default
crFile: contexts/qlik-default/qlik-default.yaml
currentContext: qlik-default
`), os.ModePerm); err != nil {
t.Fatalf("unexpected error: %v", err)
}
defaultContextDir := path.Join(tmpQlikSenseHome, "contexts", "qlik-default")
if err := os.MkdirAll(defaultContextDir, os.ModePerm); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := ioutil.WriteFile(path.Join(defaultContextDir, "qlik-default.yaml"), []byte(CR), os.ModePerm); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}

View File

@@ -4,6 +4,7 @@ import (
"archive/tar"
"archive/zip"
"compress/gzip"
b64 "encoding/base64"
"fmt"
"io"
"io/ioutil"
@@ -23,7 +24,7 @@ func checkExists(filename string) os.FileInfo {
if os.IsNotExist(err) {
return nil
}
LogDebugMessage("File exists")
LogDebugMessage("File exists\n")
return info
}
@@ -46,7 +47,7 @@ func DirExists(dirname string) bool {
// LogDebugMessage logs a debug message
func LogDebugMessage(strMessage string, args ...interface{}) {
if os.Getenv("QLIKSENSE_DEBUG") == "true" {
log.Printf(strMessage, args...)
fmt.Printf(strMessage, args...)
}
}
@@ -62,32 +63,63 @@ func ReadKeys(keyFile string) ([]byte, error) {
}
// ProcessConfigArgs processes args and returns an service, key, value slice
func ProcessConfigArgs(args []string) ([]*ServiceKeyValue, error) {
func ProcessConfigArgs(args []string, base64Encoded bool) ([]*ServiceKeyValue, error) {
// prepare received args
// split args[0] into key and value
if len(args) == 0 {
err := fmt.Errorf("No args were provided. Please provide args to configure the current context")
return nil, err
}
notValidErr := fmt.Errorf("Please provide valid args for this command")
resultSvcKV := make([]*ServiceKeyValue, len(args))
re1 := regexp.MustCompile(`([\w\-]{1,}).([\w\-]{1,})=("*[\w\-?=_/:0-9\.]+"*)`)
// qliksense.mongodb=somethig
for i, arg := range args {
LogDebugMessage("Arg received: %s", arg)
result := re1.FindStringSubmatch(arg)
// check if result array's length is == 4 (index 0 - is the full match & indices 1,2,3- are the fields we need)
if len(result) != 4 {
err := fmt.Errorf("Please provide valid args for this command")
return nil, err
LogDebugMessage("Arg received: %s\n", arg)
first := strings.SplitN(arg, "=", 2)
if len(first) != 2 {
return nil, notValidErr
}
svcKey := getSvcAndKey(first[0])
if len(svcKey) != 2 {
return nil, notValidErr
}
resultValue := strings.Trim(first[1], "\"")
if base64Encoded {
if decodeByte, err := b64.StdEncoding.DecodeString(resultValue); err != nil {
return nil, err
} else {
resultValue = strings.Trim(string(decodeByte), "\n ")
}
}
resultSvcKV[i] = &ServiceKeyValue{
SvcName: result[1],
Key: result[2],
Value: strings.ReplaceAll(result[3], `"`, ""),
SvcName: svcKey[0],
Key: svcKey[1],
Value: resultValue,
}
}
return resultSvcKV, nil
}
// input should be svc[key]
func getSvcAndKey(arg string) []string {
// for key
re := regexp.MustCompile(`\[(.*)\]`)
// for service
re2 := regexp.MustCompile(`(.*)\[`)
keys := re.FindStringSubmatch(arg)
svcs := re2.FindStringSubmatch(arg)
if len(svcs) != 2 || len(keys) != 2 {
return strings.SplitN(arg, ".", 2)
}
if svcs[1] == "" || keys[1] == "" {
return []string{}
}
return []string{svcs[1], keys[1]}
}
func ExecuteTaskWithBlinkingStdoutFeedback(task func() (interface{}, error), feedback string) (result interface{}, err error) {
taskDone := make(chan bool)
go func() {

View File

@@ -11,11 +11,12 @@ func TestProcessConfigArgs(t *testing.T) {
"test-dash.dash-key=value-dash",
"test-dot.dot-key=127.0.0.1",
"test123.key123=value123",
"test-equal.keyequal=newvalue=@hj",
}
expectedKeys := []string{"mongodb", "test", "dash-key", "dot-key", "key123"}
expectedValue := []string{"mongouri://something?ffall", "value_under", "value-dash", "127.0.0.1", "value123"}
exppectedSvc := []string{"qliksense", "test_under", "test-dash", "test-dot", "test123"}
sv, err := ProcessConfigArgs(args)
expectedKeys := []string{"mongodb", "test", "dash-key", "dot-key", "key123", "keyequal"}
expectedValue := []string{"mongouri://something?ffall", "value_under", "value-dash", "127.0.0.1", "value123", "newvalue=@hj"}
exppectedSvc := []string{"qliksense", "test_under", "test-dash", "test-dot", "test123", "test-equal"}
sv, err := ProcessConfigArgs(args, false)
if err != nil {
t.Log(err)
t.FailNow()
@@ -44,3 +45,24 @@ func contains(arr []string, str string) bool {
}
return false
}
func TestGetSvcAndKey(t *testing.T) {
s1 := "qliksense[tls.cert]"
sa := getSvcAndKey(s1)
if sa[0] != "qliksense" || sa[1] != "tls.cert" {
t.Fail()
t.Logf("expected service: qliksense but got %s", sa[0])
t.Logf("expected key: tls.cert but got %s", sa[1])
}
s1 = "qliksense-idps.tls"
sa = getSvcAndKey(s1)
for _, s := range sa {
t.Logf("|%s|", s)
}
if sa[0] != "qliksense-idps" || sa[1] != "tls" {
t.Fail()
t.Logf("expected service: qliksense-idps but got %s", sa[0])
t.Logf("expected key: tls but got %s", sa[1])
}
}

View File

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

View File

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

View File

@@ -2,96 +2,110 @@ package preflight
import (
"fmt"
. "github.com/logrusorgru/aurora"
ansi "github.com/mattn/go-colorable"
"github.com/pkg/errors"
)
func (qp *QliksensePreflight) RunAllPreflightChecks(namespace string, kubeConfigContents []byte, mongodbUrl string) {
func (qp *QliksensePreflight) RunAllPreflightChecks(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions) error {
checkCount := 0
totalCount := 0
out := ansi.NewColorableStdout()
// Preflight minimum kuberenetes version check
fmt.Printf("\nPreflight kubernetes minimum version check\n")
fmt.Println("------------------------------------------")
if err := qp.CheckK8sVersion(namespace, kubeConfigContents); err != nil {
fmt.Printf("Preflight kubernetes minimum version check: FAILED\n")
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++
}
totalCount++
// Preflight deployment check
fmt.Printf("\nPreflight deployment check\n")
fmt.Println("--------------------------")
if err := qp.CheckDeployment(namespace, kubeConfigContents); err != nil {
fmt.Printf("Preflight deployment check: FAILED\n")
if err := qp.CheckDeployment(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++
}
totalCount++
// Preflight service check
fmt.Printf("\nPreflight service check\n")
fmt.Println("-----------------------")
if err := qp.CheckService(namespace, kubeConfigContents); err != nil {
fmt.Printf("Preflight service check: FAILED\n")
if err := qp.CheckService(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++
}
totalCount++
// Preflight pod check
fmt.Printf("\nPreflight pod check\n")
fmt.Println("-----------------------")
if err := qp.CheckPod(namespace, kubeConfigContents); err != nil {
fmt.Printf("Preflight pod check: FAILED\n")
if err := qp.CheckPod(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++
}
totalCount++
// Preflight role check
fmt.Printf("\nPreflight role check\n")
fmt.Println("--------------------------")
if err := qp.CheckCreateRole(namespace); err != nil {
fmt.Printf("Preflight role check: FAILED\n")
if err := qp.CheckCreateRole(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++
}
totalCount++
// Preflight rolebinding check
fmt.Printf("\nPreflight rolebinding check\n")
fmt.Println("---------------------------------")
if err := qp.CheckCreateRoleBinding(namespace); err != nil {
fmt.Printf("Preflight rolebinding check: FAILED\n")
if err := qp.CheckCreateRoleBinding(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++
}
totalCount++
// Preflight serviceaccount check
fmt.Printf("\nPreflight serviceaccount check\n")
fmt.Println("------------------------------------")
if err := qp.CheckCreateServiceAccount(namespace); err != nil {
fmt.Printf("Preflight serviceaccount check: FAILED\n")
if err := qp.CheckCreateServiceAccount(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++
}
totalCount++
// Preflight mongo check
fmt.Printf("\nPreflight mongo check\n")
fmt.Println("---------------------")
if err := qp.CheckMongo(kubeConfigContents, namespace, mongodbUrl); err != nil {
fmt.Printf("Preflight mongo check: FAILED\n")
if err := qp.CheckMongo(kubeConfigContents, namespace, preflightOpts, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++
}
totalCount++
// Preflight DNS check
fmt.Printf("\nPreflight DNS check\n")
fmt.Println("-------------------")
if err := qp.CheckDns(namespace, kubeConfigContents); err != nil {
fmt.Printf("Preflight DNS check: FAILED\n")
if err := qp.CheckDns(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++
}
totalCount++
if checkCount == 9 {
fmt.Printf("\nAll preflight checks have PASSED\n")
} else {
fmt.Printf("\n1 or more preflight checks have FAILED\n")
if checkCount == totalCount {
// All preflight checks were successful
return nil
}
fmt.Println("Completed running all preflight checks")
return errors.New("1 or more preflight checks have FAILED")
}

View File

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

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"strings"
"github.com/qlik-oss/sense-installer/pkg/api"
"k8s.io/client-go/kubernetes"
)
const (
@@ -12,70 +12,99 @@ const (
netcat = "netcat"
)
func (qp *QliksensePreflight) CheckDns(namespace string, kubeConfigContents []byte) error {
clientset, clientConfig, err := getK8SClientSet(kubeConfigContents, "")
if err != nil {
err = fmt.Errorf("error: unable to create a kubernetes client: %v\n", err)
fmt.Println(err)
return err
}
// creating deployment
func (p *QliksensePreflight) CheckDns(namespace string, kubeConfigContents []byte, cleanup bool) error {
depName := "dep-dns-preflight-check"
dnsDeployment, err := createPreflightTestDeployment(clientset, namespace, depName, qp.GetPreflightConfigObj().GetImageName(nginx))
serviceName := "svc-dns-pf-check"
podName := "pf-pod-1"
if !cleanup {
fmt.Print("Preflight DNS check... ")
p.CG.LogVerboseMessage("\n------------------- \n")
}
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
if err != nil {
err = fmt.Errorf("error: unable to create deployment: %v\n", err)
fmt.Println(err)
err = fmt.Errorf("unable to create a kubernetes client: %v\n", err)
return err
}
defer deleteDeployment(clientset, namespace, depName)
if err := waitForDeployment(clientset, namespace, dnsDeployment); err != nil {
// delete the deployment we are going to create, if it already exists in the cluster
p.runDNSCleanup(clientset, namespace, podName, serviceName, depName)
if cleanup {
return nil
}
// creating deployment
nginxImageName, err := p.GetPreflightConfigObj().GetImageName(nginx, true)
if err != nil {
return err
}
dnsDeployment, err := p.CG.CreatePreflightTestDeployment(clientset, namespace, depName, nginxImageName)
if err != nil {
err = fmt.Errorf("unable to create deployment: %v\n", err)
return err
}
defer p.CG.DeleteDeployment(clientset, namespace, depName)
if err := p.CG.WaitForDeployment(clientset, namespace, dnsDeployment); err != nil {
return err
}
// creating service
serviceName := "svc-dns-pf-check"
dnsService, err := createPreflightTestService(clientset, namespace, serviceName)
dnsService, err := p.CG.CreatePreflightTestService(clientset, namespace, serviceName)
if err != nil {
err = fmt.Errorf("error: unable to create service : %s\n", serviceName)
err = fmt.Errorf("unable to create service : %s, %s\n", serviceName, err)
return err
}
defer deleteService(clientset, namespace, serviceName)
defer p.CG.DeleteService(clientset, namespace, serviceName)
// create a pod
podName := "pf-pod-1"
dnsPod, err := createPreflightTestPod(clientset, namespace, podName, qp.GetPreflightConfigObj().GetImageName(netcat))
commandToRun := []string{"sh", "-c", "sleep 10; nc -z -v -w 1 " + dnsService.Name + " 80"}
netcatImageName, err := p.GetPreflightConfigObj().GetImageName(netcat, true)
if err != nil {
err = fmt.Errorf("error: unable to create pod : %s\n", podName)
err = fmt.Errorf("unable to retrieve image : %v\n", err)
return err
}
defer deletePod(clientset, namespace, podName)
if err := waitForPod(clientset, namespace, dnsPod); err != nil {
dnsPod, err := p.CG.CreatePreflightTestPod(clientset, namespace, podName, netcatImageName, nil, commandToRun)
if err != nil {
err = fmt.Errorf("unable to create pod : %s, %s\n", podName, err)
return err
}
defer p.CG.DeletePod(clientset, namespace, podName)
if err := p.CG.WaitForPod(clientset, namespace, dnsPod); err != nil {
return err
}
if len(dnsPod.Spec.Containers) == 0 {
err := fmt.Errorf("error: there are no containers in the pod")
fmt.Println(err)
err := fmt.Errorf("there are no containers in the pod")
return err
}
api.LogDebugMessage("Exec-ing into the container...")
stdout, stderr, err := executeRemoteCommand(clientset, clientConfig, dnsPod.Name, dnsPod.Spec.Containers[0].Name, namespace, []string{"nc", "-z", "-v", "-w 1", dnsService.Name, "80"})
p.CG.WaitForPodToDie(clientset, namespace, dnsPod)
logStr, err := p.CG.GetPodLogs(clientset, dnsPod)
if err != nil {
err = fmt.Errorf("error: unable to execute dns check in the cluster: %v", err)
fmt.Println(err)
err = fmt.Errorf("unable to execute dns check in the cluster: %v", err)
return err
}
if strings.HasSuffix(stdout, "succeeded!") || strings.HasSuffix(stderr, "succeeded!") {
fmt.Println("Preflight DNS check: PASSED")
if strings.HasSuffix(strings.TrimSpace(logStr), "succeeded!") {
p.CG.LogVerboseMessage("Preflight DNS check: PASSED\n")
} else {
fmt.Println("Preflight DNS check: FAILED")
err = fmt.Errorf("Expected response not found\n")
return err
}
if !cleanup {
p.CG.LogVerboseMessage("Completed preflight DNS check\n")
p.CG.LogVerboseMessage("Cleaning up resources...\n")
}
fmt.Println("Completed preflight DNS check")
fmt.Println("Cleaning up resources...")
return nil
}
func (p *QliksensePreflight) runDNSCleanup(clientset kubernetes.Interface, namespace, podName, serviceName, depName string) {
p.CG.DeleteDeployment(clientset, namespace, depName)
p.CG.DeletePod(clientset, namespace, podName)
p.CG.DeleteService(clientset, namespace, serviceName)
}

View File

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

View File

@@ -1,41 +0,0 @@
package preflight
import (
"io/ioutil"
"testing"
api "github.com/qlik-oss/sense-installer/pkg/api"
)
func Test_Initalize(t *testing.T) {
tempDir, err := ioutil.TempDir("", "")
if err != nil {
t.Log(err)
t.FailNow()
}
pf := NewPreflightConfig(tempDir)
if err := pf.Initialize(); err != nil {
t.Log()
t.FailNow()
}
p := &PreflightConfig{
QliksenseHomePath: tempDir,
}
if err := api.ReadFromFile(p, pf.GetConfigFilePath()); err != nil {
t.Log(err)
t.FailNow()
}
if p.GetMinK8sVersion() != "1.15" {
t.Log("expected k8 version: 1.15, but got " + p.GetMinK8sVersion())
t.Fail()
}
p.AddImage("test", "testimage")
if err := p.Write(); err != nil {
t.Log(err)
t.Fail()
}
p2 := NewPreflightConfig(tempDir)
if p2.GetImageName("test") != "testimage" {
t.Log("expected image name: testimage, got: " + p2.GetImageName("test"))
}
}

View File

@@ -1,642 +1,51 @@
package preflight
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"net/url"
"path/filepath"
"strings"
"time"
"github.com/mitchellh/go-homedir"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/util/retry"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/kubectl/pkg/scheme"
"github.com/pkg/errors"
"github.com/qlik-oss/sense-installer/pkg/api"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
"k8s.io/api/rbac/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
)
var gracePeriod int64 = 0
type PreflightOptions struct {
Verbose bool
MongoOptions *MongoOptions
}
type MongoOptions struct {
MongodbUrl string
CaCertFile string
}
type QliksensePreflight struct {
Q *qliksense.Qliksense
Q *qliksense.Qliksense
P *PreflightOptions
CG *api.ClientGoUtils
}
func (qp *QliksensePreflight) GetPreflightConfigObj() *PreflightConfig {
return NewPreflightConfig(qp.Q.QliksenseHome)
func (qp *QliksensePreflight) GetPreflightConfigObj() *api.PreflightConfig {
return api.NewPreflightConfig(qp.Q.QliksenseHome)
}
func InitPreflight() (string, []byte, error) {
api.LogDebugMessage("Reading .kube/config file...")
func (qp *QliksensePreflight) Cleanup(namespace string, kubeConfigContents []byte) error {
qp.CG.LogVerboseMessage("Preflight clean\n")
qp.CG.LogVerboseMessage("----------------\n")
homeDir, err := homedir.Dir()
if err != nil {
err = fmt.Errorf("Unable to deduce home dir\n")
return "", nil, err
}
api.LogDebugMessage("Kube config location: %s\n\n", filepath.Join(homeDir, ".kube", "config"))
qp.CG.LogVerboseMessage("Removing deployment...\n")
qp.CheckDeployment(namespace, kubeConfigContents, true)
qp.CG.LogVerboseMessage("Removing service...\n")
qp.CheckService(namespace, kubeConfigContents, true)
qp.CG.LogVerboseMessage("Removing pod...\n")
qp.CheckPod(namespace, kubeConfigContents, true)
kubeConfig := filepath.Join(homeDir, ".kube", "config")
kubeConfigContents, err := ioutil.ReadFile(kubeConfig)
if err != nil {
err = fmt.Errorf("Unable to deduce home dir\n")
return "", nil, err
}
// retrieve namespace
namespace := api.GetKubectlNamespace()
// if namespace comes back empty, we will run checks in the default namespace
if namespace == "" {
namespace = "default"
}
api.LogDebugMessage("Namespace: %s\n", namespace)
return namespace, kubeConfigContents, nil
}
qp.CG.LogVerboseMessage("Removing role...\n")
qp.CheckCreateRole(namespace, true)
qp.CG.LogVerboseMessage("Removing rolebinding...\n")
qp.CheckCreateRoleBinding(namespace, true)
qp.CG.LogVerboseMessage("Removing serviceaccount...\n")
qp.CheckCreateServiceAccount(namespace, true)
func initiateK8sOps(opr, namespace string) error {
opr1 := strings.Fields(opr)
_, err := api.KubectlDirectOps(opr1, namespace)
if err != nil {
fmt.Println(err)
return err
}
qp.CG.LogVerboseMessage("Removing DNS check components...\n")
qp.CheckDns(namespace, kubeConfigContents, true)
qp.CG.LogVerboseMessage("Removing mongo check components...\n")
qp.CheckMongo(kubeConfigContents, namespace, &PreflightOptions{MongoOptions: &MongoOptions{}}, true)
return nil
}
func int32Ptr(i int32) *int32 { return &i }
func retryOnError(mf func() error) error {
return retry.OnError(wait.Backoff{
Duration: 1 * time.Second,
Factor: 1,
Jitter: 0.1,
Steps: 5,
}, func(err error) bool {
return k8serrors.IsConflict(err) || k8serrors.IsGone(err) || k8serrors.IsServerTimeout(err) ||
k8serrors.IsServiceUnavailable(err) || k8serrors.IsTimeout(err) || k8serrors.IsTooManyRequests(err)
}, mf)
}
func getK8SClientSet(kubeconfig []byte, contextName string) (*kubernetes.Clientset, *rest.Config, error) {
var clientConfig *rest.Config
var err error
if len(kubeconfig) == 0 {
clientConfig, err = rest.InClusterConfig()
if err != nil {
err = errors.Wrap(err, "Unable to load in-cluster kubeconfig")
fmt.Println(err)
return nil, nil, err
}
} else {
config, err := clientcmd.Load(kubeconfig)
if err != nil {
err = errors.Wrap(err, "Unable to load kubeconfig")
fmt.Println(err)
return nil, nil, err
}
if contextName != "" {
config.CurrentContext = contextName
}
clientConfig, err = clientcmd.NewDefaultClientConfig(*config, &clientcmd.ConfigOverrides{}).ClientConfig()
if err != nil {
err = errors.Wrap(err, "Unable to create client config from config")
fmt.Println(err)
return nil, nil, err
}
}
clientset, err := kubernetes.NewForConfig(clientConfig)
if err != nil {
err = errors.Wrap(err, "Unable to create clientset")
fmt.Println(err)
return nil, nil, err
}
return clientset, clientConfig, nil
}
func createPreflightTestDeployment(clientset *kubernetes.Clientset, namespace string, depName string, imageName string) (*appsv1.Deployment, error) {
deploymentsClient := clientset.AppsV1().Deployments(namespace)
deployment := &appsv1.Deployment{
ObjectMeta: v1.ObjectMeta{
Name: depName,
},
Spec: appsv1.DeploymentSpec{
Replicas: int32Ptr(1),
Selector: &v1.LabelSelector{
MatchLabels: map[string]string{
"app": "preflight-check",
},
},
Template: apiv1.PodTemplateSpec{
ObjectMeta: v1.ObjectMeta{
Labels: map[string]string{
"app": "preflight-check",
"label": "preflight-check-label",
},
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{
Name: "dep",
Image: imageName,
Ports: []apiv1.ContainerPort{
{
Name: "http",
Protocol: apiv1.ProtocolTCP,
ContainerPort: 80,
},
},
},
},
},
},
},
}
// Create Deployment
var result *appsv1.Deployment
if err := retryOnError(func() (err error) {
result, err = deploymentsClient.Create(deployment)
return err
}); err != nil {
err = errors.Wrapf(err, "error: unable to create deployments in the %s namespace", namespace)
fmt.Println(err)
return nil, err
}
fmt.Printf("Created deployment %q\n", result.GetObjectMeta().GetName())
return deployment, nil
}
func getDeployment(clientset *kubernetes.Clientset, namespace, depName string) (*appsv1.Deployment, error) {
deploymentsClient := clientset.AppsV1().Deployments(namespace)
var deployment *appsv1.Deployment
if err := retryOnError(func() (err error) {
deployment, err = deploymentsClient.Get(depName, v1.GetOptions{})
return err
}); err != nil {
err = errors.Wrapf(err, "error: unable to get deployments in the %s namespace", namespace)
api.LogDebugMessage("%v\n", err)
return nil, err
}
return deployment, nil
}
func deleteDeployment(clientset *kubernetes.Clientset, namespace, name string) error {
deploymentsClient := clientset.AppsV1().Deployments(namespace)
// Create Deployment
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
GracePeriodSeconds: &gracePeriod,
}
if err := retryOnError(func() (err error) {
return deploymentsClient.Delete(name, &deleteOptions)
}); err != nil {
fmt.Println(err)
return err
}
if err := waitForDeploymentToDelete(clientset, namespace, name); err != nil {
return err
}
fmt.Printf("Deleted deployment: %s\n", name)
return nil
}
func createPreflightTestService(clientset *kubernetes.Clientset, namespace string, svcName string) (*apiv1.Service, error) {
iptr := int32Ptr(80)
servicesClient := clientset.CoreV1().Services(namespace)
service := &apiv1.Service{
ObjectMeta: v1.ObjectMeta{
Name: svcName,
Namespace: namespace,
Labels: map[string]string{
"app": "preflight-check",
},
},
Spec: apiv1.ServiceSpec{
Ports: []apiv1.ServicePort{
{Name: "port1",
Port: *iptr,
},
},
Selector: map[string]string{
"app": "preflight-check",
},
ClusterIP: "",
},
}
var result *apiv1.Service
if err := retryOnError(func() (err error) {
result, err = servicesClient.Create(service)
return err
}); err != nil {
fmt.Println(err)
return nil, err
}
fmt.Printf("Created service %q\n", result.GetObjectMeta().GetName())
return service, nil
}
func getService(clientset *kubernetes.Clientset, namespace, svcName string) (*apiv1.Service, error) {
servicesClient := clientset.CoreV1().Services(namespace)
var svc *apiv1.Service
if err := retryOnError(func() (err error) {
svc, err = servicesClient.Get(svcName, v1.GetOptions{})
return err
}); err != nil {
err = errors.Wrapf(err, "unable to get services in the %s namespace", namespace)
fmt.Println(err)
return nil, err
}
return svc, nil
}
func deleteService(clientset *kubernetes.Clientset, namespace, name string) error {
servicesClient := clientset.CoreV1().Services(namespace)
// Create Deployment
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}
if err := retryOnError(func() (err error) {
return servicesClient.Delete(name, &deleteOptions)
}); err != nil {
fmt.Println(err)
return err
}
fmt.Printf("Deleted service: %s\n", name)
return nil
}
func deletePod(clientset *kubernetes.Clientset, namespace, name string) error {
podsClient := clientset.CoreV1().Pods(namespace)
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
GracePeriodSeconds: &gracePeriod,
}
if err := retryOnError(func() (err error) {
return podsClient.Delete(name, &deleteOptions)
}); err != nil {
fmt.Println(err)
return err
}
if err := waitForPodToDelete(clientset, namespace, name); err != nil {
return err
}
fmt.Printf("Deleted pod: %s\n", name)
return nil
}
func createPreflightTestPod(clientset *kubernetes.Clientset, namespace string, podName string, imageName string) (*apiv1.Pod, error) {
// build the pod definition we want to deploy
pod := &apiv1.Pod{
ObjectMeta: v1.ObjectMeta{
Name: podName,
Namespace: namespace,
Labels: map[string]string{
"app": "preflight",
},
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{
Name: "cnt",
Image: imageName,
ImagePullPolicy: apiv1.PullIfNotPresent,
Command: []string{
"sleep",
"3600",
},
},
},
},
}
// now create the pod in kubernetes cluster using the clientset
if err := retryOnError(func() (err error) {
pod, err = clientset.CoreV1().Pods(namespace).Create(pod)
return err
}); err != nil {
fmt.Println(err)
return nil, err
}
fmt.Printf("Created pod: %s\n", pod.Name)
return pod, nil
}
func getPod(clientset *kubernetes.Clientset, namespace, podName string) (*apiv1.Pod, error) {
api.LogDebugMessage("Fetching pod: %s\n", podName)
var pod *apiv1.Pod
if err := retryOnError(func() (err error) {
pod, err = clientset.CoreV1().Pods(namespace).Get(podName, v1.GetOptions{})
return err
}); err != nil {
api.LogDebugMessage("%v\n", err)
return nil, err
}
return pod, nil
}
func execute(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool) error {
exec, err := remotecommand.NewSPDYExecutor(config, method, url)
if err != nil {
return err
}
return exec.Stream(remotecommand.StreamOptions{
Stdin: stdin,
Stdout: stdout,
Stderr: stderr,
Tty: tty,
})
}
func executeRemoteCommand(clientset *kubernetes.Clientset, config *rest.Config, podName, containerName, namespace string, command []string) (string, string, error) {
tty := false
req := clientset.CoreV1().RESTClient().Post().
Resource("pods").
Name(podName).
Namespace(namespace).
SubResource("exec").
Param("container", containerName)
req.VersionedParams(&apiv1.PodExecOptions{
Container: containerName,
Command: command,
Stdin: false,
Stdout: true,
Stderr: true,
TTY: tty,
}, scheme.ParameterCodec)
var stdout, stderr bytes.Buffer
err := execute("POST", req.URL(), config, nil, &stdout, &stderr, tty)
return strings.TrimSpace(stdout.String()), strings.TrimSpace(stderr.String()), err
}
func waitForDeployment(clientset *kubernetes.Clientset, namespace string, pfDeployment *appsv1.Deployment) error {
timeout := time.NewTicker(2 * time.Minute)
defer timeout.Stop()
var d *appsv1.Deployment
var err error
WAIT:
for {
d, err = getDeployment(clientset, namespace, pfDeployment.GetName())
if err != nil {
err = fmt.Errorf("error: unable to retrieve deployment: %s\n", pfDeployment.GetName())
fmt.Println(err)
return err
}
select {
case <-timeout.C:
break WAIT
default:
if int(d.Status.ReadyReplicas) > 0 {
break WAIT
}
}
time.Sleep(5 * time.Second)
}
if int(d.Status.ReadyReplicas) == 0 {
err = fmt.Errorf("error: deployment took longer than expected to spin up pods")
fmt.Println(err)
return err
}
return nil
}
func waitForPod(clientset *kubernetes.Clientset, namespace string, pod *apiv1.Pod) error {
var err error
if len(pod.Spec.Containers) > 0 {
timeout := time.NewTicker(2 * time.Minute)
defer timeout.Stop()
podName := pod.Name
OUT:
for {
pod, err = getPod(clientset, namespace, podName)
if err != nil {
err = fmt.Errorf("error: unable to retrieve %s pod by name", podName)
fmt.Println(err)
return err
}
select {
case <-timeout.C:
break OUT
default:
if len(pod.Status.ContainerStatuses) > 0 && pod.Status.ContainerStatuses[0].Ready {
break OUT
}
}
time.Sleep(5 * time.Second)
}
if len(pod.Status.ContainerStatuses) == 0 || !pod.Status.ContainerStatuses[0].Ready {
err = fmt.Errorf("error: container is taking much longer than expected")
fmt.Println(err)
return err
}
return nil
}
err = fmt.Errorf("error: there are no containers in the pod")
fmt.Println(err)
return err
}
func waitForPodToDelete(clientset *kubernetes.Clientset, namespace, podName string) error {
var err error
timeout := time.NewTicker(2 * time.Minute)
defer timeout.Stop()
OUT:
for {
_, err = getPod(clientset, namespace, podName)
if err != nil {
return nil
}
select {
case <-timeout.C:
break OUT
default:
}
time.Sleep(5 * time.Second)
}
err = fmt.Errorf("error: delete pod is taking unusually long")
fmt.Println(err)
return err
}
func waitForDeploymentToDelete(clientset *kubernetes.Clientset, namespace, deploymentName string) error {
var err error
timeout := time.NewTicker(2 * time.Minute)
defer timeout.Stop()
OUT:
for {
_, err = getDeployment(clientset, namespace, deploymentName)
if err != nil {
return nil
}
select {
case <-timeout.C:
break OUT
default:
}
time.Sleep(5 * time.Second)
}
err = fmt.Errorf("error: delete deployment is taking unusually long")
fmt.Println(err)
return err
}
func createPfRole(clientset *kubernetes.Clientset, namespace, roleName string) (*v1beta1.Role, error) {
// build the role defination we want to create
var role *v1beta1.Role
roleSpec := &v1beta1.Role{
ObjectMeta: v1.ObjectMeta{
Name: roleName,
Namespace: namespace,
Labels: map[string]string{
"app": "preflight",
},
},
Rules: []v1beta1.PolicyRule{},
}
// now create the role in kubernetes cluster using the clientset
if err := retryOnError(func() (err error) {
role, err = clientset.RbacV1beta1().Roles(namespace).Create(roleSpec)
return err
}); err != nil {
fmt.Println(err)
return nil, err
}
fmt.Printf("Created role: %s\n", role.Name)
return role, nil
}
func deleteRole(clientset *kubernetes.Clientset, namespace string, role *v1beta1.Role) {
rolesClient := clientset.RbacV1beta1().Roles(namespace)
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}
err := rolesClient.Delete(role.GetName(), &deleteOptions)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Deleted role: %s\n\n", role.Name)
}
func createPfRoleBinding(clientset *kubernetes.Clientset, namespace, roleBindingName string) (*v1beta1.RoleBinding, error) {
var roleBinding *v1beta1.RoleBinding
// build the rolebinding defination we want to create
roleBindingSpec := &v1beta1.RoleBinding{
ObjectMeta: v1.ObjectMeta{
Name: roleBindingName,
Namespace: namespace,
Labels: map[string]string{
"app": "demo",
},
},
Subjects: []v1beta1.Subject{
{
Kind: "ServiceAccount",
APIGroup: "",
Name: "preflight-check-subject",
Namespace: namespace,
},
},
RoleRef: v1beta1.RoleRef{
APIGroup: "",
Kind: "Role",
Name: "preflight-check-roleref",
},
}
// now create the roleBinding in kubernetes cluster using the clientset
if err := retryOnError(func() (err error) {
roleBinding, err = clientset.RbacV1beta1().RoleBindings(namespace).Create(roleBindingSpec)
return err
}); err != nil {
fmt.Println(err)
return nil, err
}
fmt.Printf("Created RoleBinding: %s\n", roleBindingSpec.Name)
return roleBinding, nil
}
func deleteRoleBinding(clientset *kubernetes.Clientset, namespace string, roleBinding *v1beta1.RoleBinding) {
roleBindingClient := clientset.RbacV1beta1().RoleBindings(namespace)
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}
err := roleBindingClient.Delete(roleBinding.GetName(), &deleteOptions)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Deleted RoleBinding: %s\n\n", roleBinding.Name)
}
func createPfServiceAccount(clientset *kubernetes.Clientset, namespace, serviceAccountName string) (*apiv1.ServiceAccount, error) {
var serviceAccount *apiv1.ServiceAccount
// build the serviceAccount defination we want to create
serviceAccountSpec := &apiv1.ServiceAccount{
ObjectMeta: v1.ObjectMeta{
Name: "preflight-check-test-serviceaccount",
Namespace: namespace,
Labels: map[string]string{
"app": "demo",
},
},
}
// now create the serviceAccount in kubernetes cluster using the clientset
if err := retryOnError(func() (err error) {
serviceAccount, err = clientset.CoreV1().ServiceAccounts(namespace).Create(serviceAccountSpec)
return err
}); err != nil {
fmt.Println(err)
return nil, err
}
fmt.Printf("Created Service Account: %s\n", serviceAccountSpec.Name)
return serviceAccount, nil
}
func deleteServiceAccount(clientset *kubernetes.Clientset, namespace string, serviceAccount *apiv1.ServiceAccount) {
serviceAccountClient := clientset.CoreV1().ServiceAccounts(namespace)
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}
err := serviceAccountClient.Delete(serviceAccount.GetName(), &deleteOptions)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Deleted ServiceAccount: %s\n\n", serviceAccount.Name)
}

View File

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

View File

@@ -2,63 +2,78 @@ package preflight
import (
"fmt"
"path"
"path/filepath"
"strings"
"github.com/mitchellh/go-homedir"
"github.com/qlik-oss/k-apis/pkg/cr"
"github.com/pkg/errors"
"github.com/qlik-oss/sense-installer/pkg/api"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
)
func (qp *QliksensePreflight) CheckCreateRole(namespace string) error {
func (qp *QliksensePreflight) CheckCreateRole(namespace string, cleanup bool) error {
// create a Role
fmt.Printf("Preflight role check: \n")
err := qp.checkCreateEntity(namespace, "Role")
if !cleanup {
fmt.Print("Preflight role check... ")
qp.CG.LogVerboseMessage("\n--------------------- \n")
}
err := qp.checkCreateEntity(namespace, "Role", cleanup)
if err != nil {
return err
}
fmt.Println("Completed preflight role check")
if !cleanup {
qp.CG.LogVerboseMessage("Completed preflight role check\n")
}
return nil
}
func (qp *QliksensePreflight) CheckCreateRoleBinding(namespace string) error {
func (qp *QliksensePreflight) CheckCreateRoleBinding(namespace string, cleanup bool) error {
// create a RoleBinding
fmt.Printf("Preflight rolebinding check: \n")
err := qp.checkCreateEntity(namespace, "RoleBinding")
if !cleanup {
fmt.Print("Preflight rolebinding check... ")
qp.CG.LogVerboseMessage("\n---------------------------- \n")
}
err := qp.checkCreateEntity(namespace, "RoleBinding", cleanup)
if err != nil {
return err
}
fmt.Println("Completed preflight rolebinding check")
if !cleanup {
qp.CG.LogVerboseMessage("Completed preflight rolebinding check\n")
}
return nil
}
func (qp *QliksensePreflight) CheckCreateServiceAccount(namespace string) error {
func (qp *QliksensePreflight) CheckCreateServiceAccount(namespace string, cleanup bool) error {
// create a service account
fmt.Printf("Preflight serviceaccount check: \n")
err := qp.checkCreateEntity(namespace, "ServiceAccount")
if !cleanup {
fmt.Print("Preflight serviceaccount check... ")
qp.CG.LogVerboseMessage("\n------------------------------- \n")
}
err := qp.checkCreateEntity(namespace, "ServiceAccount", cleanup)
if err != nil {
return err
}
fmt.Println("Completed preflight serviceaccount check")
if !cleanup {
qp.CG.LogVerboseMessage("Completed preflight serviceaccount check\n")
}
return nil
}
func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string) error {
func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string, cleanup bool) error {
qConfig := qapi.NewQConfig(qp.Q.QliksenseHome)
var currentCR *qapi.QliksenseCR
mfroot := ""
kusDir := ""
resultYamlBytes := []byte("")
var err error
currentCR, err = qConfig.GetCurrentCR()
if err != nil {
fmt.Printf("Unable to retrieve current CR: %v\n", err)
qp.CG.LogVerboseMessage("Unable to retrieve current CR: %v\n", err)
return err
}
if currentCR.IsRepoExist() {
mfroot = currentCR.Spec.GetManifestsRoot()
} else if tempDownloadedDir, err := qliksense.DownloadFromGitRepoToTmpDir(qliksense.QLIK_GIT_REPO, "master"); err != nil {
fmt.Printf("Unable to Download from git repo to tmp dir: %v\n", err)
qp.CG.LogVerboseMessage("Unable to Download from git repo to tmp dir: %v\n", err)
return err
} else {
mfroot = tempDownloadedDir
@@ -69,80 +84,91 @@ func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string)
} else {
kusDir = filepath.Join(mfroot, "manifests", currentCR.Spec.Profile)
}
currentCR.SetName("random")
currentCR.Spec.RotateKeys = "None"
currentCR.Spec.ManifestsRoot = mfroot
userHomeDir, err := homedir.Dir()
if err != nil {
fmt.Printf(`error fetching user's home directory: %v\n`, err)
return err
if len(resultYamlBytes) == 0 {
resultYamlBytes, err = qliksense.ExecuteKustomizeBuild(kusDir)
if err != nil {
err := fmt.Errorf("Unable to retrieve manifests from executing kustomize: %s, error: %v", kusDir, err)
return err
}
}
cr.GeneratePatches(&currentCR.KApiCr, path.Join(userHomeDir, ".kube", "config"))
resultYamlString, err := qliksense.ExecuteKustomizeBuild(kusDir)
if err != nil {
fmt.Printf("Unable to retrieve manifests from executing kustomize: %v\n", err)
return err
}
sa := qliksense.GetYamlsFromMultiDoc(string(resultYamlString), entityToTest)
sa := qliksense.GetYamlsFromMultiDoc(string(resultYamlBytes), entityToTest)
if sa != "" {
// sa = strings.ReplaceAll(sa, "namespace: default\n", fmt.Sprintf("namespace: %s\n", namespace))
sa = strings.Replace(sa, "name: qliksense", "name: preflight", -1)
} else {
err := fmt.Errorf("Unable to retrieve yamls to apply on cluster")
fmt.Println(err)
err := fmt.Errorf("Unable to retrieve yamls to apply on cluster from dir: %s, error: %v", kusDir, err)
return err
}
namespace = "" // namespace is handled when generating the manifests
// check if entity already exists in the cluster, if so - delete it
api.KubectlDeleteVerbose(sa, namespace, qp.P.Verbose)
if cleanup {
return nil
}
defer func() {
fmt.Println("Cleaning up resources")
api.KubectlDelete(sa, namespace)
qp.CG.LogVerboseMessage("Cleaning up resources...\n")
err := api.KubectlDeleteVerbose(sa, namespace, qp.P.Verbose)
if err != nil {
fmt.Println("Preflight cleanup failed!")
qp.CG.LogVerboseMessage("Preflight cleanup failed!\n")
}
}()
err = api.KubectlApply(sa, namespace)
err = api.KubectlApplyVerbose(sa, namespace, qp.P.Verbose)
if err != nil {
err := fmt.Errorf("Failed to create entity on the cluster: %v", err)
fmt.Println(err)
return err
}
fmt.Printf("Preflight %s check: PASSED\n", entityToTest)
qp.CG.LogVerboseMessage("Preflight %s check: PASSED\n", entityToTest)
return nil
}
func (qp *QliksensePreflight) CheckCreateRB(namespace string, kubeConfigContents []byte) error {
// create a role
fmt.Printf("Preflight createRole check: \n")
err := qp.checkCreateEntity(namespace, "Role")
if err != nil {
fmt.Println("Preflight role check: FAILED")
qp.CG.LogVerboseMessage("Preflight createRole check: \n")
qp.CG.LogVerboseMessage("--------------------------- \n")
errStr := strings.Builder{}
err1 := qp.checkCreateEntity(namespace, "Role", false)
if err1 != nil {
errStr.WriteString(err1.Error())
errStr.WriteString("\n")
qp.CG.LogVerboseMessage("%v\n", err1)
qp.CG.LogVerboseMessage("Preflight role check: FAILED\n")
}
fmt.Printf("Completed preflight role check\n\n")
qp.CG.LogVerboseMessage("Completed preflight role check\n\n")
// create a roleBinding
fmt.Printf("Preflight rolebinding check: \n")
err = qp.checkCreateEntity(namespace, "RoleBinding")
if err != nil {
fmt.Println("Preflight rolebinding check: FAILED")
qp.CG.LogVerboseMessage("Preflight rolebinding check: \n")
qp.CG.LogVerboseMessage("---------------------------- \n")
err2 := qp.checkCreateEntity(namespace, "RoleBinding", false)
if err2 != nil {
errStr.WriteString(err2.Error())
errStr.WriteString("\n")
qp.CG.LogVerboseMessage("%v\n", err2)
qp.CG.LogVerboseMessage("Preflight rolebinding check: FAILED\n")
}
fmt.Printf("Completed preflight rolebinding check\n\n")
qp.CG.LogVerboseMessage("Completed preflight rolebinding check\n\n")
// create a service account
fmt.Printf("Preflight serviceaccount check: \n")
err = qp.checkCreateEntity(namespace, "ServiceAccount")
if err != nil {
fmt.Println("Preflight serviceaccount check: FAILED")
qp.CG.LogVerboseMessage("Preflight serviceaccount check: \n")
qp.CG.LogVerboseMessage("------------------------------- \n")
err3 := qp.checkCreateEntity(namespace, "ServiceAccount", false)
if err3 != nil {
errStr.WriteString(err3.Error())
errStr.WriteString("\n")
qp.CG.LogVerboseMessage("%v\n", err3)
qp.CG.LogVerboseMessage("Preflight serviceaccount check: FAILED\n")
}
fmt.Printf("Completed preflight serviceaccount check\n\n")
qp.CG.LogVerboseMessage("Completed preflight serviceaccount check\n\n")
fmt.Println("Preflight RB check: PASSED")
fmt.Println("Completed preflight CreateRB check")
if err1 != nil || err2 != nil || err3 != nil {
qp.CG.LogVerboseMessage("Preflight authcheck: FAILED\n")
qp.CG.LogVerboseMessage("Completed preflight authcheck\n")
return errors.New(errStr.String())
}
qp.CG.LogVerboseMessage("Preflight authcheck: PASSED\n")
qp.CG.LogVerboseMessage("Completed preflight authcheck\n")
return nil
}

View File

@@ -4,53 +4,49 @@ import (
"fmt"
"github.com/Masterminds/semver/v3"
"github.com/qlik-oss/sense-installer/pkg/api"
"k8s.io/apimachinery/pkg/version"
)
func (qp *QliksensePreflight) CheckK8sVersion(namespace string, kubeConfigContents []byte) error {
func (p *QliksensePreflight) CheckK8sVersion(namespace string, kubeConfigContents []byte) error {
fmt.Print("Preflight kubernetes version check... ")
p.CG.LogVerboseMessage("\n----------------------------------- \n")
var currentVersion *semver.Version
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
clientset, _, err := p.CG.GetK8SClientSet(kubeConfigContents, "")
if err != nil {
err = fmt.Errorf("Unable to create clientset: %v\n", err)
return err
}
var serverVersion *version.Info
if err := retryOnError(func() (err error) {
if err := p.CG.RetryOnError(func() (err error) {
serverVersion, err = clientset.ServerVersion()
return err
}); err != nil {
err = fmt.Errorf("Unable to get server version: %v\n", err)
//fmt.Println(err)
return err
}
fmt.Printf("Kubernetes API Server version: %s\n", serverVersion.String())
p.CG.LogVerboseMessage("Kubernetes API Server version: %s\n", serverVersion.String())
// Compare K8s version on the cluster with minimum supported k8s version
currentVersion, err = semver.NewVersion(serverVersion.String())
if err != nil {
err = fmt.Errorf("Unable to convert server version into semver version: %v\n", err)
//fmt.Println(err)
return err
}
//fmt.Printf("Current K8s Version: %v\n", currentVersion)
api.LogDebugMessage("Current Kubernetes Version: %v\n", currentVersion)
minK8sVersionSemver, err := semver.NewVersion(qp.GetPreflightConfigObj().GetMinK8sVersion())
minK8sVersionSemver, err := semver.NewVersion(p.GetPreflightConfigObj().GetMinK8sVersion())
if err != nil {
err = fmt.Errorf("Unable to convert minimum Kubernetes version into semver version:%v\n", err)
fmt.Println(err)
return err
}
if currentVersion.GreaterThan(minK8sVersionSemver) {
//fmt.Printf("\n\nCurrent %s Component version: %s is less than minimum required version:%s\n", component, currentComponentVersion, componentVersionFromDependenciesYaml)
fmt.Printf("Current %s is greater than minimum required version:%s\n", currentVersion, minK8sVersionSemver)
fmt.Println("Preflight minimum kubernetes version check: PASSED")
p.CG.LogVerboseMessage("Current Kubernetes API Server version %s is greater than or equal to minimum required version: %s\n", currentVersion, minK8sVersionSemver)
} else {
fmt.Printf("Current %s is less than minimum required version:%s\n", currentVersion, minK8sVersionSemver)
fmt.Println("Preflight minimum kubernetes version check: FAILED")
err = fmt.Errorf("Current Kubernetes API Server version %s is less than minimum required version: %s", currentVersion, minK8sVersionSemver)
return err
}
fmt.Printf("Completed Preflight kubernetes minimum version check\n")
return nil
}

View File

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

View File

@@ -5,6 +5,7 @@ import (
"io/ioutil"
"os"
"path"
"path/filepath"
"reflect"
"testing"
@@ -234,11 +235,12 @@ func Test_About_getConfigDirectory(t *testing.T) {
verifyAsdBranch := func(configDir string) (ok bool, reason string) {
tmpDir := os.TempDir()
if (path.Clean(path.Dir(path.Dir(configDir))) != path.Clean(tmpDir)) || (path.Base(configDir) != "repo") {
configParentDir := filepath.Dir(configDir)
if (filepath.Clean(filepath.Dir(configParentDir)) != filepath.Clean(tmpDir)) || (filepath.Base(configDir) != "repo") {
return false, fmt.Sprintf("expected config directory path: %v to be under: %v and terminate with repo", configDir, tmpDir)
}
if info, err := os.Stat(path.Join(configDir, "asdczxc")); err != nil || !info.Mode().IsRegular() {
if info, err := os.Stat(filepath.Join(configDir, "asdczxc")); err != nil || !info.Mode().IsRegular() {
return false, fmt.Sprintf(`expected to find file: "asdczxc" under directory: %v`, configDir)
}
return true, ""
@@ -247,15 +249,16 @@ func Test_About_getConfigDirectory(t *testing.T) {
verifyMasterBranch := func(configDir string) (ok bool, reason string) {
tmpDir := os.TempDir()
if (path.Clean(path.Dir(path.Dir(configDir))) != path.Clean(tmpDir)) || (path.Base(configDir) != "repo") {
configParentDir := filepath.Dir(configDir)
if (filepath.Clean(filepath.Dir(configParentDir)) != filepath.Clean(tmpDir)) || (filepath.Base(configDir) != "repo") {
return false, fmt.Sprintf("expected config directory path: %v to be under: %v and terminate with repo", configDir, tmpDir)
}
if _, err := os.Stat(path.Join(configDir, "asdczxc")); err == nil || !os.IsNotExist(err) {
if _, err := os.Stat(filepath.Join(configDir, "asdczxc")); err == nil || !os.IsNotExist(err) {
return false, fmt.Sprintf(`expected to NOT find file: "asdczxc"" under directory: %v`, configDir)
}
if info, err := os.Stat(path.Join(configDir, "sad")); err != nil || !info.Mode().IsRegular() {
if info, err := os.Stat(filepath.Join(configDir, "sad")); err != nil || !info.Mode().IsRegular() {
return false, fmt.Sprintf(`expected to find file: "sad"" under directory: %v`, configDir)
}
return true, ""
@@ -305,7 +308,7 @@ func Test_About_getConfigDirectory(t *testing.T) {
cleanup: func(_ *Qliksense, configDir string) error {
if currentDirectory, err := os.Getwd(); err != nil {
return err
} else if err := os.RemoveAll(path.Join(currentDirectory, "manifests")); err != nil {
} else if err := os.RemoveAll(filepath.Join(currentDirectory, "manifests")); err != nil {
return err
}
return nil
@@ -320,7 +323,7 @@ func Test_About_getConfigDirectory(t *testing.T) {
}
profileEntered = "foo"
defaultProfilePath := path.Join(currentDirectory, "manifests", profileEntered)
defaultProfilePath := filepath.Join(currentDirectory, "manifests", profileEntered)
err = os.MkdirAll(defaultProfilePath, os.ModePerm)
if err != nil {
t.Fatalf("error making path: %v, err: %v\n", defaultProfilePath, err)
@@ -350,7 +353,7 @@ func Test_About_getConfigDirectory(t *testing.T) {
cleanup: func(_ *Qliksense, configDir string) error {
if currentDirectory, err := os.Getwd(); err != nil {
return err
} else if err := os.RemoveAll(path.Join(currentDirectory, "manifests")); err != nil {
} else if err := os.RemoveAll(filepath.Join(currentDirectory, "manifests")); err != nil {
return err
}
return nil
@@ -380,8 +383,8 @@ func Test_About_getConfigDirectory(t *testing.T) {
cleanup: func(_ *Qliksense, configDir string) error {
tmpDir := os.TempDir()
if path.Clean(path.Dir(path.Dir(configDir))) == path.Clean(tmpDir) && path.Base(configDir) == "repo" {
tmpTmpDir := path.Dir(configDir)
tmpTmpDir := filepath.Dir(configDir)
if filepath.Clean(filepath.Dir(tmpTmpDir)) == filepath.Clean(tmpDir) && filepath.Base(configDir) == "repo" {
if err := os.RemoveAll(tmpTmpDir); err != nil {
return err
}
@@ -413,8 +416,8 @@ func Test_About_getConfigDirectory(t *testing.T) {
cleanup: func(_ *Qliksense, configDir string) error {
tmpDir := os.TempDir()
if path.Clean(path.Dir(path.Dir(configDir))) == path.Clean(tmpDir) && path.Base(configDir) == "repo" {
tmpTmpDir := path.Dir(configDir)
tmpTmpDir := filepath.Dir(configDir)
if filepath.Clean(filepath.Dir(tmpTmpDir)) == filepath.Clean(tmpDir) && filepath.Base(configDir) == "repo" {
if err := os.RemoveAll(tmpTmpDir); err != nil {
return err
}
@@ -456,8 +459,9 @@ func Test_About_getConfigDirectory(t *testing.T) {
},
cleanup: func(q *Qliksense, configDir string) error {
tmpDir := os.TempDir()
if path.Clean(path.Dir(path.Dir(configDir))) == path.Clean(tmpDir) && path.Base(configDir) == "repo" {
tmpTmpDir := path.Dir(configDir)
tmpTmpDir := filepath.Dir(configDir)
if filepath.Clean(filepath.Dir(tmpTmpDir)) == filepath.Clean(tmpDir) && filepath.Base(configDir) == "repo" {
if err := os.RemoveAll(tmpTmpDir); err != nil {
return err
}
@@ -502,8 +506,9 @@ func Test_About_getConfigDirectory(t *testing.T) {
},
cleanup: func(q *Qliksense, configDir string) error {
tmpDir := os.TempDir()
if path.Clean(path.Dir(path.Dir(configDir))) == path.Clean(tmpDir) && path.Base(configDir) == "repo" {
tmpTmpDir := path.Dir(configDir)
tmpTmpDir := filepath.Dir(configDir)
if filepath.Clean(filepath.Dir(tmpTmpDir)) == filepath.Clean(tmpDir) && filepath.Base(configDir) == "repo" {
if err := os.RemoveAll(tmpTmpDir); err != nil {
return err
}
@@ -525,12 +530,16 @@ func Test_About_getConfigDirectory(t *testing.T) {
if err := q.SetUpQliksenseDefaultContext(); err != nil {
t.Fatalf("error setting up default context in the tmp dir: %v\n", err)
return nil, "", "", ""
} else if err := q.FetchQK8s("master"); err != nil {
t.Fatalf("error fetching master config to the tmp dir: %v\n", err)
} else if qConfig, err := qapi.NewQConfigE(q.QliksenseHome); err != nil {
t.Fatalf("cannot initiallize qConfig: %v\n", err)
return nil, "", "", ""
} else {
return q, "no-git-clone-for-you", "", ""
} else if !qConfig.IsRepoExistForCurrent("master") {
if err := q.FetchQK8s("master"); err != nil {
t.Fatalf("error fetching master config to the tmp dir: %v\n", err)
return nil, "", "", ""
}
}
return q, "no-git-clone-for-you", "", ""
}
},
verify: func(q *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {

View File

@@ -1,12 +1,13 @@
package qliksense
import (
"fmt"
"io"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func (q *Qliksense) ApplyCRFromReader(r io.Reader, opts *InstallCommandOptions, keepPatchFiles, overwriteExistingContext bool) error {
func (q *Qliksense) ApplyCRFromReader(r io.Reader, opts *InstallCommandOptions, cleanPatchFiles, overwriteExistingContext, pull, push bool) error {
if err := q.LoadCr(r, overwriteExistingContext); err != nil {
return err
}
@@ -15,16 +16,33 @@ func (q *Qliksense) ApplyCRFromReader(r io.Reader, opts *InstallCommandOptions,
if err != nil {
return err
}
version := cr.GetLabelFromCr("version")
if pull {
fmt.Println("Pulling images...")
if err := q.PullImages(version, ""); err != nil {
return err
}
}
if push {
fmt.Println("Pushing images...")
if err := q.PushImagesForCurrentCR(); err != nil {
return err
}
}
if IsQliksenseInstalled(cr.GetName()) {
// it is needed in case want to upgrade from one version to another
if cr.Spec.ManifestsRoot == "" && cr.Spec.Git == nil {
if err := q.FetchQK8s(cr.GetLabelFromCr("version")); err != nil {
return err
if !qConfig.IsRepoExistForCurrent(version) {
if err := q.FetchQK8s(version); err != nil {
return err
}
}
}
return q.UpgradeQK8s(keepPatchFiles)
return q.UpgradeQK8s(cleanPatchFiles)
}
return q.InstallQK8s(cr.GetLabelFromCr("version"), opts, keepPatchFiles)
return q.InstallQK8s(version, opts, cleanPatchFiles)
}
func IsQliksenseInstalled(crName string) bool {

View File

@@ -18,7 +18,7 @@ import (
)
const (
Q_INIT_CRD_PATH = "manifests/base/manifests/qliksense-init"
Q_INIT_CRD_PATH = "manifests/base/crds"
agreementTempalte = `
Please read the agreement at https://www.qlik.com/us/legal/license-terms
Accept the end user license agreement by providing acceptEULA=yes
@@ -39,9 +39,9 @@ func (q *Qliksense) ConfigApplyQK8s() error {
return errors.New(agreementTempalte + "\nPlease do $ qliksense config set-configs qliksense.acceptEULA=yes\n")
}
// create patch dependent resoruces
fmt.Println("Installing resoruces used kuztomize patch")
if err := q.createK8sResoruceBeforePatch(qcr); err != nil {
// create patch dependent resources
fmt.Println("Installing resources used by the kuztomize patch")
if err := q.createK8sResourceBeforePatch(qcr); err != nil {
return err
}
@@ -89,11 +89,13 @@ func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error {
cr.GeneratePatches(&qcr.KApiCr, path.Join(userHomeDir, ".kube", "config"))
// apply generated manifests
profilePath := filepath.Join(qcr.Spec.GetManifestsRoot(), qcr.Spec.GetProfileDir())
fmt.Printf("Generating manifests for profile: %v\n", profilePath)
mByte, err := ExecuteKustomizeBuild(profilePath)
if err != nil {
fmt.Println("cannot generate manifests for "+profilePath, err)
fmt.Printf("error generating manifests: %v\n", err)
return err
}
fmt.Println("Applying manifests to the cluster")
if err = qapi.KubectlApply(string(mByte), qcr.GetNamespace()); err != nil {
return err
}
@@ -146,18 +148,22 @@ func (q *Qliksense) getCurrentCrDependentResourceAsString() (string, error) {
var crString strings.Builder
for svcName, v := range qcr.Spec.Secrets {
hasFile := false
for _, item := range v {
if item.ValueFrom != nil && item.ValueFrom.SecretKeyRef != nil {
secretFilePath := filepath.Join(q.QliksenseHome, QliksenseContextsDir, qcr.GetName(), QliksenseSecretsDir, svcName+".yaml")
if api.FileExists(secretFilePath) {
secretFile, err := ioutil.ReadFile(secretFilePath)
if err != nil {
return "", err
}
crString.WriteString("\n---\n")
crString.Write(secretFile)
hasFile = true
break
}
}
if hasFile {
secretFilePath := filepath.Join(q.QliksenseHome, QliksenseContextsDir, qcr.GetName(), QliksenseSecretsDir, svcName+".yaml")
if api.FileExists(secretFilePath) {
secretFile, err := ioutil.ReadFile(secretFilePath)
if err != nil {
return "", err
}
crString.WriteString("\n---\n")
crString.Write(secretFile)
}
}
}

View File

@@ -0,0 +1,24 @@
package qliksense
import (
"fmt"
"log"
"strings"
)
func AskForConfirmation(s string) bool {
for {
fmt.Printf("%s [y/n]: ", s)
var response string
_, err := fmt.Scanln(&response)
if err != nil {
log.Fatal(err)
}
if strings.EqualFold(response, "y") || strings.EqualFold(response, "yes") {
return true
} else if strings.EqualFold(response, "n") || strings.EqualFold(response, "no") {
return false
}
}
}

View File

@@ -1,8 +1,9 @@
package qliksense
import (
"crypto/rsa"
"errors"
"fmt"
"io"
"github.com/qlik-oss/k-apis/pkg/config"
"github.com/robfig/cron/v3"
@@ -17,9 +18,9 @@ import (
b64 "encoding/base64"
. "github.com/logrusorgru/aurora"
ansi "github.com/mattn/go-colorable"
"github.com/qlik-oss/sense-installer/pkg/api"
"github.com/ttacon/chalk"
_ "gopkg.in/yaml.v2"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -33,12 +34,27 @@ const (
MaxContextNameLength = 17
QliksenseSecretsDir = "secrets"
imageRegistryConfigKey = "imageRegistry"
pullSecretName = "artifactory-docker-secret"
imageRegistryConfigKey = "imageRegistry"
pullSecretName = "artifactory-docker-secret"
qliksenseOperatorImageRepo = "qlik-docker-oss.bintray.io"
qliksenseOperatorImageName = "qliksense-operator"
)
func (q *Qliksense) SetSecretsFromReader(arg string, reader io.Reader, createSecret, base64Encoded bool) error {
//take only name from the arguments, value should be from reader
argName := strings.SplitN(arg, "=", 1)
if len(argName) != 1 {
return errors.New("can only have one argument from pipe")
}
valueBytes, err := ioutil.ReadAll(reader)
if err != nil {
return err
}
return q.SetSecrets([]string{argName[0] + "=" + string(valueBytes)}, createSecret, base64Encoded)
}
// SetSecrets - set-secrets <key>=<value> commands
func (q *Qliksense) SetSecrets(args []string, isSecretSet bool) error {
func (q *Qliksense) SetSecrets(args []string, isSecretSet bool, base64Encoded bool) error {
qConfig := api.NewQConfig(q.QliksenseHome)
qliksenseCR, err := qConfig.GetCurrentCR()
if err != nil {
@@ -46,18 +62,18 @@ func (q *Qliksense) SetSecrets(args []string, isSecretSet bool) error {
}
// Metadata name in qliksense CR is the name of the current context
api.LogDebugMessage("Current context: %s", qliksenseCR.GetName())
rsaPublicKey, _, err := qConfig.GetCurrentContextEncryptionKeyPair()
api.LogDebugMessage("Current context: %s\n", qliksenseCR.GetName())
encryptionKey, err := qConfig.GetEncryptionKeyForCurrent()
if err != nil {
return err
}
resultArgs, err := api.ProcessConfigArgs(args)
resultArgs, err := api.ProcessConfigArgs(args, base64Encoded)
if err != nil {
return err
}
for _, ra := range resultArgs {
api.LogDebugMessage("value args to be encrypted: %s", ra.Value)
if err := q.processSecret(ra, rsaPublicKey, qliksenseCR, isSecretSet); err != nil {
api.LogDebugMessage("value args to be encrypted: %s\n", ra.Value)
if err := q.processSecret(ra, encryptionKey, qliksenseCR, isSecretSet); err != nil {
return err
}
}
@@ -65,14 +81,11 @@ func (q *Qliksense) SetSecrets(args []string, isSecretSet bool) error {
return qConfig.WriteCR(qliksenseCR)
}
func (q *Qliksense) processSecret(ra *api.ServiceKeyValue, rsaPublicKey *rsa.PublicKey, qliksenseCR *api.QliksenseCR, isSecretSet bool) error {
// encrypt value with RSA key pair
valueBytes := []byte(ra.Value)
cipherText, e2 := api.Encrypt(valueBytes, rsaPublicKey)
func (q *Qliksense) processSecret(ra *api.ServiceKeyValue, encryptionKey string, qliksenseCR *api.QliksenseCR, isSecretSet bool) error {
cipherText, e2 := api.EncryptData([]byte(ra.Value), encryptionKey)
if e2 != nil {
return e2
}
base64EncodedSecret := b64.StdEncoding.EncodeToString(cipherText)
secretName := ""
if isSecretSet {
secretFolder := qliksenseCR.GetK8sSecretsFolder(q.QliksenseHome)
@@ -104,7 +117,8 @@ func (q *Qliksense) processSecret(ra *api.ServiceKeyValue, rsaPublicKey *rsa.Pub
if k8sSecret.Data == nil {
k8sSecret.Data = map[string][]byte{}
}
k8sSecret.Data[ra.Key] = []byte(base64EncodedSecret)
// v1.Secret does enconding, so no need to encode again
k8sSecret.Data[ra.Key] = []byte(cipherText)
// Write secret to file
k8sSecretBytes, err := api.K8sSecretToYaml(k8sSecret)
@@ -117,18 +131,28 @@ func (q *Qliksense) processSecret(ra *api.ServiceKeyValue, rsaPublicKey *rsa.Pub
return err
}
api.LogDebugMessage("Created a Kubernetes secret")
// Prepare args to update CR in the next step
base64EncodedSecret = ""
}
base64EncodedSecret := b64.StdEncoding.EncodeToString([]byte(cipherText))
// write into CR the keyref of the secret
qliksenseCR.Spec.AddToSecrets(ra.SvcName, ra.Key, base64EncodedSecret, secretName)
return nil
}
func (q *Qliksense) SetConfigFromReader(arg string, reader io.Reader, base64Encoded bool) error {
//take only name from the arguments, value should be from reader
argName := strings.SplitN(arg, "=", 1)
if len(argName) != 1 {
return errors.New("can only have one argument from pipe")
}
valueBytes, err := ioutil.ReadAll(reader)
if err != nil {
return err
}
return q.SetConfigs([]string{argName[0] + "=" + string(valueBytes)}, base64Encoded)
}
// SetConfigs - set-configs <key>=<value> commands
func (q *Qliksense) SetConfigs(args []string) error {
func (q *Qliksense) SetConfigs(args []string, base64Encoded bool) error {
// retieve current context from config.yaml
qConfig := api.NewQConfig(q.QliksenseHome)
qliksenseCR, err := qConfig.GetCurrentCR()
@@ -136,7 +160,7 @@ func (q *Qliksense) SetConfigs(args []string) error {
return err
}
resultArgs, err := api.ProcessConfigArgs(args)
resultArgs, err := api.ProcessConfigArgs(args, base64Encoded)
if err != nil {
return err
}
@@ -189,8 +213,8 @@ func validateCR(key string, keySub string, value string, crSpec *api.QliksenseCR
}
} else {
switch key {
case "gitops":
crSpec.Spec.GitOps = &config.GitOps{}
case "opsrunner":
crSpec.Spec.OpsRunner = &config.OpsRunner{}
case "git":
crSpec.Spec.Git = &config.Repo{}
}
@@ -216,43 +240,130 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
}
for _, arg := range args {
argsString := strings.Split(arg, "=")
key := strings.ToLower(argsString[0])
value := argsString[1]
// check if key is for git or gitops (sub objects)
keySplit := strings.Split(key, ".")
key = keySplit[0]
keySub := ""
if len(keySplit) == 2 {
keySub = strings.ToLower(keySplit[1])
}
valid := true
valid, qliksenseCR = validateCR(key, keySub, value, qliksenseCR)
field := caseInsenstiveFieldByName(reflect.Indirect(reflect.ValueOf(qliksenseCR.Spec)), key)
if !valid {
err := fmt.Errorf("Please enter one of: profile, storageClassName,rotateKeys, manifestRoot, git.repository or gitops arguments to configure the current context")
return err
} else if strings.EqualFold("", keySub) {
// set spec for everything excluding git and gitops
if field.CanSet() {
field.SetString(value)
if strings.HasPrefix(arg, "fetchSource.") {
if err := q.processSetFetchSource(arg, qliksenseCR); err != nil {
return err
}
} else if strings.HasPrefix(arg, "git.") {
if err := q.processSetGit(arg, qliksenseCR); err != nil {
return err
}
} else if strings.HasPrefix(arg, "opsRunner.") {
if err := q.processSetOpsRunner(arg, qliksenseCR); err != nil {
return err
}
} else {
// set spec for git or gitops
subField := caseInsenstiveFieldByName(reflect.Indirect(field), keySub)
if subField.CanSet() {
subField.SetString(value)
if err := processSetSingleArg(arg, qliksenseCR); err != nil {
return err
}
}
fmt.Println(chalk.Green.Color("Successfully added to Custom Resource Spec"))
fmt.Println(Green("Successfully added to Custom Resource Spec"))
}
// write modified content into context.yaml
return qConfig.WriteCR(qliksenseCR)
}
func processSetSingleArg(arg string, cr *api.QliksenseCR) error {
nv := strings.Split(arg, "=")
switch nv[0] {
case "manifestsRoot":
cr.Spec.ManifestsRoot = nv[1]
case "profile":
cr.Spec.Profile = nv[1]
case "storageClassName":
cr.Spec.StorageClassName = nv[1]
case "rotateKeys":
valid := false
for _, v := range []string{"yes", "no", "None"} {
if nv[1] == v {
valid = true
}
}
if !valid {
return errors.New("please povide rotateKeys=yes|no|None")
}
cr.Spec.RotateKeys = nv[1]
default:
return errors.New("Please enter one of: profile, storageClassName,rotateKeys, manifestRoot to configure the current context")
}
return nil
}
func (q *Qliksense) processSetFetchSource(arg string, cr *api.QliksenseCR) error {
args := strings.Split(arg, "=")
subs := strings.Split(args[0], ".")
if cr.Spec.FetchSource == nil {
cr.Spec.FetchSource = &config.Repo{}
}
switch subs[1] {
case "repository":
cr.Spec.FetchSource.Repository = args[1]
case "accessToken":
qConfig := api.NewQConfig(q.QliksenseHome)
key, err := qConfig.GetEncryptionKeyFor(cr.GetName())
if err != nil {
return err
}
return cr.SetFetchAccessToken(args[1], key)
case "secretName":
cr.Spec.FetchSource.SecretName = args[1]
case "userName":
cr.Spec.FetchSource.UserName = args[1]
default:
return errors.New(arg + " does not match any cr spec")
}
return nil
}
func (q *Qliksense) processSetGit(arg string, cr *api.QliksenseCR) error {
args := strings.Split(arg, "=")
subs := strings.Split(args[0], ".")
if cr.Spec.Git == nil {
cr.Spec.Git = &config.Repo{}
}
switch subs[1] {
case "repository":
cr.Spec.Git.Repository = args[1]
case "accessToken":
cr.Spec.Git.AccessToken = args[1]
case "secretName":
cr.Spec.Git.SecretName = args[1]
case "userName":
cr.Spec.Git.UserName = args[1]
default:
return errors.New(arg + " does not match any cr spec")
}
return nil
}
func (q *Qliksense) processSetOpsRunner(arg string, cr *api.QliksenseCR) error {
args := strings.Split(arg, "=")
subs := strings.Split(args[0], ".")
if cr.Spec.OpsRunner == nil {
cr.Spec.OpsRunner = &config.OpsRunner{}
}
switch subs[1] {
case "enabled":
if args[1] != "yes" && args[1] != "no" {
return errors.New("Please use yes or no for key enabled")
}
cr.Spec.OpsRunner.Enabled = args[1]
case "schedule":
if _, err := cron.ParseStandard(args[1]); err != nil {
return errors.New("Please enter string with standard cron scheduling syntax ")
}
cr.Spec.OpsRunner.Schedule = args[1]
case "watchBranch":
cr.Spec.OpsRunner.WatchBranch = args[1]
case "image":
cr.Spec.OpsRunner.Image = args[1]
default:
return errors.New(arg + " does not match any cr spec")
}
return nil
}
// SetContextConfig - set the context for qliksense kubernetes resources to live in
func (q *Qliksense) SetContextConfig(args []string) error {
if len(args) == 1 {
@@ -277,7 +388,7 @@ func (q *Qliksense) ListContextConfigs() error {
}
out := ansi.NewColorableStdout()
w := tabwriter.NewWriter(out, 5, 8, 0, '\t', 0)
fmt.Fprintln(w, chalk.Underline.TextStyle("Context Name"), "\t", chalk.Underline.TextStyle("CR File Location"))
fmt.Fprintln(w, Underline("Context Name"), "\t", Underline("CR File Location"))
w.Flush()
if len(qliksenseConfig.Spec.Contexts) > 0 {
for _, cont := range qliksenseConfig.Spec.Contexts {
@@ -285,14 +396,14 @@ func (q *Qliksense) ListContextConfigs() error {
}
w.Flush()
fmt.Fprintln(out, "")
fmt.Fprintln(out, chalk.Bold.TextStyle("Current Context : "), qliksenseConfig.Spec.CurrentContext)
fmt.Fprintln(out, Bold("Current Context : "), qliksenseConfig.Spec.CurrentContext)
} else {
fmt.Fprintln(out, "No Contexts Available")
}
return nil
}
func (q *Qliksense) DeleteContextConfig(args []string) error {
func (q *Qliksense) DeleteContextConfig(args []string, flag bool) error {
if len(args) == 1 {
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
var qliksenseConfig api.QliksenseConfig
@@ -300,11 +411,11 @@ func (q *Qliksense) DeleteContextConfig(args []string) error {
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)))
fmt.Fprintln(out, Yellow("Please switch contexts to be able to delete this context."))
err := fmt.Errorf(Red("Cannot delete current context - %s").String(), White(Bold(qliksenseConfig.Spec.CurrentContext)))
return err
case DefaultQliksenseContext:
err := fmt.Errorf(chalk.Red.Color("Cannot delete default qliksense context"))
err := fmt.Errorf(Red("Cannot delete default qliksense context").String())
return err
default:
qliksenseContextsDir1 := filepath.Join(q.QliksenseHome, QliksenseContextsDir)
@@ -334,11 +445,19 @@ func (q *Qliksense) DeleteContextConfig(args []string) error {
}
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]))
ans := flag
if ans == false {
ans = AskForConfirmation("Are You Sure? ")
}
if ans == true {
api.WriteToFile(&qliksenseConfig, qliksenseConfigFile)
fmt.Fprintln(out, Yellow(Underline("Warning: Active resources may still be running in-cluster")))
fmt.Fprintln(out, Green("Successfully deleted context: "), Bold(args[0]))
} else {
return nil
}
} else {
err := fmt.Errorf(chalk.Red.Color("Context not found"))
err := fmt.Errorf(Red("Context not found").String())
return err
}
}
@@ -400,8 +519,8 @@ func (q *Qliksense) SetUpQliksenseContext(contextName string) error {
return err
}
// set the encrypted default mongo
return q.SetSecrets([]string{`qliksense.mongoDbUri="mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"`}, false)
// set the encrypted default mongo for the context in current CR
return q.SetSecrets([]string{fmt.Sprintf("qliksense.mongodbUri=mongodb://%s-mongodb:27017/qliksense?ssl=false", contextName)}, false, false)
}
func validateInput(input string) (string, error) {
@@ -422,7 +541,8 @@ func validateInput(input string) (string, error) {
return input, err
}
// PrepareK8sSecret decodes and decrypts the secret value in the secret.yaml file and returns a B64encoded string
// PrepareK8sSecret targetFile contains base64 encoded value of encrypted value.
// this method decodes and decrypts the secret value in the secret.yaml file and returns a B64encoded string
func (q *Qliksense) PrepareK8sSecret(targetFile string) (string, error) {
// check if targetFile exists
if !api.FileExists(targetFile) {
@@ -431,7 +551,7 @@ func (q *Qliksense) PrepareK8sSecret(targetFile string) (string, error) {
return "", err
}
qConfig := api.NewQConfig(q.QliksenseHome)
_, rsaPrivateKey, err := qConfig.GetCurrentContextEncryptionKeyPair()
encryptionKey, err := qConfig.GetEncryptionKeyForCurrent()
if err != nil {
return "", err
}
@@ -449,17 +569,13 @@ func (q *Qliksense) PrepareK8sSecret(targetFile string) (string, error) {
dataMap := k8sSecret1.Data
var resultMap = make(map[string][]byte)
for k, v := range dataMap {
ba, err := b64.StdEncoding.DecodeString(string(v))
if err != nil {
err := fmt.Errorf("Not able to decode message: %v", err)
return "", err
}
decryptedString, err := api.Decrypt(ba, rsaPrivateKey)
//k8s secrets has already base64 decoed value
decryptedString, err := api.DecryptData(v, encryptionKey)
if err != nil {
err := fmt.Errorf("Not able to decrypt message: %v", err)
return "", err
}
resultMap[k] = decryptedString
resultMap[k] = []byte(decryptedString)
}
// putting the above map back into the k8sSecret struct

View File

@@ -29,125 +29,41 @@ const (
var targetFileStringTemplate = `
apiVersion: v1
data:
mongoDbUri: %s
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-----
`)
func setupTargetFileAndPrivateKey() (string, string, error) {
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 {
secretKeyLocation := filepath.Join(testDir, secrets, contexts, qlikDefaultContext, secrets)
if err := os.MkdirAll(secretKeyLocation, 0777); err != nil {
err = fmt.Errorf("Not able to create directories")
log.Fatal(err)
}
os.Setenv("QLIKSENSE_KEY_LOCATION", secretKeyPairDir)
os.Setenv("QLIKSENSE_KEY_LOCATION", secretKeyLocation)
privKeyFile := filepath.Join(secretKeyPairDir, "qliksensePriv")
// construct and write priv key file into secretsDir location
err = ioutil.WriteFile(privKeyFile, privKeyBytes, 0777)
//privKeyFile := filepath.Join(secretKeyLocation, "user_secret_key")
key, err := api.LoadSecretKey(secretKeyLocation)
if key == "" {
key, err = api.GenerateAndStoreSecretKey(secretKeyLocation)
}
encData, _ := api.EncryptData([]byte(decText), key)
encText := b64.StdEncoding.EncodeToString(encData)
targetFileString := fmt.Sprintf(targetFileStringTemplate, encText)
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
return "", "", 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
return targetFile, key, err
}
func setup() func() {
@@ -328,7 +244,7 @@ func TestSetOtherConfigs(t *testing.T) {
q: &Qliksense{
QliksenseHome: testDir,
},
args: []string{"profile=minikube", "rotateKeys=yes", "storageClassName=efs", "gitops.enabled=yes", "gitops.schedule=30 * * * *", "git.repository=master", "git.username=foo", "git.accesstoken=1234"},
args: []string{"profile=minikube", "rotateKeys=yes", "storageClassName=efs", "opsRunner.enabled=yes", "opsRunner.schedule=30 * * * *", "git.repository=master", "git.userName=foo", "git.accessToken=1234"},
},
wantErr: false,
},
@@ -338,7 +254,7 @@ func TestSetOtherConfigs(t *testing.T) {
q: &Qliksense{
QliksenseHome: testDir,
},
args: []string{"someconfig=somevalue, gitops.schedule=bar", "gitops.enabled=bar", "git.foo=bar", "rotatekeys=bar"},
args: []string{"someconfig=somevalue, opsRunner.schedule=bar", "opsRunner.enabled=bar", "git.foo=bar", "rotateKeys=bar"},
},
wantErr: true,
},
@@ -380,7 +296,7 @@ func TestSetConfigs(t *testing.T) {
q: &Qliksense{
QliksenseHome: testDir,
},
args: []string{"qliksense.acceptEULA=\"yes\"", "qliksense.mongoDbUri=\"mongo://mongo:3307\""},
args: []string{"qliksense.acceptEULA=\"yes\"", "qliksense.mongodbUri=\"mongo://mongo:3307\""},
},
wantErr: false,
},
@@ -389,7 +305,7 @@ func TestSetConfigs(t *testing.T) {
defer tearDown()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.args.q.SetConfigs(tt.args.args); (err != nil) != tt.wantErr {
if err := tt.args.q.SetConfigs(tt.args.args, false); (err != nil) != tt.wantErr {
t.Errorf("SetConfigs() error = %v, wantErr %v", err, tt.wantErr)
}
})
@@ -508,9 +424,14 @@ spec:
})
}
}
func removePrivateKey() {
err := os.Remove(filepath.Join(testDir, secrets, contexts, qlikDefaultContext, secrets, "user_secret_key"))
if err != nil {
log.Fatalf("Could not delete private key %v", err)
}
return
}
func Test_PrepareK8sSecret(t *testing.T) {
type fields struct {
QliksenseHome string
}
@@ -530,7 +451,7 @@ func Test_PrepareK8sSecret(t *testing.T) {
wantErr: false,
setup: func() (string, func()) {
tearDown := setup()
_, _, targetFile, _ := setupTargetFileAndPrivateKey()
targetFile, _, _ := setupTargetFileAndPrivateKey()
return targetFile, tearDown
},
},
@@ -543,7 +464,7 @@ func Test_PrepareK8sSecret(t *testing.T) {
wantErr: true,
setup: func() (string, func()) {
tearDown := setup()
_, _, targetFile, _ := setupTargetFileAndPrivateKey()
targetFile, _, _ := setupTargetFileAndPrivateKey()
removePrivateKey()
return targetFile, tearDown
},
@@ -557,8 +478,7 @@ func Test_PrepareK8sSecret(t *testing.T) {
wantErr: true,
setup: func() (string, func()) {
tearDown := setup()
_, _, _, _ = setupTargetFileAndPrivateKey()
removePrivateKey()
setupTargetFileAndPrivateKey()
return "", tearDown
},
},
@@ -638,6 +558,7 @@ func Test_SetSecrets(t *testing.T) {
type args struct {
args []string
isSecretSet bool
base64 bool
}
tests := []struct {
name string
@@ -651,18 +572,30 @@ func Test_SetSecrets(t *testing.T) {
QliksenseHome: testDir,
},
args: args{
args: []string{"qliksense.mongoDbUri=\"mongodb://qlik-default-mongodb:27017/qliksense?ssl=false\""},
args: []string{"qliksense.mongodbUri=\"mongodb://qlik-default-mongodb:27017/qliksense?ssl=false\""},
isSecretSet: false,
},
wantErr: false,
},
{
name: "valid secret secrets=false base64 encoded",
fields: fields{
QliksenseHome: testDir,
},
args: args{
args: []string{"qliksense.mongodbUri=bW9uZ29kYjovL3FsaWstZGVmYXVsdC1tb25nb2RiOjI3MDE3L3FsaWtzZW5zZT9zc2w9ZmFsc2U="},
isSecretSet: false,
base64: true,
},
wantErr: false,
},
{
name: "test1 valid secret secrets=true",
fields: fields{
QliksenseHome: testDir,
},
args: args{
args: []string{"qliksense.mongoDbUri=\"mongo://mongo:3307\""},
args: []string{"qliksense.mongodbUri=\"mongo://mongo:3307\""},
isSecretSet: true,
},
wantErr: false,
@@ -673,7 +606,7 @@ func Test_SetSecrets(t *testing.T) {
QliksenseHome: testDir,
},
args: args{
args: []string{"qliksense.mongoDbUri=\"mongodb://qlik-default-mongodb:27017/qliksense?ssl=false\""},
args: []string{"qliksense.mongodbUri=\"mongodb://qlik-default-mongodb:27017/qliksense?ssl=false\""},
isSecretSet: true,
},
wantErr: false,
@@ -691,22 +624,17 @@ func Test_SetSecrets(t *testing.T) {
},
}
tearDown := setup()
_, privateKeyBytes, _, err := setupTargetFileAndPrivateKey()
_, encryptionKey, 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 {
if err := q.SetSecrets(tt.args.args, tt.args.isSecretSet, tt.args.base64); (err != nil) != tt.wantErr {
t.Errorf("SetSecrets() error = %v, wantErr %v", err, tt.wantErr)
t.FailNow()
}
@@ -717,7 +645,10 @@ func Test_SetSecrets(t *testing.T) {
// extract the value for testing
testValueArr := strings.SplitN(tt.args.args[0], "=", 2)
testValue := strings.ReplaceAll(testValueArr[1], "\"", "")
if tt.args.base64 {
d, _ := b64.StdEncoding.DecodeString(testValue)
testValue = strings.Trim(string(d), "\n ")
}
qliksenseCR, err := readCRFile()
if err != nil {
err = fmt.Errorf("Not able to read from context file: %v", err)
@@ -734,13 +665,7 @@ func Test_SetSecrets(t *testing.T) {
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)
decryptedVal, err := api.DecryptData([]byte(valToBeEncrypted), encryptionKey)
if err != nil {
err := fmt.Errorf("Error occurred while testing decryption: %v", err)
log.Printf("No Data in Secret: %v", err)
@@ -781,7 +706,8 @@ func getValueToBeDecodedForSetSecrets(item config.NameValue, qliksenseCR *api.Ql
}
// secret=false
if item.Value != "" {
return item.Value, nil
b, err := b64.RawStdEncoding.DecodeString(item.Value)
return string(b), err
}
err := fmt.Errorf("Both Value and ValueFrom are empty")
return "", err
@@ -929,9 +855,10 @@ func TestDeleteContexts(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 {
if err := q.DeleteContextConfig(arg, true); (err != nil) != tt.wantErr {
t.Errorf("DeleteContext() error = %v, wantErr %v", err, tt.wantErr)
}
})
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,8 +31,10 @@ const (
func (q *Qliksense) PullImages(version, profile string) error {
qConfig := qapi.NewQConfig(q.QliksenseHome)
if version != "" {
if err := q.FetchQK8s(version); err != nil {
return err
if !qConfig.IsRepoExistForCurrent(version) {
if err := q.FetchQK8s(version); err != nil {
return err
}
}
}
qcr, err := qConfig.GetCurrentCR()
@@ -44,8 +46,8 @@ func (q *Qliksense) PullImages(version, profile string) error {
}
if profile != "" {
qcr.Spec.Profile = profile
if e := qConfig.WriteCR(qcr); e != nil {
return e
if err := qConfig.WriteCR(qcr); err != nil {
return err
}
}
return q.PullImagesForCurrentCR()
@@ -73,7 +75,7 @@ func (q *Qliksense) PullImagesForCurrentCR() error {
}
images := versionOut.Images
if err := q.appendOperatorImages(&images); err != nil {
if err := q.appendAdditionalImages(&images, qcr); err != nil {
return err
}
@@ -93,6 +95,19 @@ func (q *Qliksense) PullImagesForCurrentCR() error {
return nil
}
func (q *Qliksense) appendOpsRunnerImage(images *[]string, qcr *qapi.QliksenseCR) {
if qcr.Spec.OpsRunner != nil && qcr.Spec.OpsRunner.Image != "" {
*images = append(*images, qcr.Spec.OpsRunner.Image)
}
}
func (q *Qliksense) appendPreflightImages(images *[]string) {
pf := qapi.NewPreflightConfig(q.QliksenseHome)
for _, preflightImage := range pf.GetImageMap() {
*images = append(*images, preflightImage)
}
}
func (q *Qliksense) appendOperatorImages(images *[]string) error {
if operatorImages, err := getImageList([]byte(q.GetOperatorControllerString())); err != nil {
return err
@@ -140,7 +155,6 @@ func pullImage(image, imagesDir string) error {
return nil
}
// TagAndPushImages ...
func (q *Qliksense) PushImagesForCurrentCR() error {
qConfig := qapi.NewQConfig(q.QliksenseHome)
qcr, err := qConfig.GetCurrentCR()
@@ -155,7 +169,7 @@ func (q *Qliksense) PushImagesForCurrentCR() error {
if err != nil {
if os.IsNotExist(err) {
dockerConfigJsonSecret = &qapi.DockerConfigJsonSecret{
Uri: qcr.GetImageRegistry(),
Uri: qcr.Spec.GetImageRegistry(),
}
} else {
return err
@@ -173,7 +187,7 @@ func (q *Qliksense) PushImagesForCurrentCR() error {
}
images := versionOut.Images
if err := q.appendOperatorImages(&images); err != nil {
if err := q.appendAdditionalImages(&images, qcr); err != nil {
return err
}
@@ -194,6 +208,15 @@ func (q *Qliksense) PushImagesForCurrentCR() error {
return nil
}
func (q *Qliksense) appendAdditionalImages(images *[]string, qcr *qapi.QliksenseCR) error {
if err := q.appendOperatorImages(images); err != nil {
return err
}
q.appendOpsRunnerImage(images, qcr)
q.appendPreflightImages(images)
return nil
}
func pushImage(image, imagesDir string, dockerConfigJsonSecret *qapi.DockerConfigJsonSecret) error {
imageNameParts := getImageNameParts(image)
srcDir := filepath.Join(imagesDir, imageIndexDirName, imageNameParts.name, imageNameParts.tag)

View File

@@ -68,6 +68,9 @@ const (
)
func Test_Pull_Push_ImagesForCurrentCR(t *testing.T) {
if testing.Short() {
t.Skip("Skipping pull/push tests in short mode")
}
var testCases = []struct {
name string
registryAuth bool
@@ -131,7 +134,7 @@ func Test_Pull_Push_ImagesForCurrentCR(t *testing.T) {
}
q := &Qliksense{
QliksenseHome: tmpQlikSenseHome,
CrdBox: packr.New("crds", "./crds"),
CrdBox: &packr.Box{},
}
var versionOut VersionOutput
@@ -170,29 +173,88 @@ func Test_Pull_Push_ImagesForCurrentCR(t *testing.T) {
}
}
func setupQlikSenseHome(t *testing.T, tmpQlikSenseHome string, registry *testRegistryV2, clientAuth clientAuthType) error {
if err := ioutil.WriteFile(path.Join(tmpQlikSenseHome, "config.yaml"), []byte(`
apiVersion: config.qlik.com/v1
kind: QliksenseConfig
func Test_appendAdditionalImages(t *testing.T) {
tmpQlikSenseHome, err := ioutil.TempDir("", "tmp-qlik-sense-home-")
if err != nil {
t.Fatalf("unexpected error creating tmp dir: %v", err)
}
defer os.RemoveAll(tmpQlikSenseHome)
setupQliksenseTestDefaultContext(t, tmpQlikSenseHome, `
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: QliksenseConfigMetadata
name: qlik-default
spec:
contexts:
- name: qlik-default
crFile: contexts/qlik-default/qlik-default.yaml
currentContext: qlik-default
`), os.ModePerm); err != nil {
return err
opsRunner:
image: some-gitops-image
`)
q := &Qliksense{
QliksenseHome: tmpQlikSenseHome,
CrdBox: packr.New("crds", "./crds"),
}
defaultContextDir := path.Join(tmpQlikSenseHome, "contexts", "qlik-default")
if err := os.MkdirAll(defaultContextDir, os.ModePerm); err != nil {
return err
pf := api.NewPreflightConfig(q.QliksenseHome)
if err := pf.Initialize(); err != nil {
t.Fatalf("unexpected error initializing preflight: %v", err)
}
qConfig := api.NewQConfig(q.QliksenseHome)
qcr, err := qConfig.GetCurrentCR()
if err != nil {
t.Fatalf("unexpected error getting current CR: %v", err)
}
images := make([]string, 0)
if err := q.appendAdditionalImages(&images, qcr); err != nil {
t.Fatalf("unexpected error appending additional images: %v", err)
}
expectedNumberAdditionalImages := 5
if len(images) != expectedNumberAdditionalImages {
t.Fatalf("unexpected number of additional images: %v, expected: %v", len(images), expectedNumberAdditionalImages)
}
haveMatchingImage := func(test func(string) bool) bool {
for _, image := range images {
if test(image) {
return true
}
}
return false
}
if !haveMatchingImage(func(image string) bool {
return strings.Contains(image, "qlik-docker-oss.bintray.io/qliksense-operator:")
}) {
t.Fatal("expected to find the operator image in the list, but it wasn't there")
}
if !haveMatchingImage(func(image string) bool {
return image == "some-gitops-image"
}) {
t.Fatal("expected to find the GitOps image in the list, but it wasn't there")
}
if !haveMatchingImage(func(image string) bool {
return image == "nginx"
}) {
t.Fatal("expected to find the nginx Preflight image in the list, but it wasn't there")
}
if !haveMatchingImage(func(image string) bool {
return image == "subfuzion/netcat"
}) {
t.Fatal("expected to find the netcat Preflight image in the list, but it wasn't there")
}
if !haveMatchingImage(func(image string) bool {
return image == "qlik-docker-oss.bintray.io/preflight-mongo"
}) {
t.Fatal("expected to find the preflight-mongo image in the list, but it wasn't there")
}
}
func setupQlikSenseHome(t *testing.T, tmpQlikSenseHome string, registry *testRegistryV2, clientAuth clientAuthType) error {
version := "foo"
manifestsRootDir := fmt.Sprintf("%s/repo/%s", defaultContextDir, version)
if err := ioutil.WriteFile(path.Join(defaultContextDir, "qlik-default.yaml"), []byte(fmt.Sprintf(`
manifestsRootDir := filepath.ToSlash(path.Join(tmpQlikSenseHome, "contexts", "qlik-default", "repo", version))
cr := fmt.Sprintf(`
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
@@ -208,9 +270,8 @@ spec:
manifestsRoot: %s
rotateKeys: "yes"
releaseName: qlik-default
`, version, registry.url, manifestsRootDir)), os.ModePerm); err != nil {
return err
}
`, version, registry.url, manifestsRootDir)
setupQliksenseTestDefaultContext(t, tmpQlikSenseHome, cr)
if clientAuth == clientAuthProvided || clientAuth == clientAuthProvidedButIncorrect {
if registry.username == "" || clientAuth == clientAuthProvidedButIncorrect {
@@ -254,23 +315,23 @@ spec:
return err
}
transformersDir := path.Join(manifestsRootDir, "transformers")
transformersDir := path.Join(manifestsRootDir, "manifests", "base", "transformers", "release")
if err := os.MkdirAll(transformersDir, os.ModePerm); err != nil {
return err
}
if err := ioutil.WriteFile(path.Join(transformersDir, "qseokversion.yaml"), []byte(`
apiVersion: qlik.com/v1
kind: SelectivePatch
if err := ioutil.WriteFile(path.Join(transformersDir, "annotations.yaml"), []byte(`
apiVersion: builtin
kind: AnnotationsTransformer
metadata:
name: qseokversion
enabled: true
patches:
- target:
kind: HelmChart
labelSelector: name!=qliksense-init
patch: |-
chartName: qliksense
chartVersion: 1.21.23
name: common-annotations
annotations:
app.kubernetes.io/name: qliksense
app.kubernetes.io/instance: $(PREFIX)
app.kubernetes.io/version: 1.21.23
app.kubernetes.io/managed-by: qliksense-operator
fieldSpecs:
- path: metadata/annotations
create: true
`), os.ModePerm); err != nil {
return err
}

View File

@@ -1,13 +1,27 @@
package qliksense
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"github.com/google/uuid"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
kapis_git "github.com/qlik-oss/k-apis/pkg/git"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
type FetchCommandOptions struct {
GitUrl string
AccessToken string
Version string
SecretName string
Overwrite bool
}
const (
QLIK_GIT_REPO = "https://github.com/qlik-oss/qliksense-k8s"
)
@@ -17,24 +31,127 @@ func (q *Qliksense) FetchQK8s(version string) error {
return fetchAndUpdateCR(qConfig, version)
}
func (q *Qliksense) FetchK8sWithOpts(opts *FetchCommandOptions) error {
qConfig := qapi.NewQConfig(q.QliksenseHome)
cr, err := qConfig.GetCurrentCR()
if err != nil {
return err
}
if opts.AccessToken != "" {
encKey, err := qConfig.GetEncryptionKeyFor(cr.GetName())
if err != nil {
return err
}
if err := cr.SetFetchAccessToken(opts.AccessToken, encKey); err != nil {
return err
}
}
if opts.SecretName != "" {
cr.SetFetchAccessSecretName(opts.SecretName)
}
if opts.GitUrl != "" {
cr.SetFetchUrl(opts.GitUrl)
}
v := getVersion(opts, cr)
if v != "" && qConfig.IsRepoExistForCurrent(v) {
if opts.Overwrite || getVerionsOverwriteConfirmation(v) == "y" {
if err := qConfig.DeleteRepoForCurrent(v); err != nil {
return err
}
} else {
// nothing to do
return nil
}
}
qConfig.WriteCR(cr)
return fetchAndUpdateCR(qConfig, v)
}
// fetchAndUpdateCR fetch
func fetchAndUpdateCR(qConfig *qapi.QliksenseConfig, version string) error {
qcr, err := qConfig.GetCurrentCR()
if err != nil {
fmt.Println("cannot get the current-context cr", err)
return err
}
if qConfig.IsRepoExistForCurrent(version) {
return nil
if version == "" {
if qcr.GetLabelFromCr("version") == "" {
if encKey, err := qConfig.GetEncryptionKeyFor(qcr.GetName()); err != nil {
return err
} else if version, err = getLatestTag(qcr.GetFetchUrl(), qcr.GetFetchAccessToken(encKey)); err != nil {
return err
}
} else {
version = qcr.GetLabelFromCr("version")
}
}
encKey, err := qConfig.GetEncryptionKeyFor(qcr.GetName())
if err != nil {
return err
}
// downlaod to temp first
tempDest, err := fetchToTempDir(qcr.GetFetchUrl(), version, qcr.GetFetchAccessToken(encKey))
if err != nil {
return err
}
destDir := qConfig.BuildRepoPath(version)
fmt.Printf("fetching version [%s] from %s\n", version, QLIK_GIT_REPO)
if repo, err := kapis_git.CloneRepository(destDir, QLIK_GIT_REPO, nil); err != nil {
return err
} else if err = kapis_git.Checkout(repo, version, fmt.Sprintf("%v-by-operator-%v", version, uuid.New().String()), nil); err != nil {
return err
destDir := qConfig.BuildRepoPath(version)
fmt.Printf("fetching version [%s] from %s\n", version, qcr.GetFetchUrl())
if err := qapi.CopyDirectory(tempDest, destDir); err != nil {
return nil
}
qcr.Spec.ManifestsRoot = qConfig.BuildCurrentManifestsRoot(version)
qcr.AddLabelToCr("version", version)
return qConfig.WriteCurrentContextCR(qcr)
}
func fetchToTempDir(gitUrl, gitRef, accessToken string) (string, error) {
tmpDir, err := ioutil.TempDir("", "")
if err != nil {
return "", err
}
downloadPath := path.Join(tmpDir, "repo")
var auth transport.AuthMethod
if accessToken != "" {
auth = &http.BasicAuth{
Username: "something",
Password: accessToken,
}
}
if repo, err := kapis_git.CloneRepository(downloadPath, gitUrl, auth); err != nil {
return "", err
} else if err := kapis_git.Checkout(repo, gitRef, "", auth); err != nil {
return "", err
} else {
return downloadPath, nil
}
}
func getVersion(opts *FetchCommandOptions, qcr *qapi.QliksenseCR) string {
if opts.Version == "" {
if qcr.GetLabelFromCr("version") != "" {
return qcr.GetLabelFromCr("version")
}
}
return opts.Version
}
func getVerionsOverwriteConfirmation(version string) string {
reader := bufio.NewReader(os.Stdin)
fmt.Println("The version [" + version + "] already exists")
cfm := "n"
for {
fmt.Print("Do you want to delete and fetch again [y/N]: ")
cfm, _ = reader.ReadString('\n')
cfm = strings.Replace(cfm, "\n", "", -1)
cfm = strings.TrimSpace(cfm)
if cfm == "" {
cfm = "n"
}
cfm = strings.ToLower(cfm)
if cfm == "y" || cfm == "n" {
break
}
}
return cfm
}

View File

@@ -32,4 +32,19 @@ func TestFetchAndUpdateCR(t *testing.T) {
t.Log("actual path: " + cr.Spec.ManifestsRoot + ", expected path: contexts/test1/qlik-k8s/v0.0.2")
t.FailNow()
}
//testing latest tag is fetched
cr.AddLabelToCr("version", "")
qConfig.WriteCR(cr)
err := fetchAndUpdateCR(qConfig, "")
if err != nil {
t.Log(err)
t.Fail()
}
cr = &qapi.QliksenseCR{}
qapi.ReadFromFile(cr, actualCrFile)
v := cr.GetLabelFromCr("version")
if v == "" || v == "v0.0.2" {
t.Log("should get latest but got version: " + v)
t.Fail()
}
}

View File

@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"github.com/Masterminds/semver/v3"
"github.com/qlik-oss/k-apis/pkg/git"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
@@ -86,3 +87,69 @@ func (q *Qliksense) GetInstallableVersions(opts *LsRemoteCmdOptions) error {
return nil
}
func getLatestTag(repoUrl, accessToken string) (string, error) {
if repoUrl == "" {
return "", errors.New("repo url is empty")
}
repoPath, err := fetchToTempDir(repoUrl, "master", accessToken)
if err != nil {
return "", err
}
r, err := git.OpenRepository(repoPath)
if err != nil {
return "", err
}
remoteRefsList, err := git.GetRemoteRefs(r, nil,
&git.RemoteRefConstraints{
Include: true,
Sort: true,
SortOrder: git.RefSortOrderDescending,
},
&git.RemoteRefConstraints{
Include: false,
Sort: true,
SortOrder: git.RefSortOrderAscending,
})
if err != nil {
return "", err
}
if len(remoteRefsList) < 1 {
return "", errors.New("cannot find git remote information in the config repository")
}
var originRemoteRefs *git.RemoteRefs
for _, remoteRefs := range remoteRefsList {
if remoteRefs.Name == "origin" {
originRemoteRefs = remoteRefs
break
}
}
if originRemoteRefs == nil {
return "", errors.New(`cannot find git remote called "origin" in the config repository`)
}
tags := originRemoteRefs.Tags
if len(tags) == 0 {
return "", errors.New(("no tags exists in the repo: " + repoPath))
}
maxSem, _ := semver.NewVersion(tags[0])
for _, sv := range tags[1:] {
if sv == "" {
continue
}
v, err := semver.NewVersion(sv)
if err != nil {
// it may happen, in the repo some tags may not conform to semver
//fmt.Println("the tag is not conform to semver: " + sv)
continue
}
if maxSem == nil || maxSem.LessThan(v) {
maxSem = v
}
}
return maxSem.Original(), nil
}

View File

@@ -0,0 +1,25 @@
package qliksense
import (
"testing"
"github.com/Masterminds/semver/v3"
)
func TestGetLatestTag(t *testing.T) {
s, err := getLatestTag(defaultConfigRepoGitUrl, "")
if s == "" || err != nil {
t.Log(err)
t.Fail()
}
sv, err := semver.NewVersion(s)
if err != nil {
t.Log(err)
t.Log(sv)
}
baseV, _ := semver.NewVersion("v0.0.8")
if !sv.GreaterThan(baseV) {
t.Log("Expected greater than v0.0.8, but got: " + s)
t.Fail()
}
}

View File

@@ -3,9 +3,14 @@ package qliksense
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"github.com/mitchellh/go-homedir"
"github.com/qlik-oss/k-apis/pkg/config"
"github.com/qlik-oss/k-apis/pkg/cr"
"sigs.k8s.io/kustomize/api/filesys"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
@@ -13,11 +18,12 @@ import (
type InstallCommandOptions struct {
StorageClass string
MongoDbUri string
MongodbUri string
RotateKeys string
DryRun bool
}
func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, keepPatchFiles bool) error {
func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, cleanPatchFiles bool) error {
// step1: fetch 1.0.0 # pull down qliksense-k8s@1.0.0
// step2: operator view | kubectl apply -f # operator manifest (CRD)
@@ -26,12 +32,10 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, kee
// fetch the version
qConfig := qapi.NewQConfig(q.QliksenseHome)
if !keepPatchFiles {
defer func() {
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
fmt.Printf("error removing temporary changes to the config: %v\n", err)
}
}()
if cleanPatchFiles {
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
fmt.Printf("error removing temporary changes to the config: %v\n", err)
}
}
qcr, err := qConfig.GetCurrentCR()
@@ -41,8 +45,8 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, kee
}
qcr.SetEULA("yes")
if opts.MongoDbUri != "" {
qcr.Spec.AddToSecrets("qliksense", "mongoDbUri", opts.MongoDbUri, "")
if opts.MongodbUri != "" {
qcr.Spec.AddToSecrets("qliksense", "mongodbUri", opts.MongodbUri, "")
}
if opts.StorageClass != "" {
qcr.Spec.StorageClassName = opts.StorageClass
@@ -50,42 +54,46 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, kee
if opts.RotateKeys != "" {
qcr.Spec.RotateKeys = opts.RotateKeys
}
// for debugging purpose
if opts.DryRun {
// generate patches
qcr.Spec.RotateKeys = "None"
userHomeDir, _ := homedir.Dir()
fmt.Println("Generating patches only")
cr.GeneratePatches(&qcr.KApiCr, path.Join(userHomeDir, ".kube", "config"))
return nil
}
qConfig.WriteCurrentContextCR(qcr)
//if 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 {
if installed, err := q.CheckAllCrdsInstalled(); err != nil {
fmt.Println("error verifying whether CRDs are installed", err)
return err
} else if !installed {
return errors.New(`please install CRDs by executing: $ qliksense crds install`)
}
// check if acceptEULA is yes or not
if !qcr.IsEULA() {
return errors.New(agreementTempalte + "\n Please do $ qliksense install --acceptEULA=yes\n")
if err := applyImagePullSecret(qConfig); err != nil {
return err
}
//CRD will be installed outside of operator
//install operator controller into the namespace
fmt.Println("Installing operator controller")
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)
if operatorControllerString, err := q.getProcessedOperatorControllerString(qcr); err != nil {
fmt.Println("error extracting/transforming operator controller", err)
return err
} else if err := qapi.KubectlApply(operatorControllerString, ""); err != nil {
fmt.Println("cannot do kubectl apply on operator controller", err)
return err
}
// create patch dependent resoruces
fmt.Println("Installing resoruces used kuztomize patch")
if err := q.createK8sResoruceBeforePatch(qcr); err != nil {
// create patch dependent resources
fmt.Println("Installing resources used by the kuztomize patch")
if err := q.createK8sResourceBeforePatch(qcr); err != nil {
return err
}
if qcr.Spec.Git != nil && qcr.Spec.Git.Repository != "" {
if qcr.Spec.OpsRunner != nil {
// fetching and applying manifest will be in the operator controller
// get decrypted cr
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
@@ -94,11 +102,10 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, kee
return q.applyCR(dcr)
}
}
if version == "" {
version = qcr.GetLabelFromCr("version")
}
if err := fetchAndUpdateCR(qConfig, version); err != nil {
return err
if !qcr.IsRepoExist() {
if err := fetchAndUpdateCR(qConfig, version); err != nil {
return err
}
}
qcr, err = qConfig.GetCurrentCR()
@@ -110,43 +117,54 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, kee
}
// install generated manifests into cluster
fmt.Println("Installing generated manifests into cluster")
fmt.Println("Installing generated manifests into the cluster")
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
return err
} else if err := q.applyConfigToK8s(dcr); err != nil {
fmt.Println("cannot do kubectl apply on manifests")
return err
} else {
return q.applyCR(dcr)
if IsQliksenseInstalled(dcr.GetName()) {
return q.UpgradeQK8s(cleanPatchFiles)
}
if err := q.applyConfigToK8s(dcr); err != nil {
fmt.Println("cannot do kubectl apply on manifests")
return err
} else {
return q.applyCR(dcr)
}
}
}
func installOrRemoveImagePullSecret(qConfig *qapi.QliksenseConfig) error {
func (q *Qliksense) getProcessedOperatorControllerString(qcr *qapi.QliksenseCR) (string, error) {
operatorControllerString := q.GetOperatorControllerString()
if imageRegistry := qcr.Spec.GetImageRegistry(); imageRegistry != "" {
return kustomizeForImageRegistry(operatorControllerString, pullSecretName,
path.Join(qliksenseOperatorImageRepo, qliksenseOperatorImageName),
path.Join(imageRegistry, qliksenseOperatorImageName))
}
return operatorControllerString, nil
}
func applyImagePullSecret(qConfig *qapi.QliksenseConfig) error {
if pullDockerConfigJsonSecret, err := qConfig.GetPullDockerConfigJsonSecret(); err == nil {
if dockerConfigJsonSecretYaml, err := pullDockerConfigJsonSecret.ToYaml(nil); err != nil {
if dockerConfigJsonSecretYaml, err := pullDockerConfigJsonSecret.ToYaml(""); err != nil {
return err
} else if err := qapi.KubectlApply(string(dockerConfigJsonSecretYaml), ""); err != nil {
return err
}
} else {
deleteDockerConfigJsonSecret := qapi.DockerConfigJsonSecret{
Name: pullSecretName,
}
if deleteDockerConfigJsonSecretYaml, err := deleteDockerConfigJsonSecret.ToYaml(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 {
dir, err := ioutil.TempDir("", "")
if err != nil {
return "", err
} else if err := fSys.WriteFile("/addImagePullSecrets.yaml", []byte(fmt.Sprintf(`
}
defer os.RemoveAll(dir)
if err := ioutil.WriteFile(filepath.Join(dir, "resources.yaml"), []byte(resources), os.ModePerm); err != nil {
return "", err
} else if err := ioutil.WriteFile(filepath.Join(dir, "addImagePullSecrets.yaml"), []byte(fmt.Sprintf(`
apiVersion: builtin
kind: PatchTransformer
metadata:
@@ -155,9 +173,9 @@ patch: '[{"op": "add", "path": "/spec/template/spec/imagePullSecrets", "value":
target:
name: .*-operator
kind: Deployment
`, dockerConfigJsonSecretName))); err != nil {
`, dockerConfigJsonSecretName)), os.ModePerm); err != nil {
return "", err
} else if err := fSys.WriteFile("/kustomization.yaml", []byte(fmt.Sprintf(`
} else if err := ioutil.WriteFile(filepath.Join(dir, "kustomization.yaml"), []byte(fmt.Sprintf(`
resources:
- resources.yaml
transformers:
@@ -165,9 +183,9 @@ transformers:
images:
- name: %s
newName: %s
`, name, newName))); err != nil {
`, name, newName)), os.ModePerm); err != nil {
return "", err
} else if out, err := executeKustomizeBuildForFileSystem("/", fSys); err != nil {
} else if out, err := executeKustomizeBuildForFileSystem(dir, filesys.MakeFsOnDisk()); err != nil {
return "", err
} else {
return string(out), nil
@@ -177,7 +195,7 @@ images:
func (q *Qliksense) applyCR(cr *qapi.QliksenseCR) error {
// install operator cr into cluster
//get the current context cr
fmt.Println("Install operator CR into cluster")
fmt.Println("Installing operator CR into the cluster")
r, err := cr.GetString()
if err != nil {
return err
@@ -189,7 +207,7 @@ func (q *Qliksense) applyCR(cr *qapi.QliksenseCR) error {
return nil
}
func (q *Qliksense) createK8sResoruceBeforePatch(qcr *qapi.QliksenseCR) error {
func (q *Qliksense) createK8sResourceBeforePatch(qcr *qapi.QliksenseCR) error {
for svc, nvs := range qcr.Spec.Secrets {
for _, nv := range nvs {
if isK8sSecretNeedToCreate(nv) {

View File

@@ -1,15 +1,24 @@
package qliksense
import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"testing"
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/api/resid"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
"github.com/gobuffalo/packr/v2"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func TestCreateK8sResoruceBeforePatch(t *testing.T) {
func TestCreateK8sResourceBeforePatch(t *testing.T) {
td := setup()
sampleCr := `
apiVersion: qlik.com/v1
@@ -34,7 +43,7 @@ spec:
value: "yes"
secrets:
qliksense:
- name: mongoDbUri
- name: mongodbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
profile: docker-desktop
rotateKeys: "yes"`
@@ -57,9 +66,111 @@ spec:
t.Log(err)
t.FailNow()
}
if err = q.createK8sResoruceBeforePatch(cr); err != nil {
if err = q.createK8sResourceBeforePatch(cr); err != nil {
t.Log(err)
t.FailNow()
}
td()
}
func setupQliksenseTestDefaultContext(t *testing.T, tmpQlikSenseHome, CR string) {
if err := ioutil.WriteFile(path.Join(tmpQlikSenseHome, "config.yaml"), []byte(`
apiVersion: config.qlik.com/v1
kind: QliksenseConfig
metadata:
name: QliksenseConfigMetadata
spec:
contexts:
- name: qlik-default
crFile: contexts/qlik-default/qlik-default.yaml
currentContext: qlik-default
`), os.ModePerm); err != nil {
t.Fatalf("unexpected error: %v", err)
}
defaultContextDir := path.Join(tmpQlikSenseHome, "contexts", "qlik-default")
if err := os.MkdirAll(defaultContextDir, os.ModePerm); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := ioutil.WriteFile(path.Join(defaultContextDir, "qlik-default.yaml"), []byte(CR), os.ModePerm); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func Test_getProcessedOperatorControllerString(t *testing.T) {
tmpQlikSenseHome, err := ioutil.TempDir("", "tmp-qlik-sense-home-")
if err != nil {
t.Fatalf("unexpected error creating tmp dir: %v", err)
}
defer os.RemoveAll(tmpQlikSenseHome)
registry := "registryFoo"
setupQliksenseTestDefaultContext(t, tmpQlikSenseHome, fmt.Sprintf(`
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: qlik-default
spec:
configs:
qliksense:
- name: imageRegistry
value: %v
`, registry))
q := &Qliksense{
QliksenseHome: tmpQlikSenseHome,
CrdBox: packr.New("crds", "./crds"),
}
qConfig := qapi.NewQConfig(q.QliksenseHome)
qcr, err := qConfig.GetCurrentCR()
if err != nil {
t.Fatalf("unexpected error getting current CR: %v", err)
}
originalOperatorString := q.GetOperatorControllerString()
processedOperatorString, err := q.getProcessedOperatorControllerString(qcr)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
controllerImageChecks := map[string]func(t *testing.T, controllerImage string){
originalOperatorString: func(t *testing.T, controllerImage string) {
expectedControllerImagePrefix := fmt.Sprintf("%v/%v:", qliksenseOperatorImageRepo, qliksenseOperatorImageName)
if !strings.HasPrefix(controllerImage, expectedControllerImagePrefix) {
t.Fatalf("expected controller image: %v to have prefix: %v", controllerImage, expectedControllerImagePrefix)
}
},
processedOperatorString: func(t *testing.T, controllerImage string) {
expectedControllerImagePrefix := fmt.Sprintf("%v/%v:", registry, qliksenseOperatorImageName)
if !strings.HasPrefix(controllerImage, expectedControllerImagePrefix) {
t.Fatalf("expected controller image: %v to have prefix: %v", controllerImage, expectedControllerImagePrefix)
}
},
}
resourceFactory := resmap.NewFactory(resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl()), nil)
for operatorString, controllerImageCheck := range controllerImageChecks {
resMap, err := resourceFactory.NewResMapFromBytes([]byte(operatorString))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
res, err := resMap.GetById(resid.NewResId(resid.Gvk{
Group: "apps",
Version: "v1",
Kind: "Deployment",
}, "qliksense-operator"))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
controllerImage, err := res.GetString("spec.template.spec.containers[0].image")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
controllerImageCheck(t, controllerImage)
}
}

View File

@@ -3,15 +3,26 @@ package qliksense
import (
"bytes"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"gopkg.in/yaml.v3"
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/api/resid"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
"gopkg.in/yaml.v2"
"github.com/Shopify/ejson"
"github.com/qlik-oss/k-apis/pkg/config"
@@ -27,7 +38,7 @@ func Test_ExecuteKustomizeBuild(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
kustomizationYamlFilePath := path.Join(tmpDir, "kustomization.yaml")
kustomizationYamlFilePath := filepath.Join(tmpDir, "kustomization.yaml")
kustomizationYaml := `
generatorOptions:
disableNameSuffixHash: true
@@ -58,6 +69,203 @@ metadata:
}
}
func Test_executeKustomizeBuild_onQlikConfig_withConcurrency(t *testing.T) {
if testing.Short() {
t.Skip("Skipping parallel kustomize test in short mode")
}
tmpDir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
defer os.RemoveAll(tmpDir)
configPath := filepath.Join(tmpDir, "config")
if repo, err := kapis_git.CloneRepository(configPath, defaultConfigRepoGitUrl, nil); err != nil {
t.Fatalf("unexpected error: %v\n", err)
} else if err := kapis_git.Checkout(repo, "v0.0.8", "v0.0.8", nil); err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
configsDir := filepath.Join(configPath, "manifests", "base", "manifests")
shouldSkipDir := func(dirName string) bool {
skipMap := map[string]bool{
"sense-client": true,
}
_, ok := skipMap[dirName]
return ok
}
insertConcatenationKuzYaml := func(dir string) error {
concatenatedMap := map[string]interface{}{}
concatenatedMap["apiVersion"] = "kustomize.config.k8s.io/v1beta1"
concatenatedMap["kind"] = "Kustomization"
resources := make([]string, 0)
infos, err := ioutil.ReadDir(dir)
if err != nil {
return err
}
for _, info := range infos {
if !info.IsDir() {
resources = append(resources, info.Name())
}
}
concatenatedMap["resources"] = resources
if concatenatedMapBytes, err := yaml.Marshal(&concatenatedMap); err != nil {
return err
} else if err := ioutil.WriteFile(filepath.Join(dir, "kustomization.yaml"), concatenatedMapBytes, os.ModePerm); err != nil {
return err
}
return nil
}
//sequential test:
sequentialManifestsDir := filepath.Join(tmpDir, "sequential")
if err := os.RemoveAll(sequentialManifestsDir); err != nil {
t.Fatalf("unexpected error: %v\n", err)
} else if err := os.MkdirAll(sequentialManifestsDir, os.ModePerm); err != nil {
t.Fatalf("unexpected error: %v\n", err)
} else if err := os.RemoveAll(filepath.Join(os.TempDir(), "dotHelm")); err != nil {
t.Fatalf("unexpected error: %v\n", err)
} else if err := os.RemoveAll(filepath.Join(os.TempDir(), ".chartcache")); err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
infos, err := ioutil.ReadDir(configsDir)
if err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
fmt.Print("running a sequential execution test...\n")
t1 := time.Now()
for _, info := range infos {
if shouldSkipDir(info.Name()) {
continue
}
if info.IsDir() {
if yamlResources, err := ExecuteKustomizeBuild(filepath.Join(configsDir, info.Name())); err != nil {
t.Fatalf("unexpected error kustomizing: %v, error: %v\n", info.Name(), err)
} else if err := ioutil.WriteFile(filepath.Join(sequentialManifestsDir, fmt.Sprintf("%v.yaml", info.Name())), yamlResources, os.ModePerm); err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
}
}
t2 := time.Now()
fmt.Printf("sequential execution test took: %vs\n", t2.Sub(t1).Seconds())
if err := insertConcatenationKuzYaml(sequentialManifestsDir); err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
concatenatedSequentialManifests, err := ExecuteKustomizeBuild(sequentialManifestsDir)
if err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
//concurrent tests:
numConcurrentTests := 5
concatenatedConcurrentManifestsList := make([][]byte, 0)
for i := 0; i < numConcurrentTests; i++ {
concurrentManifestsDir := filepath.Join(tmpDir, fmt.Sprintf("concurrent-%v", i))
if err := os.RemoveAll(concurrentManifestsDir); err != nil {
t.Fatalf("unexpected error: %v\n", err)
} else if err := os.MkdirAll(concurrentManifestsDir, os.ModePerm); err != nil {
t.Fatalf("unexpected error: %v\n", err)
} else if err := os.RemoveAll(filepath.Join(os.TempDir(), "dotHelm")); err != nil {
t.Fatalf("unexpected error: %v\n", err)
} else if err := os.RemoveAll(filepath.Join(os.TempDir(), ".chartcache")); err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
var wg sync.WaitGroup
var concurrentErrorCounter int32
osStderrBackup := os.Stderr
tmpStdErrFile, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
os.Stderr = tmpStdErrFile
fmt.Print("running a concurrent execution test...\n")
t1 = time.Now()
for _, fi := range infos {
wg.Add(1)
go func(info os.FileInfo) {
defer wg.Done()
if shouldSkipDir(info.Name()) {
return
}
if info.IsDir() {
if yamlResources, err := ExecuteKustomizeBuild(filepath.Join(configsDir, info.Name())); err != nil {
fmt.Printf("unexpected error: %v\n", err)
atomic.AddInt32(&concurrentErrorCounter, 1)
} else if err := ioutil.WriteFile(filepath.Join(concurrentManifestsDir, fmt.Sprintf("%v.yaml", info.Name())), yamlResources, os.ModePerm); err != nil {
fmt.Printf("unexpected error: %v\n", err)
atomic.AddInt32(&concurrentErrorCounter, 1)
}
}
}(fi)
}
wg.Wait()
t2 = time.Now()
os.Stderr = osStderrBackup
os.Remove(tmpStdErrFile.Name())
if concurrentErrorCounter > 0 {
t.Fatalf("there were %v errors during the concurrent execution", concurrentErrorCounter)
}
fmt.Printf("concurrent execution test took: %vs\n", t2.Sub(t1).Seconds())
if err := insertConcatenationKuzYaml(concurrentManifestsDir); err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
concatenatedConcurrentManifests, err := ExecuteKustomizeBuild(concurrentManifestsDir)
if err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
concatenatedConcurrentManifestsList = append(concatenatedConcurrentManifestsList, concatenatedConcurrentManifests)
}
getResMapBytesAdjustedForCaCertsJobName := func(resBytes []byte) ([]byte, error) {
resMapFactory := resmap.NewFactory(resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl()), nil)
if resMap, err := resMapFactory.NewResMapFromBytes(resBytes); err != nil {
return nil, err
} else if resources, err := resMap.Select(types.Selector{
Gvk: resid.Gvk{
Group: "batch",
Version: "v1",
Kind: "Job",
},
}); err != nil {
return nil, err
} else if re, err := regexp.Compile(`^.+-ca-certificates-[a-z]{5}$`); err != nil {
return nil, err
} else {
for _, res := range resources {
res.SetName(re.ReplaceAllString(res.GetName(), "qliksense-ca-certificates"))
}
return resMap.AsYaml()
}
}
sequentialFinalManifest, err := getResMapBytesAdjustedForCaCertsJobName(concatenatedSequentialManifests)
if err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
concurrentFinalManifestsList := make([][]byte, 0)
for _, concatenatedConcurrentManifest := range concatenatedConcurrentManifestsList {
if concurrentFinalManifest, err := getResMapBytesAdjustedForCaCertsJobName(concatenatedConcurrentManifest); err != nil {
t.Fatalf("unexpected error: %v\n", err)
} else {
concurrentFinalManifestsList = append(concurrentFinalManifestsList, concurrentFinalManifest)
}
}
for _, concurrentFinalManifest := range concurrentFinalManifestsList {
if !bytes.Equal(concurrentFinalManifest, sequentialFinalManifest) {
t.Fatalf("expected the concatenated concurrent manifest to equal the concatenated sequential manifest, but they didn't..."+
"\nconcurrent:\n%v\nsequential:\n%v", string(concurrentFinalManifest), string(sequentialFinalManifest))
}
}
}
func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "")
if err != nil {
@@ -65,10 +273,10 @@ func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
configPath := path.Join(tmpDir, "config")
configPath := filepath.Join(tmpDir, "config")
if repo, err := kapis_git.CloneRepository(configPath, defaultConfigRepoGitUrl, nil); err != nil {
t.Fatalf("unexpected error: %v\n", err)
} else if err := kapis_git.Checkout(repo, "v1.21.23-edge", "", nil); err != nil {
} else if err := kapis_git.Checkout(repo, "e38df644e759abf0b5941c1511d1a2cd5e3c42fa", "", nil); err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
@@ -86,7 +294,7 @@ func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
generateKeys(cr, "won't-use")
yamlResources, err := ExecuteKustomizeBuild(path.Join(configPath, "manifests", "base", "resources", "users"))
yamlResources, err := ExecuteKustomizeBuild(filepath.Join(configPath, "manifests", "base", "resources", "users"))
if err != nil {
t.Fatalf("unexpected kustomize error: %v\n", err)
}
@@ -102,8 +310,8 @@ func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
}
break
}
if resource["kind"].(string) == "Secret" && strings.Contains(resource["metadata"].(map[string]interface{})["name"].(string), "users-secrets-") {
keyIdBase64 = resource["data"].(map[string]interface{})["tokenAuthPrivateKeyId"].(string)
if resource["kind"].(string) == "Secret" && strings.Contains(resource["metadata"].(map[interface {}]interface {})["name"].(string), "users-secrets-") {
keyIdBase64 = resource["data"].(map[interface {}]interface {})["tokenAuthPrivateKeyId"].(string)
break
}
}
@@ -127,7 +335,7 @@ func generateKeys(cr *config.CRSpec, defaultKeyDir string) {
log.Printf("error generating application keys: %v\n", err)
} else if err := os.MkdirAll(keyDir, os.ModePerm); err != nil {
log.Printf("error makeing sure private key storage directory: %v exists, error: %v\n", keyDir, err)
} else if err := ioutil.WriteFile(path.Join(keyDir, ejsonPublicKey), []byte(ejsonPrivateKey), os.ModePerm); err != nil {
} else if err := ioutil.WriteFile(filepath.Join(keyDir, ejsonPublicKey), []byte(ejsonPrivateKey), os.ModePerm); err != nil {
log.Printf("error storing ejson private key: %v\n", err)
}
}
@@ -147,8 +355,8 @@ func Test_GetYamlDocKindFromMultiDoc(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
kustomizationYamlFilePath := path.Join(tmpDir, "kustomization.yaml")
testResFileYamlFilePath := path.Join(tmpDir, "test-file.yaml")
kustomizationYamlFilePath := filepath.Join(tmpDir, "kustomization.yaml")
testResFileYamlFilePath := filepath.Join(tmpDir, "test-file.yaml")
kustomizationYaml := `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

View File

@@ -51,7 +51,7 @@ func (q *Qliksense) loadCrStringIntoFileSystem(crstr string, overwriteExistingCo
}
// encrypt the secrets and do base64 then update the CR
rsaPublicKey, _, err := qConfig.GetContextEncryptionKeyPair(cr.GetName())
encryptionKey, err := qConfig.GetEncryptionKeyFor(cr.GetName())
if err != nil {
return "", err
}
@@ -63,13 +63,17 @@ func (q *Qliksense) loadCrStringIntoFileSystem(crstr string, overwriteExistingCo
Value: nv.Value,
SvcName: svc,
}
if err := q.processSecret(skv, rsaPublicKey, cr, false); err != nil {
if err := q.processSecret(skv, encryptionKey, cr, false); err != nil {
return cr.GetName(), err
}
}
}
}
if cr.Spec.FetchSource != nil && cr.Spec.FetchSource.AccessToken != "" {
if err := cr.SetFetchAccessToken(cr.Spec.FetchSource.AccessToken, encryptionKey); err != nil {
return "", err
}
}
// update manifestsRoot in case already exist
if existingCr, err := qConfig.GetCR(cr.GetName()); err == nil {
// cr exists, so update the manifestsRoot if version exist

View File

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

View File

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

View File

@@ -6,16 +6,24 @@ import (
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func (q *Qliksense) UninstallQK8s(contextName string) error {
qConfig := qapi.NewQConfig(q.QliksenseHome)
if contextName == "" {
contextName = qConfig.Spec.CurrentContext
} else if !qConfig.IsContextExist(contextName) {
return errors.New("context name [ " + contextName + " ] not found")
func (q *Qliksense) UninstallQK8s(contextName string, skipConfirmation bool) error {
ans := skipConfirmation
if ans == false {
ans = AskForConfirmation("Are You Sure? ")
}
str, err := q.getCRString(contextName)
if err != nil {
return err
if ans == true {
qConfig := qapi.NewQConfig(q.QliksenseHome)
if contextName == "" {
contextName = qConfig.Spec.CurrentContext
} else if !qConfig.IsContextExist(contextName) {
return errors.New("context name [ " + contextName + " ] not found")
}
str, err := q.getCRString(contextName)
if err != nil {
return err
}
return qapi.KubectlDelete(str, "")
}
return qapi.KubectlDelete(str, "")
return nil
}

View File

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