Compare commits
130 Commits
circle
...
context_de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6070a33c2 | ||
|
|
22b9b902a9 | ||
|
|
5795988d01 | ||
|
|
449642e6f4 | ||
|
|
14b6447154 | ||
|
|
7a8926773f | ||
|
|
0b868732a7 | ||
|
|
4f2581cde2 | ||
|
|
cb78b4da9f | ||
|
|
f66a4bf245 | ||
|
|
72497d7255 | ||
|
|
b6235f20d4 | ||
|
|
93af9b4386 | ||
|
|
37fad3dbcf | ||
|
|
7a6a2b2d2b | ||
|
|
184bc6f81a | ||
|
|
140d9a6c33 | ||
|
|
68ec172226 | ||
|
|
e3c81fd717 | ||
|
|
864d186f0b | ||
|
|
a0f25848c7 | ||
|
|
9469bd8893 | ||
|
|
6ea5c3e1a8 | ||
|
|
085e718ba8 | ||
|
|
29ebf2b499 | ||
|
|
a4a7b3f0bd | ||
|
|
f1871279d0 | ||
|
|
e7b256dfd5 | ||
|
|
775f438762 | ||
|
|
aa180b4af1 | ||
|
|
af679c89bf | ||
|
|
dcd3c0a99b | ||
|
|
ddcaba4fff | ||
|
|
19c4d37b42 | ||
|
|
dcd90ed81a | ||
|
|
05e90c057c | ||
|
|
2ddfab9440 | ||
|
|
2bc65f0bad | ||
|
|
1eccc50e66 | ||
|
|
1a2de669ba | ||
|
|
b2a980de3a | ||
|
|
bfba8198cf | ||
|
|
3638994b91 | ||
|
|
86e8805bc7 | ||
|
|
7e9dea4e5f | ||
|
|
c2430c3817 | ||
|
|
436162f173 | ||
|
|
2f039f2d2e | ||
|
|
48ee673ddc | ||
|
|
57a80a9533 | ||
|
|
4fe04d6142 | ||
|
|
1fd3310e05 | ||
|
|
b85269d908 | ||
|
|
cbdafadbaf | ||
|
|
c0e2128d5d | ||
|
|
df19cadcb6 | ||
|
|
d9cbbf54cc | ||
|
|
c4f0ddcea3 | ||
|
|
f57457029d | ||
|
|
69aca05a86 | ||
|
|
aa737b0594 | ||
|
|
e4d69f059a | ||
|
|
b7c0fd48b7 | ||
|
|
4530d1d9e2 | ||
|
|
ca20f8c992 | ||
|
|
b2c16a490b | ||
|
|
7f70cc661e | ||
|
|
2c054cd54e | ||
|
|
0b2fdae015 | ||
|
|
cfc8fbb1f1 | ||
|
|
30f00461ec | ||
|
|
d38852398e | ||
|
|
e85636822d | ||
|
|
b9a80f588d | ||
|
|
b9074d9f3c | ||
|
|
f3a3e97618 | ||
|
|
5c56013a70 | ||
|
|
134dbd44ed | ||
|
|
9898d3b9ec | ||
|
|
613b918dde | ||
|
|
bdcadebeca | ||
|
|
626a2ebe68 | ||
|
|
1f64641ab1 | ||
|
|
b764fd179d | ||
|
|
e8d1899a41 | ||
|
|
32fa0a6570 | ||
|
|
0bf1f3ca3a | ||
|
|
8f56872842 | ||
|
|
defdb899b7 | ||
|
|
c7478fb8c1 | ||
|
|
34df4b3a5c | ||
|
|
c7bac06533 | ||
|
|
89d5e261ab | ||
|
|
6cd70cb643 | ||
|
|
941bb76444 | ||
|
|
513daa54f4 | ||
|
|
46b40d6011 | ||
|
|
7893329ab7 | ||
|
|
a127127317 | ||
|
|
d8f1ab4f30 | ||
|
|
37bf4eae2b | ||
|
|
376f6ae838 | ||
|
|
659db113d7 | ||
|
|
19e8eda3a3 | ||
|
|
12e511ab04 | ||
|
|
3fec90e50b | ||
|
|
36c32d4ca6 | ||
|
|
21d7e63588 | ||
|
|
7397fb3b34 | ||
|
|
8608a69406 | ||
|
|
e530a6a79e | ||
|
|
096ba5062b | ||
|
|
2719da19a5 | ||
|
|
0d3ba901ef | ||
|
|
9630453a24 | ||
|
|
a6d81fa8a5 | ||
|
|
758496cac7 | ||
|
|
7fadbb8392 | ||
|
|
1c8e4df00a | ||
|
|
27226568fb | ||
|
|
36008ab0dc | ||
|
|
9758746361 | ||
|
|
1bbf82a15a | ||
|
|
c65fad8f5c | ||
|
|
b29c1ec193 | ||
|
|
287ff62507 | ||
|
|
74d6863acf | ||
|
|
d261be6c13 | ||
|
|
a3a6c47375 | ||
|
|
b413e1bca9 |
@@ -1,43 +0,0 @@
|
|||||||
# Golang CircleCI 2.0 configuration file
|
|
||||||
#
|
|
||||||
# Check https://circleci.com/docs/2.0/language-go/ for more details
|
|
||||||
version: 2
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
docker:
|
|
||||||
- image: circleci/golang:stretch
|
|
||||||
working_directory: /go/src/github.com/qlik-oss/sense-installer
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- run: make test
|
|
||||||
- run: make build
|
|
||||||
build_release:
|
|
||||||
docker:
|
|
||||||
- image: circleci/golang:stretch
|
|
||||||
working_directory: /go/src/github.com/qlik-oss/sense-installer
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- run: make test
|
|
||||||
- run: make xbuild-all
|
|
||||||
- run:
|
|
||||||
name: "Publish Release on GitHub"
|
|
||||||
command: |
|
|
||||||
go get github.com/tcnksm/ghr
|
|
||||||
ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${CIRCLE_TAG} /go/src/github.com/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/bin/${CIRCLE_TAG}/
|
|
||||||
workflows:
|
|
||||||
version: 2
|
|
||||||
commit:
|
|
||||||
jobs:
|
|
||||||
- build:
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
ignore: /^v.*/
|
|
||||||
build_release:
|
|
||||||
jobs:
|
|
||||||
- build_release:
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /^v.*/
|
|
||||||
branches:
|
|
||||||
ignore: /.*/
|
|
||||||
|
|
||||||
5
.gitattributes
vendored
Normal file
5
.gitattributes
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Ignore all files and folders that start with .; .circleci, .github, .git, etc.
|
||||||
|
# Warning! This will ignore files in subfolders as well.
|
||||||
|
# If you needs files starting with . then change condition below to be specific
|
||||||
|
# for each file and folder that needs to be ignored
|
||||||
|
.* export-ignore
|
||||||
27
.github/workflows/build.yml
vendored
Normal file
27
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: Build Sense installer
|
||||||
|
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up Go 1.13
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
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
|
||||||
21
.github/workflows/mkdocs.yml
vendored
Normal file
21
.github/workflows/mkdocs.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
name: Publish docs via GitHub Pages
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths:
|
||||||
|
- 'docs/**'
|
||||||
|
- 'mkdocs.yml'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Deploy docs
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout master
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Deploy docs
|
||||||
|
uses: mhausenblas/mkdocs-deploy-gh-pages@1.11
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
37
.github/workflows/release.yml
vendored
Normal file
37
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: Release Sense installer binaries
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*.*.*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up Go 1.13
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
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
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
files: bin/**/*
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,3 +5,5 @@ pkg/qliksense/crds
|
|||||||
pkg/qliksense/packrd
|
pkg/qliksense/packrd
|
||||||
pkg/qliksense/qliksense-packr.go
|
pkg/qliksense/qliksense-packr.go
|
||||||
pkg/qliksense/docker-registry
|
pkg/qliksense/docker-registry
|
||||||
|
/pkg/qliksense/tests
|
||||||
|
.DS_Store
|
||||||
|
|||||||
23
MKDOCS.md
Normal file
23
MKDOCS.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Qlik Sense installer documentation
|
||||||
|
|
||||||
|
## Local development of documentation
|
||||||
|
|
||||||
|
Documentation is built using [mkdocs](https://www.mkdocs.org/) and uses [Material for MKDocs theme](https://squidfunk.github.io/mkdocs-material/)
|
||||||
|
|
||||||
|
Requirements: Python and PIP or Docker
|
||||||
|
|
||||||
|
```console
|
||||||
|
pip install mkdocs
|
||||||
|
pip install mkdocs-material
|
||||||
|
```
|
||||||
|
|
||||||
|
View live changes locally at http://localhost:8000
|
||||||
|
```console
|
||||||
|
mkdocs serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
```console
|
||||||
|
docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material
|
||||||
|
```
|
||||||
50
Makefile
50
Makefile
@@ -44,51 +44,63 @@ build: clean generate
|
|||||||
$(MAKE) clean
|
$(MAKE) clean
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test: clean generate
|
||||||
ifeq ($(shell ${WHICH} docker-registry 2>${DEVNUL}),)
|
ifeq ($(shell ${WHICH} docker-registry 2>${DEVNUL}),)
|
||||||
$(eval TMP := $(shell mktemp -d))
|
$(eval TMP-docker-distribution := $(shell mktemp -d))
|
||||||
git clone https://github.com/docker/distribution.git $(TMP)/docker-distribution
|
git clone https://github.com/docker/distribution.git $(TMP-docker-distribution)/docker-distribution
|
||||||
cd $(TMP)/docker-distribution; git checkout -b v2.7.1; make
|
cd $(TMP-docker-distribution)/docker-distribution; git checkout -b v2.7.1; make
|
||||||
cp $(TMP)/docker-distribution/bin/registry pkg/qliksense/docker-registry
|
cp $(TMP-docker-distribution)/docker-distribution/bin/registry pkg/qliksense/docker-registry
|
||||||
|
-rm -rf $(TMP-docker-distribution)
|
||||||
endif
|
endif
|
||||||
go test -short -count=1 -tags "$(BUILDTAGS)" -v ./...
|
go test -short -count=1 -tags "$(BUILDTAGS)" -v ./...
|
||||||
|
$(MAKE) clean
|
||||||
|
|
||||||
xbuild-all: clean generate
|
xbuild-all: clean generate
|
||||||
$(foreach OS, $(SUPPORTED_PLATFORMS), \
|
$(foreach OS, $(SUPPORTED_PLATFORMS), \
|
||||||
$(foreach ARCH, $(SUPPORTED_ARCHES), \
|
$(foreach ARCH, $(SUPPORTED_ARCHES), \
|
||||||
$(MAKE) $(MAKE_OPTS) CLIENT_PLATFORM=$(OS) CLIENT_ARCH=$(ARCH) MIXIN=$(MIXIN) xbuild; \
|
$(MAKE) $(MAKE_OPTS) CLIENT_PLATFORM=$(OS) CLIENT_ARCH=$(ARCH) MIXIN=$(MIXIN) xbuild; \
|
||||||
))
|
))
|
||||||
|
|
||||||
$(MAKE) clean
|
$(MAKE) clean
|
||||||
|
|
||||||
xbuild: $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
xbuild: $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
||||||
|
|
||||||
$(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT):
|
$(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT):
|
||||||
mkdir -p $(dir $@)
|
mkdir -p $(dir $@)
|
||||||
GOOS=$(CLIENT_PLATFORM) GOARCH=$(CLIENT_ARCH) $(XBUILD) -o $@ ./cmd/$(MIXIN)
|
GOOS=$(CLIENT_PLATFORM) GOARCH=$(CLIENT_ARCH) $(XBUILD) -o $@ ./cmd/$(MIXIN)
|
||||||
tar -czvf $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).tar.gz -C $(BINDIR)/$(VERSION)/ $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
|
||||||
#tar -C $(BINDIR)/$(VERSION)/ -cvf $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).tar.gz $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
|
||||||
|
|
||||||
|
ifeq ($(CLIENT_PLATFORM),windows)
|
||||||
|
zip $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).zip $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
||||||
|
else
|
||||||
|
tar -czvf $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).tar.gz -C $(BINDIR)/$(VERSION)/ $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
||||||
|
endif
|
||||||
|
upx $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
||||||
|
|
||||||
generate: get-crds packr2
|
generate: get-crds packr2
|
||||||
go generate ./...
|
go generate ./...
|
||||||
|
|
||||||
HAS_PACKR2 := $(shell packr2)
|
|
||||||
packr2:
|
packr2:
|
||||||
ifndef HAS_PACKR2
|
ifeq ($(shell ${WHICH} packr2 2>${DEVNUL}),)
|
||||||
go get -u github.com/gobuffalo/packr/v2/packr2
|
go get -u github.com/gobuffalo/packr/v2/packr2@v2.7.1
|
||||||
endif
|
endif
|
||||||
|
|
||||||
clean: clean-packr
|
clean: clean-packr
|
||||||
-rm -rf /tmp/operator
|
|
||||||
-rm -fr pkg/qliksense/crds
|
-rm -fr pkg/qliksense/crds
|
||||||
|
|
||||||
clean-packr: packr2
|
clean-packr: packr2
|
||||||
cd pkg/qliksense && packr2 clean
|
cd pkg/qliksense && packr2 clean
|
||||||
|
|
||||||
get-crds:
|
get-crds:
|
||||||
$(eval TMP := $(shell mktemp -d))
|
ifeq ($(QLIKSENSE_OPERATOR_DIR),)
|
||||||
git clone git@github.com:qlik-oss/qliksense-operator.git -b ms-3 $(TMP)/operator
|
$(eval TMP-operator := $(shell mktemp -d))
|
||||||
|
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)
|
||||||
|
else
|
||||||
mkdir -p pkg/qliksense/crds/cr
|
mkdir -p pkg/qliksense/crds/cr
|
||||||
mkdir -p pkg/qliksense/crds/crd
|
mkdir -p pkg/qliksense/crds/crd
|
||||||
mkdir -p pkg/qliksense/crds/crd-deploy
|
mkdir -p pkg/qliksense/crds/crd-deploy
|
||||||
cp $(TMP)/operator/deploy/*.yaml pkg/qliksense/crds/crd-deploy
|
cp $(QLIKSENSE_OPERATOR_DIR)/deploy/*.yaml pkg/qliksense/crds/crd-deploy
|
||||||
cp $(TMP)/operator/deploy/crds/*_crd.yaml pkg/qliksense/crds/crd
|
cp $(QLIKSENSE_OPERATOR_DIR)/deploy/crds/*_crd.yaml pkg/qliksense/crds/crd
|
||||||
cp $(TMP)/operator/deploy/crds/*_cr.yaml pkg/qliksense/crds/cr
|
cp $(QLIKSENSE_OPERATOR_DIR)/deploy/crds/*_cr.yaml pkg/qliksense/crds/cr
|
||||||
|
endif
|
||||||
|
|||||||
141
README.md
141
README.md
@@ -1,150 +1,25 @@
|
|||||||
# (WIP) Qlik Sense installation and operations CLI
|
# (WIP) Qlik Sense installation and operations CLI
|
||||||
|
|
||||||
- [Qlik Sense installation and operations CLI](#qlik-sense-installation-and-operations-cli)
|
## Documentation
|
||||||
- [About](#about)
|
|
||||||
- [Future Direction](#future-direction)
|
To learn more about Qlik Sense installer go to https://qlik-oss.github.io/sense-installer/
|
||||||
- [Getting Started](#getting-started)
|
|
||||||
- [Requirements](#requirements)
|
|
||||||
- [Download](#download)
|
|
||||||
- [TL;DR](#TL;DR)
|
|
||||||
- [How qliksense CLI works](#how-qliksense-cli-works)
|
|
||||||
- [Witout Git Repo](#Without-git-repo)
|
|
||||||
- [With Git Repo](#With-a-git-repo)
|
|
||||||
- [Air Gapped](#air-gaped)
|
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
The Qlik Sense installer CLI (qliksense) provides an imperitive interface to many of the configurations that need to be applied against the declaritive structure described in [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s). This cli faciliates to do
|
The Qlik Sense installer CLI (qliksense) provides an imperative interface to many of the configurations that need to be applied against the declarative structure described in [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s). This cli facilitates:
|
||||||
|
|
||||||
- installation of QSEoK
|
- installation of QSEoK
|
||||||
- installation of qliksense operator to manage QSEoK
|
- installation of qliksense operator to manage QSEoK
|
||||||
- air gapped installation of QSEoK
|
- air gapped installation of QSEoK
|
||||||
|
|
||||||
This is a technology preview that uses qlik modified [kustomize](https://github.com/qlik-oss/kustomize) to kubernetes manifests of the versions of the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository.
|
This is a technology preview that uses Qlik modified [kustomize](https://github.com/qlik-oss/kustomize) to kubernetes manifests of the versions of the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository.
|
||||||
|
|
||||||
For each version of a qliksense sense edge build there should be a corresponding release in [qliksense-k8s] repository under [releases](https://github.com/qlik-oss/qliksense-k8s/releases)
|
For each version of a qliksense edge build there should be a corresponding release in [qliksense-k8s] repository under [releases](https://github.com/qlik-oss/qliksense-k8s/releases)
|
||||||
|
|
||||||
### Future Direction
|
### Future Direction
|
||||||
|
|
||||||
- More operations:
|
- More operations:
|
||||||
- Expanded preflight checks
|
- Expand preflight checks
|
||||||
- backup/restore operations
|
- backup/restore operations
|
||||||
- fully support airgap installation of QSEoK
|
- fully support airgap installation of QSEoK
|
||||||
- restore unwanted deletion of kubernetes resoureces
|
- restore unwanted deletion of kubernetes resources
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
### Requirements
|
|
||||||
|
|
||||||
- `kubectl` need to be installed and configured properly so that `kubectl` can connect to the kubernetes cluser. The `qliksense` CLI uses `kubectl` under the hood to perform operations on cluster
|
|
||||||
- (Docker Desktop setup tested for these instructions)
|
|
||||||
|
|
||||||
### Download
|
|
||||||
|
|
||||||
- Download the appropriate executable for your platform from the [releases page](https://github.com/qlik-oss/sense-installer/releases) and rename it to `qliksense`. All the examplease down below uses `qliksense`.
|
|
||||||
|
|
||||||
### TL;DR
|
|
||||||
|
|
||||||
- To download the version `v0.0.2` from qliksense-k8s [releases](https://github.com/qlik-oss/qliksense-k8s/releases).
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$qliksense fetch v0.0.2
|
|
||||||
```
|
|
||||||
|
|
||||||
- To install CRDs for QSEoK and qliksense operator into the kubernetes cluster.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$qliksense crds install --all
|
|
||||||
```
|
|
||||||
|
|
||||||
- To install QSEoK into a namespace in the kubernetes cluster where `kubectl` is pointing to.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$qliksense install --acceptEULA="yes"
|
|
||||||
```
|
|
||||||
|
|
||||||
## How qliksense cli works
|
|
||||||
|
|
||||||
At the initialization `qliksense` cli create few files in the director `~/.qliksene` and it contains following files
|
|
||||||
|
|
||||||
```console
|
|
||||||
.qliksense
|
|
||||||
├── config.yaml
|
|
||||||
├── contexts
|
|
||||||
│ └── qlik-default
|
|
||||||
│ └── qlik-default.yaml
|
|
||||||
└── ejson
|
|
||||||
└── keys
|
|
||||||
```
|
|
||||||
|
|
||||||
`qlik-default.yaml` is a default CR has been created with some default values like this
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
apiVersion: qlik.com/v1
|
|
||||||
kind: Qliksense
|
|
||||||
metadata:
|
|
||||||
name: qlik-default
|
|
||||||
spec:
|
|
||||||
profile: docker-desktop
|
|
||||||
secrets:
|
|
||||||
qliksense:
|
|
||||||
- name: mongoDbUri
|
|
||||||
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
|
|
||||||
rotateKeys: "yes"
|
|
||||||
releaseName: qlik-default
|
|
||||||
```
|
|
||||||
|
|
||||||
The `qliksense` cli creates a default qliksense context (different from kubectl context) named `qlik-default` which will be the prefix for all kubernetes resources created by the cli under this context latter on. New context and configuration can be created by the cli.
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ qliksense config
|
|
||||||
do operations on/around CR
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
qliksense config [command]
|
|
||||||
|
|
||||||
Available Commands:
|
|
||||||
apply generate the patchs and apply manifests to k8s
|
|
||||||
list-contexts retrieves the contexts and lists them
|
|
||||||
set configure a key value pair into the current context
|
|
||||||
set-configs set configurations into the qliksense context
|
|
||||||
set-context Sets the context in which the Kubernetes cluster and resources live in
|
|
||||||
set-secrets set secrets configurations into the qliksense context
|
|
||||||
view view the qliksense operator CR
|
|
||||||
|
|
||||||
Flags:
|
|
||||||
-h, --help help for config
|
|
||||||
|
|
||||||
Use "qliksense config [command] --help" for more information about a command.
|
|
||||||
```
|
|
||||||
|
|
||||||
`qliksense` cli works in two modes
|
|
||||||
|
|
||||||
- with a git repo fork/clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s)
|
|
||||||
- without git repo
|
|
||||||
|
|
||||||
### Without git repo
|
|
||||||
|
|
||||||
In this mode `qliksense` CLI download the specified version from [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and put it into folder `~/.qliksense/contexts/<context-name>/qlik-k8s`.
|
|
||||||
|
|
||||||
The qliksense cli create a CR for the qliksense operator and all the config operations are peformed to edit the CR. So when `qliksense install` or `qliksense config apply` both generate patches in local file system (i.e `~/.qliksense/contexts/<context-name>/qlik-k8s`) and install those manifests into the cluster and create a custom resoruce (CR) for the `qliksene operator` then the operator make association to the isntalled resoruces so that when `qliksenes uninstall` is performed the operator can delete all those kubernetes resources related to QSEoK for the current context.
|
|
||||||
|
|
||||||
### With a git repo
|
|
||||||
|
|
||||||
User has to create fork or clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and push it to their own git server. When user perform `qliksense install` or `qliksene config apply` the qliksense operator do these tasks
|
|
||||||
|
|
||||||
- downloads the corresponding version of manifests from the user's git repo.
|
|
||||||
- generate kustomize patches
|
|
||||||
- install kubernetes resoruces
|
|
||||||
- push those generated patches into a new branch in the provided git repo. so that user user can merge those patches into their master branch.
|
|
||||||
- spinup a cornjob to monitor master branch. If user modifies anything in the master branch those changes will be applied into the cluster. This is a light weight `git-ops` model
|
|
||||||
|
|
||||||
This is how repo info is provided into the CR
|
|
||||||
|
|
||||||
```console
|
|
||||||
qliksense config set git.repository="https://github.com/my-org/qliksense-k8s"
|
|
||||||
|
|
||||||
qliksense config set git.accessToken=blablalaala
|
|
||||||
```
|
|
||||||
|
|
||||||
## Air gaped
|
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
# qliksense about
|
|
||||||
|
|
||||||
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`
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
apiVersion: qlik.com/v1
|
|
||||||
kind: QlikSense
|
|
||||||
metadata:
|
|
||||||
name: myqliksense
|
|
||||||
spec:
|
|
||||||
profile: docker-desktop
|
|
||||||
manifestsRoot: /Usr/ddd/my-k8-repo/manifests
|
|
||||||
namespace: myqliksense
|
|
||||||
storageClassName: efs
|
|
||||||
configs:
|
|
||||||
qliksense:
|
|
||||||
- name: acceptEULA
|
|
||||||
value: "yes"
|
|
||||||
secrets:
|
|
||||||
qliksense:
|
|
||||||
- name: mongoDbUri
|
|
||||||
value: "mongo://mongo:3307"
|
|
||||||
- name: messagingPassword
|
|
||||||
valueFromKey: messagingPassword
|
|
||||||
```
|
|
||||||
|
|
||||||
In that case the command would be
|
|
||||||
|
|
||||||
- `qliksense about`
|
|
||||||
- display from `/Usr/ddd/my-k8-repo/manifests/docker-desktop` location
|
|
||||||
- pull from `master` if directory invalid/empty
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# qliksense config
|
|
||||||
|
|
||||||
Config action will perform operations on configurations and contexts regarding the [qliksense-k8](https://github.com/qlik-oss/qliksense-k8s) release.
|
|
||||||
|
|
||||||
it will support following commands:
|
|
||||||
|
|
||||||
- `qliksense config apply` - generate the patchs and apply manifests to k8s
|
|
||||||
- `qliksense config list-contexts` - retrieves the contexts and lists them
|
|
||||||
- `qliksense config set` - configure a key value pair into the current context
|
|
||||||
- `qliksense config set-configs` - set configurations into the qliksense context
|
|
||||||
- `qliksense config set-context` - sets the context in which the Kubernetes cluster and resources live in
|
|
||||||
- `qliksense config set-secrets` - set secrets configurations into the qliksense context
|
|
||||||
- `qliksense config view` - view the qliksense operator CR
|
|
||||||
|
|
||||||
the global file that abstracts all the contexts is `config.yaml`, located at: `~/.qliksense/config.yaml`:
|
|
||||||
```yaml
|
|
||||||
apiVersion: config.qlik.com/v1
|
|
||||||
kind: QliksenseConfig
|
|
||||||
metadata:
|
|
||||||
name: QliksenseConfigMetadata
|
|
||||||
spec:
|
|
||||||
contexts:
|
|
||||||
- name: qlik-default
|
|
||||||
crFile: /Users/fff/.qliksense/contexts/qlik-default/qlik-default.yaml
|
|
||||||
- name: myqliksense
|
|
||||||
crFile: /Users/fff/.qliksense/contexts/myqliksense/myqliksense.yaml
|
|
||||||
- name: hello
|
|
||||||
crFile: /Users/fff/.qliksense/contexts/hello/hello.yaml
|
|
||||||
currentContext: hello
|
|
||||||
```
|
|
||||||
|
|
||||||
37
cmd/qliksense/apply.go
Normal file
37
cmd/qliksense/apply.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func applyCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
opts := &qliksense.InstallCommandOptions{}
|
||||||
|
filePath := ""
|
||||||
|
keepPatchFiles := 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)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
f := c.Flags()
|
||||||
|
f.StringVarP(&filePath, "file", "f", "", "Install from a CR file")
|
||||||
|
c.MarkFlagRequired("file")
|
||||||
|
f.StringVarP(&opts.StorageClass, "storageClass", "s", "", "Storage class for qliksense")
|
||||||
|
f.StringVarP(&opts.MongoDbUri, "mongoDbUri", "m", "", "mongoDbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
|
||||||
|
f.StringVarP(&opts.RotateKeys, "rotateKeys", "r", "", "Rotate JWT keys for qliksense (yes:rotate keys/ no:use exising keys from cluster/ None: use default EJSON_KEY from env")
|
||||||
|
f.BoolVar(&keepPatchFiles, keepPatchFilesFlagName, keepPatchFiles, keepPatchFilesFlagUsage)
|
||||||
|
|
||||||
|
eulaPreRunHooks.addValidator(c.Name(), loadOrApplyCommandEulaPreRunHook)
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
@@ -5,16 +5,22 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var configCmd = &cobra.Command{
|
func configCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||||
Use: "config",
|
var configCmd = &cobra.Command{
|
||||||
Short: "do operations on/around CR",
|
Use: "config",
|
||||||
Long: `do operations on/around CR`,
|
Short: "do operations on/around CR",
|
||||||
|
Long: `do operations on/around CR`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return q.ConfigViewCR()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return configCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func configApplyCmd(q *qliksense.Qliksense) *cobra.Command {
|
func configApplyCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||||
c := &cobra.Command{
|
c := &cobra.Command{
|
||||||
Use: "apply",
|
Use: "apply",
|
||||||
Short: "generate the patchs and apply manifests to k8s",
|
Short: "generate the patches and apply manifests to k8s",
|
||||||
Long: `generate patches based on CR and apply manifests to k8s`,
|
Long: `generate patches based on CR and apply manifests to k8s`,
|
||||||
Example: `qliksense config apply`,
|
Example: `qliksense config apply`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
|
||||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -13,9 +16,12 @@ func setContextConfigCmd(q *qliksense.Qliksense) *cobra.Command {
|
|||||||
)
|
)
|
||||||
|
|
||||||
cmd = &cobra.Command{
|
cmd = &cobra.Command{
|
||||||
Use: "set-context",
|
Use: "set-context",
|
||||||
Short: "Sets the context in which the Kubernetes cluster and resources live in",
|
Short: "Sets the context in which the Kubernetes cluster and resources live in",
|
||||||
Example: `qliksense config set-context <context_name>`,
|
Example: `
|
||||||
|
qliksense config set-context <context_name>
|
||||||
|
- The above configuration will be displayed in the CR
|
||||||
|
`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return q.SetContextConfig(args)
|
return q.SetContextConfig(args)
|
||||||
},
|
},
|
||||||
@@ -45,9 +51,12 @@ func setOtherConfigsCmd(q *qliksense.Qliksense) *cobra.Command {
|
|||||||
)
|
)
|
||||||
|
|
||||||
cmd = &cobra.Command{
|
cmd = &cobra.Command{
|
||||||
Use: "set",
|
Use: "set",
|
||||||
Short: "configure a key value pair into the current context",
|
Short: "configure a key value pair into the current context",
|
||||||
Example: `qliksense config set <key>=<value>`,
|
Example: `
|
||||||
|
qliksense config set <key>=<value>
|
||||||
|
- The above configuration will be displayed in the CR
|
||||||
|
`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return q.SetOtherConfigs(args)
|
return q.SetOtherConfigs(args)
|
||||||
},
|
},
|
||||||
@@ -61,9 +70,12 @@ func setConfigsCmd(q *qliksense.Qliksense) *cobra.Command {
|
|||||||
)
|
)
|
||||||
|
|
||||||
cmd = &cobra.Command{
|
cmd = &cobra.Command{
|
||||||
Use: "set-configs",
|
Use: "set-configs",
|
||||||
Short: "set configurations into the qliksense context",
|
Short: "set configurations into the qliksense context as key-value pairs",
|
||||||
Example: `qliksense config set-configs <key>=<value>`,
|
Example: `
|
||||||
|
qliksense config set-configs <service_name>.<attribute>="<value>"
|
||||||
|
- The above configuration will be displayed in the CR
|
||||||
|
`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return q.SetConfigs(args)
|
return q.SetConfigs(args)
|
||||||
},
|
},
|
||||||
@@ -78,9 +90,18 @@ func setSecretsCmd(q *qliksense.Qliksense) *cobra.Command {
|
|||||||
)
|
)
|
||||||
|
|
||||||
cmd = &cobra.Command{
|
cmd = &cobra.Command{
|
||||||
Use: "set-secrets",
|
Use: "set-secrets",
|
||||||
Short: "set secrets configurations into the qliksense context",
|
Short: "set secrets configurations into the qliksense context as key-value pairs",
|
||||||
Example: `qliksense config set-secrets <key>=<value> --secret=true`,
|
Example: `
|
||||||
|
qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=true
|
||||||
|
- Encrypt the secret value into a new Kubernetes secret resource
|
||||||
|
- The secret resource is placed in the location: <qliksense_home>/<contexts>/<context_name>/secrets/<service_name>.yaml
|
||||||
|
- Include it's key reference in the current context
|
||||||
|
|
||||||
|
qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=false
|
||||||
|
- Encrypt the secret value and display it in the current context
|
||||||
|
- No secret resource is created
|
||||||
|
- The above configuration will be displayed in the CR `,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return q.SetSecrets(args, secret)
|
return q.SetSecrets(args, secret)
|
||||||
},
|
},
|
||||||
@@ -90,6 +111,25 @@ func setSecretsCmd(q *qliksense.Qliksense) *cobra.Command {
|
|||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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, skipConfirmation)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
f := cmd.Flags()
|
||||||
|
|
||||||
|
f.BoolVar(&skipConfirmation, "yes", skipConfirmation, "skips confirmation")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
func setImageRegistryCmd(q *qliksense.Qliksense) *cobra.Command {
|
func setImageRegistryCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||||
var (
|
var (
|
||||||
cmd *cobra.Command
|
cmd *cobra.Command
|
||||||
@@ -140,3 +180,19 @@ qliksense config set-image-registry https://your.private.registry.example.com:50
|
|||||||
f.StringVar(&password, "password", "", "Password used for both pushing and pulling images")
|
f.StringVar(&password, "password", "", "Password used for both pushing and pulling images")
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanConfigRepoPatchesCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "clean-config-repo-patches",
|
||||||
|
Short: "Clean config repo patch files",
|
||||||
|
Example: "qliksense config clean-config-repo-patches",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||||
|
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
|
||||||
|
return fmt.Errorf("error removing temporary changes to the config: %v\n", err)
|
||||||
|
}
|
||||||
|
fmt.Println("done")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
106
cmd/qliksense/eula.go
Normal file
106
cmd/qliksense/eula.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mattn/go-tty"
|
||||||
|
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type eulaPreRunHooksT struct {
|
||||||
|
validators map[string]func(cmd *cobra.Command, q *qliksense.Qliksense) (bool, error)
|
||||||
|
postValidationArtifacts map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *eulaPreRunHooksT) addValidator(command string, validator func(cmd *cobra.Command, q *qliksense.Qliksense) (bool, error)) {
|
||||||
|
e.validators[command] = validator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *eulaPreRunHooksT) getValidator(command string) func(cmd *cobra.Command, q *qliksense.Qliksense) (bool, error) {
|
||||||
|
if validator, ok := e.validators[command]; ok {
|
||||||
|
return validator
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *eulaPreRunHooksT) addPostValidationArtifact(artifactName string, artifact interface{}) {
|
||||||
|
e.postValidationArtifacts[artifactName] = artifact
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *eulaPreRunHooksT) getPostValidationArtifact(artifactName string) interface{} {
|
||||||
|
if artifact, ok := e.postValidationArtifacts[artifactName]; ok {
|
||||||
|
return artifact
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var eulaEnforced = os.Getenv("QLIKSENSE_EULA_ENFORCE") == "true"
|
||||||
|
var eulaText = "Please read the end user license agreement at: https://www.qlik.com/us/legal/license-terms"
|
||||||
|
var eulaPrompt = "Do you accept our EULA? (y/n): "
|
||||||
|
var eulaErrorInstruction = `You must enter "y" to continue`
|
||||||
|
var eulaPreRunHooks = eulaPreRunHooksT{
|
||||||
|
validators: make(map[string]func(cmd *cobra.Command, q *qliksense.Qliksense) (bool, error)),
|
||||||
|
postValidationArtifacts: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
func commandAlwaysRequiresEulaAcceptance(commandName string) bool {
|
||||||
|
return commandName == "install" || commandName == "upgrade" || commandName == "apply"
|
||||||
|
}
|
||||||
|
|
||||||
|
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 eulaAccepted, err := eulaPreRunHook(cmd, q); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else if !eulaAccepted {
|
||||||
|
doEnforceEula()
|
||||||
|
}
|
||||||
|
} else if qConfig, err := qapi.NewQConfigE(q.QliksenseHome); err != nil {
|
||||||
|
doEnforceEula()
|
||||||
|
} else if qcr, err := qConfig.GetCurrentCR(); err != nil || !qcr.IsEULA() {
|
||||||
|
doEnforceEula()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func globalEulaPostRun(cmd *cobra.Command, q *qliksense.Qliksense) {
|
||||||
|
if isEulaEnforced(cmd.Name()) {
|
||||||
|
if err := q.SetEulaAccepted(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEulaEnforced(commandName string) bool {
|
||||||
|
return eulaEnforced || commandAlwaysRequiresEulaAcceptance(commandName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doEnforceEula() {
|
||||||
|
fmt.Println(eulaText)
|
||||||
|
fmt.Print(eulaPrompt)
|
||||||
|
answer := readRuneFromTty()
|
||||||
|
fmt.Printf("%v\n", answer)
|
||||||
|
if strings.ToLower(answer) != "y" {
|
||||||
|
fmt.Println(eulaErrorInstruction)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readRuneFromTty() string {
|
||||||
|
t, err := tty.Open()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer t.Close()
|
||||||
|
answer, err := t.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return string(answer)
|
||||||
|
}
|
||||||
29
cmd/qliksense/get_installable_versions.go
Normal file
29
cmd/qliksense/get_installable_versions.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultVersionsLimit = 10
|
||||||
|
|
||||||
|
func getInstallableVersionsCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
opts := &qliksense.LsRemoteCmdOptions{
|
||||||
|
IncludeBranches: false,
|
||||||
|
Limit: defaultVersionsLimit,
|
||||||
|
}
|
||||||
|
c := &cobra.Command{
|
||||||
|
Use: "get-versions",
|
||||||
|
Short: "list remote/installable versions",
|
||||||
|
Long: `list remote/installable versions`,
|
||||||
|
Example: `qliksense get-versions`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return q.GetInstallableVersions(opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
f := c.Flags()
|
||||||
|
f.BoolVarP(&opts.IncludeBranches, "include-branches", "", opts.IncludeBranches, "Include branches")
|
||||||
|
f.IntVarP(&opts.Limit, "limit", "", opts.Limit, "Maximum versions to list (starting with the highest)")
|
||||||
|
return c
|
||||||
|
}
|
||||||
@@ -7,24 +7,26 @@ import (
|
|||||||
|
|
||||||
func installCmd(q *qliksense.Qliksense) *cobra.Command {
|
func installCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||||
opts := &qliksense.InstallCommandOptions{}
|
opts := &qliksense.InstallCommandOptions{}
|
||||||
|
keepPatchFiles := false
|
||||||
c := &cobra.Command{
|
c := &cobra.Command{
|
||||||
Use: "install",
|
Use: "install",
|
||||||
Short: "install a qliksense release",
|
Short: "install a qliksense release",
|
||||||
Long: `install a qliksense release`,
|
Long: `install a qliksense release`,
|
||||||
Example: `qliksense install <version>`,
|
Example: `qliksense install <version> #if no version provides, expect manifestsRoot is set somewhere in the file system`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) == 0 {
|
version := ""
|
||||||
return q.InstallQK8s("", opts)
|
if len(args) != 0 {
|
||||||
|
version = args[0]
|
||||||
}
|
}
|
||||||
return q.InstallQK8s(args[0], opts)
|
return q.InstallQK8s(version, opts, keepPatchFiles)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
f := c.Flags()
|
f := c.Flags()
|
||||||
f.StringVarP(&opts.AcceptEULA, "acceptEULA", "a", "", "AcceptEULA for qliksense")
|
|
||||||
f.StringVarP(&opts.Namespace, "namespace", "n", "", "Namespace where to install the qliksense")
|
|
||||||
f.StringVarP(&opts.StorageClass, "storageClass", "s", "", "Storage class for qliksense")
|
f.StringVarP(&opts.StorageClass, "storageClass", "s", "", "Storage class for qliksense")
|
||||||
f.StringVarP(&opts.MongoDbUri, "mongoDbUri", "m", "", "mongoDbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
|
f.StringVarP(&opts.MongoDbUri, "mongoDbUri", "m", "", "mongoDbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
|
||||||
f.StringVarP(&opts.RotateKeys, "rotateKeys", "r", "", "Rotate JWT keys for qliksense (yes:rotate keys/ no:use exising keys from cluster/ None: use default EJSON_KEY from env")
|
f.StringVarP(&opts.RotateKeys, "rotateKeys", "r", "", "Rotate JWT keys for qliksense (yes:rotate keys/ no:use exising keys from cluster/ None: use default EJSON_KEY from env")
|
||||||
|
f.BoolVar(&keepPatchFiles, keepPatchFilesFlagName, keepPatchFiles, keepPatchFilesFlagUsage)
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|||||||
84
cmd/qliksense/load.go
Normal file
84
cmd/qliksense/load.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadCrFile(q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
filePath := ""
|
||||||
|
overwriteExistingContext := false
|
||||||
|
c := &cobra.Command{
|
||||||
|
Use: "load",
|
||||||
|
Short: "load a CR a file and create necessary structure for future use",
|
||||||
|
Long: `load a CR a file and create necessary structure for future use`,
|
||||||
|
Example: `qliksense load -f file_name or cat cr_file | qliksense load -f -`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runLoadOrApplyCommandE(cmd, func(reader io.Reader) error {
|
||||||
|
return q.LoadCr(reader, overwriteExistingContext)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
f := c.Flags()
|
||||||
|
f.StringVarP(&filePath, "file", "f", "", "File to load CR from")
|
||||||
|
c.MarkFlagRequired("file")
|
||||||
|
f.BoolVarP(&overwriteExistingContext, "overwrite", "o", overwriteExistingContext, "Overwrite any existing contexts with the same name")
|
||||||
|
|
||||||
|
eulaPreRunHooks.addValidator(c.Name(), loadOrApplyCommandEulaPreRunHook)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCrFileFromFlag(cmd *cobra.Command, flagName string) (*os.File, error) {
|
||||||
|
filePath := cmd.Flag(flagName).Value.String()
|
||||||
|
if filePath == "-" {
|
||||||
|
if !isInputFromPipe() {
|
||||||
|
return nil, errors.New("No input pipe present")
|
||||||
|
}
|
||||||
|
return os.Stdin, nil
|
||||||
|
}
|
||||||
|
file, e := os.Open(filePath)
|
||||||
|
if e != nil {
|
||||||
|
return nil, errors.Wrapf(e,
|
||||||
|
"unable to read the file %s", filePath)
|
||||||
|
}
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isInputFromPipe() bool {
|
||||||
|
fileInfo, _ := os.Stdin.Stat()
|
||||||
|
return fileInfo.Mode()&os.ModeCharDevice == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadOrApplyCommandEulaPreRunHook(cmd *cobra.Command, q *qliksense.Qliksense) (bool, error) {
|
||||||
|
file, err := getCrFileFromFlag(cmd, "file")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
if crBytes, err := ioutil.ReadAll(file); err != nil {
|
||||||
|
return false, err
|
||||||
|
} else {
|
||||||
|
eulaPreRunHooks.addPostValidationArtifact("CR", crBytes)
|
||||||
|
return q.IsEulaAcceptedInCrFile(bytes.NewBuffer(crBytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLoadOrApplyCommandE(cmd *cobra.Command, callBack func(io.Reader) error) error {
|
||||||
|
if crBytes := eulaPreRunHooks.getPostValidationArtifact("CR"); crBytes != nil {
|
||||||
|
return callBack(bytes.NewBuffer(crBytes.([]byte)))
|
||||||
|
} else {
|
||||||
|
file, err := getCrFileFromFlag(cmd, "file")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
return callBack(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
81
cmd/qliksense/preflight.go
Normal file
81
cmd/qliksense/preflight.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/qlik-oss/sense-installer/pkg/preflight"
|
||||||
|
|
||||||
|
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func preflightCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
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>`,
|
||||||
|
}
|
||||||
|
return preflightCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func preflightCheckDnsCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
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}
|
||||||
|
err := qp.DownloadPreflight()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("There has been an error downloading preflight: %+v", err)
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return qp.CheckDns()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return preflightDnsCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func preflightCheckK8sVersionCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
var preflightCheckK8sVersionCmd = &cobra.Command{
|
||||||
|
Use: "k8s-version",
|
||||||
|
Short: "check k8s version",
|
||||||
|
Long: `check minimum valid k8s version on the cluster`,
|
||||||
|
Example: `qliksense preflight k8s-version`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
qp := &preflight.QliksensePreflight{Q: q}
|
||||||
|
err := qp.DownloadPreflight()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("There has been an error downloading preflight: %+v", err)
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return qp.CheckK8sVersion()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return preflightCheckK8sVersionCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func preflightAllChecksCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
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}
|
||||||
|
err := qp.DownloadPreflight()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("There has been an error downloading preflight: %+v", err)
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return qp.RunAllPreflightChecks()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return preflightAllChecksCmd
|
||||||
|
}
|
||||||
@@ -21,13 +21,22 @@ func pullQliksenseImages(q *qliksense.Qliksense) *cobra.Command {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||||
|
if version == "" {
|
||||||
|
if qcr, err := qConfig.GetCurrentCR(); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
version = qcr.GetLabelFromCr("version")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if version != "" {
|
if version != "" {
|
||||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
|
||||||
if !qConfig.IsRepoExistForCurrent(version) {
|
if !qConfig.IsRepoExistForCurrent(version) {
|
||||||
if err := q.FetchQK8s(version); err != nil {
|
if err := q.FetchQK8s(version); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if err := qConfig.SwitchCurrentCRToVersionAndProfile(version, opts.Profile); err != nil {
|
}
|
||||||
|
if err := qConfig.SwitchCurrentCRToVersionAndProfile(version, opts.Profile); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,12 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
ansi "github.com/mattn/go-colorable"
|
ansi "github.com/mattn/go-colorable"
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
"github.com/qlik-oss/sense-installer/pkg"
|
"github.com/qlik-oss/sense-installer/pkg"
|
||||||
@@ -10,21 +16,17 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/ttacon/chalk"
|
"github.com/ttacon/chalk"
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// To run this project in ddebug mode, run:
|
// To run this project in debug mode, run:
|
||||||
// export QLIKSENSE_DEBUG=true
|
// export QLIKSENSE_DEBUG=true
|
||||||
// qliksense <command>
|
// qliksense <command>
|
||||||
|
|
||||||
const (
|
const (
|
||||||
qlikSenseHomeVar = "QLIKSENSE_HOME"
|
qlikSenseHomeVar = "QLIKSENSE_HOME"
|
||||||
qlikSenseDirVar = ".qliksense"
|
qlikSenseDirVar = ".qliksense"
|
||||||
|
keepPatchFilesFlagName = "keep-config-repo-patches"
|
||||||
|
keepPatchFilesFlagUsage = "Keep config repo patch files (for debugging)"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initAndExecute() error {
|
func initAndExecute() error {
|
||||||
@@ -39,18 +41,14 @@ func initAndExecute() error {
|
|||||||
// create dirs and appropriate files for setting up contexts
|
// create dirs and appropriate files for setting up contexts
|
||||||
api.LogDebugMessage("QliksenseHomeDir: %s", qlikSenseHome)
|
api.LogDebugMessage("QliksenseHomeDir: %s", qlikSenseHome)
|
||||||
|
|
||||||
qliksenseClient, err := qliksense.New(qlikSenseHome)
|
qliksenseClient := qliksense.New(qlikSenseHome)
|
||||||
if err != nil {
|
cmd := rootCmd(qliksenseClient)
|
||||||
|
if err := cmd.Execute(); err != nil {
|
||||||
|
//levenstein checks (auto-suggestions)
|
||||||
|
levenstein(cmd)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
qliksenseClient.SetUpQliksenseDefaultContext()
|
|
||||||
cmd := rootCmd(qliksenseClient)
|
|
||||||
//levenstein checks
|
|
||||||
if levenstein(cmd) == false {
|
|
||||||
if err := cmd.Execute(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,30 +78,60 @@ func setUpPaths() (string, error) {
|
|||||||
var versionCmd = &cobra.Command{
|
var versionCmd = &cobra.Command{
|
||||||
Use: "version",
|
Use: "version",
|
||||||
Short: "Print the version number of qliksense cli",
|
Short: "Print the version number of qliksense cli",
|
||||||
Long: `All software has versions. This is Hugo's`,
|
Long: "Print the version number of qliksense cli",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
fmt.Printf("%s (%s, %s)\n", pkg.Version, pkg.Commit, pkg.CommitDate)
|
fmt.Printf("%s (%s, %s)\n", pkg.Version, pkg.Commit, pkg.CommitDate)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
func commandUsesContext(commandName string) bool {
|
||||||
var (
|
return commandName != "" && commandName != "qliksense" && commandName != "help" && commandName != "version"
|
||||||
cmd *cobra.Command
|
}
|
||||||
)
|
|
||||||
|
|
||||||
cmd = &cobra.Command{
|
func getRootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
Use: "qliksense",
|
Use: "qliksense",
|
||||||
Short: "Qliksense cli tool",
|
Short: "Qliksense cli tool",
|
||||||
Long: `qliksense cli tool provides functionality to perform operations on qliksense-k8s, qliksense operator, and kubernetes cluster`,
|
Long: `qliksense cli tool provides functionality to perform operations on qliksense-k8s, qliksense operator, and kubernetes cluster`,
|
||||||
Args: cobra.ArbitraryArgs,
|
Args: cobra.ArbitraryArgs,
|
||||||
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
if commandUsesContext(cmd.Name()) {
|
||||||
|
globalEulaPreRun(cmd, p)
|
||||||
|
if err := p.SetUpQliksenseDefaultContext(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
globalEulaPostRun(cmd, p)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PersistentPostRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
if commandUsesContext(cmd.Name()) {
|
||||||
|
globalEulaPostRun(cmd, p)
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
origHelpFunc := cmd.HelpFunc()
|
||||||
|
cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
|
||||||
|
if !commandUsesContext(cmd.Name()) {
|
||||||
|
cmd.Flags().MarkHidden("acceptEULA")
|
||||||
|
}
|
||||||
|
origHelpFunc(cmd, args)
|
||||||
|
})
|
||||||
|
accept := ""
|
||||||
|
cmd.PersistentFlags().StringVarP(&accept, "acceptEULA", "a", "", "Accept EULA for qliksense")
|
||||||
cmd.Flags().SetInterspersed(false)
|
cmd.Flags().SetInterspersed(false)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func initConfig() {
|
||||||
|
viper.SetEnvPrefix("QLIKSENSE")
|
||||||
|
viper.AutomaticEnv()
|
||||||
|
}
|
||||||
|
|
||||||
|
func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||||
|
cmd := getRootCmd(p)
|
||||||
cobra.OnInitialize(initConfig)
|
cobra.OnInitialize(initConfig)
|
||||||
|
|
||||||
// For qliksense overrides/commands
|
cmd.AddCommand(getInstallableVersionsCmd(p))
|
||||||
|
|
||||||
cmd.AddCommand(pullQliksenseImages(p))
|
cmd.AddCommand(pullQliksenseImages(p))
|
||||||
cmd.AddCommand(pushQliksenseImages(p))
|
cmd.AddCommand(pushQliksenseImages(p))
|
||||||
cmd.AddCommand(about(p))
|
cmd.AddCommand(about(p))
|
||||||
@@ -123,6 +151,7 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
|||||||
cmd.AddCommand(installCmd(p))
|
cmd.AddCommand(installCmd(p))
|
||||||
|
|
||||||
// add config command
|
// add config command
|
||||||
|
configCmd := configCmd(p)
|
||||||
cmd.AddCommand(configCmd)
|
cmd.AddCommand(configCmd)
|
||||||
configCmd.AddCommand(configApplyCmd(p))
|
configCmd.AddCommand(configApplyCmd(p))
|
||||||
configCmd.AddCommand(configViewCmd(p))
|
configCmd.AddCommand(configViewCmd(p))
|
||||||
@@ -147,9 +176,15 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
|||||||
// add the list config command as a sub-command to the app config sub-command
|
// add the list config command as a sub-command to the app config sub-command
|
||||||
configCmd.AddCommand(listContextConfigCmd(p))
|
configCmd.AddCommand(listContextConfigCmd(p))
|
||||||
|
|
||||||
|
// add the delete-context config command as a sub-command to the app config command
|
||||||
|
configCmd.AddCommand(deleteContextConfigCmd(p))
|
||||||
|
|
||||||
// add set-image-registry command as a sub-command to the app config sub-command
|
// add set-image-registry command as a sub-command to the app config sub-command
|
||||||
configCmd.AddCommand(setImageRegistryCmd(p))
|
configCmd.AddCommand(setImageRegistryCmd(p))
|
||||||
|
|
||||||
|
// add clean-config-repo-patches command as a sub-command to the app config sub-command
|
||||||
|
configCmd.AddCommand(cleanConfigRepoPatchesCmd(p))
|
||||||
|
|
||||||
// add uninstall command
|
// add uninstall command
|
||||||
cmd.AddCommand(uninstallCmd(p))
|
cmd.AddCommand(uninstallCmd(p))
|
||||||
|
|
||||||
@@ -157,40 +192,21 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
|||||||
cmd.AddCommand(crdsCmd)
|
cmd.AddCommand(crdsCmd)
|
||||||
crdsCmd.AddCommand(crdsViewCmd(p))
|
crdsCmd.AddCommand(crdsViewCmd(p))
|
||||||
crdsCmd.AddCommand(crdsInstallCmd(p))
|
crdsCmd.AddCommand(crdsInstallCmd(p))
|
||||||
|
|
||||||
|
// add preflight command
|
||||||
|
preflightCmd := preflightCmd(p)
|
||||||
|
preflightCmd.AddCommand(preflightCheckDnsCmd(p))
|
||||||
|
preflightCmd.AddCommand(preflightCheckK8sVersionCmd(p))
|
||||||
|
preflightCmd.AddCommand(preflightAllChecksCmd(p))
|
||||||
|
//preflightCmd.AddCommand(preflightCheckMongoCmd(p))
|
||||||
|
//preflightCmd.AddCommand(preflightCheckAllCmd(p))
|
||||||
|
|
||||||
|
cmd.AddCommand(preflightCmd)
|
||||||
|
cmd.AddCommand(loadCrFile(p))
|
||||||
|
cmd.AddCommand((applyCmd(p)))
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func initConfig() {
|
|
||||||
viper.SetEnvPrefix("QLIKSENSE")
|
|
||||||
viper.AutomaticEnv()
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadFile(url string, filepath string) error {
|
|
||||||
var (
|
|
||||||
out *os.File
|
|
||||||
err error
|
|
||||||
resp *http.Response
|
|
||||||
)
|
|
||||||
// Create the file
|
|
||||||
if out, err = os.Create(filepath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer out.Close()
|
|
||||||
|
|
||||||
// Get the data
|
|
||||||
if resp, err = http.Get(url); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// Write the body to file
|
|
||||||
if _, err = io.Copy(out, resp.Body); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func copy(src, dst string) (int64, error) {
|
func copy(src, dst string) (int64, error) {
|
||||||
var (
|
var (
|
||||||
source, destination *os.File
|
source, destination *os.File
|
||||||
@@ -219,28 +235,21 @@ func copy(src, dst string) (int64, error) {
|
|||||||
return nBytes, err
|
return nBytes, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func levenstein(cmd *cobra.Command) bool {
|
func levenstein(cmd *cobra.Command) {
|
||||||
cmd.SuggestionsMinimumDistance = 4
|
cmd.SuggestionsMinimumDistance = 2
|
||||||
if len(os.Args) > 1 {
|
if len(os.Args) > 1 {
|
||||||
args := os.Args[1]
|
args := os.Args[1]
|
||||||
for _, ctx := range cmd.Commands() {
|
suggest := cmd.SuggestionsFor(args)
|
||||||
val := *ctx
|
|
||||||
if args == val.Name() {
|
|
||||||
//found command
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
suggest := cmd.SuggestionsFor(os.Args[1])
|
|
||||||
if len(suggest) > 0 {
|
if len(suggest) > 0 {
|
||||||
arg := []string{}
|
arg := []string{}
|
||||||
for _, cm := range os.Args {
|
for _, cm := range os.Args {
|
||||||
arg = append(arg, cm)
|
arg = append(arg, cm)
|
||||||
}
|
}
|
||||||
arg[1] = suggest[0]
|
if !strings.EqualFold(arg[1], suggest[0]) {
|
||||||
out := ansi.NewColorableStdout()
|
arg[1] = suggest[0]
|
||||||
fmt.Fprintln(out, chalk.Green.Color("Did you mean: "), chalk.Bold.TextStyle(strings.Join(arg, " ")), "?")
|
out := ansi.NewColorableStdout()
|
||||||
return true
|
fmt.Fprintln(out, chalk.Green.Color("Did you mean: "), chalk.Bold.TextStyle(strings.Join(arg, " ")), "?")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
|
||||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -9,7 +8,7 @@ import (
|
|||||||
func uninstallCmd(q *qliksense.Qliksense) *cobra.Command {
|
func uninstallCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||||
c := &cobra.Command{
|
c := &cobra.Command{
|
||||||
Use: "uninstall",
|
Use: "uninstall",
|
||||||
Short: "Uninstall the deployed qliksense with release name [ " + qapi.NewQConfig(q.QliksenseHome).Spec.CurrentContext + " ]",
|
Short: "Uninstall the deployed qliksense.",
|
||||||
Long: `Uninstall the deployed qliksense. By default uninstall the current context`,
|
Long: `Uninstall the deployed qliksense. By default uninstall the current context`,
|
||||||
Example: `qliksense uninstall <context-name>`,
|
Example: `qliksense uninstall <context-name>`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|||||||
@@ -6,15 +6,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func upgradeCmd(q *qliksense.Qliksense) *cobra.Command {
|
func upgradeCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
keepPatchFiles := false
|
||||||
c := &cobra.Command{
|
c := &cobra.Command{
|
||||||
Use: "upgrade",
|
Use: "upgrade",
|
||||||
Short: "upgrade qliksense release",
|
Short: "upgrade qliksense release",
|
||||||
Long: `upgrade qliksense release`,
|
Long: `upgrade qliksense release`,
|
||||||
Example: `qliksense upgrade <version>`,
|
Example: `qliksense upgrade <version>`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return q.UpgradeQK8s()
|
return q.UpgradeQK8s(keepPatchFiles)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f := c.Flags()
|
||||||
|
f.BoolVar(&keepPatchFiles, keepPatchFilesFlagName, keepPatchFiles, keepPatchFilesFlagUsage)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|||||||
0
docs/air_gap.md
Normal file
0
docs/air_gap.md
Normal file
119
docs/command_reference.md
Normal file
119
docs/command_reference.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# qliksense command reference
|
||||||
|
|
||||||
|
## qliksense apply
|
||||||
|
|
||||||
|
`qliksense apply` command takes input a cr file or input 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
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: qlik.com/v1
|
||||||
|
kind: Qliksense
|
||||||
|
metadata:
|
||||||
|
name: qlik-test
|
||||||
|
labels:
|
||||||
|
version: v0.0.2
|
||||||
|
spec:
|
||||||
|
configs:
|
||||||
|
qliksense:
|
||||||
|
- name: acceptEULA
|
||||||
|
value: "yes"
|
||||||
|
secrets:
|
||||||
|
qliksense:
|
||||||
|
- 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 load
|
||||||
|
|
||||||
|
`qliksense load` command takes input a cr file or input from pipe.
|
||||||
|
|
||||||
|
- `qliksense load -f cr-file.yaml`
|
||||||
|
- `cat cr-file.yaml | qliksense load -f -`
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: qlik.com/v1
|
||||||
|
kind: QlikSense
|
||||||
|
metadata:
|
||||||
|
name: myqliksense
|
||||||
|
spec:
|
||||||
|
profile: docker-desktop
|
||||||
|
manifestsRoot: /Usr/ddd/my-k8-repo/manifests
|
||||||
|
namespace: myqliksense
|
||||||
|
storageClassName: efs
|
||||||
|
configs:
|
||||||
|
qliksense:
|
||||||
|
- name: acceptEULA
|
||||||
|
value: "yes"
|
||||||
|
secrets:
|
||||||
|
qliksense:
|
||||||
|
- name: mongoDbUri
|
||||||
|
value: "mongo://mongo:3307"
|
||||||
|
- name: messagingPassword
|
||||||
|
valueFromKey: messagingPassword
|
||||||
|
```
|
||||||
|
|
||||||
|
In that case the command would be
|
||||||
|
|
||||||
|
- `qliksense about`
|
||||||
|
- display from `/Usr/ddd/my-k8-repo/manifests/docker-desktop` location
|
||||||
|
- pull from `master` if directory invalid/empty
|
||||||
|
|
||||||
|
|
||||||
|
## qliksense config
|
||||||
|
|
||||||
|
Config action will perform operations on configurations and contexts regarding the [qliksense-k8](https://github.com/qlik-oss/qliksense-k8s) release.
|
||||||
|
|
||||||
|
it will support following commands:
|
||||||
|
|
||||||
|
- `qliksense config apply` - generate the patchs and apply manifests to k8s
|
||||||
|
- `qliksense config list-contexts` - retrieves the contexts and lists them
|
||||||
|
- `qliksense config set` - configure a key value pair into the current context
|
||||||
|
- `qliksense config set-configs` - set configurations into the qliksense context 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 view` - view the qliksense operator CR
|
||||||
|
- `qliksense config delete-context` - deletes a specific context locally (not in-cluster). Deletes context in spec of `config.yaml` and locally deletes entire folder of specified context (does not delete in-cluster secrets)
|
||||||
|
|
||||||
|
|
||||||
|
the global file that abstracts all the contexts is `config.yaml`, located at: `~/.qliksense/config.yaml`:
|
||||||
|
```yaml
|
||||||
|
apiVersion: config.qlik.com/v1
|
||||||
|
kind: QliksenseConfig
|
||||||
|
metadata:
|
||||||
|
name: QliksenseConfigMetadata
|
||||||
|
spec:
|
||||||
|
contexts:
|
||||||
|
- name: qlik-default
|
||||||
|
crFile: /Users/fff/.qliksense/contexts/qlik-default/qlik-default.yaml
|
||||||
|
- name: myqliksense
|
||||||
|
crFile: /Users/fff/.qliksense/contexts/myqliksense/myqliksense.yaml
|
||||||
|
- name: hello
|
||||||
|
crFile: /Users/fff/.qliksense/contexts/hello/hello.yaml
|
||||||
|
currentContext: hello
|
||||||
|
```
|
||||||
97
docs/concepts.md
Normal file
97
docs/concepts.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# How qliksense cli works
|
||||||
|
|
||||||
|
At the initialization, `qliksense` cli creates few files in the director `~/.qliksene` and it contains following files:
|
||||||
|
|
||||||
|
```console
|
||||||
|
.qliksense
|
||||||
|
├── config.yaml
|
||||||
|
├── contexts
|
||||||
|
│ └── qlik-default
|
||||||
|
│ └── qlik-default.yaml
|
||||||
|
└── ejson
|
||||||
|
└── keys
|
||||||
|
```
|
||||||
|
|
||||||
|
`qlik-default.yaml` is a default CR created with some default values like:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: qlik.com/v1
|
||||||
|
kind: Qliksense
|
||||||
|
metadata:
|
||||||
|
name: qlik-default
|
||||||
|
spec:
|
||||||
|
profile: docker-desktop
|
||||||
|
secrets:
|
||||||
|
qliksense:
|
||||||
|
- name: mongoDbUri
|
||||||
|
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
|
||||||
|
rotateKeys: "yes"
|
||||||
|
releaseName: qlik-default
|
||||||
|
```
|
||||||
|
|
||||||
|
The `qliksense` cli creates a default qliksense context (different from kubectl context) named `qlik-default` which will be the prefix for all kubernetes resources created by the cli under this context later on.
|
||||||
|
|
||||||
|
New context and configuration can be created by the cli, get available commands using:
|
||||||
|
|
||||||
|
```console
|
||||||
|
qliksense config -h
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`qliksense` cli works in two modes
|
||||||
|
|
||||||
|
- With a git repo fork/clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s)
|
||||||
|
- Without git repo
|
||||||
|
|
||||||
|
## Without git repo
|
||||||
|
|
||||||
|
In this mode `qliksense` CLI 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.
|
||||||
|
|
||||||
|
`qliksense install` or `qliksense config apply` 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`.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## With a git repo
|
||||||
|
|
||||||
|
Create a fork or clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and push it to your git repo/server
|
||||||
|
|
||||||
|
To add your repo into CR, perform the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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:
|
||||||
|
|
||||||
|
- Download corresponding version of manifests from the your git repo
|
||||||
|
- Generate kustomize patches
|
||||||
|
- Install kubernetes resources
|
||||||
|
- Push generated patches into a new branch in the provided git repo. _Gives you ability to merge patches into your master branch_
|
||||||
|
- Create a CronJob to monitor master branch. Any changes pushed to master branch will be applied into the cluster. _This is a light weight `git-ops` model_
|
||||||
|
|
||||||
|
## GitOps
|
||||||
|
|
||||||
|
To enable gitops, the following section should be in the CR
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
....
|
||||||
|
spec:
|
||||||
|
git:
|
||||||
|
repository: https://github.com/<OWNER>/<REPO>
|
||||||
|
accessToken: "<git-token>"
|
||||||
|
userName: "<git-username>"
|
||||||
|
gitOps:
|
||||||
|
enabled: "yes"
|
||||||
|
schedule: "*/5 * * * *"
|
||||||
|
watchBranch: <myBranch>
|
||||||
|
image: qlik-docker-oss.bintray.io/qliksense-repo-watcher
|
||||||
|
....
|
||||||
|
```
|
||||||
|
|
||||||
53
docs/getting_started.md
Normal file
53
docs/getting_started.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Getting started
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Kubernetes cluster (Docker Desktop with enabled Kubernetes)
|
||||||
|
- `kubectl` installed, configured and able to communicate with kubernetes cluster. _`qliksense` CLI uses `kubectl` under the hood to perform operations on cluster_
|
||||||
|
|
||||||
|
## Installing Sense installer
|
||||||
|
|
||||||
|
Download the executable for your platform from [releases page](https://github.com/qlik-oss/sense-installer/releases) and rename it to `qliksense`
|
||||||
|
|
||||||
|
??? tldr "Linux"
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
curl -Lo qliksense https://github.com/qlik-oss/sense-installer/releases/download/v0.7.0/qliksense-linux-amd64
|
||||||
|
chmod +x qliksense
|
||||||
|
sudo mv qliksense /usr/local/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
??? tldr "MacOS"
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
curl -Lo qliksense https://github.com/qlik-oss/sense-installer/releases/download/v0.7.0/qliksense-darwin-amd64
|
||||||
|
chmod +x qliksense
|
||||||
|
sudo mv qliksense /usr/local/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
??? tldr "Windows"
|
||||||
|
Download Windows executable and add it in your `PATH` as `qliksense.exe`
|
||||||
|
|
||||||
|
[https://github.com/qlik-oss/sense-installer/releases/download/v0.7.0/qliksense-windows-amd64.exe](https://github.com/qlik-oss/sense-installer/releases/download/v0.7.0/qliksense-windows-amd64.exe)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
- To download the version `v0.0.2` from qliksense-k8s [releases](https://github.com/qlik-oss/qliksense-k8s/releases).
|
||||||
|
|
||||||
|
```shell
|
||||||
|
qliksense fetch v0.0.2
|
||||||
|
```
|
||||||
|
|
||||||
|
- To install CRDs for QSEoK and qliksense operator into the kubernetes cluster.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
qliksense crds install --all
|
||||||
|
```
|
||||||
|
|
||||||
|
- To install QSEoK into a namespace in the kubernetes cluster where `kubectl` is pointing to.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
qliksense install --acceptEULA="yes"
|
||||||
|
```
|
||||||
22
docs/index.md
Normal file
22
docs/index.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Overview
|
||||||
|
|
||||||
|
The Qlik Sense installer CLI (`qliksense`) provides an imperative interface to many of the configurations that need to be applied against the declarative structure described in [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s). This cli facilitates:
|
||||||
|
|
||||||
|
- Installation of QSEoK
|
||||||
|
- Installation of qliksense operator to manage QSEoK
|
||||||
|
- Air gapped installation of QSEoK
|
||||||
|
|
||||||
|
!!! info ""
|
||||||
|
This is a technology preview that uses Qlik modified [kustomize](https://github.com/qlik-oss/kustomize) for kubernetes manifests on [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository
|
||||||
|
|
||||||
|
!!! info ""
|
||||||
|
See QlikSense [edge releases on qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s/releases) repository
|
||||||
|
|
||||||
|
## Future Direction
|
||||||
|
|
||||||
|
Operations:
|
||||||
|
|
||||||
|
- Expand preflight checks
|
||||||
|
- Backup/restore operations
|
||||||
|
- Fully support airgap installation of QSEoK
|
||||||
|
- Restore unwanted deletion of kubernetes resources
|
||||||
100
docs/preflight_checks.md
Normal file
100
docs/preflight_checks.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
##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.
|
||||||
|
|
||||||
|
Run the following command to view help about the commands supported by preflight at any moment:
|
||||||
|
```console
|
||||||
|
$ qliksense preflight
|
||||||
|
perform preflight checks on the cluster
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
qliksense preflight [command]
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
qliksense preflight <preflight_check_to_run>
|
||||||
|
|
||||||
|
Available Commands:
|
||||||
|
all perform all checks
|
||||||
|
dns perform preflight dns check
|
||||||
|
k8s-version check k8s version
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h, --help help for preflight
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
Creating resources to run preflight checks
|
||||||
|
deployment.apps/qnginx001 created
|
||||||
|
service/qnginx001 created
|
||||||
|
pod/qnginx001-6db5fc95c5-s9sl2 condition met
|
||||||
|
Running Preflight checks ⠇
|
||||||
|
--- PASS DNS check
|
||||||
|
--- DNS check passed
|
||||||
|
--- PASS cluster-preflight-checks
|
||||||
|
PASS
|
||||||
|
|
||||||
|
DNS check completed, cleaning up resources now
|
||||||
|
service "qnginx001" deleted
|
||||||
|
deployment.extensions "qnginx001" deleted
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
Minimum Kubernetes version supported: 1.11.0
|
||||||
|
Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.3", GitCommit:"06ad960bfd03b39c8310aaf92d1e7c12ce618213", GitTreeState:"clean", BuildDate:"2020-02-13T18:08:14Z", GoVersion:"go1.13.8", Compiler:"gc", Platform:"darwin/amd64"}
|
||||||
|
Server Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.5", GitCommit:"20c265fef0741dd71a66480e35bd69f18351daea", GitTreeState:"clean", BuildDate:"2019-10-15T19:07:57Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"linux/amd64"}
|
||||||
|
Running Preflight checks ⠇
|
||||||
|
--- PASS Required Kubernetes Version
|
||||||
|
--- Good to go.
|
||||||
|
--- PASS cluster-preflight-checks
|
||||||
|
PASS
|
||||||
|
|
||||||
|
Minimum kubernetes version check completed
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running all checks
|
||||||
|
Run the command shown below to execute all preflight checks.
|
||||||
|
```console
|
||||||
|
$ qliksense preflight all
|
||||||
|
|
||||||
|
Running all preflight checks
|
||||||
|
|
||||||
|
Running DNS check...
|
||||||
|
Creating resources to run preflight checks
|
||||||
|
deployment.apps/qnginx001 created
|
||||||
|
service/qnginx001 created
|
||||||
|
pod/qnginx001-6db5fc95c5-grwv2 condition met
|
||||||
|
Running Preflight checks ⠇
|
||||||
|
--- PASS DNS check
|
||||||
|
--- DNS check passed
|
||||||
|
--- PASS cluster-preflight-checks
|
||||||
|
PASS
|
||||||
|
|
||||||
|
DNS check completed, cleaning up resources now
|
||||||
|
service "qnginx001" deleted
|
||||||
|
deployment.extensions "qnginx001" deleted
|
||||||
|
|
||||||
|
Running minimum kubernetes version check...
|
||||||
|
Minimum Kubernetes version supported: 1.11.0
|
||||||
|
Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.3", GitCommit:"06ad960bfd03b39c8310aaf92d1e7c12ce618213", GitTreeState:"clean", BuildDate:"2020-02-13T18:08:14Z", GoVersion:"go1.13.8", Compiler:"gc", Platform:"darwin/amd64"}
|
||||||
|
Server Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.5", GitCommit:"20c265fef0741dd71a66480e35bd69f18351daea", GitTreeState:"clean", BuildDate:"2019-10-15T19:07:57Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"linux/amd64"}
|
||||||
|
Running Preflight checks ⠧
|
||||||
|
--- PASS Required Kubernetes Version
|
||||||
|
--- Good to go.
|
||||||
|
--- PASS cluster-preflight-checks
|
||||||
|
PASS
|
||||||
|
|
||||||
|
Minimum kubernetes version check completed
|
||||||
|
Completed running all preflight checks
|
||||||
|
```
|
||||||
15
go.mod
15
go.mod
@@ -33,29 +33,32 @@ require (
|
|||||||
github.com/golang/protobuf v1.3.3 // indirect
|
github.com/golang/protobuf v1.3.3 // indirect
|
||||||
github.com/google/uuid v1.1.1
|
github.com/google/uuid v1.1.1
|
||||||
github.com/gorilla/mux v1.7.3 // indirect
|
github.com/gorilla/mux v1.7.3 // indirect
|
||||||
|
|
||||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.4
|
github.com/mattn/go-colorable v0.1.4
|
||||||
|
github.com/mattn/go-tty v0.0.3
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/qlik-oss/k-apis v0.0.11
|
github.com/pkg/errors v0.8.1
|
||||||
|
github.com/qlik-oss/k-apis v0.0.22
|
||||||
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/rogpeppe/go-internal v1.5.2 // indirect
|
github.com/rogpeppe/go-internal v1.5.2 // indirect
|
||||||
github.com/spf13/cobra v0.0.6
|
github.com/spf13/cobra v0.0.6
|
||||||
github.com/spf13/viper v1.6.1
|
github.com/spf13/viper v1.6.1
|
||||||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31
|
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31
|
||||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d // indirect
|
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 // indirect
|
||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a // indirect
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a // indirect
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae // indirect
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect
|
||||||
golang.org/x/tools v0.0.0-20200228135638-5c7c66ced534 // indirect
|
golang.org/x/tools v0.0.0-20200312194400-c312e98713c2 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b // indirect
|
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b // indirect
|
||||||
google.golang.org/grpc v1.27.0 // indirect
|
google.golang.org/grpc v1.27.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.2.8
|
gopkg.in/yaml.v2 v2.2.8
|
||||||
gopkg.in/yaml.v3 v3.0.0-20190924164351-c8b7dadae555
|
gopkg.in/yaml.v3 v3.0.0-20190924164351-c8b7dadae555
|
||||||
k8s.io/api v0.17.0
|
k8s.io/api v0.17.0
|
||||||
k8s.io/apimachinery v0.17.0
|
k8s.io/apimachinery v0.17.0
|
||||||
|
k8s.io/client-go v11.0.0+incompatible
|
||||||
sigs.k8s.io/kustomize/api v0.3.2
|
sigs.k8s.io/kustomize/api v0.3.2
|
||||||
sigs.k8s.io/yaml v1.1.0
|
sigs.k8s.io/yaml v1.1.0
|
||||||
)
|
)
|
||||||
|
|||||||
32
go.sum
32
go.sum
@@ -380,7 +380,6 @@ github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4
|
|||||||
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
|
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
|
||||||
github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM=
|
github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM=
|
||||||
github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI=
|
github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI=
|
||||||
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
|
|
||||||
github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o=
|
github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o=
|
||||||
github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc=
|
github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc=
|
||||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
@@ -697,9 +696,13 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME
|
|||||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
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.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.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.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 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI=
|
||||||
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||||
|
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=
|
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
@@ -844,14 +847,16 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
|
|||||||
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
|
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
|
||||||
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
github.com/qlik-oss/k-apis v0.0.11 h1:dhbcH1+8r14OMlTSrP2RAlr+707QXcrj1iVnA/y9r5I=
|
github.com/qlik-oss/k-apis v0.0.22 h1:tntQEeRqDYkBi2Ku5+xt7ABGMeFPck7+DOKrHUnpzwI=
|
||||||
github.com/qlik-oss/k-apis v0.0.11/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE=
|
github.com/qlik-oss/k-apis v0.0.22/go.mod h1:DNiWYqCqPIN216l7+1rccArNIYPb1Le7kYDcPSyNp+Q=
|
||||||
github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9 h1:iqeqTS4zjp6rPEaxmFB7pemA2CMjOEN5dYSXZaQ82uw=
|
github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9 h1:iqeqTS4zjp6rPEaxmFB7pemA2CMjOEN5dYSXZaQ82uw=
|
||||||
github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9/go.mod h1:OCt7FTrRVHj4kmR2xLJJUIqu00BTr6GeF09hSmM17Kw=
|
github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9/go.mod h1:OCt7FTrRVHj4kmR2xLJJUIqu00BTr6GeF09hSmM17Kw=
|
||||||
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
|
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
|
||||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
|
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
|
||||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
|
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
@@ -1041,8 +1046,8 @@ golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
|
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
|
||||||
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
|
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA=
|
||||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
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=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@@ -1077,6 +1082,8 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG
|
|||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -1117,6 +1124,8 @@ golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCT
|
|||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -1194,21 +1203,14 @@ golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtn
|
|||||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c h1:2EA2K0k9bcvvEDlqD8xdlOhCOqq+O/p9Voqi4x9W1YU=
|
golang.org/x/tools v0.0.0-20200117161641-43d50277825c h1:2EA2K0k9bcvvEDlqD8xdlOhCOqq+O/p9Voqi4x9W1YU=
|
||||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2 h1:L/G4KZvrQn7FWLN/LlulBtBzrLUhqjiGfTWWDmrh+IQ=
|
golang.org/x/tools v0.0.0-20200312194400-c312e98713c2 h1:6TB4+MaZlkcSsJDu+BS5yxSEuZIYhjWz+jhbSLEZylI=
|
||||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200312194400-c312e98713c2/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
golang.org/x/tools v0.0.0-20200225230052-807dcd883420 h1:4RJNOV+2rLxMEfr6QIpC7GEv9MjD6ApGXTCLrNF9+eA=
|
|
||||||
golang.org/x/tools v0.0.0-20200225230052-807dcd883420/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200226205201-eb7c56241bdb h1:RXjcsi6scaPhM5uXm7JRqP2JibKvbgMqx9zDLDB9voM=
|
|
||||||
golang.org/x/tools v0.0.0-20200226205201-eb7c56241bdb/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d h1:7M9AXzLrJWWGdDYtBblPHBTnHtaN6KKQ98OYb35mLlY=
|
|
||||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200228135638-5c7c66ced534 h1:XVzrScQUlfS6ssloilmEJdJhlMDtnculCx+0zmVHSA8=
|
|
||||||
golang.org/x/tools v0.0.0-20200228135638-5c7c66ced534/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
24
mkdocs.yml
Normal file
24
mkdocs.yml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
site_name: Qlik Sense installer
|
||||||
|
repo_url: 'https://github.com/qlik-oss/sense-installer'
|
||||||
|
strict: true
|
||||||
|
theme:
|
||||||
|
name: "material"
|
||||||
|
palette:
|
||||||
|
primary: 'green'
|
||||||
|
accent: 'indigo'
|
||||||
|
markdown_extensions:
|
||||||
|
- toc:
|
||||||
|
permalink: true
|
||||||
|
- admonition
|
||||||
|
- codehilite
|
||||||
|
- pymdownx.inlinehilite
|
||||||
|
- pymdownx.superfences
|
||||||
|
- pymdownx.details
|
||||||
|
nav:
|
||||||
|
- Overview: index.md
|
||||||
|
- getting_started.md
|
||||||
|
- command_reference.md
|
||||||
|
- concepts.md
|
||||||
|
- preflight_checks.md
|
||||||
|
- air_gap.md
|
||||||
|
- Releases ⧉: https://github.com/qlik-oss/sense-installer/releases
|
||||||
BIN
pkg/.DS_Store
vendored
Normal file
BIN
pkg/.DS_Store
vendored
Normal file
Binary file not shown.
316
pkg/api/apis.go
316
pkg/api/apis.go
@@ -8,9 +8,13 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/qlik-oss/k-apis/pkg/config"
|
||||||
|
|
||||||
|
b64 "encoding/base64"
|
||||||
|
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -18,38 +22,44 @@ const (
|
|||||||
pullSecretFileName = "image-registry-pull-secret.yaml"
|
pullSecretFileName = "image-registry-pull-secret.yaml"
|
||||||
qliksenseContextsDirName = "contexts"
|
qliksenseContextsDirName = "contexts"
|
||||||
qliksenseSecretsDirName = "secrets"
|
qliksenseSecretsDirName = "secrets"
|
||||||
|
qliksenseEjsonDirName = "ejson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewQConfig create QliksenseConfig object from file ~/.qliksense/config.yaml
|
// NewQConfig create QliksenseConfig object from file ~/.qliksense/config.yaml
|
||||||
func NewQConfig(qsHome string) *QliksenseConfig {
|
func NewQConfig(qsHome string) *QliksenseConfig {
|
||||||
configFile := filepath.Join(qsHome, "config.yaml")
|
qc, err := NewQConfigE(qsHome)
|
||||||
data, err := ioutil.ReadFile(configFile)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Cannot read config file from: "+configFile, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
qc := &QliksenseConfig{}
|
|
||||||
err = yaml.Unmarshal(data, qc)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("yaml unmarshalling error ", err)
|
fmt.Println("yaml unmarshalling error ", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
qc.QliksenseHomePath = qsHome
|
|
||||||
return qc
|
return qc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewQConfigE(qsHome string) (*QliksenseConfig, error) {
|
||||||
|
configFile := filepath.Join(qsHome, "config.yaml")
|
||||||
|
qc := &QliksenseConfig{}
|
||||||
|
|
||||||
|
err := ReadFromFile(qc, configFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
qc.QliksenseHomePath = qsHome
|
||||||
|
return qc, nil
|
||||||
|
}
|
||||||
|
func NewQConfigEmpty(qsHome string) *QliksenseConfig {
|
||||||
|
return &QliksenseConfig{
|
||||||
|
QliksenseHomePath: qsHome,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetCR create a QliksenseCR object for a particular context
|
// GetCR create a QliksenseCR object for a particular context
|
||||||
// from file ~/.qliksense/contexts/<contx-name>/<contx-name>.yaml
|
// from file ~/.qliksense/contexts/<contx-name>/<contx-name>.yaml
|
||||||
func (qc *QliksenseConfig) GetCR(contextName string) (*QliksenseCR, error) {
|
func (qc *QliksenseConfig) GetCR(contextName string) (*QliksenseCR, error) {
|
||||||
crFilePath := qc.getCRFilePath(contextName)
|
crFilePath := qc.GetCRFilePath(contextName)
|
||||||
if crFilePath == "" {
|
if crFilePath == "" {
|
||||||
return nil, errors.New("context name " + contextName + " not found")
|
return nil, errors.New("context name " + contextName + " not found")
|
||||||
}
|
}
|
||||||
return getCRObject(crFilePath)
|
return qc.GetAndTransformCrObject(crFilePath)
|
||||||
}
|
|
||||||
|
|
||||||
func getUnencryptedCR() {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentCR create a QliksenseCR object for current context
|
// GetCurrentCR create a QliksenseCR object for current context
|
||||||
@@ -58,14 +68,14 @@ func (qc *QliksenseConfig) GetCurrentCR() (*QliksenseCR, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetCrLocation sets the CR location for a context. Helpful during test
|
// SetCrLocation sets the CR location for a context. Helpful during test
|
||||||
func (qc *QliksenseConfig) SetCrLocation(contextName, filepath string) (*QliksenseConfig, error) {
|
func (qc *QliksenseConfig) SetCrLocation(contextName, filePath string) (*QliksenseConfig, error) {
|
||||||
tempQc := &QliksenseConfig{}
|
tempQc := &QliksenseConfig{}
|
||||||
copier.Copy(tempQc, qc)
|
copier.Copy(tempQc, qc)
|
||||||
found := false
|
found := false
|
||||||
tempQc.Spec.Contexts = []Context{}
|
tempQc.Spec.Contexts = []Context{}
|
||||||
for _, c := range qc.Spec.Contexts {
|
for _, c := range qc.Spec.Contexts {
|
||||||
if c.Name == contextName {
|
if c.Name == contextName {
|
||||||
c.CrFile = filepath
|
c.CrFile = filePath
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
tempQc.Spec.Contexts = append(tempQc.Spec.Contexts, []Context{c}...)
|
tempQc.Spec.Contexts = append(tempQc.Spec.Contexts, []Context{c}...)
|
||||||
@@ -76,14 +86,36 @@ func (qc *QliksenseConfig) SetCrLocation(contextName, filepath string) (*Qliksen
|
|||||||
return nil, errors.New("cannot find the context")
|
return nil, errors.New("cannot find the context")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCRObject(crfile string) (*QliksenseCR, error) {
|
// GetCRObject create a qliksense CR object from file
|
||||||
data, err := ioutil.ReadFile(crfile)
|
func GetCRObject(crfile string) (*QliksenseCR, error) {
|
||||||
|
cr := &QliksenseCR{}
|
||||||
|
err := ReadFromFile(cr, crfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Cannot read config file from: "+crfile, err)
|
fmt.Println("cannot unmarshal cr ", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return cr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qc *QliksenseConfig) GetAndTransformCrObject(crfile string) (*QliksenseCR, error) {
|
||||||
|
cr, err := GetCRObject(crfile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cr.Spec.ManifestsRoot != "" && !filepath.IsAbs(cr.Spec.ManifestsRoot) {
|
||||||
|
cr.Spec.ManifestsRoot = filepath.Join(qc.QliksenseHomePath, cr.Spec.ManifestsRoot)
|
||||||
|
}
|
||||||
|
return cr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//CreateCRObjectFromString create a QliksenseCR from string content
|
||||||
|
func CreateCRObjectFromString(crContent string) (*QliksenseCR, error) {
|
||||||
|
if crContent == "" {
|
||||||
|
return nil, errors.New("empty string cannot qliksensecr")
|
||||||
|
}
|
||||||
cr := &QliksenseCR{}
|
cr := &QliksenseCR{}
|
||||||
err = yaml.Unmarshal(data, cr)
|
err := ReadFromStream(cr, strings.NewReader(crContent))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("cannot unmarshal cr ", err)
|
fmt.Println("cannot unmarshal cr ", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -91,11 +123,11 @@ func getCRObject(crfile string) (*QliksenseCR, error) {
|
|||||||
return cr, nil
|
return cr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qc *QliksenseConfig) getCRFilePath(contextName string) string {
|
func (qc *QliksenseConfig) GetCRFilePath(contextName string) string {
|
||||||
crFilePath := ""
|
crFilePath := ""
|
||||||
for _, ctx := range qc.Spec.Contexts {
|
for _, ctx := range qc.Spec.Contexts {
|
||||||
if ctx.Name == contextName {
|
if ctx.Name == contextName {
|
||||||
crFilePath = ctx.CrFile
|
crFilePath = filepath.Join(qc.QliksenseHomePath, ctx.CrFile)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,7 +152,7 @@ func (qc *QliksenseConfig) BuildRepoPath(version string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (qc *QliksenseConfig) BuildRepoPathForContext(contextName, version string) string {
|
func (qc *QliksenseConfig) BuildRepoPathForContext(contextName, version string) string {
|
||||||
return filepath.Join(qc.QliksenseHomePath, qliksenseContextsDirName, contextName, "qlik-k8s", version)
|
return filepath.Join(qc.GetContextPath(contextName), "qlik-k8s", version)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qc *QliksenseConfig) BuildCurrentManifestsRoot(version string) string {
|
func (qc *QliksenseConfig) BuildCurrentManifestsRoot(version string) string {
|
||||||
@@ -128,19 +160,59 @@ func (qc *QliksenseConfig) BuildCurrentManifestsRoot(version string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (qc *QliksenseConfig) WriteCR(cr *QliksenseCR, contextName string) error {
|
func (qc *QliksenseConfig) WriteCR(cr *QliksenseCR, contextName string) error {
|
||||||
crf := qc.getCRFilePath(contextName)
|
crf := qc.GetCRFilePath(contextName)
|
||||||
if crf == "" {
|
if crf == "" {
|
||||||
return errors.New("context name " + contextName + " not found")
|
return errors.New("context name " + contextName + " not found")
|
||||||
}
|
}
|
||||||
by, err := yaml.Marshal(cr)
|
|
||||||
if err != nil {
|
return qc.TransformAndWriteCr(cr, crf)
|
||||||
fmt.Println("cannot marshal cr ", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ioutil.WriteFile(crf, by, 0644)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//CreateOrWriteCrAndContext create necessary folder structure, update config.yaml and context yaml files
|
||||||
|
func (qc *QliksenseConfig) CreateOrWriteCrAndContext(cr *QliksenseCR) error {
|
||||||
|
if qc.QliksenseHomePath == "" {
|
||||||
|
return errors.New("qliksense home is not set")
|
||||||
|
}
|
||||||
|
crf := qc.GetCRFilePath(cr.GetName())
|
||||||
|
if crf == "" {
|
||||||
|
// create direcotry structure for context
|
||||||
|
cDir := filepath.Join(qc.QliksenseHomePath, "contexts", cr.GetName())
|
||||||
|
if err := os.MkdirAll(cDir, os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
crf = filepath.Join(cDir, cr.GetName()+".yaml")
|
||||||
|
ctx := Context{
|
||||||
|
Name: cr.GetName(),
|
||||||
|
CrFile: filepath.Join("contexts", cr.GetName(), cr.GetName()+".yaml"),
|
||||||
|
}
|
||||||
|
qc.AddToContexts(ctx)
|
||||||
|
|
||||||
|
if err := qc.Write(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return qc.TransformAndWriteCr(cr, crf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qc *QliksenseConfig) TransformAndWriteCr(cr *QliksenseCR, file string) error {
|
||||||
|
if strings.HasPrefix(cr.Spec.ManifestsRoot, qc.QliksenseHomePath) {
|
||||||
|
cr.Spec.ManifestsRoot = strings.Replace(cr.Spec.ManifestsRoot, qc.QliksenseHomePath+"/", "", 1)
|
||||||
|
}
|
||||||
|
if err := WriteToFile(cr, file); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if cr.Spec.ManifestsRoot != "" {
|
||||||
|
cr.Spec.ManifestsRoot = filepath.Join(qc.QliksenseHomePath, cr.Spec.ManifestsRoot)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (qc *QliksenseConfig) AddToContexts(ctx Context) error {
|
||||||
|
//TODO: additional duplicate check may be added latter
|
||||||
|
qc.Spec.Contexts = append(qc.Spec.Contexts, ctx)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
func (qc *QliksenseConfig) WriteCurrentContextCR(cr *QliksenseCR) error {
|
func (qc *QliksenseConfig) WriteCurrentContextCR(cr *QliksenseCR) error {
|
||||||
return qc.WriteCR(cr, qc.Spec.CurrentContext)
|
return qc.WriteCR(cr, qc.Spec.CurrentContext)
|
||||||
}
|
}
|
||||||
@@ -158,7 +230,7 @@ func (qc *QliksenseConfig) GetCurrentContextDir() (string, error) {
|
|||||||
if qcr, err := qc.GetCurrentCR(); err != nil {
|
if qcr, err := qc.GetCurrentCR(); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
} else {
|
} else {
|
||||||
return filepath.Join(qc.QliksenseHomePath, qliksenseContextsDirName, qcr.Metadata.Name), nil
|
return filepath.Join(qc.QliksenseHomePath, qliksenseContextsDirName, qcr.GetObjectMeta().GetName()), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,10 +265,30 @@ func (qc *QliksenseConfig) SetPullDockerConfigJsonSecret(dockerConfigJsonSecret
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (qc *QliksenseConfig) GetPushDockerConfigJsonSecret() (*DockerConfigJsonSecret, error) {
|
func (qc *QliksenseConfig) GetPushDockerConfigJsonSecret() (*DockerConfigJsonSecret, error) {
|
||||||
return qc.GetDockerConfigJsonSecret(pushSecretFileName)
|
return qc.getDockerConfigJsonSecret(pushSecretFileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qc *QliksenseConfig) GetDockerConfigJsonSecret(name string) (*DockerConfigJsonSecret, error) {
|
func (qc *QliksenseConfig) GetPullDockerConfigJsonSecret() (*DockerConfigJsonSecret, error) {
|
||||||
|
return qc.getDockerConfigJsonSecret(pullSecretFileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qc *QliksenseConfig) DeletePushDockerConfigJsonSecret() error {
|
||||||
|
return qc.deleteDockerConfigJsonSecret(pushSecretFileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qc *QliksenseConfig) DeletePullDockerConfigJsonSecret() error {
|
||||||
|
return qc.deleteDockerConfigJsonSecret(pullSecretFileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qc *QliksenseConfig) deleteDockerConfigJsonSecret(name string) error {
|
||||||
|
if secretsDir, err := qc.GetCurrentContextSecretsDir(); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
return os.Remove(filepath.Join(secretsDir, name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qc *QliksenseConfig) getDockerConfigJsonSecret(name string) (*DockerConfigJsonSecret, error) {
|
||||||
dockerConfigJsonSecret := &DockerConfigJsonSecret{}
|
dockerConfigJsonSecret := &DockerConfigJsonSecret{}
|
||||||
if secretsDir, err := qc.GetCurrentContextSecretsDir(); err != nil {
|
if secretsDir, err := qc.GetCurrentContextSecretsDir(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -211,6 +303,15 @@ func (qc *QliksenseConfig) GetDockerConfigJsonSecret(name string) (*DockerConfig
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (qc *QliksenseConfig) getCurrentContextEncryptionKeyPairLocation() (string, error) {
|
func (qc *QliksenseConfig) getCurrentContextEncryptionKeyPairLocation() (string, error) {
|
||||||
|
|
||||||
|
if qcr, err := qc.GetCurrentCR(); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else {
|
||||||
|
return qc.getContextEncryptionKeyPairLocation(qcr.GetName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qc *QliksenseConfig) getContextEncryptionKeyPairLocation(contextName string) (string, error) {
|
||||||
// Check env var: QLIKSENSE_KEY_LOCATION to determine location to store keypair
|
// Check env var: QLIKSENSE_KEY_LOCATION to determine location to store keypair
|
||||||
var secretKeyPairLocation string
|
var secretKeyPairLocation string
|
||||||
if os.Getenv("QLIKSENSE_KEY_LOCATION") != "" {
|
if os.Getenv("QLIKSENSE_KEY_LOCATION") != "" {
|
||||||
@@ -219,25 +320,40 @@ func (qc *QliksenseConfig) getCurrentContextEncryptionKeyPairLocation() (string,
|
|||||||
} else {
|
} else {
|
||||||
// QLIKSENSE_KEY_LOCATION has not been set, hence storing key pair in default location:
|
// QLIKSENSE_KEY_LOCATION has not been set, hence storing key pair in default location:
|
||||||
// /.qliksense/secrets/contexts/<current-context>/secrets/
|
// /.qliksense/secrets/contexts/<current-context>/secrets/
|
||||||
if qcr, err := qc.GetCurrentCR(); err != nil {
|
secretKeyPairLocation = filepath.Join(qc.QliksenseHomePath, qliksenseSecretsDirName, qliksenseContextsDirName, contextName, qliksenseSecretsDirName)
|
||||||
return "", err
|
|
||||||
} else {
|
|
||||||
secretKeyPairLocation = filepath.Join(qc.QliksenseHomePath, qliksenseSecretsDirName, qliksenseContextsDirName, qcr.Metadata.Name, qliksenseSecretsDirName)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
LogDebugMessage("SecretKeyLocation to store key pair: %s", secretKeyPairLocation)
|
|
||||||
return secretKeyPairLocation, nil
|
return secretKeyPairLocation, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (qc *QliksenseConfig) GetCurrentContextEjsonKeyDir() (string, error) {
|
||||||
|
if qcr, err := qc.GetCurrentCR(); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else {
|
||||||
|
ejsonKeyDir := filepath.Join(qc.QliksenseHomePath, qliksenseSecretsDirName, qliksenseContextsDirName, qcr.GetObjectMeta().GetName(), qliksenseEjsonDirName)
|
||||||
|
if err := os.MkdirAll(ejsonKeyDir, os.ModePerm); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return ejsonKeyDir, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (qc *QliksenseConfig) GetCurrentContextEncryptionKeyPair() (*rsa.PublicKey, *rsa.PrivateKey, error) {
|
func (qc *QliksenseConfig) GetCurrentContextEncryptionKeyPair() (*rsa.PublicKey, *rsa.PrivateKey, error) {
|
||||||
secretKeyPairLocation, err := qc.getCurrentContextEncryptionKeyPairLocation()
|
if qcr, err := qc.GetCurrentCR(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
} else {
|
||||||
|
return qc.GetContextEncryptionKeyPair(qcr.GetName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qc *QliksenseConfig) GetContextEncryptionKeyPair(contextName string) (*rsa.PublicKey, *rsa.PrivateKey, error) {
|
||||||
|
secretKeyPairLocation, err := qc.getContextEncryptionKeyPairLocation(contextName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
publicKeyFilePath := filepath.Join(secretKeyPairLocation, QliksensePublicKey)
|
publicKeyFilePath := filepath.Join(secretKeyPairLocation, QliksensePublicKey)
|
||||||
privateKeyFilePath := filepath.Join(secretKeyPairLocation, QliksensePrivateKey)
|
privateKeyFilePath := filepath.Join(secretKeyPairLocation, QliksensePrivateKey)
|
||||||
|
|
||||||
// try to create the dir if it doesn't exist
|
// try to create the dir if it doesn't exist
|
||||||
if !FileExists(publicKeyFilePath) || !FileExists(privateKeyFilePath) {
|
if !FileExists(publicKeyFilePath) || !FileExists(privateKeyFilePath) {
|
||||||
LogDebugMessage("Qliksense secretKeyLocation dir does not exist, creating it now: %s", secretKeyPairLocation)
|
LogDebugMessage("Qliksense secretKeyLocation dir does not exist, creating it now: %s", secretKeyPairLocation)
|
||||||
@@ -271,22 +387,20 @@ func (qc *QliksenseConfig) GetCurrentContextEncryptionKeyPair() (*rsa.PublicKey,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cr *QliksenseCR) AddLabelToCr(key, value string) {
|
func (cr *QliksenseCR) AddLabelToCr(key, value string) {
|
||||||
if cr.Metadata.Labels == nil {
|
m := cr.GetObjectMeta().GetLabels()
|
||||||
cr.Metadata.Labels = make(map[string]string)
|
if m == nil {
|
||||||
|
m = make(map[string]string)
|
||||||
}
|
}
|
||||||
cr.Metadata.Labels[key] = value
|
m[key] = value
|
||||||
|
cr.GetObjectMeta().SetLabels(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cr *QliksenseCR) GetLabelFromCr(key string) string {
|
func (cr *QliksenseCR) GetLabelFromCr(key string) string {
|
||||||
val := ""
|
return cr.GetObjectMeta().GetLabels()[key]
|
||||||
if cr.Metadata.Labels != nil {
|
|
||||||
val = cr.Metadata.Labels[key]
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cr *QliksenseCR) GetString() (string, error) {
|
func (cr *QliksenseCR) GetString() (string, error) {
|
||||||
out, err := yaml.Marshal(cr)
|
out, err := K8sToYaml(cr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("cannot unmarshal cr ", err)
|
fmt.Println("cannot unmarshal cr ", err)
|
||||||
return "", err
|
return "", err
|
||||||
@@ -304,3 +418,99 @@ func (cr *QliksenseCR) GetImageRegistry() string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cr *QliksenseCR) GetK8sSecretsFolder(qlikSenseHomeDir string) string {
|
||||||
|
return filepath.Join(qlikSenseHomeDir, qliksenseContextsDirName, cr.GetName(), qliksenseSecretsDirName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *QliksenseCR) IsEULA() bool {
|
||||||
|
for k, nvs := range cr.Spec.Configs {
|
||||||
|
if k == "qliksense" {
|
||||||
|
for _, nv := range nvs {
|
||||||
|
if nv.Name == "acceptEULA" {
|
||||||
|
return nv.Value == "yes"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *QliksenseCR) SetEULA(value string) {
|
||||||
|
cr.Spec.AddToConfigs("qliksense", "acceptEULA", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDecryptedCr it decrypts all the encrypted value and return a new CR
|
||||||
|
func (qc *QliksenseConfig) GetDecryptedCr(cr *QliksenseCR) (*QliksenseCR, error) {
|
||||||
|
newCr := &QliksenseCR{}
|
||||||
|
copier.Copy(newCr, cr)
|
||||||
|
_, rsaPrivateKey, err := qc.GetCurrentContextEncryptionKeyPair()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
finalSecrets := map[string]config.NameValues{}
|
||||||
|
for k, nvs := range newCr.Spec.Secrets {
|
||||||
|
newNvs := config.NameValues{}
|
||||||
|
for _, nv := range nvs {
|
||||||
|
if nv.Value != "" {
|
||||||
|
b, err := b64.StdEncoding.DecodeString(strings.TrimSpace(nv.Value))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
db, err := Decrypt(b, rsaPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newNvs = append(newNvs, config.NameValue{
|
||||||
|
Name: nv.Name,
|
||||||
|
Value: string(db),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finalSecrets[k] = newNvs
|
||||||
|
}
|
||||||
|
newCr.Spec.Secrets = finalSecrets
|
||||||
|
return newCr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Validate validate CR
|
||||||
|
func (cr *QliksenseCR) Validate() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
//CreateContextDirs create context dir structure ~/.qliksense/contexts/contextName
|
||||||
|
func (qc *QliksenseConfig) CreateContextDirs(contextName string) error {
|
||||||
|
return os.MkdirAll(qc.GetContextPath(contextName), os.ModePerm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qc *QliksenseConfig) GetContextPath(contextName string) string {
|
||||||
|
return filepath.Join(qc.QliksenseHomePath, qliksenseContextsDirName, contextName)
|
||||||
|
}
|
||||||
|
|
||||||
|
//BuildCrFileAbsolutePath build absolute path for a cr ie. ~/.qliksense/contexts/qlik-defautl/qlik-default.yaml
|
||||||
|
func (qc *QliksenseConfig) BuildCrFileAbsolutePath(contextName string) string {
|
||||||
|
return filepath.Join(qc.GetContextPath(contextName), contextName+".yaml")
|
||||||
|
}
|
||||||
|
|
||||||
|
//BuildCrFilePath build cr file path i.e. contexts/qlik-default/qlik-default.yaml
|
||||||
|
func (qc *QliksenseConfig) BuildCrFilePath(contextName string) string {
|
||||||
|
return filepath.Join(qc.GetContextPath(contextName), contextName+".yaml")
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddToContexts add the context into qc.Spec.Contexts
|
||||||
|
func (qc *QliksenseConfig) AddToContextsRaw(crName, crFile string) {
|
||||||
|
qc.Spec.Contexts = append(qc.Spec.Contexts, []Context{
|
||||||
|
{CrFile: crFile,
|
||||||
|
Name: crName},
|
||||||
|
}...)
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetCurrentContextName set the qc.Spec.CurrentContext
|
||||||
|
func (qc *QliksenseConfig) SetCurrentContextName(name string) {
|
||||||
|
qc.Spec.CurrentContext = name
|
||||||
|
}
|
||||||
|
|
||||||
|
//Write write QliksenseConfig into config.yaml
|
||||||
|
func (qc *QliksenseConfig) Write() error {
|
||||||
|
return WriteToFile(qc, filepath.Join(qc.QliksenseHomePath, "config.yaml"))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -45,12 +47,17 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
profile: docker-desktop
|
profile: docker-desktop
|
||||||
manifestsRoot: /Users/mqb/.qliksense/contexts/contx1/qlik-k8s/v0.0.1/manifests
|
manifestsRoot: /Users/mqb/.qliksense/contexts/contx1/qlik-k8s/v0.0.1/manifests
|
||||||
namespace: myqliksense
|
|
||||||
storageClassName: efs
|
storageClassName: efs
|
||||||
configs:
|
configs:
|
||||||
qliksense:
|
qliksense:
|
||||||
- name: acceptEULA
|
- name: acceptEULA
|
||||||
value: "yes"
|
value: "yes"
|
||||||
|
secrets:
|
||||||
|
qliksense:
|
||||||
|
- name: mongoDbUri
|
||||||
|
# this is rsa encrypted value, the pub and pri keys are in setuPublicAndPrivateKey() method
|
||||||
|
# actual value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
|
||||||
|
value: n/pDi7Z/A3i16cAHFFwMp19/egNKc8WZxm6MKHLT/B1DMv3U6pDXWyXT5fYYDV1wDTO3Vk43yECST1UgZYmMpgUOwgSfGgqTVi2VqS0JQsnwI+Twwhnvha8RJANX8b/XIoSFVWaOgy7+RP35ZkvOqHdCfC2aT8JMIHgBQqqCbsNgimCuRSxi0klR000ic/Tp5PYSz5mD+WLrkPw2FbS0OVBsQ/hIp5GZrmVpvEOZdbT63Sz+n/G4Br6GTv2LkZcU7JBuKQm2wfB+mRjJmJnNrPawLfn2UZ89Rz0BLwIy+6b24/RoIUgoNowfGkJreGiwItGK8fjCcx11oavK/yAo6pYZXCcru46pmHbxxle1OlkdTKkG6EVtJuKjSZXtVmBHZYRFzsR7HnAiXnL7QzSEcS7ieZlQvTmNLfpidJhK199oSbyKREqXGl2S8DzPKM9RLccVbQTy6X8qWimP3MYCnO4K0KoQnNQAgfuV8ZxnvdDecByLDPIpmFMGy0Xm9pUZWxmSoDBq+p5WBI2HdCX2gCYVv5yxS2iBqO5SMKo8iOglHtPI9NIMvloERdN1vZtxSRkY5uDEfrU9ysYwfayEXxvXmdWv0HxlotcgUinP02j7k+OfIapTmY/jGfvF4euyCGRKuJ9JlSD9pIiRdAcekjL6hCxXLJLdajCV4sL/YDo=
|
||||||
`
|
`
|
||||||
ctx1Dir := filepath.Join(homeDir, "contexts", "contx1")
|
ctx1Dir := filepath.Join(homeDir, "contexts", "contx1")
|
||||||
crFile := filepath.Join(ctx1Dir, "contx1.yaml")
|
crFile := filepath.Join(ctx1Dir, "contx1.yaml")
|
||||||
@@ -67,7 +74,7 @@ func TestGetCR(t *testing.T) {
|
|||||||
// create CR
|
// create CR
|
||||||
createCRFile(dir)
|
createCRFile(dir)
|
||||||
|
|
||||||
crFile := filepath.Join(dir, "contexts", "contx1", "contx1.yaml")
|
crFile := filepath.Join("contexts", "contx1", "contx1.yaml")
|
||||||
qct, e := qc.SetCrLocation("contx1", crFile)
|
qct, e := qc.SetCrLocation("contx1", crFile)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
@@ -83,3 +90,136 @@ func TestGetCR(t *testing.T) {
|
|||||||
}
|
}
|
||||||
td()
|
td()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetDecryptedCr(t *testing.T) {
|
||||||
|
td, dir := setup()
|
||||||
|
qc := NewQConfig(dir)
|
||||||
|
if qc.Spec.CurrentContext != "contx1" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
// create CR
|
||||||
|
createCRFile(dir)
|
||||||
|
|
||||||
|
crFile := filepath.Join("contexts", "contx1", "contx1.yaml")
|
||||||
|
qct, e := qc.SetCrLocation("contx1", crFile)
|
||||||
|
if e != nil {
|
||||||
|
t.Fail()
|
||||||
|
t.Log(e)
|
||||||
|
}
|
||||||
|
qcr, err := qct.GetCurrentCR()
|
||||||
|
if err != nil {
|
||||||
|
t.Fail()
|
||||||
|
t.Log(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
setuPublicAndPrivateKey(dir)
|
||||||
|
newCr, err := qct.GetDecryptedCr(qcr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fail()
|
||||||
|
t.Log(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptedValue := newCr.Spec.GetFromSecrets("qliksense", "mongoDbUri")
|
||||||
|
orignalValue := qcr.Spec.GetFromSecrets("qliksense", "mongoDbUri")
|
||||||
|
if decryptedValue != "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false" {
|
||||||
|
t.Fail()
|
||||||
|
b, _ := K8sToYaml(newCr)
|
||||||
|
t.Log(b)
|
||||||
|
}
|
||||||
|
if decryptedValue == orignalValue {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
td()
|
||||||
|
}
|
||||||
|
func setuPublicAndPrivateKey(homeDir string) ([]byte, []byte, error) {
|
||||||
|
privKeyBytes := []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIJJwIBAAKCAgEAwUCimKCidbF3UxEHPy8K+hvhklRB9JYhj5sJy0if4lTVibkK
|
||||||
|
1MrYCykOnmC40pPU9GLY1b8HxAg9tvyRn0YHUxOra6vVQaVcOVJhTM8D18d+lSr3
|
||||||
|
Lp1yiX+UGT4nzWI9+R1CCbwXrqeQVoZs6QZKynEXMkFI9/wNMOwPOvQFOSTuoEoC
|
||||||
|
O+zyTyUWEkNbUq825ELUQdIsjgmlWUOONudxsAr7ESRXW9QTHVh6uWmr3VRKZHby
|
||||||
|
1JdU3I/wjdlGg5M2dDuXy5nQO9w/nYLjJXiw+zzOetZ/+t7/VOkOpNTeJQhwTM1W
|
||||||
|
F7Y2VLetbi9FHgyzHatrduh07+XEiTbgDf3GIx2bp2p6oh0G3N2zpiLcK/aZj8ro
|
||||||
|
uWWydfFfsU3MZ4FfJDP8I6b9awxjmKYqIr6hiPQCJaLBED8mwK+I5evIbnKv6E6u
|
||||||
|
K+BApWA/R7ElragoFYbqQ1VpvntVMtJt9Dy5ZrI+IQARdXD3bb34oh0IPBhClnvv
|
||||||
|
MUc1cWxDoXEX6oJ4I+LzxE87Zkwnan9qOwengolMVKFwPx1o37qrbmrXID21kKt7
|
||||||
|
FL6xN4HxHLkItr1fKzdyWDFRHgASTAWfx5BIwvPuUW0vZHkvO80VyV2L63whVhPn
|
||||||
|
PASmFkbviomrBttYfpr2aGQqF/qR1Nlxe834MFxk1pS9LMa/WnzvFr0gWakCAwEA
|
||||||
|
AQKCAgARSp9B2N2wejibDiL/3E23I1eDqFZedDB8kPrHXbAwqDaTJCN79spt9TaB
|
||||||
|
pVXkQaYEV/Pe7EDdoX8kKGU/QxzUqiXkdHOYdBtUZbKfFMbbP9ZrsnR7j0r4UpoF
|
||||||
|
yDH3hprU93E5PcNAtW2M0GpeT1nR01yn+n908PCdOAIE3GC7RDq1zOl2QzVLL55R
|
||||||
|
9ATv2Q2oTvJ/ETc7XlGVMx4+e2cIwXLFjeLjLI6pSYlxnarrGuetJZeEviWxto9n
|
||||||
|
odFVZI6yx8JFTXX8ZTCr/1IjwDDVyhMPmrHI2Lsv9cqBpSpbVe32cUkKxhsGaYjz
|
||||||
|
GvesQKamOPhco2ATNxPm0yopFlPsGKMfVl0BK0J6BqFh1BvU/SYJmXfnFuUNO3vV
|
||||||
|
4u2Saa0q1iddxV0rXDwIqUfn+S6rwzK0G7y8bH2yvpB2VwiG3TFPnULep4wsefNq
|
||||||
|
Fj92kqFBjacGpQLEEslUY0CMgeZ2+NuBQSUTscP3wBRsottMR6YXJtINdvfHBx+e
|
||||||
|
EcN71z8D00w3mYqIQ7qb4Ml6HOqknunn58g43L9sACMUMTlEBXa9pUnScNYgWBAz
|
||||||
|
W2q2mH37cIydM2JRZPpA8B4yTHt5ugJmChwyNFM7941arjKrebH+6AzLkofGedOP
|
||||||
|
zg+vZQuPEXWs+3MBBnkWoyJW3Y0fbQdjsuQTtnd+7iyoxoBroQKCAQEA4dIiFlIS
|
||||||
|
MDfRhQQWSiDvaw9aneDEJ3uo63ZRH5tm/IynLgtjYgEm/ZxlBCQgqRKLYELBxhu8
|
||||||
|
SaF0uPK8pmpFJt0mIwSlsdeVhuE2obQeKUCczaqrKeaHS3PdWLjTlwph81BGRkHy
|
||||||
|
qfqtNylyyMxrdEbnR51EtsWgFq6anTUAui1Q09JMuMNZRMOzDs1F4gExgD22rc0V
|
||||||
|
c9YQ+jHJRxBGtNKMpMEqc8cvaxBidbItrN9SMTSWog7uYPBuEuaJ6K9vpgyJMOzJ
|
||||||
|
SYcQEFGqgIqIDCg+ABE4d/4YROMKZ1DV/bJCind9brUHSx6XALsF0nC5c1Q9TnUL
|
||||||
|
qI2khOwts4KYKwKCAQEA2xRC6Az97Vkdzu7BjLJ1FKmx4S2nEEgVS12ds82U+5Xf
|
||||||
|
BHKAJnjqlqmmpzzJG+d77IYktz0+mey1QCNkqlm2fhuKs8LZMnpZRf0l8VcoBsUP
|
||||||
|
/xKz7wfiE7RRFZtLJhPp4hhe43GzX5/JFMWMnC6UykwQbj4t1E/GNM/Suqwvg12M
|
||||||
|
wktAJ6nqLgfhjQSO4xWo+nPzcbX+fNtrPCZVrBhYXihhcwRRNImWUCGJ6J4LMdPY
|
||||||
|
Y9Z59qhOvE9cReH/Xw1av46omyiSyAqlgPyZ/kzA2IJSqYCjiQR/2+RD/g13jpcJ
|
||||||
|
jatXLVZ8MJSL5OTS40G/HHTNNpNHbKKh0GOyxBA3ewKCAQBAn8UXhCcmW2L/YPsL
|
||||||
|
/b7mcX9qPP+FmRLvR23R0MQ5M/tH5wRq8I969n3GIJykJeVzB8eybQ+GNslTgEvS
|
||||||
|
iAkAJTubu+G7MkndTqg2wHf9MDtvdA8Fr646Po8yq7oJuHPtkKR7yLWsRUu6xIbP
|
||||||
|
xgheP0hCq1QVxhqZQyCGKrvpi7xc0gsYuPbcAfFFJCOCmPrUi1SzCkTAYJt9LjA+
|
||||||
|
wP6rErIjGBCRD4iXaBn1OqdtmH9KC5WsDP/VCBlIGWeQCly2NVIxiSHVg+xp7yUP
|
||||||
|
IhXq/L05gbQaSsIhPKQmivCiaJg4The8TdwneDqYf+0bmxzHT203/bD3bImPbJNr
|
||||||
|
ksz/AoIBAEwu4Y1cZzkQUmNRd5D7xecnk6ngfEYXKwCIT3zlMrfCSEl9n77BMaKu
|
||||||
|
4Dsr0iuX9eosQ7xM2eYhAG6LYEg05lc4MKWOToVVMpI6E+W3Dz47bPKgiF3I+f8s
|
||||||
|
Jz5CQIG/TwfGvciOE3hfUkec4ua09BzdEqGjkcBQ9XYMBxXPJr6h2379OBQS7FKR
|
||||||
|
fwfQ2/dv4tElXTTfut2kV8gU9Jnh5Wjo1epvR+XjKpg28YQo4W+0YX1magcyRB8L
|
||||||
|
4eSTUIC3XiVa8Jr0IwbZXPBb5xkdi7o+p4w2JahSHjxTRqmj+T1mnHXdbXVgq9Mg
|
||||||
|
9Pzl7cgFZvX4UBx4XtASRf73jITNtt0CggEADH9K+O7FrIOSQly0sMvsRCMtejp3
|
||||||
|
o+MDh1Q+vEg2kEgNXjS4ZFVljUpM2kg1OdUz7feS4dLXUJiIQ8ZWtZPedcq7wjHd
|
||||||
|
02he5+s06l0jPifN3tX1ADfXGpXg5R2fbkrIzakkPP5/RO/aDxIUo7qhklNsVTXO
|
||||||
|
VlGGfWLdk0ekA4upKm02Q1+YOlbIcAicEYYY8K7IffUwnohzKwL9yfuGi1VKTXpE
|
||||||
|
4fzdegsHI03FSqR7V+LvtBpIupQ7RO4kuBmCEyI4E9FVknchg4te4gO3qwd9y0rJ
|
||||||
|
Gu7HNIOrwOHzviI7J6Nd/l9MmeKqklHSgJvko/f5TmiXuQQ8xDZf84rcjQ==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
`)
|
||||||
|
|
||||||
|
publicKeyBytes := []byte(`-----BEGIN RSA PUBLIC KEY-----
|
||||||
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwUCimKCidbF3UxEHPy8K
|
||||||
|
+hvhklRB9JYhj5sJy0if4lTVibkK1MrYCykOnmC40pPU9GLY1b8HxAg9tvyRn0YH
|
||||||
|
UxOra6vVQaVcOVJhTM8D18d+lSr3Lp1yiX+UGT4nzWI9+R1CCbwXrqeQVoZs6QZK
|
||||||
|
ynEXMkFI9/wNMOwPOvQFOSTuoEoCO+zyTyUWEkNbUq825ELUQdIsjgmlWUOONudx
|
||||||
|
sAr7ESRXW9QTHVh6uWmr3VRKZHby1JdU3I/wjdlGg5M2dDuXy5nQO9w/nYLjJXiw
|
||||||
|
+zzOetZ/+t7/VOkOpNTeJQhwTM1WF7Y2VLetbi9FHgyzHatrduh07+XEiTbgDf3G
|
||||||
|
Ix2bp2p6oh0G3N2zpiLcK/aZj8rouWWydfFfsU3MZ4FfJDP8I6b9awxjmKYqIr6h
|
||||||
|
iPQCJaLBED8mwK+I5evIbnKv6E6uK+BApWA/R7ElragoFYbqQ1VpvntVMtJt9Dy5
|
||||||
|
ZrI+IQARdXD3bb34oh0IPBhClnvvMUc1cWxDoXEX6oJ4I+LzxE87Zkwnan9qOwen
|
||||||
|
golMVKFwPx1o37qrbmrXID21kKt7FL6xN4HxHLkItr1fKzdyWDFRHgASTAWfx5BI
|
||||||
|
wvPuUW0vZHkvO80VyV2L63whVhPnPASmFkbviomrBttYfpr2aGQqF/qR1Nlxe834
|
||||||
|
MFxk1pS9LMa/WnzvFr0gWakCAwEAAQ==
|
||||||
|
-----END RSA PUBLIC KEY-----
|
||||||
|
`)
|
||||||
|
secretKeyPairDir := filepath.Join(homeDir, "secrets", "contexts", "contx1", "secrets")
|
||||||
|
if err := os.MkdirAll(secretKeyPairDir, 0777); err != nil {
|
||||||
|
err = fmt.Errorf("Not able to create directories")
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
os.Setenv("QLIKSENSE_KEY_LOCATION", secretKeyPairDir)
|
||||||
|
|
||||||
|
privKeyFile := filepath.Join(secretKeyPairDir, "qliksensePriv")
|
||||||
|
// construct and write priv key file into secretsDir location
|
||||||
|
err := ioutil.WriteFile(privKeyFile, privKeyBytes, 0777)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error while creating file: %v", err)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
pubKeyFile := filepath.Join(secretKeyPairDir, "qliksensePub")
|
||||||
|
// construct and write pub key file into secretsDir location
|
||||||
|
err = ioutil.WriteFile(pubKeyFile, publicKeyBytes, 0777)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error while creating file: %v", err)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return publicKeyBytes, privKeyBytes, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,51 +1,56 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/qlik-oss/k-apis/pkg/config"
|
"github.com/qlik-oss/k-apis/pkg/config"
|
||||||
"gopkg.in/yaml.v2"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
machine_yaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
QliksenseConfigApiVersion = "config.qlik.com/v1"
|
QliksenseConfigApiVersion = "v1"
|
||||||
QliksenseConfigKind = "QliksenseConfig"
|
QliksenseConfigApiGroup = "config.qlik.com"
|
||||||
QliksenseContextApiVersion = "qlik.com/v1"
|
QliksenseConfigKind = "QliksenseConfig"
|
||||||
QliksenseContextKind = "Qliksense"
|
|
||||||
QliksenseDefaultProfile = "docker-desktop"
|
QliksenseApiVersion = "v1"
|
||||||
DefaultRotateKeys = "yes"
|
QliksenseKind = "Qliksense"
|
||||||
QliksenseMetadataName = "QliksenseConfigMetadata"
|
QliksenseGroup = "qlik.com"
|
||||||
DefaultMongoDbUri = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
|
QliksenseDefaultProfile = "docker-desktop"
|
||||||
DefaultMongoDbUriKey = "mongoDbUri"
|
DefaultRotateKeys = "yes"
|
||||||
|
QliksenseMetadataName = "QliksenseConfigMetadata"
|
||||||
|
DefaultMongoDbUri = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
|
||||||
|
DefaultMongoDbUriKey = "mongoDbUri"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddCommonConfig adds common configs into CRs
|
// AddCommonConfig adds common configs into CRs
|
||||||
func (qliksenseCR *QliksenseCR) AddCommonConfig(contextName string) {
|
func (qliksenseCR *QliksenseCR) AddCommonConfig(contextName string) {
|
||||||
qliksenseCR.ApiVersion = QliksenseContextApiVersion
|
qliksenseCR.SetGroupVersionKind(schema.GroupVersionKind{
|
||||||
qliksenseCR.Kind = QliksenseContextKind
|
Group: QliksenseGroup,
|
||||||
if qliksenseCR.Metadata == nil {
|
Kind: QliksenseKind,
|
||||||
qliksenseCR.Metadata = &Metadata{}
|
Version: QliksenseApiVersion,
|
||||||
|
})
|
||||||
|
qliksenseCR.SetName(contextName)
|
||||||
|
qliksenseCR.Spec = &config.CRSpec{
|
||||||
|
Profile: QliksenseDefaultProfile,
|
||||||
|
RotateKeys: DefaultRotateKeys,
|
||||||
}
|
}
|
||||||
if qliksenseCR.Metadata.Name == "" {
|
|
||||||
qliksenseCR.Metadata.Name = contextName
|
|
||||||
}
|
|
||||||
qliksenseCR.Spec = &config.CRSpec{}
|
|
||||||
qliksenseCR.Spec.Profile = QliksenseDefaultProfile
|
|
||||||
qliksenseCR.Spec.ReleaseName = contextName
|
|
||||||
qliksenseCR.Spec.RotateKeys = DefaultRotateKeys
|
|
||||||
qliksenseCR.Spec.AddToSecrets("qliksense", DefaultMongoDbUriKey, DefaultMongoDbUri, "")
|
qliksenseCR.Spec.AddToSecrets("qliksense", DefaultMongoDbUriKey, DefaultMongoDbUri, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddBaseQliksenseConfigs adds configs into config.yaml
|
// AddBaseQliksenseConfigs adds configs into config.yaml
|
||||||
func (qliksenseConfig *QliksenseConfig) AddBaseQliksenseConfigs(defaultQliksenseContext string) {
|
func (qliksenseConfig *QliksenseConfig) AddBaseQliksenseConfigs(defaultQliksenseContext string) {
|
||||||
qliksenseConfig.ApiVersion = QliksenseConfigApiVersion
|
qliksenseConfig.SetGroupVersionKind(schema.GroupVersionKind{
|
||||||
qliksenseConfig.Kind = QliksenseConfigKind
|
Group: QliksenseConfigApiGroup,
|
||||||
if qliksenseConfig.Metadata == nil {
|
Kind: QliksenseConfigKind,
|
||||||
qliksenseConfig.Metadata = &Metadata{}
|
Version: QliksenseConfigApiVersion,
|
||||||
}
|
})
|
||||||
qliksenseConfig.Metadata.Name = QliksenseMetadataName
|
qliksenseConfig.SetName(QliksenseMetadataName)
|
||||||
if defaultQliksenseContext != "" {
|
if defaultQliksenseContext != "" {
|
||||||
if qliksenseConfig.Spec == nil {
|
if qliksenseConfig.Spec == nil {
|
||||||
qliksenseConfig.Spec = &ContextSpec{}
|
qliksenseConfig.Spec = &ContextSpec{}
|
||||||
@@ -79,13 +84,12 @@ func WriteToFile(content interface{}, targetFile string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
x, err := yaml.Marshal(content)
|
x, err := K8sToYaml(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("An error occurred during marshalling CR: %v", err)
|
err = fmt.Errorf("An error occurred during marshalling CR: %v", err)
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writing content
|
// Writing content
|
||||||
err = ioutil.WriteFile(targetFile, x, 0644)
|
err = ioutil.WriteFile(targetFile, x, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -101,14 +105,22 @@ func ReadFromFile(content interface{}, sourceFile string) error {
|
|||||||
if content == nil || sourceFile == "" {
|
if content == nil || sourceFile == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
contents, err := ioutil.ReadFile(sourceFile)
|
file, e := os.Open(sourceFile)
|
||||||
if err != nil {
|
if e != nil {
|
||||||
err = fmt.Errorf("There was an error reading from file: %s, %v", sourceFile, err)
|
return e
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
if err := yaml.Unmarshal(contents, content); err != nil {
|
return ReadFromStream(content, file)
|
||||||
err = fmt.Errorf("An error occurred during unmarshalling: %v", err)
|
}
|
||||||
return err
|
|
||||||
}
|
// ReadFromStream reads from input stream and creat yaml struct of type content
|
||||||
return nil
|
func ReadFromStream(content interface{}, reader io.Reader) error {
|
||||||
|
contents, err := ioutil.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("There was an error reading from reader: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// reading k8s style object
|
||||||
|
// https://stackoverflow.com/questions/44306554/how-to-deserialize-kubernetes-yaml-file
|
||||||
|
dec := machine_yaml.NewYAMLOrJSONDecoder(bytes.NewReader(contents), 10000)
|
||||||
|
return dec.Decode(content)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/qlik-oss/k-apis/pkg/config"
|
"github.com/qlik-oss/k-apis/pkg/config"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -12,6 +13,26 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestAddCommonConfig(t *testing.T) {
|
func TestAddCommonConfig(t *testing.T) {
|
||||||
|
gvk := schema.GroupVersionKind{
|
||||||
|
Group: QliksenseGroup,
|
||||||
|
Kind: QliksenseKind,
|
||||||
|
Version: QliksenseApiVersion,
|
||||||
|
}
|
||||||
|
q := &QliksenseCR{}
|
||||||
|
q.SetName("myqliksense")
|
||||||
|
q.SetGroupVersionKind(gvk)
|
||||||
|
q.Spec = &config.CRSpec{
|
||||||
|
Profile: QliksenseDefaultProfile,
|
||||||
|
RotateKeys: DefaultRotateKeys,
|
||||||
|
Secrets: map[string]config.NameValues{
|
||||||
|
"qliksense": []config.NameValue{{
|
||||||
|
Name: DefaultMongoDbUriKey,
|
||||||
|
Value: DefaultMongoDbUri,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
type args struct {
|
type args struct {
|
||||||
qliksenseCR *QliksenseCR
|
qliksenseCR *QliksenseCR
|
||||||
contextName string
|
contextName string
|
||||||
@@ -27,27 +48,7 @@ func TestAddCommonConfig(t *testing.T) {
|
|||||||
qliksenseCR: &QliksenseCR{},
|
qliksenseCR: &QliksenseCR{},
|
||||||
contextName: "myqliksense",
|
contextName: "myqliksense",
|
||||||
},
|
},
|
||||||
want: &QliksenseCR{
|
want: q,
|
||||||
CommonConfig: CommonConfig{
|
|
||||||
ApiVersion: QliksenseContextApiVersion,
|
|
||||||
Kind: QliksenseContextKind,
|
|
||||||
Metadata: &Metadata{
|
|
||||||
Name: "myqliksense",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Spec: &config.CRSpec{
|
|
||||||
Profile: QliksenseDefaultProfile,
|
|
||||||
ReleaseName: "myqliksense",
|
|
||||||
RotateKeys: DefaultRotateKeys,
|
|
||||||
Secrets: map[string]config.NameValues{
|
|
||||||
"qliksense": []config.NameValue{{
|
|
||||||
Name: DefaultMongoDbUriKey,
|
|
||||||
Value: DefaultMongoDbUri,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -61,6 +62,18 @@ func TestAddCommonConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAddBaseQliksenseConfigs(t *testing.T) {
|
func TestAddBaseQliksenseConfigs(t *testing.T) {
|
||||||
|
gvk := schema.GroupVersionKind{
|
||||||
|
Group: QliksenseConfigApiGroup,
|
||||||
|
Kind: QliksenseConfigKind,
|
||||||
|
Version: QliksenseConfigApiVersion,
|
||||||
|
}
|
||||||
|
qc := &QliksenseConfig{}
|
||||||
|
qc.SetGroupVersionKind(gvk)
|
||||||
|
qc.SetName(QliksenseMetadataName)
|
||||||
|
qc.Spec = &ContextSpec{
|
||||||
|
CurrentContext: "qlik-default",
|
||||||
|
}
|
||||||
|
|
||||||
type args struct {
|
type args struct {
|
||||||
qliksenseConfig *QliksenseConfig
|
qliksenseConfig *QliksenseConfig
|
||||||
defaultQliksenseContext string
|
defaultQliksenseContext string
|
||||||
@@ -76,18 +89,7 @@ func TestAddBaseQliksenseConfigs(t *testing.T) {
|
|||||||
qliksenseConfig: &QliksenseConfig{},
|
qliksenseConfig: &QliksenseConfig{},
|
||||||
defaultQliksenseContext: "qlik-default",
|
defaultQliksenseContext: "qlik-default",
|
||||||
},
|
},
|
||||||
want: &QliksenseConfig{
|
want: qc,
|
||||||
CommonConfig: CommonConfig{
|
|
||||||
ApiVersion: QliksenseConfigApiVersion,
|
|
||||||
Kind: QliksenseConfigKind,
|
|
||||||
Metadata: &Metadata{
|
|
||||||
Name: QliksenseMetadataName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Spec: &ContextSpec{
|
|
||||||
CurrentContext: "qlik-default",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ func (kdcjt *k8sDockerConfigJsonType) GenerateAuth() {
|
|||||||
|
|
||||||
type DockerConfigJsonSecret struct {
|
type DockerConfigJsonSecret struct {
|
||||||
Name string
|
Name string
|
||||||
Namespace string
|
|
||||||
Uri string
|
Uri string
|
||||||
Username string
|
Username string
|
||||||
Password string
|
Password string
|
||||||
@@ -51,9 +50,13 @@ func (d *DockerConfigJsonSecret) ToYaml(encryptionKey *rsa.PublicKey) ([]byte, e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
k8sDockerConfigJsonMapEncryptedBytes, err := Encrypt(k8sDockerConfigJsonMapBytes, encryptionKey)
|
var k8sDockerConfigJsonMapMaybeEncryptedBytes []byte
|
||||||
if err != nil {
|
if encryptionKey != nil {
|
||||||
return nil, err
|
if k8sDockerConfigJsonMapMaybeEncryptedBytes, err = Encrypt(k8sDockerConfigJsonMapBytes, encryptionKey); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
k8sDockerConfigJsonMapMaybeEncryptedBytes = k8sDockerConfigJsonMapBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
k8sSecret := v1.Secret{
|
k8sSecret := v1.Secret{
|
||||||
@@ -63,11 +66,10 @@ func (d *DockerConfigJsonSecret) ToYaml(encryptionKey *rsa.PublicKey) ([]byte, e
|
|||||||
},
|
},
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: d.Name,
|
Name: d.Name,
|
||||||
Namespace: d.Namespace,
|
|
||||||
},
|
},
|
||||||
Type: v1.SecretTypeDockerConfigJson,
|
Type: v1.SecretTypeDockerConfigJson,
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
".dockerconfigjson": k8sDockerConfigJsonMapEncryptedBytes,
|
".dockerconfigjson": k8sDockerConfigJsonMapMaybeEncryptedBytes,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +92,6 @@ func (d *DockerConfigJsonSecret) FromYaml(secretBytes []byte, decryptionKey *rsa
|
|||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
d.Name = k8sSecret.ObjectMeta.Name
|
d.Name = k8sSecret.ObjectMeta.Name
|
||||||
d.Namespace = k8sSecret.ObjectMeta.Namespace
|
|
||||||
for registry, k8sDockerConfigJson := range k8sDockerConfigJsonMap.Auths {
|
for registry, k8sDockerConfigJson := range k8sDockerConfigJsonMap.Auths {
|
||||||
d.Uri = registry
|
d.Uri = registry
|
||||||
d.Username = k8sDockerConfigJson.Username
|
d.Username = k8sDockerConfigJson.Username
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
func TestDockerConfigJsonSecret(t *testing.T) {
|
func TestDockerConfigJsonSecret(t *testing.T) {
|
||||||
dockerConfigJsonSecret := DockerConfigJsonSecret{
|
dockerConfigJsonSecret := DockerConfigJsonSecret{
|
||||||
Name: "some-name",
|
Name: "some-name",
|
||||||
Namespace: "some-namespace",
|
|
||||||
Uri: "some-uri",
|
Uri: "some-uri",
|
||||||
Username: "some-username",
|
Username: "some-username",
|
||||||
Password: "some-password",
|
Password: "some-password",
|
||||||
@@ -38,7 +37,6 @@ func TestDockerConfigJsonSecret(t *testing.T) {
|
|||||||
} else if validYamlMap["apiVersion"] != "v1" ||
|
} else if validYamlMap["apiVersion"] != "v1" ||
|
||||||
validYamlMap["kind"] != "Secret" ||
|
validYamlMap["kind"] != "Secret" ||
|
||||||
validYamlMap["metadata"].(map[string]interface{})["name"] != dockerConfigJsonSecret.Name ||
|
validYamlMap["metadata"].(map[string]interface{})["name"] != dockerConfigJsonSecret.Name ||
|
||||||
validYamlMap["metadata"].(map[string]interface{})["namespace"] != dockerConfigJsonSecret.Namespace ||
|
|
||||||
validYamlMap["type"] != "kubernetes.io/dockerconfigjson" {
|
validYamlMap["type"] != "kubernetes.io/dockerconfigjson" {
|
||||||
t.Fatalf("error verifying validity of secret yaml: %v", string(dockerConfigJsonSecretYamlBytes))
|
t.Fatalf("error verifying validity of secret yaml: %v", string(dockerConfigJsonSecretYamlBytes))
|
||||||
} else if dockerConfigJsonBytesBase64, ok := validYamlMap["data"].(map[string]interface{})[".dockerconfigjson"]; !ok {
|
} else if dockerConfigJsonBytesBase64, ok := validYamlMap["data"].(map[string]interface{})[".dockerconfigjson"]; !ok {
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ func writeContentToFile(keyData []byte, fileName string) error {
|
|||||||
|
|
||||||
// Encrypt encrypts data with public key
|
// Encrypt encrypts data with public key
|
||||||
func Encrypt(pt []byte, pub *rsa.PublicKey) ([]byte, error) {
|
func Encrypt(pt []byte, pub *rsa.PublicKey) ([]byte, error) {
|
||||||
// hash := sha512.New()
|
//hash := sha512.New()
|
||||||
// ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, pub, msg, nil)
|
//ct, err := rsa.EncryptOAEP(hash, rand.Reader, pub, pt, nil)
|
||||||
ct, err := rsa.EncryptPKCS1v15(rand.Reader, pub, pt)
|
ct, err := rsa.EncryptPKCS1v15(rand.Reader, pub, pt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ MFxk1pS9LMa/WnzvFr0gWakCAwEAAQ==
|
|||||||
}
|
}
|
||||||
|
|
||||||
encDataStr := base64.StdEncoding.EncodeToString(encData)
|
encDataStr := base64.StdEncoding.EncodeToString(encData)
|
||||||
log.Printf("encrypted data: %s\n", encDataStr)
|
log.Println("Encoded text:", encDataStr)
|
||||||
dec, _ := base64.StdEncoding.DecodeString(encDataStr)
|
dec, _ := base64.StdEncoding.DecodeString(encDataStr)
|
||||||
data, err := Decrypt(dec, privKey)
|
data, err := Decrypt(dec, privKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -125,5 +125,4 @@ MFxk1pS9LMa/WnzvFr0gWakCAwEAAQ==
|
|||||||
t.Error("original string and decrypted string don't match")
|
t.Error("original string and decrypted string don't match")
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
log.Printf("decrypted data: %s\n", data)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func K8sSecretToYaml(k8sSecret v1.Secret) ([]byte, error) {
|
func K8sSecretToYaml(k8sSecret v1.Secret) ([]byte, error) {
|
||||||
k8sSecretYamlMap := map[string]interface{}{}
|
return K8sToYaml(k8sSecret)
|
||||||
if k8sSecretYamlBytes, err := yaml.Marshal(k8sSecret); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if err := yaml.Unmarshal(k8sSecretYamlBytes, &k8sSecretYamlMap); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
delete(k8sSecretYamlMap["metadata"].(map[string]interface{}), "creationTimestamp")
|
|
||||||
return yaml.Marshal(k8sSecretYamlMap)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func K8sSecretFromYaml(k8sSecretBytes []byte) (v1.Secret, error) {
|
func K8sSecretFromYaml(k8sSecretBytes []byte) (v1.Secret, error) {
|
||||||
@@ -24,3 +16,15 @@ func K8sSecretFromYaml(k8sSecretBytes []byte) (v1.Secret, error) {
|
|||||||
}
|
}
|
||||||
return k8sSecret, nil
|
return k8sSecret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func K8sToYaml(k8sObj interface{}) ([]byte, error) {
|
||||||
|
k8sSecretYamlMap := map[string]interface{}{}
|
||||||
|
if k8sSecretYamlBytes, err := yaml.Marshal(k8sObj); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if err := yaml.Unmarshal(k8sSecretYamlBytes, &k8sSecretYamlMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
delete(k8sSecretYamlMap["metadata"].(map[string]interface{}), "creationTimestamp")
|
||||||
|
return yaml.Marshal(k8sSecretYamlMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,20 +1,65 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// KubectlApply create resoruces in the provided namespace,
|
||||||
|
// if namespace="" then use whatever the kubectl default is
|
||||||
func KubectlApply(manifests, namespace string) error {
|
func KubectlApply(manifests, namespace string) error {
|
||||||
return kubectlOperation(manifests, "apply", namespace)
|
return kubectlOperation(manifests, "apply", namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KubectlDelete delete resoruces in the provided namespace,
|
||||||
|
// if namespace="" then use whatever the kubectl default is
|
||||||
func KubectlDelete(manifests, namespace string) error {
|
func KubectlDelete(manifests, namespace string) error {
|
||||||
return kubectlOperation(manifests, "delete", namespace)
|
return kubectlOperation(manifests, "delete", namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetKubectlNamespace() string {
|
||||||
|
namespace := ""
|
||||||
|
cmd := exec.Command("kubectl", "config", "current-context")
|
||||||
|
var out, out2 bytes.Buffer
|
||||||
|
|
||||||
|
cmd.Stdout = &out
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("kubectl config current-context %q\n", err)
|
||||||
|
return namespace
|
||||||
|
}
|
||||||
|
if out.String() == "" {
|
||||||
|
fmt.Println("kubectl config current-context does not return anything")
|
||||||
|
return namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = exec.Command("kubectl", "config", "view", "-o", `jsonpath={.contexts[?(@.name == "`+strings.TrimSpace(out.String())+`")].context.namespace}`)
|
||||||
|
cmd.Stdout = &out2
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("kubectl config view failed with %q\n", err)
|
||||||
|
return namespace
|
||||||
|
}
|
||||||
|
namespace = out2.String()
|
||||||
|
return namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetKubectlNamespace(ns string) {
|
||||||
|
cmd := exec.Command("kubectl", "config", "set-context", "--namespace="+ns, "--current")
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("kubectl config set-context --namespace failed with %q\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func kubectlOperation(manifests string, oprName string, namespace string) error {
|
func kubectlOperation(manifests string, oprName string, namespace string) error {
|
||||||
tempYaml, err := ioutil.TempFile("", "")
|
tempYaml, err := ioutil.TempFile("", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -42,14 +87,31 @@ func kubectlOperation(manifests string, oprName string, namespace string) error
|
|||||||
cmd = exec.Command("kubectl", arguments...)
|
cmd = exec.Command("kubectl", arguments...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sterrBuffer := &bytes.Buffer{}
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = sterrBuffer
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("kubectl apply failed with %s\n", err)
|
return fmt.Errorf("kubectl %v failed with: %v, %v, temp k8s yaml file:%v\n", oprName, err, sterrBuffer.String(), tempYaml.Name())
|
||||||
fmt.Println("temp CRD file: " + tempYaml.Name())
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
os.Remove(tempYaml.Name())
|
os.Remove(tempYaml.Name())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func KubectlDirectOps(opr []string, namespace string) error {
|
||||||
|
arguments := []string{}
|
||||||
|
if namespace != "" {
|
||||||
|
arguments = append(arguments, "-n", namespace)
|
||||||
|
}
|
||||||
|
arguments = append(arguments, opr...)
|
||||||
|
|
||||||
|
cmd := exec.Command("kubectl", arguments...)
|
||||||
|
LogDebugMessage("Kubectl command: %s %v\n", "kubectl", arguments)
|
||||||
|
sterrBuffer := &bytes.Buffer{}
|
||||||
|
cmd.Stderr = sterrBuffer
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("kubectl %v failed with: %v, %v\n", opr, err, sterrBuffer.String())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
32
pkg/api/kubectl_test.go
Normal file
32
pkg/api/kubectl_test.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetKubectlNamespace(t *testing.T) {
|
||||||
|
t.Skip()
|
||||||
|
ns := GetKubectlNamespace()
|
||||||
|
SetKubectlNamespace("tada")
|
||||||
|
got := GetKubectlNamespace()
|
||||||
|
if got != "tada" {
|
||||||
|
t.Log(got)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
SetKubectlNamespace(ns)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKubectlDirectOps(t *testing.T) {
|
||||||
|
t.Skip()
|
||||||
|
SetKubectlNamespace("test")
|
||||||
|
ns := GetKubectlNamespace()
|
||||||
|
opr := fmt.Sprintf("version")
|
||||||
|
opr1 := strings.Fields(opr)
|
||||||
|
err := KubectlDirectOps(opr1, ns)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,26 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import "github.com/qlik-oss/k-apis/pkg/config"
|
import (
|
||||||
|
kapi_config "github.com/qlik-oss/k-apis/pkg/config"
|
||||||
// CommonConfig is exported
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
type CommonConfig struct {
|
)
|
||||||
ApiVersion string `json:"apiVersion" yaml:"apiVersion"`
|
|
||||||
Kind string `json:"kind" yaml:"kind"`
|
|
||||||
Metadata *Metadata `json:"metadata" yaml:"metadata"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// QliksenseConfig is exported
|
// QliksenseConfig is exported
|
||||||
type QliksenseConfig struct {
|
type QliksenseConfig struct {
|
||||||
CommonConfig `json:",inline" yaml:",inline"`
|
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
|
||||||
Spec *ContextSpec `json:"spec" yaml:"spec"`
|
Spec *ContextSpec `json:"spec" yaml:"spec"`
|
||||||
QliksenseHomePath string `json:"-" yaml:"-"`
|
QliksenseHomePath string `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*type CommonConfig struct {
|
||||||
|
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
|
||||||
|
}
|
||||||
|
*/
|
||||||
// QliksenseCR is exported
|
// QliksenseCR is exported
|
||||||
type QliksenseCR struct {
|
type QliksenseCR struct {
|
||||||
CommonConfig `json:",inline" yaml:",inline"`
|
kapi_config.KApiCr `json:",inline" yaml:",inline"`
|
||||||
Spec *config.CRSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContextSpec is exported
|
// ContextSpec is exported
|
||||||
|
|||||||
162
pkg/api/utils.go
162
pkg/api/utils.go
@@ -1,13 +1,21 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"archive/zip"
|
||||||
|
"compress/gzip"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkExists(filename string) os.FileInfo {
|
func checkExists(filename string) os.FileInfo {
|
||||||
@@ -59,24 +67,22 @@ func ProcessConfigArgs(args []string) ([]*ServiceKeyValue, error) {
|
|||||||
// split args[0] into key and value
|
// split args[0] into key and value
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
err := fmt.Errorf("No args were provided. Please provide args to configure the current context")
|
err := fmt.Errorf("No args were provided. Please provide args to configure the current context")
|
||||||
log.Println(err)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
resultSvcKV := make([]*ServiceKeyValue, len(args))
|
resultSvcKV := make([]*ServiceKeyValue, len(args))
|
||||||
re1 := regexp.MustCompile(`(\w{1,}).(\w{1,})=("*[\w\-_/:0-9]+"*)`)
|
re1 := regexp.MustCompile(`(\w{1,}).(\w{1,})=("*[\w\-?=_/:0-9]+"*)`)
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
LogDebugMessage("Arg received: %s", arg)
|
LogDebugMessage("Arg received: %s", arg)
|
||||||
result := re1.FindStringSubmatch(arg)
|
result := re1.FindStringSubmatch(arg)
|
||||||
// check if result array's length is == 4 (index 0 - is the full match & indices 1,2,3- are the fields we need)
|
// check if result array's length is == 4 (index 0 - is the full match & indices 1,2,3- are the fields we need)
|
||||||
if len(result) != 4 {
|
if len(result) != 4 {
|
||||||
err := fmt.Errorf("Please provide valid args for this command")
|
err := fmt.Errorf("Please provide valid args for this command")
|
||||||
log.Println(err)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
resultSvcKV[i] = &ServiceKeyValue{
|
resultSvcKV[i] = &ServiceKeyValue{
|
||||||
SvcName: result[1],
|
SvcName: result[1],
|
||||||
Key: result[2],
|
Key: result[2],
|
||||||
Value: result[3],
|
Value: strings.ReplaceAll(result[3], `"`, ""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resultSvcKV, nil
|
return resultSvcKV, nil
|
||||||
@@ -111,3 +117,151 @@ func ExecuteTaskWithBlinkingStdoutFeedback(task func() (interface{}, error), fee
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DownloadFile(url, baseFolder, installerName string) error {
|
||||||
|
var (
|
||||||
|
out *os.File
|
||||||
|
err error
|
||||||
|
resp *http.Response
|
||||||
|
)
|
||||||
|
// Create the file
|
||||||
|
fileName := filepath.Join(baseFolder, installerName)
|
||||||
|
LogDebugMessage("Installer Filename: %s\n", fileName)
|
||||||
|
if out, err = os.Create(fileName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
// Get the data
|
||||||
|
if resp, err = http.Get(url); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
err = fmt.Errorf("unable to download the file from URL: %s, status: %s", url, resp.Status)
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the body to file
|
||||||
|
if _, err = io.Copy(out, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.Chmod(fileName, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExplodePackage(destination, fileToUntar string) error {
|
||||||
|
LogDebugMessage("Destination: %s\n", destination)
|
||||||
|
LogDebugMessage("fileToUntar: %s\n", fileToUntar)
|
||||||
|
|
||||||
|
if strings.HasSuffix(fileToUntar, "zip") {
|
||||||
|
LogDebugMessage("This is a windows file : %s", fileToUntar)
|
||||||
|
err := UnZipFile(destination, fileToUntar)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else if strings.HasSuffix(fileToUntar, "tar.gz") {
|
||||||
|
LogDebugMessage("This is a mac/linux file: %s", fileToUntar)
|
||||||
|
err := UntarGzFile(destination, fileToUntar)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UntarGzFile(destination, fileToUntar string) error {
|
||||||
|
lFile, err := os.Open(fileToUntar)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrapf(err, "unable to read the local file %s", fileToUntar)
|
||||||
|
log.Fatal(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gzReader, err := gzip.NewReader(lFile)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "unable to load the file into a gz reader")
|
||||||
|
log.Fatal(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer gzReader.Close()
|
||||||
|
|
||||||
|
tarReader := tar.NewReader(gzReader)
|
||||||
|
for {
|
||||||
|
header, err := tarReader.Next()
|
||||||
|
switch {
|
||||||
|
case err == io.EOF:
|
||||||
|
return nil
|
||||||
|
case err != nil:
|
||||||
|
err = errors.Wrap(err, "error during untar")
|
||||||
|
log.Fatal(err)
|
||||||
|
return err
|
||||||
|
case header == nil:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInLoop := filepath.Join(destination, header.Name)
|
||||||
|
switch header.Typeflag {
|
||||||
|
case tar.TypeDir:
|
||||||
|
if _, err := os.Stat(fileInLoop); err != nil {
|
||||||
|
if err := os.MkdirAll(fileInLoop, 0755); err != nil {
|
||||||
|
err = errors.Wrapf(err, "error creating directory %s", fileInLoop)
|
||||||
|
log.Fatal(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case tar.TypeReg:
|
||||||
|
fileAtLoc, err := os.OpenFile(fileInLoop, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrapf(err, "error opening file %s", fileInLoop)
|
||||||
|
log.Fatal(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.Copy(fileAtLoc, tarReader); err != nil {
|
||||||
|
err = errors.Wrapf(err, "error writing file %s", fileInLoop)
|
||||||
|
log.Fatal(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fileAtLoc.Close()
|
||||||
|
fileAtLoc.Chmod(os.ModePerm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnZipFile(destination, fileToUnzip string) error {
|
||||||
|
zipReader, _ := zip.OpenReader(fileToUnzip)
|
||||||
|
for _, file := range zipReader.Reader.File {
|
||||||
|
|
||||||
|
zippedFile, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer zippedFile.Close()
|
||||||
|
extractedFilePath := filepath.Join(
|
||||||
|
destination,
|
||||||
|
file.Name,
|
||||||
|
)
|
||||||
|
outputFile, err := os.OpenFile(
|
||||||
|
extractedFilePath,
|
||||||
|
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
|
||||||
|
file.Mode(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer outputFile.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(outputFile, zippedFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
LogDebugMessage("File extracted: %s, Extracted file path: %s\n", file.Name, extractedFilePath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
16
pkg/preflight/all_checks.go
Normal file
16
pkg/preflight/all_checks.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package preflight
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (qp *QliksensePreflight) RunAllPreflightChecks() error {
|
||||||
|
//run all preflight checks
|
||||||
|
fmt.Println("Running all preflight checks")
|
||||||
|
fmt.Printf("\nRunning DNS check...\n")
|
||||||
|
qp.CheckDns()
|
||||||
|
fmt.Printf("\nRunning minimum kubernetes version check...\n")
|
||||||
|
qp.CheckK8sVersion()
|
||||||
|
fmt.Println("Completed running all preflight checks")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
128
pkg/preflight/dns_check.go
Normal file
128
pkg/preflight/dns_check.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package preflight
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
const dnsCheckYAML = `
|
||||||
|
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||||
|
kind: Preflight
|
||||||
|
metadata:
|
||||||
|
name: cluster-preflight-checks
|
||||||
|
namespace: {{ . }}
|
||||||
|
spec:
|
||||||
|
collectors:
|
||||||
|
- run:
|
||||||
|
collectorName: spin-up-pod
|
||||||
|
args: ["-z", "-v", "-w 1", "qnginx001", "80"]
|
||||||
|
command: ["nc"]
|
||||||
|
image: subfuzion/netcat:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
name: spin-up-pod-check-dns
|
||||||
|
namespace: {{ . }}
|
||||||
|
timeout: 30s
|
||||||
|
|
||||||
|
analyzers:
|
||||||
|
- textAnalyze:
|
||||||
|
checkName: DNS check
|
||||||
|
collectorName: spin-up-pod-check-dns
|
||||||
|
fileName: spin-up-pod.txt
|
||||||
|
regex: succeeded
|
||||||
|
outcomes:
|
||||||
|
- fail:
|
||||||
|
message: DNS check failed
|
||||||
|
- pass:
|
||||||
|
message: DNS check passed
|
||||||
|
`
|
||||||
|
|
||||||
|
func (qp *QliksensePreflight) CheckDns() error {
|
||||||
|
// retrieve namespace
|
||||||
|
namespace := api.GetKubectlNamespace()
|
||||||
|
|
||||||
|
api.LogDebugMessage("Namespace: %s\n", namespace)
|
||||||
|
|
||||||
|
tmpl, err := template.New("dnsCheckYAML").Parse(dnsCheckYAML)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("cannot parse template: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tempYaml, err := ioutil.TempFile("", "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("cannot create file: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
api.LogDebugMessage("Temp Yaml file: %s\n", tempYaml.Name())
|
||||||
|
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
err = tmpl.Execute(&b, namespace)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tempYaml.WriteString(b.String())
|
||||||
|
|
||||||
|
// creating Kubectl resources
|
||||||
|
appName := "qnginx001"
|
||||||
|
const PreflightChecksDirName = "preflight_checks"
|
||||||
|
|
||||||
|
fmt.Println("Creating resources to run preflight checks")
|
||||||
|
|
||||||
|
// kubectl create deployment
|
||||||
|
opr := fmt.Sprintf("create deployment %s --image=nginx", appName)
|
||||||
|
err = initiateK8sOps(opr, namespace)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// Deleting deployment..
|
||||||
|
opr = fmt.Sprintf("delete deployment %s", appName)
|
||||||
|
// we want to delete the k8s resource here, we dont care a lot about an error here
|
||||||
|
_ = initiateK8sOps(opr, namespace)
|
||||||
|
api.LogDebugMessage("delete deployment executed")
|
||||||
|
}()
|
||||||
|
|
||||||
|
// create service
|
||||||
|
opr = fmt.Sprintf("create service clusterip %s --tcp=80:80", appName)
|
||||||
|
err = initiateK8sOps(opr, namespace)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// delete service
|
||||||
|
opr = fmt.Sprintf("delete service %s", appName)
|
||||||
|
// we want to delete the k8s resource here, we dont care a lot about an error here
|
||||||
|
_ = initiateK8sOps(opr, namespace)
|
||||||
|
api.LogDebugMessage("delete service executed")
|
||||||
|
}()
|
||||||
|
|
||||||
|
//kubectl -n $namespace wait --for=condition=ready pod -l app=$appName --timeout=120s
|
||||||
|
opr = fmt.Sprintf("wait --for=condition=ready pod -l app=%s --timeout=120s", appName)
|
||||||
|
err = initiateK8sOps(opr, namespace)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
api.LogDebugMessage("kubectl wait executed")
|
||||||
|
|
||||||
|
// call preflight
|
||||||
|
preflightCommand := filepath.Join(qp.Q.QliksenseHome, PreflightChecksDirName, preflightFileName)
|
||||||
|
|
||||||
|
err = invokePreflight(preflightCommand, tempYaml)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("DNS check completed, cleaning up resources now")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
187
pkg/preflight/preflight_utils.go
Normal file
187
pkg/preflight/preflight_utils.go
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
package preflight
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||||
|
|
||||||
|
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type QliksensePreflight struct {
|
||||||
|
Q *qliksense.Qliksense
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// preflight releases have the same version
|
||||||
|
preflightRelease = "v0.9.26"
|
||||||
|
preflightLinuxFile = "preflight_linux_amd64.tar.gz"
|
||||||
|
preflightMacFile = "preflight_darwin_amd64.tar.gz"
|
||||||
|
preflightWindowsFile = "preflight_windows_amd64.zip"
|
||||||
|
PreflightChecksDirName = "preflight_checks"
|
||||||
|
preflightFileName = "preflight"
|
||||||
|
)
|
||||||
|
|
||||||
|
var preflightBaseURL = fmt.Sprintf("https://github.com/replicatedhq/troubleshoot/releases/download/%s/", preflightRelease)
|
||||||
|
|
||||||
|
func (qp *QliksensePreflight) DownloadPreflight() error {
|
||||||
|
const preflightExecutable = "preflight"
|
||||||
|
|
||||||
|
preflightInstallDir := filepath.Join(qp.Q.QliksenseHome, PreflightChecksDirName)
|
||||||
|
platform := runtime.GOOS
|
||||||
|
|
||||||
|
exists, err := checkInstalled(preflightInstallDir, preflightExecutable)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("There has been an error when trying to determine if preflight installer exists")
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
// preflight exist, no need to download again.
|
||||||
|
api.LogDebugMessage("Preflight already exists, proceeding to perform checks")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the Preflight-check directory, download and install preflight
|
||||||
|
if !api.DirExists(preflightInstallDir) {
|
||||||
|
api.LogDebugMessage("%s does not exist, creating now\n", preflightInstallDir)
|
||||||
|
if err := os.Mkdir(preflightInstallDir, os.ModePerm); err != nil {
|
||||||
|
err = fmt.Errorf("Not able to create %s dir: %v", preflightInstallDir, err)
|
||||||
|
log.Println(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
api.LogDebugMessage("Preflight-checks install Dir: %s exists", preflightInstallDir)
|
||||||
|
|
||||||
|
preflightUrl, preflightFile, err := determinePlatformSpecificUrls(platform)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("There was an error when trying to determine platform specific paths")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download Preflight
|
||||||
|
err = downloadAndExplode(preflightUrl, preflightInstallDir, preflightFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("Downloaded Preflight")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkInstalled(preflightInstallDir, preflightExecutable string) (bool, error) {
|
||||||
|
installerExists := true
|
||||||
|
preflightInstaller := filepath.Join(preflightInstallDir, preflightExecutable)
|
||||||
|
if api.DirExists(preflightInstallDir) {
|
||||||
|
if !api.FileExists(preflightInstaller) {
|
||||||
|
installerExists = false
|
||||||
|
api.LogDebugMessage("Preflight install directory exists, but preflight installer does not exist")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
installerExists = false
|
||||||
|
}
|
||||||
|
return installerExists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadAndExplode(url, installDir, file string) error {
|
||||||
|
err := api.DownloadFile(url, installDir, file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
api.LogDebugMessage("Downloaded File: %s", file)
|
||||||
|
|
||||||
|
fileToUntar := filepath.Join(installDir, file)
|
||||||
|
api.LogDebugMessage("File to explode: %s", file)
|
||||||
|
|
||||||
|
err = api.ExplodePackage(installDir, fileToUntar)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func determinePlatformSpecificUrls(platform string) (string, string, error) {
|
||||||
|
|
||||||
|
var preflightUrl, preflightFile string
|
||||||
|
|
||||||
|
if runtime.GOARCH != `amd64` {
|
||||||
|
err := fmt.Errorf("%s architecture is not supported", runtime.GOARCH)
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch platform {
|
||||||
|
case "windows":
|
||||||
|
preflightFile = preflightWindowsFile
|
||||||
|
case "darwin":
|
||||||
|
preflightFile = preflightMacFile
|
||||||
|
case "linux":
|
||||||
|
preflightFile = preflightLinuxFile
|
||||||
|
default:
|
||||||
|
err := fmt.Errorf("Unable to download the preflight executable for the underlying platform\n")
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
preflightUrl = fmt.Sprintf("%s%s", preflightBaseURL, preflightFile)
|
||||||
|
|
||||||
|
return preflightUrl, preflightFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initiateK8sOps(opr, namespace string) error {
|
||||||
|
opr1 := strings.Fields(opr)
|
||||||
|
err := api.KubectlDirectOps(opr1, namespace)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokePreflight(preflightCommand string, yamlFile *os.File) error {
|
||||||
|
arguments := []string{}
|
||||||
|
arguments = append(arguments, yamlFile.Name(), "--interactive=false")
|
||||||
|
cmd := exec.Command(preflightCommand, arguments...)
|
||||||
|
|
||||||
|
sterrBuffer := &bytes.Buffer{}
|
||||||
|
cmd.Stdout = sterrBuffer
|
||||||
|
cmd.Stderr = sterrBuffer
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("Error when running preflight command: %v\n", err)
|
||||||
|
}
|
||||||
|
ind := strings.Index(sterrBuffer.String(), "---")
|
||||||
|
output := sterrBuffer.String()
|
||||||
|
if ind > -1 {
|
||||||
|
output = fmt.Sprintf("%s\n%s", output[:ind], output[ind:])
|
||||||
|
}
|
||||||
|
fmt.Printf("%v\n", output)
|
||||||
|
|
||||||
|
// Maybe good to retain this part in case we need to process the output in future.
|
||||||
|
// We are going to look for the first occurance of PASS or FAIL from the end
|
||||||
|
// there are also some space-like deceiving characters which are being hard to get by
|
||||||
|
|
||||||
|
//outputArr := strings.Fields(strings.TrimSpace(output))
|
||||||
|
//trackSuccess := false
|
||||||
|
//trackPrg := false
|
||||||
|
|
||||||
|
//for i := len(outputArr) - 1; i >= 0; i-- {
|
||||||
|
// if strings.TrimSpace(outputArr[i]) != "" {
|
||||||
|
// if outputArr[i] == "PASS" {
|
||||||
|
// trackSuccess = true
|
||||||
|
// trackPrg = true
|
||||||
|
// } else if outputArr[i] == "FAIL" {
|
||||||
|
// trackPrg = true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if trackPrg {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
90
pkg/preflight/preflight_utils_test.go
Normal file
90
pkg/preflight/preflight_utils_test.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
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: "ash-ns",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid case",
|
||||||
|
args: args{
|
||||||
|
opr: fmt.Sprintf("versions"),
|
||||||
|
namespace: "ash-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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_determinePlatformSpecificUrls(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
platform string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
want1 string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid platform",
|
||||||
|
args: args{
|
||||||
|
platform: "windows",
|
||||||
|
},
|
||||||
|
want: fmt.Sprintf("%s%s", preflightBaseURL, preflightWindowsFile),
|
||||||
|
want1: preflightWindowsFile,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid platform",
|
||||||
|
args: args{
|
||||||
|
platform: "unix",
|
||||||
|
},
|
||||||
|
want: "",
|
||||||
|
want1: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, got1, err := determinePlatformSpecificUrls(tt.args.platform)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("determinePlatformSpecificUrls() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("determinePlatformSpecificUrls() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
if got1 != tt.want1 {
|
||||||
|
t.Errorf("determinePlatformSpecificUrls() got1 = %v, want %v", got1, tt.want1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
84
pkg/preflight/version_check.go
Normal file
84
pkg/preflight/version_check.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package preflight
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
const minK8sVersion = "1.11.0"
|
||||||
|
const checkVersionYAML = `
|
||||||
|
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||||
|
kind: Preflight
|
||||||
|
metadata:
|
||||||
|
name: cluster-preflight-checks
|
||||||
|
namespace: {{ .namespace }}
|
||||||
|
spec:
|
||||||
|
analyzers:
|
||||||
|
- clusterVersion:
|
||||||
|
outcomes:
|
||||||
|
- fail:
|
||||||
|
when: "< {{ .minK8sVersion }}"
|
||||||
|
message: The application requires at least Kubernetes {{ .minK8sVersion }} or later.
|
||||||
|
uri: https://www.kubernetes.io
|
||||||
|
- pass:
|
||||||
|
when: ">= {{ .minK8sVersion }}"
|
||||||
|
message: Good to go.
|
||||||
|
`
|
||||||
|
|
||||||
|
func (qp *QliksensePreflight) CheckK8sVersion() error {
|
||||||
|
// retrieve namespace
|
||||||
|
namespace := api.GetKubectlNamespace()
|
||||||
|
|
||||||
|
api.LogDebugMessage("Namespace: %s\n", namespace)
|
||||||
|
|
||||||
|
tmpl, err := template.New("checkVersionYAML").Parse(checkVersionYAML)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("cannot parse template: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tempYaml, err := ioutil.TempFile("", "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("cannot create file: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
api.LogDebugMessage("Temp Yaml file: %s\n", tempYaml.Name())
|
||||||
|
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
err = tmpl.Execute(&b, map[string]string{
|
||||||
|
"namespace": namespace,
|
||||||
|
"minK8sVersion": minK8sVersion,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tempYaml.WriteString(b.String())
|
||||||
|
//api.LogDebugMessage("Temp yaml contents: %s", b.String())
|
||||||
|
fmt.Printf("Minimum Kubernetes version supported: %s\n", minK8sVersion)
|
||||||
|
|
||||||
|
// current kubectl version
|
||||||
|
opr := fmt.Sprintf("version")
|
||||||
|
err = initiateK8sOps(opr, namespace)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// call preflight
|
||||||
|
preflightCommand := filepath.Join(qp.Q.QliksenseHome, PreflightChecksDirName, preflightFileName)
|
||||||
|
|
||||||
|
err = invokePreflight(preflightCommand, tempYaml)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Minimum kubernetes version check completed")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
BIN
pkg/qliksense/.DS_Store
vendored
Normal file
BIN
pkg/qliksense/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -58,42 +58,33 @@ func (nw *nullWriter) Write(p []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultProfile = "docker-desktop"
|
defaultProfile = "docker-desktop"
|
||||||
defaultGitUrl = "https://github.com/qlik-oss/qliksense-k8s"
|
defaultConfigRepoGitUrl = "https://github.com/qlik-oss/qliksense-k8s"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (q *Qliksense) About(gitRef, profile string) (*VersionOutput, error) {
|
func (q *Qliksense) About(gitRef, profile string) (*VersionOutput, error) {
|
||||||
configDirectory, isTemporary, profile, err := q.getConfigDirectory(defaultGitUrl, gitRef, profile)
|
configDirectory, isTemporary, profile, err := q.getConfigDirectory(defaultConfigRepoGitUrl, gitRef, profile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
} else if isTemporary {
|
||||||
if isTemporary {
|
|
||||||
defer os.RemoveAll(configDirectory)
|
defer os.RemoveAll(configDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
return q.AboutDir(configDirectory, profile)
|
return q.AboutDir(configDirectory, profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Qliksense) AboutDir(configDirectory, profile string) (*VersionOutput, error) {
|
func (q *Qliksense) AboutDir(configDirectory, profile string) (*VersionOutput, error) {
|
||||||
chartVersion, err := getChartVersion(filepath.Join(configDirectory, "transformers", "qseokversion.yaml"), "qliksense")
|
if chartVersion, err := getChartVersion(filepath.Join(configDirectory, "transformers", "qseokversion.yaml"), "qliksense"); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
} else if kuzManifest, err := executeKustomizeBuildWithStdoutProgress(filepath.Join(configDirectory, "manifests", profile)); err != nil {
|
||||||
|
|
||||||
kuzManifest, err := executeKustomizeBuildWithStdoutProgress(filepath.Join(configDirectory, "manifests", profile))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
} else if images, err := getImageList(kuzManifest); err != nil {
|
||||||
|
|
||||||
images, err := getImageList(kuzManifest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return &VersionOutput{
|
||||||
|
QliksenseVersion: chartVersion,
|
||||||
|
Images: images,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &VersionOutput{
|
|
||||||
QliksenseVersion: chartVersion,
|
|
||||||
Images: images,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Qliksense) getConfigDirectory(gitUrl, gitRef, profileEntered string) (dir string, isTemporary bool, profile string, err error) {
|
func (q *Qliksense) getConfigDirectory(gitUrl, gitRef, profileEntered string) (dir string, isTemporary bool, profile string, err error) {
|
||||||
|
|||||||
22
pkg/qliksense/apply.go
Normal file
22
pkg/qliksense/apply.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package qliksense
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (q *Qliksense) ApplyCRFromReader(r io.Reader, opts *InstallCommandOptions, keepPatchFiles, overwriteExistingContext bool) error {
|
||||||
|
if err := q.LoadCr(r, overwriteExistingContext); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||||
|
cr, err := qConfig.GetCurrentCR()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := q.InstallQK8s(cr.GetLabelFromCr("version"), opts, keepPatchFiles); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package qliksense
|
package qliksense
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@@ -9,7 +10,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
|
|
||||||
"github.com/qlik-oss/k-apis/pkg/cr"
|
"github.com/qlik-oss/k-apis/pkg/cr"
|
||||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
@@ -17,7 +17,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Q_INIT_CRD_PATH = "manifests/base/manifests/qliksense-init"
|
Q_INIT_CRD_PATH = "manifests/base/manifests/qliksense-init"
|
||||||
|
agreementTempalte = `
|
||||||
|
Please read the agreement at https://www.qlik.com/us/legal/license-terms
|
||||||
|
Accept the end user license agreement by providing acceptEULA=yes
|
||||||
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
func (q *Qliksense) ConfigApplyQK8s() error {
|
func (q *Qliksense) ConfigApplyQK8s() error {
|
||||||
@@ -29,22 +33,47 @@ func (q *Qliksense) ConfigApplyQK8s() error {
|
|||||||
fmt.Println("cannot get the current-context cr", err)
|
fmt.Println("cannot get the current-context cr", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// check if acceptEULA is yes or not
|
||||||
|
if !qcr.IsEULA() {
|
||||||
|
return errors.New(agreementTempalte + "\nPlease do $ qliksense config set-configs qliksense.acceptEULA=yes\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// create patch dependent resoruces
|
||||||
|
fmt.Println("Installing resoruces used kuztomize patch")
|
||||||
|
if err := q.createK8sResoruceBeforePatch(qcr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if qcr.Spec.Git.Repository != "" {
|
if qcr.Spec.Git.Repository != "" {
|
||||||
// fetching and applying manifest will be in the operator controller
|
// fetching and applying manifest will be in the operator controller
|
||||||
return q.applyCR(qcr.Spec.NameSpace)
|
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
return q.applyCR(dcr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return q.applyConfigToK8s(qcr)
|
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
return q.applyConfigToK8s(dcr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Qliksense) configEjson() error {
|
||||||
|
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||||
|
if ejsonKeyDir, err := qConfig.GetCurrentContextEjsonKeyDir(); err != nil {
|
||||||
|
return err
|
||||||
|
} else if err := os.Unsetenv("EJSON_KEY"); err != nil {
|
||||||
|
return err
|
||||||
|
} else if err := os.Setenv("EJSON_KEYDIR", ejsonKeyDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error {
|
func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error {
|
||||||
if qcr.Spec.RotateKeys != "None" {
|
if qcr.Spec.RotateKeys != "None" {
|
||||||
if err := os.Unsetenv("EJSON_KEY"); err != nil {
|
if err := q.configEjson(); err != nil {
|
||||||
fmt.Printf("error unsetting EJSON_KEY environment variable: %v\n", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := os.Setenv("EJSON_KEYDIR", q.QliksenseEjsonKeyDir); err != nil {
|
|
||||||
fmt.Printf("error setting EJSON_KEYDIR environment variable: %v\n", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,8 +83,9 @@ func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("Manifests root: " + qcr.Spec.GetManifestsRoot())
|
fmt.Println("Manifests root: " + qcr.Spec.GetManifestsRoot())
|
||||||
|
qcr.SetNamespace(qapi.GetKubectlNamespace())
|
||||||
// generate patches
|
// generate patches
|
||||||
cr.GeneratePatches(qcr.Spec, path.Join(userHomeDir, ".kube", "config"))
|
cr.GeneratePatches(&qcr.KApiCr, path.Join(userHomeDir, ".kube", "config"))
|
||||||
// apply generated manifests
|
// apply generated manifests
|
||||||
profilePath := filepath.Join(qcr.Spec.GetManifestsRoot(), qcr.Spec.GetProfileDir())
|
profilePath := filepath.Join(qcr.Spec.GetManifestsRoot(), qcr.Spec.GetProfileDir())
|
||||||
mByte, err := executeKustomizeBuild(profilePath)
|
mByte, err := executeKustomizeBuild(profilePath)
|
||||||
@@ -63,7 +93,7 @@ func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error {
|
|||||||
fmt.Println("cannot generate manifests for "+profilePath, err)
|
fmt.Println("cannot generate manifests for "+profilePath, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = qapi.KubectlApply(string(mByte), qcr.Spec.NameSpace); err != nil {
|
if err = qapi.KubectlApply(string(mByte), qcr.GetNamespace()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +106,11 @@ func (q *Qliksense) ConfigViewCR() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println(r)
|
oth, err := q.getCurrentCrDependentResourceAsString()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(r + "\n" + oth)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,18 +126,28 @@ func (q *Qliksense) getCRString(contextName string) (string, error) {
|
|||||||
fmt.Println("cannot get the context cr", err)
|
fmt.Println("cannot get the context cr", err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
out, err := yaml.Marshal(qcr)
|
out, err := qapi.K8sToYaml(qcr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("cannot unmarshal cr ", err)
|
fmt.Println("cannot unmarshal cr ", err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
return string(out), nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Qliksense) getCurrentCrDependentResourceAsString() (string, error) {
|
||||||
|
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||||
|
qcr, err := qConfig.GetCR(qConfig.Spec.CurrentContext)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("cannot get the context cr", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
var crString strings.Builder
|
var crString strings.Builder
|
||||||
crString.Write(out)
|
|
||||||
|
|
||||||
for svcName, v := range qcr.Spec.Secrets {
|
for svcName, v := range qcr.Spec.Secrets {
|
||||||
for _, item := range v {
|
for _, item := range v {
|
||||||
if item.ValueFrom != nil && item.ValueFrom.SecretKeyRef != nil {
|
if item.ValueFrom != nil && item.ValueFrom.SecretKeyRef != nil {
|
||||||
secretFilePath := filepath.Join(q.QliksenseHome, QliksenseContextsDir, qcr.Metadata.Name, QliksenseSecretsDir, svcName+".yaml")
|
secretFilePath := filepath.Join(q.QliksenseHome, QliksenseContextsDir, qcr.GetName(), QliksenseSecretsDir, svcName+".yaml")
|
||||||
|
|
||||||
if api.FileExists(secretFilePath) {
|
if api.FileExists(secretFilePath) {
|
||||||
secretFile, err := ioutil.ReadFile(secretFilePath)
|
secretFile, err := ioutil.ReadFile(secretFilePath)
|
||||||
@@ -116,5 +160,6 @@ func (q *Qliksense) getCRString(contextName string) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
crString.WriteString("\n---\n")
|
||||||
return crString.String(), nil
|
return crString.String(), nil
|
||||||
}
|
}
|
||||||
|
|||||||
24
pkg/qliksense/confirmation.go
Normal file
24
pkg/qliksense/confirmation.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,25 +10,27 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"encoding/base64"
|
"github.com/robfig/cron/v3"
|
||||||
|
|
||||||
|
"github.com/qlik-oss/k-apis/pkg/config"
|
||||||
|
|
||||||
b64 "encoding/base64"
|
b64 "encoding/base64"
|
||||||
|
|
||||||
ansi "github.com/mattn/go-colorable"
|
ansi "github.com/mattn/go-colorable"
|
||||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
"github.com/ttacon/chalk"
|
"github.com/ttacon/chalk"
|
||||||
|
_ "gopkg.in/yaml.v2"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Below are some constants to support qliksense context setup
|
// Below are some constants to support qliksense context setup
|
||||||
QliksenseConfigHome = "/.qliksense"
|
QliksenseConfigFile = "config.yaml"
|
||||||
QliksenseConfigContextHome = "/.qliksense/contexts"
|
QliksenseContextsDir = "contexts"
|
||||||
QliksenseConfigFile = "config.yaml"
|
DefaultQliksenseContext = "qlik-default"
|
||||||
QliksenseContextsDir = "contexts"
|
MaxContextNameLength = 17
|
||||||
DefaultQliksenseContext = "qlik-default"
|
QliksenseSecretsDir = "secrets"
|
||||||
MaxContextNameLength = 17
|
|
||||||
QliksenseSecretsDir = "secrets"
|
|
||||||
|
|
||||||
imageRegistryConfigKey = "imageRegistry"
|
imageRegistryConfigKey = "imageRegistry"
|
||||||
pullSecretName = "artifactory-docker-secret"
|
pullSecretName = "artifactory-docker-secret"
|
||||||
@@ -43,17 +45,17 @@ func (q *Qliksense) SetSecrets(args []string, isSecretSet bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Metadata name in qliksense CR is the name of the current context
|
// Metadata name in qliksense CR is the name of the current context
|
||||||
api.LogDebugMessage("Current context: %s", qliksenseCR.Metadata.Name)
|
api.LogDebugMessage("Current context: %s", qliksenseCR.GetName())
|
||||||
rsaPublicKey, _, err := qConfig.GetCurrentContextEncryptionKeyPair()
|
rsaPublicKey, _, err := qConfig.GetCurrentContextEncryptionKeyPair()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
resultArgs, err := api.ProcessConfigArgs(args)
|
resultArgs, err := api.ProcessConfigArgs(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, ra := range resultArgs {
|
for _, ra := range resultArgs {
|
||||||
|
api.LogDebugMessage("value args to be encrypted: %s", ra.Value)
|
||||||
if err := q.processSecret(ra, rsaPublicKey, qliksenseCR, isSecretSet); err != nil {
|
if err := q.processSecret(ra, rsaPublicKey, qliksenseCR, isSecretSet); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -64,7 +66,7 @@ func (q *Qliksense) SetSecrets(args []string, isSecretSet bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Qliksense) processSecret(ra *api.ServiceKeyValue, rsaPublicKey *rsa.PublicKey, qliksenseCR api.QliksenseCR, isSecretSet bool) error {
|
func (q *Qliksense) processSecret(ra *api.ServiceKeyValue, rsaPublicKey *rsa.PublicKey, qliksenseCR *api.QliksenseCR, isSecretSet bool) error {
|
||||||
// encrypt value with RSA key pair
|
// encrypt value with RSA key pair
|
||||||
valueBytes := []byte(ra.Value)
|
valueBytes := []byte(ra.Value)
|
||||||
cipherText, e2 := api.Encrypt(valueBytes, rsaPublicKey)
|
cipherText, e2 := api.Encrypt(valueBytes, rsaPublicKey)
|
||||||
@@ -74,10 +76,10 @@ func (q *Qliksense) processSecret(ra *api.ServiceKeyValue, rsaPublicKey *rsa.Pub
|
|||||||
base64EncodedSecret := b64.StdEncoding.EncodeToString(cipherText)
|
base64EncodedSecret := b64.StdEncoding.EncodeToString(cipherText)
|
||||||
secretName := ""
|
secretName := ""
|
||||||
if isSecretSet {
|
if isSecretSet {
|
||||||
secretFolder := filepath.Join(q.QliksenseHome, QliksenseContextsDir, qliksenseCR.Metadata.Name, QliksenseSecretsDir)
|
secretFolder := qliksenseCR.GetK8sSecretsFolder(q.QliksenseHome)
|
||||||
secretFileName := filepath.Join(secretFolder, ra.SvcName+".yaml")
|
secretFileName := filepath.Join(secretFolder, ra.SvcName+".yaml")
|
||||||
|
|
||||||
secretName = fmt.Sprintf("%s-%s-%s", qliksenseCR.Metadata.Name, ra.SvcName, "sense_installer")
|
secretName = fmt.Sprintf("%s-%s-%s", qliksenseCR.GetName(), ra.SvcName, "senseinstaller")
|
||||||
api.LogDebugMessage("Constructed secret name: %s", secretName)
|
api.LogDebugMessage("Constructed secret name: %s", secretName)
|
||||||
|
|
||||||
k8sSecret := v1.Secret{
|
k8sSecret := v1.Secret{
|
||||||
@@ -115,7 +117,6 @@ func (q *Qliksense) processSecret(ra *api.ServiceKeyValue, rsaPublicKey *rsa.Pub
|
|||||||
api.LogDebugMessage("Error while writing K8s secret to file")
|
api.LogDebugMessage("Error while writing K8s secret to file")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// api.WriteToFile(&k8sSecret, secretFileName)
|
|
||||||
api.LogDebugMessage("Created a Kubernetes secret")
|
api.LogDebugMessage("Created a Kubernetes secret")
|
||||||
|
|
||||||
// Prepare args to update CR in the next step
|
// Prepare args to update CR in the next step
|
||||||
@@ -148,13 +149,12 @@ func (q *Qliksense) SetConfigs(args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func retrieveCurrentContextInfo(q *Qliksense) (api.QliksenseCR, string, error) {
|
func retrieveCurrentContextInfo(q *Qliksense) (*api.QliksenseCR, string, error) {
|
||||||
var qliksenseConfig api.QliksenseConfig
|
var qliksenseConfig api.QliksenseConfig
|
||||||
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
|
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
|
||||||
|
|
||||||
if err := api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile); err != nil {
|
if err := api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return api.QliksenseCR{}, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
currentContext := qliksenseConfig.Spec.CurrentContext
|
currentContext := qliksenseConfig.Spec.CurrentContext
|
||||||
api.LogDebugMessage("Current-context from config.yaml: %s", currentContext)
|
api.LogDebugMessage("Current-context from config.yaml: %s", currentContext)
|
||||||
@@ -162,32 +162,33 @@ func retrieveCurrentContextInfo(q *Qliksense) (api.QliksenseCR, string, error) {
|
|||||||
// current-context is empty
|
// current-context is empty
|
||||||
err := fmt.Errorf(`Please run the "qliksense config set-context <context-name>" first before viewing the current context info`)
|
err := fmt.Errorf(`Please run the "qliksense config set-context <context-name>" first before viewing the current context info`)
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return api.QliksenseCR{}, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
// read the context.yaml file
|
// read the context.yaml file
|
||||||
var qliksenseCR api.QliksenseCR
|
qliksenseCR := &api.QliksenseCR{}
|
||||||
if currentContext == "" {
|
if currentContext == "" {
|
||||||
// current-context is empty
|
// current-context is empty
|
||||||
err := fmt.Errorf(`Please run the "qliksense config set-context <context-name>" first before viewing the current context info`)
|
err := fmt.Errorf(`Please run the "qliksense config set-context <context-name>" first before viewing the current context info`)
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return api.QliksenseCR{}, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
qliksenseContextsFile := filepath.Join(q.QliksenseHome, QliksenseContextsDir, currentContext, currentContext+".yaml")
|
qliksenseContextsFile := filepath.Join(q.QliksenseHome, QliksenseContextsDir, currentContext, currentContext+".yaml")
|
||||||
if !api.FileExists(qliksenseContextsFile) {
|
if !api.FileExists(qliksenseContextsFile) {
|
||||||
err := fmt.Errorf("Context file does not exist.\nPlease try re-running `qliksense config set-context <context-name>` and then `qliksense config view` again")
|
err := fmt.Errorf("Context file does not exist.\nPlease try re-running `qliksense config set-context <context-name>` and then `qliksense config view` again")
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return api.QliksenseCR{}, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
if err := api.ReadFromFile(&qliksenseCR, qliksenseContextsFile); err != nil {
|
if err := api.ReadFromFile(qliksenseCR, qliksenseContextsFile); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return api.QliksenseCR{}, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
api.LogDebugMessage("Read context file: %s, Read QliksenseCR: %v", qliksenseContextsFile, qliksenseCR)
|
api.LogDebugMessage("Read context file: %s, Read QliksenseCR: %v", qliksenseContextsFile, qliksenseCR)
|
||||||
|
|
||||||
return qliksenseCR, qliksenseContextsFile, nil
|
return qliksenseCR, qliksenseContextsFile, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetOtherConfigs - set profile/namespace/storageclassname/git.repository commands
|
// SetOtherConfigs - set profile/storageclassname/git.repository/manifestRoot commands
|
||||||
func (q *Qliksense) SetOtherConfigs(args []string) error {
|
func (q *Qliksense) SetOtherConfigs(args []string) error {
|
||||||
// retieve current context from config.yaml
|
// retieve current context from config.yaml
|
||||||
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q)
|
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q)
|
||||||
@@ -208,10 +209,10 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
|
|||||||
case "profile":
|
case "profile":
|
||||||
qliksenseCR.Spec.Profile = argsString[1]
|
qliksenseCR.Spec.Profile = argsString[1]
|
||||||
api.LogDebugMessage("Current profile after modification: %s ", qliksenseCR.Spec.Profile)
|
api.LogDebugMessage("Current profile after modification: %s ", qliksenseCR.Spec.Profile)
|
||||||
case "namespace":
|
|
||||||
qliksenseCR.Spec.NameSpace = argsString[1]
|
|
||||||
api.LogDebugMessage("Current namespace after modification: %s ", qliksenseCR.Spec.NameSpace)
|
|
||||||
case "git.repository":
|
case "git.repository":
|
||||||
|
if qliksenseCR.Spec.Git == nil {
|
||||||
|
qliksenseCR.Spec.Git = &config.Repo{}
|
||||||
|
}
|
||||||
qliksenseCR.Spec.Git.Repository = argsString[1]
|
qliksenseCR.Spec.Git.Repository = argsString[1]
|
||||||
api.LogDebugMessage("Current git repository after modification: %s ", qliksenseCR.Spec.Git.Repository)
|
api.LogDebugMessage("Current git repository after modification: %s ", qliksenseCR.Spec.Git.Repository)
|
||||||
case "storageClassName":
|
case "storageClassName":
|
||||||
@@ -226,8 +227,44 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
|
|||||||
}
|
}
|
||||||
qliksenseCR.Spec.RotateKeys = rotateKeys
|
qliksenseCR.Spec.RotateKeys = rotateKeys
|
||||||
api.LogDebugMessage("Current rotateKeys after modification: %s ", qliksenseCR.Spec.RotateKeys)
|
api.LogDebugMessage("Current rotateKeys after modification: %s ", qliksenseCR.Spec.RotateKeys)
|
||||||
|
case "gitops.enabled":
|
||||||
|
if qliksenseCR.Spec.GitOps == nil {
|
||||||
|
qliksenseCR.Spec.GitOps = &config.GitOps{}
|
||||||
|
}
|
||||||
|
if strings.EqualFold(argsString[1], "yes") || strings.EqualFold(argsString[1], "no") {
|
||||||
|
qliksenseCR.Spec.GitOps.Enabled = argsString[1]
|
||||||
|
api.LogDebugMessage("Current gitOps enabled status : %s ", qliksenseCR.Spec.GitOps.Enabled)
|
||||||
|
} else {
|
||||||
|
err := fmt.Errorf("Please use yes or no")
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "gitops.schedule":
|
||||||
|
if qliksenseCR.Spec.GitOps == nil {
|
||||||
|
qliksenseCR.Spec.GitOps = &config.GitOps{}
|
||||||
|
}
|
||||||
|
if _, err := cron.ParseStandard(argsString[1]); err != nil {
|
||||||
|
err := fmt.Errorf("Please enter string with standard cron scheduling syntax ")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
qliksenseCR.Spec.GitOps.Schedule = argsString[1]
|
||||||
|
api.LogDebugMessage("Current gitOps schedule is : %s ", qliksenseCR.Spec.GitOps.Schedule)
|
||||||
|
case "gitops.watchbranch":
|
||||||
|
if qliksenseCR.Spec.GitOps == nil {
|
||||||
|
qliksenseCR.Spec.GitOps = &config.GitOps{}
|
||||||
|
}
|
||||||
|
qliksenseCR.Spec.GitOps.WatchBranch = argsString[1]
|
||||||
|
api.LogDebugMessage("Current gitOps watchbranch is : %s ", qliksenseCR.Spec.GitOps.WatchBranch)
|
||||||
|
case "gitops.image":
|
||||||
|
if qliksenseCR.Spec.GitOps == nil {
|
||||||
|
qliksenseCR.Spec.GitOps = &config.GitOps{}
|
||||||
|
}
|
||||||
|
qliksenseCR.Spec.GitOps.Image = argsString[1]
|
||||||
|
api.LogDebugMessage("Current gitOps image is : %s ", qliksenseCR.Spec.GitOps.Image)
|
||||||
default:
|
default:
|
||||||
log.Println("As part of the `qliksense config set` command, please enter one of: profile, namespace, storageClassName,rotateKeys or git.repository arguments")
|
err := fmt.Errorf("Please enter one of: profile, storageClassName,rotateKeys, manifestRoot, git.repository or gitops arguments to configure the current context")
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// write modified content into context.yaml
|
// write modified content into context.yaml
|
||||||
@@ -239,7 +276,10 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
|
|||||||
// SetContextConfig - set the context for qliksense kubernetes resources to live in
|
// SetContextConfig - set the context for qliksense kubernetes resources to live in
|
||||||
func (q *Qliksense) SetContextConfig(args []string) error {
|
func (q *Qliksense) SetContextConfig(args []string) error {
|
||||||
if len(args) == 1 {
|
if len(args) == 1 {
|
||||||
q.SetUpQliksenseContext(args[0], false)
|
err := q.SetUpQliksenseContext(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
err := fmt.Errorf("Please provide a name to configure the context with")
|
err := fmt.Errorf("Please provide a name to configure the context with")
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@@ -261,7 +301,7 @@ func (q *Qliksense) ListContextConfigs() error {
|
|||||||
w.Flush()
|
w.Flush()
|
||||||
if len(qliksenseConfig.Spec.Contexts) > 0 {
|
if len(qliksenseConfig.Spec.Contexts) > 0 {
|
||||||
for _, cont := range qliksenseConfig.Spec.Contexts {
|
for _, cont := range qliksenseConfig.Spec.Contexts {
|
||||||
fmt.Fprintln(w, cont.Name, "\t", cont.CrFile, "\t")
|
fmt.Fprintln(w, cont.Name, "\t", qliksenseConfig.GetCRFilePath(cont.Name), "\t")
|
||||||
}
|
}
|
||||||
w.Flush()
|
w.Flush()
|
||||||
fmt.Fprintln(out, "")
|
fmt.Fprintln(out, "")
|
||||||
@@ -272,13 +312,86 @@ func (q *Qliksense) ListContextConfigs() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *Qliksense) DeleteContextConfig(args []string, flag bool) error {
|
||||||
|
if len(args) == 1 {
|
||||||
|
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
|
||||||
|
var qliksenseConfig api.QliksenseConfig
|
||||||
|
api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile)
|
||||||
|
out := ansi.NewColorableStdout()
|
||||||
|
switch args[0] {
|
||||||
|
case qliksenseConfig.Spec.CurrentContext:
|
||||||
|
fmt.Fprintln(out, chalk.Yellow.Color("Please switch contexts to be able to delete this context."))
|
||||||
|
err := fmt.Errorf(chalk.Red.Color("Cannot delete current context - %s"), chalk.White.Color(chalk.Bold.TextStyle(qliksenseConfig.Spec.CurrentContext)))
|
||||||
|
return err
|
||||||
|
case DefaultQliksenseContext:
|
||||||
|
err := fmt.Errorf(chalk.Red.Color("Cannot delete default qliksense context"))
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
qliksenseContextsDir1 := filepath.Join(q.QliksenseHome, QliksenseContextsDir)
|
||||||
|
qliksenseContextFile := filepath.Join(qliksenseContextsDir1, args[0])
|
||||||
|
qliksenseSecretsDir1 := filepath.Join(q.QliksenseHome, QliksenseSecretsDir, QliksenseContextsDir)
|
||||||
|
qliksenseSecretsFile := filepath.Join(qliksenseSecretsDir1, args[0])
|
||||||
|
if err := os.RemoveAll(qliksenseContextFile); err != nil {
|
||||||
|
err = fmt.Errorf("Not able to delete %s dir: %v", qliksenseContextsDir1, err)
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
} else if err := os.RemoveAll(qliksenseSecretsFile); err != nil {
|
||||||
|
err = fmt.Errorf("No Secrets Folder Detected")
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
currentLength := len(qliksenseConfig.Spec.Contexts)
|
||||||
|
if currentLength > 0 {
|
||||||
|
temp := qliksenseConfig.Spec.Contexts
|
||||||
|
qliksenseConfig.Spec.Contexts = nil
|
||||||
|
for _, ctx := range temp {
|
||||||
|
if ctx.Name != args[0] {
|
||||||
|
qliksenseConfig.Spec.Contexts = append(qliksenseConfig.Spec.Contexts, api.Context{
|
||||||
|
Name: ctx.Name,
|
||||||
|
CrFile: ctx.CrFile,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newLength := len(qliksenseConfig.Spec.Contexts)
|
||||||
|
if currentLength != newLength {
|
||||||
|
ans := flag
|
||||||
|
if ans == false {
|
||||||
|
ans = AskForConfirmation("Are You Sure? ")
|
||||||
|
}
|
||||||
|
if ans == true {
|
||||||
|
api.WriteToFile(&qliksenseConfig, qliksenseConfigFile)
|
||||||
|
fmt.Fprintln(out, chalk.Yellow.Color(chalk.Underline.TextStyle("Warning: Active resources may still be running in-cluster")))
|
||||||
|
fmt.Fprintln(out, chalk.Green.Color("Successfully deleted context: "), chalk.Bold.TextStyle(args[0]))
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := fmt.Errorf(chalk.Red.Color("Context not found"))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := fmt.Errorf("Please provide a context as an argument to delete")
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetUpQliksenseDefaultContext - to setup dir structure for default qliksense context
|
// SetUpQliksenseDefaultContext - to setup dir structure for default qliksense context
|
||||||
func (q *Qliksense) SetUpQliksenseDefaultContext() error {
|
func (q *Qliksense) SetUpQliksenseDefaultContext() error {
|
||||||
return q.SetUpQliksenseContext(DefaultQliksenseContext, true)
|
return q.SetUpQliksenseContext(DefaultQliksenseContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUpQliksenseContext - to setup qliksense context
|
// SetUpQliksenseContext - to setup qliksense context
|
||||||
func (q *Qliksense) SetUpQliksenseContext(contextName string, isDefaultContext bool) error {
|
func (q *Qliksense) SetUpQliksenseContext(contextName string) error {
|
||||||
|
if contextName == "" {
|
||||||
|
err := fmt.Errorf("Please enter a non-empty context-name")
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
// check the length of the context name entered by the user, it should not exceed 17 chars
|
// check the length of the context name entered by the user, it should not exceed 17 chars
|
||||||
if len(contextName) > MaxContextNameLength {
|
if len(contextName) > MaxContextNameLength {
|
||||||
err := fmt.Errorf("Please enter a context-name with utmost 17 characters")
|
err := fmt.Errorf("Please enter a context-name with utmost 17 characters")
|
||||||
@@ -287,78 +400,29 @@ func (q *Qliksense) SetUpQliksenseContext(contextName string, isDefaultContext b
|
|||||||
}
|
}
|
||||||
|
|
||||||
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
|
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
|
||||||
var qliksenseConfig api.QliksenseConfig
|
qliksenseConfig := api.NewQConfigEmpty(q.QliksenseHome)
|
||||||
configFileTrack := false
|
|
||||||
|
|
||||||
if !api.FileExists(qliksenseConfigFile) {
|
if !api.FileExists(qliksenseConfigFile) {
|
||||||
qliksenseConfig.AddBaseQliksenseConfigs(contextName)
|
qliksenseConfig.AddBaseQliksenseConfigs(contextName)
|
||||||
} else {
|
} else {
|
||||||
if err := api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile); err != nil {
|
if err := api.ReadFromFile(qliksenseConfig, qliksenseConfigFile); err != nil {
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if isDefaultContext { // if config file exits but a default context is requested, we want to prevent writing to config file
|
|
||||||
configFileTrack = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// creating a file in the name of the context if it does not exist/ opening it to append/modify content if it already exists
|
|
||||||
|
|
||||||
qliksenseContextsDir1 := filepath.Join(q.QliksenseHome, QliksenseContextsDir)
|
|
||||||
if !api.DirExists(qliksenseContextsDir1) {
|
|
||||||
if err := os.Mkdir(qliksenseContextsDir1, os.ModePerm); err != nil {
|
|
||||||
err = fmt.Errorf("Not able to create %s dir: %v", qliksenseContextsDir1, err)
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
api.LogDebugMessage("%s exists", qliksenseContextsDir1)
|
|
||||||
|
|
||||||
// creating contexts/qlik-default/qlik-default.yaml file
|
|
||||||
qliksenseContextFile := filepath.Join(qliksenseContextsDir1, contextName, contextName+".yaml")
|
|
||||||
var qliksenseCR api.QliksenseCR
|
|
||||||
|
|
||||||
defaultContextsDir := filepath.Join(qliksenseContextsDir1, contextName)
|
|
||||||
if !api.DirExists(defaultContextsDir) {
|
|
||||||
if err := os.Mkdir(defaultContextsDir, os.ModePerm); err != nil {
|
|
||||||
err = fmt.Errorf("Not able to create %s: %v", defaultContextsDir, err)
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
api.LogDebugMessage("%s exists", defaultContextsDir)
|
|
||||||
if !api.FileExists(qliksenseContextFile) {
|
|
||||||
qliksenseCR.AddCommonConfig(contextName)
|
|
||||||
api.LogDebugMessage("Added Context: %s", contextName)
|
|
||||||
} else {
|
|
||||||
if err := api.ReadFromFile(&qliksenseCR, qliksenseContextFile); err != nil {
|
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
api.WriteToFile(&qliksenseCR, qliksenseContextFile)
|
if qliksenseConfig.IsContextExist(contextName) {
|
||||||
ctxTrack := false
|
return nil
|
||||||
if len(qliksenseConfig.Spec.Contexts) > 0 {
|
|
||||||
for _, ctx := range qliksenseConfig.Spec.Contexts {
|
|
||||||
if ctx.Name == contextName {
|
|
||||||
ctx.CrFile = qliksenseContextFile
|
|
||||||
ctxTrack = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !ctxTrack {
|
|
||||||
qliksenseConfig.Spec.Contexts = append(qliksenseConfig.Spec.Contexts, api.Context{
|
|
||||||
Name: contextName,
|
|
||||||
CrFile: qliksenseContextFile,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
qliksenseCR := &api.QliksenseCR{}
|
||||||
|
qliksenseCR.AddCommonConfig(contextName)
|
||||||
qliksenseConfig.Spec.CurrentContext = contextName
|
qliksenseConfig.Spec.CurrentContext = contextName
|
||||||
if !configFileTrack {
|
if err := qliksenseConfig.CreateOrWriteCrAndContext(qliksenseCR); err != nil {
|
||||||
api.WriteToFile(&qliksenseConfig, qliksenseConfigFile)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
// set the encrypted default mongo
|
||||||
|
return q.SetSecrets([]string{`qliksense.mongoDbUri="mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"`}, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateInput(input string) (string, error) {
|
func validateInput(input string) (string, error) {
|
||||||
@@ -380,19 +444,19 @@ func validateInput(input string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PrepareK8sSecret decodes and decrypts the secret value in the secret.yaml file and returns a B64encoded string
|
// PrepareK8sSecret decodes and decrypts the secret value in the secret.yaml file and returns a B64encoded string
|
||||||
func (q *Qliksense) PrepareK8sSecret(qliksenseCR api.QliksenseCR, targetFile string) (string, error) {
|
func (q *Qliksense) PrepareK8sSecret(targetFile string) (string, error) {
|
||||||
qConfig := api.NewQConfig(q.QliksenseHome)
|
|
||||||
_, rsaPrivateKey, err := qConfig.GetCurrentContextEncryptionKeyPair()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if targetFile exists
|
// check if targetFile exists
|
||||||
if !api.FileExists(targetFile) {
|
if !api.FileExists(targetFile) {
|
||||||
err := fmt.Errorf("Target file does not exist in the path provided")
|
err := fmt.Errorf("Target file does not exist in the path provided")
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
qConfig := api.NewQConfig(q.QliksenseHome)
|
||||||
|
_, rsaPrivateKey, err := qConfig.GetCurrentContextEncryptionKeyPair()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
// read the target file
|
// read the target file
|
||||||
k8sSecret, err := readTargetfile(targetFile)
|
k8sSecret, err := readTargetfile(targetFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -403,34 +467,28 @@ func (q *Qliksense) PrepareK8sSecret(qliksenseCR api.QliksenseCR, targetFile str
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
dataMap := k8sSecret1.Data
|
dataMap := k8sSecret1.Data
|
||||||
var base64EncodedSecret string
|
|
||||||
var resultMap = make(map[string][]byte)
|
var resultMap = make(map[string][]byte)
|
||||||
for k, v := range dataMap {
|
for k, v := range dataMap {
|
||||||
// base64 decode every value
|
ba, err := b64.StdEncoding.DecodeString(string(v))
|
||||||
decodedStr, _ := base64.StdEncoding.DecodeString(string(v))
|
|
||||||
|
|
||||||
decryptedString, err := api.Decrypt(decodedStr, rsaPrivateKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Not able to decrypt message")
|
err := fmt.Errorf("Not able to decode message: %v", err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
decryptedString, err := api.Decrypt(ba, rsaPrivateKey)
|
||||||
// base64 encode the values
|
if err != nil {
|
||||||
base64EncodedSecret = b64.StdEncoding.EncodeToString(decryptedString)
|
err := fmt.Errorf("Not able to decrypt message: %v", err)
|
||||||
resultMap[k] = []byte(base64EncodedSecret)
|
return "", err
|
||||||
|
}
|
||||||
|
resultMap[k] = decryptedString
|
||||||
}
|
}
|
||||||
api.LogDebugMessage("B64 encoded Map: %v\n", resultMap)
|
|
||||||
|
|
||||||
// putting the above map back into the k8sSecret struct
|
// putting the above map back into the k8sSecret struct
|
||||||
k8sSecret1.Data = resultMap
|
k8sSecret1.Data = resultMap
|
||||||
k8sSecretBytes, err := api.K8sSecretToYaml(k8sSecret1)
|
k8sSecretBytes, err := api.K8sSecretToYaml(k8sSecret1)
|
||||||
api.LogDebugMessage("Final Yaml: %v\n", string(k8sSecretBytes))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(k8sSecretBytes), nil
|
return string(k8sSecretBytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,16 +516,33 @@ func (q *Qliksense) SetImageRegistry(registry, pushUsername, pushPassword, pullU
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if err := qConfig.SetPullDockerConfigJsonSecret(&api.DockerConfigJsonSecret{
|
} else if err := qConfig.SetPullDockerConfigJsonSecret(&api.DockerConfigJsonSecret{
|
||||||
Name: pullSecretName,
|
Name: pullSecretName,
|
||||||
Namespace: qliksenseCR.Spec.NameSpace,
|
Uri: registry,
|
||||||
Uri: registry,
|
Username: pullUsername,
|
||||||
Username: pullUsername,
|
Password: pullPassword,
|
||||||
Password: pullPassword,
|
Email: pullUsername,
|
||||||
Email: pullUsername,
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
} else if err := qConfig.DeletePushDockerConfigJsonSecret(); err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
} else if err := qConfig.DeletePullDockerConfigJsonSecret(); err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
qliksenseCR.Spec.AddToConfigs("qliksense", imageRegistryConfigKey, registry)
|
qliksenseCR.Spec.AddToConfigs("qliksense", imageRegistryConfigKey, registry)
|
||||||
return api.WriteToFile(&qliksenseCR, qliksenseContextsFile)
|
return api.WriteToFile(&qliksenseCR, qliksenseContextsFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *Qliksense) SetEulaAccepted() error {
|
||||||
|
qConfig := api.NewQConfig(q.QliksenseHome)
|
||||||
|
qcr, err := qConfig.GetCurrentCR()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !qcr.IsEULA() {
|
||||||
|
qcr.SetEULA("yes")
|
||||||
|
return qConfig.WriteCurrentContextCR(qcr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,23 +1,158 @@
|
|||||||
package qliksense
|
package qliksense
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
b64 "encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/qlik-oss/k-apis/pkg/config"
|
||||||
|
|
||||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const (
|
||||||
testDir = "./tests"
|
testDir = "./tests"
|
||||||
|
qlikDefaultContext = "qlik-default"
|
||||||
|
secrets = "secrets"
|
||||||
|
contexts = "contexts"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var targetFileStringTemplate = `
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
mongoDbUri: %s
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: testctx-qliksense-senseinstaller
|
||||||
|
type: Opaque
|
||||||
|
`
|
||||||
|
var encText = "SFpVZ2t5SGsrN2lLQjlTYm9rbFUxSDFRcmVYdUxhTW9MUHlQOGtGditxMEcwZTlIZDl1dVRrV0tEYm5qUURSWFp3dStuNklueGk3anI2c1djSVdsbWlKTHdWQUJwdUg0a1NXd3llMUlMa2oxK3FRSFlMM2dQUExvN1pBYkVDeDROMUVvam12M0t0NmQwbkdhSXlWWEpmWWJUVVFDM1Y4L0ZTVXBVN0JUb0l4OVZWdmlPam5HTHk4RlF2a3RUaHJxWTUvZEh2N3pVUmhiOTc2Q2YwbEovZ3I2L2NwRk9RMUFXVXdodVhrTG9lYjVzNFdtTEZzNldqT3k0bWlKM1J6VllLaWVUSFJ2SE85eDB6dUthanRwSGEzWEZkaE5QNnpySVJJNTRFalUyblVYYUNlYXVnWnZEOUxjdWluOFhFcjExbkFINURCUDAycXhoZk5BejVoMlV2eFNWVmR0aW1QTDBhMVBJTUxGQTgyWUkrQkFOQkhkSUNnZGU5SkxIRFBoTzR6c0llaE1LRmhVQkNoOUhQa3kyRnhTeDJ3YWp3M1UycEsvcFJVZUxDazRUbkhmL25LN3h5ekdpV3dSUFFFZHdsWE5JbUhjVlVPV3gvNWh4WlJCUTZtb3pGYk1HbXR1Mkh5Z3RVV2gzNFYzd1BhS01TNFRsa0hyODFjRjVCWVpxenBFK1pKWnVyLy8zbzJsU0tFMjMxTG1pcGk1K0FqbXZvUVcyWHBocjFNVWJQY1pXUkJFRkkyQXBCM0FhQXFPa0k1MkRqNG43Mko5bCtaMzdydTk1aHk5K1lzY0FxMjZVbExYRlc0S3RUUkRLSjlMNnVmdlIrUUNudER3em5UTFRHUnEwZU5COWt6S0Q4MFlUdXozeHNXK3cxdjlHbDJaMnBZMTZWTCtEV1k9"
|
||||||
|
var decText = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
|
||||||
|
|
||||||
|
func setupTargetFileAndPrivateKey() ([]byte, []byte, string, error) {
|
||||||
|
targetFileString := fmt.Sprintf(targetFileStringTemplate, encText)
|
||||||
|
privKeyBytes := []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIJJwIBAAKCAgEApFf3qCQhAr2QLRRZdhLyB8exLjrQiXLr8hwDe0xHSLJX3w7v
|
||||||
|
5z4ujJWrHUulQ2/hvS8uffxMVrp2YqeA4sjy/ku1KqZVQv/WNTdL2v9Z1ewbRnBj
|
||||||
|
DQvmkWDKZ+cP8VPdHGzQ4iM2z6BZ4RQTkdQMKqsVwUsLO9amI2TOny/M696eFRW0
|
||||||
|
pk4+W3QZZRawT0HqJPvKKXKqoO2+62W54rOV8glJi29Do06e4S6CZUl1hBUy0VlL
|
||||||
|
trLlRSOHTois0dF2a9f7+GGgU11MHO6w1k1NesSlfZ0vnrrkW8WFLqewk+Jj+w1x
|
||||||
|
eQnYHOeHjx6zi9f9DC96eSylSB3iJ71NmXcMc0IEZ2LiQIqTL83BLOgMBCsK3FSl
|
||||||
|
GMakQUR2GJ+M0I/selYkRMhid6eOmhlsTNMPbpcTHxZ+ReIzS+5B1X0FZ7RIL+jS
|
||||||
|
L9mFcxmD3dxurrrt/DkLpXcuWdi1s1bpVn3jIQhU0+bgA6hT0k8Kj2f3Q9QnvkHS
|
||||||
|
Eff+XyLvLwQeSsSAcnM+1I7fNSPEo2cq0au6ZtjHcirXmMminAQ6cKW1XrEvJBef
|
||||||
|
HHibtjJjIM4bHH7MKRA5H3km/J4CCwI1VogSTcE05Z6ypAFU2TCrnec9c9VXkRDP
|
||||||
|
94h0GuoG8sdhmQudqvghr/8T7CV+sGRQbdeqrXwanfnGPrjcMVIO/dSOxOUCAwEA
|
||||||
|
AQKCAgA5b2TmJnpC8u0IVCxPz582iNurRHLNFpTPMGsnFCl1hp6fHiFJt7mc+FGt
|
||||||
|
E1rWjqtd6rdc4Gfth40IPXIV0BTcOqk+FpOFrtO2FXU1PDixQqrlmzGCxb324NTc
|
||||||
|
KyyvMpf77yuxXI0zUt8WgmW0eV8nKlOYEhoC96lohTqQ96uuY0bsJ4HS/VVdsN2P
|
||||||
|
Lra/fFHQSw8EHUb0pyIqMoscZ5bn18cUK/Z/hGKSYCbCL0Iavy3bbFHBsBPgbeJD
|
||||||
|
2BBN4953Iiy1Sak2eUy4b9LtkmaZmVAc7mpOFxLn38gD3icgB+bZPoGBw6b7sw71
|
||||||
|
Pc2R+hI9x/oNj0TUR11adhZApBJ9RhBbnSCUt8OUt9U5prNj+9qs8cHJGywtz1da
|
||||||
|
ZT1M6mn2MFSsaOyOlJPzGUzSf4AhI7HiouDpLHtHDqLmc8Vv4rZUqrcFw6kZTCY5
|
||||||
|
564yE8hh/UimOgQr7467/hADHZ0kBsupFEDWRqQ3qTIikHmGhTYZehDrSGL/3BMG
|
||||||
|
rvsFdv0krUHyW4FfHqPN09jfP3LTqd5vVbzRhxcGsoGmP/1kXIDtO8Cp1s/K6Mse
|
||||||
|
tInRCRla8ttZ3CZZ+Vf8HLi/n8XSRfbnMGYi7lVVxnp6kNsTEBgosbdU/r1zbMRJ
|
||||||
|
8mMMHygyugaRLHmOD/8fkWLfyR88cxPH8u9NufTfvJgiFpZboQKCAQEA0uSU6IGZ
|
||||||
|
pXIVZdmDWt4mxpS5T2UYarw3V9z/Isd3kkUU5YrC2XrvPRwmx5Jh9GXl9WENYJAR
|
||||||
|
wH7PaJT0HpBwpxJa1RqHHDSka7DnDcy44oRXyM7e2AmcW8QvcDty/0HPo4oZq4IT
|
||||||
|
m/+ot1R+bIpmJOweGRhVauzxJEUlQyt+kiH/ad8GiOS6LwqFPq42alnUxPQ106wF
|
||||||
|
EZZ2WQdzkyV6tF9aMG18AT1fJsGwNjCLRxJ52t/aEUP5mYwlL2UTT5Acn8KbtrTO
|
||||||
|
fFLAxGuB9LDdT1tGgIpzsXmxAaaeuPvSK4TDFdQyLAUdQJdz0GD9j9ciMPQH3UPe
|
||||||
|
Vjt6qtpfY6QK2wKCAQEAx36Vys5BlQI0TG6qORI0fiOYpLG1GqmdbCNRgBUsMj5T
|
||||||
|
LFe7uSd4qnDvGmns4MdkSSOlpF17bQiWhWKbjKRQpT0U/46zcIT4pWyajXJe+i9H
|
||||||
|
M/DpSRkMq2kGkx6KX2u9L66QBzcxJjtS17amdSpDAfsrvJgOWkxxInzw9n1u6aTe
|
||||||
|
ZjRDXdVX0KjPebEPOaoJToxne21Od3t+47TnDsQPsO1dvvrXX76IfH8cAlD5+0C/
|
||||||
|
b2YvDqWDmh9ICjKShwuDWgi4KjCV5PMHCIxH0FQ2L6mSbwIb9YgGin3wjN3KbWqz
|
||||||
|
dgEu7MeDxEwxZSSg4OstYVLQVgM39G/2ZA4YVJEbPwKCAQAo9FjymhBzb6c2Izp+
|
||||||
|
D/wpvkIKaBCI0cpRlso5P9E5p466UOsr/tKs5GWnhgbdxlgVAebuJKw93KJ8pciO
|
||||||
|
kvA9kbPwBHnOgW6Ytz73kBUrcBX4GixueddSftPTkMfxSB+Bm9UGWHlkZw6lo5P1
|
||||||
|
kh7p9qyVpQMZg7AEoiTtWWn4CQAn2DbVqM17Syi7Fmvc1VsbcG1vkM1fMAAFpAvO
|
||||||
|
vI2Kr6W9F9XoC7oJtb15mI3DnJPrbGNVzQSQzAWAoblRTyQv5kQFBDHBNPTYcCRJ
|
||||||
|
l3sy6P/VAI4dHgvAzVGvjL+w0dRszct8fvXCUGceRWeYYmfyZ8GLN53a0ywsN8Ik
|
||||||
|
gHvXAoIBACee5HEa9bt6bJihgf1DuFk1CKPtB2L8PN+1RAKEMfrolexAoG/tfvGa
|
||||||
|
7GH6l6ks8KX2BnfWeST2h66GHw6Xs8ydjQYUeV7nidqQ70EYbfSSXznZpvt1liaU
|
||||||
|
/VFKx4CcDT7jFIfaVlCZh6KADB9I/XXvRIh4SqF0fSO0XMcXsmeE7watapPAQ2iV
|
||||||
|
nl804yk4tBB9oi/JTcQ9Kr5et2UfW15wRiYf+5ZwaPsQ46cyHfPgsCSXztDB3plF
|
||||||
|
jTE5ShC4IKZJBQqcC6kk+0ifU8P0da6RpxuU96iUE3h9+sB/bCy+/FV7dq5gEbNy
|
||||||
|
znygAbOqAaFKqUXr7bkGY5ELm5lwGFECggEACcyaF9mMqLGghR55ew+cMmdeYdK3
|
||||||
|
meMLi5nrgtbQpVLlz+IV7Vdmrv7lZjeTr4nvU/5miU+p+If14CCFBiSucGq3Kmyp
|
||||||
|
OSM5cNCjDhw8uIDfY2qWCrZ2NSMR3qaAoBAQyQ2ER1IL98TDF/Qui0ZatbPiM4Ns
|
||||||
|
GErhkBZh3MCDSt24yiVKcUB79BxatWB4K7h7y8wqpX4Rj7rpfJMF7wz/I1cgyuCE
|
||||||
|
7XFpRwj7F1B2MmXnvV3KAgAD0EqrJDLeM0vIlDhpOUEaFUkuqmQyeB8qQkWfyXbD
|
||||||
|
jzloS3cNq0MBijB8oixwD2b4dVhBM7z8vQMX6OntN+97luWgO8OIukoYAg==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
`)
|
||||||
|
|
||||||
|
publicKeyBytes := []byte(`-----BEGIN RSA PUBLIC KEY-----
|
||||||
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApFf3qCQhAr2QLRRZdhLy
|
||||||
|
B8exLjrQiXLr8hwDe0xHSLJX3w7v5z4ujJWrHUulQ2/hvS8uffxMVrp2YqeA4sjy
|
||||||
|
/ku1KqZVQv/WNTdL2v9Z1ewbRnBjDQvmkWDKZ+cP8VPdHGzQ4iM2z6BZ4RQTkdQM
|
||||||
|
KqsVwUsLO9amI2TOny/M696eFRW0pk4+W3QZZRawT0HqJPvKKXKqoO2+62W54rOV
|
||||||
|
8glJi29Do06e4S6CZUl1hBUy0VlLtrLlRSOHTois0dF2a9f7+GGgU11MHO6w1k1N
|
||||||
|
esSlfZ0vnrrkW8WFLqewk+Jj+w1xeQnYHOeHjx6zi9f9DC96eSylSB3iJ71NmXcM
|
||||||
|
c0IEZ2LiQIqTL83BLOgMBCsK3FSlGMakQUR2GJ+M0I/selYkRMhid6eOmhlsTNMP
|
||||||
|
bpcTHxZ+ReIzS+5B1X0FZ7RIL+jSL9mFcxmD3dxurrrt/DkLpXcuWdi1s1bpVn3j
|
||||||
|
IQhU0+bgA6hT0k8Kj2f3Q9QnvkHSEff+XyLvLwQeSsSAcnM+1I7fNSPEo2cq0au6
|
||||||
|
ZtjHcirXmMminAQ6cKW1XrEvJBefHHibtjJjIM4bHH7MKRA5H3km/J4CCwI1VogS
|
||||||
|
TcE05Z6ypAFU2TCrnec9c9VXkRDP94h0GuoG8sdhmQudqvghr/8T7CV+sGRQbdeq
|
||||||
|
rXwanfnGPrjcMVIO/dSOxOUCAwEAAQ==
|
||||||
|
-----END RSA PUBLIC KEY-----
|
||||||
|
`)
|
||||||
|
|
||||||
|
targetFile := filepath.Join(testDir, "targetfile.yaml")
|
||||||
|
// tests/config.yaml exists
|
||||||
|
err := ioutil.WriteFile(targetFile, []byte(targetFileString), 0777)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error while creating file: %v", err)
|
||||||
|
return nil, nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
secretKeyPairDir := filepath.Join(testDir, secrets, contexts, qlikDefaultContext, secrets)
|
||||||
|
if err := os.MkdirAll(secretKeyPairDir, 0777); err != nil {
|
||||||
|
err = fmt.Errorf("Not able to create directories")
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
os.Setenv("QLIKSENSE_KEY_LOCATION", secretKeyPairDir)
|
||||||
|
|
||||||
|
privKeyFile := filepath.Join(secretKeyPairDir, "qliksensePriv")
|
||||||
|
// construct and write priv key file into secretsDir location
|
||||||
|
err = ioutil.WriteFile(privKeyFile, privKeyBytes, 0777)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error while creating file: %v", err)
|
||||||
|
return nil, nil, "", err
|
||||||
|
}
|
||||||
|
pubKeyFile := filepath.Join(secretKeyPairDir, "qliksensePub")
|
||||||
|
api.LogDebugMessage("Test setup - \npub key path: %s\n, priv key path: %s\n", pubKeyFile, privKeyFile)
|
||||||
|
// construct and write pub key file into secretsDir location
|
||||||
|
err = ioutil.WriteFile(pubKeyFile, publicKeyBytes, 0777)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error while creating file: %v", err)
|
||||||
|
return nil, nil, "", err
|
||||||
|
}
|
||||||
|
return publicKeyBytes, privKeyBytes, targetFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removePrivateKey() {
|
||||||
|
err := os.Remove(filepath.Join(testDir, secrets, contexts, qlikDefaultContext, secrets, "qliksensePriv"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Could not delete private key %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func setup() func() {
|
func setup() func() {
|
||||||
// create tests dir
|
// create tests dir
|
||||||
|
os.RemoveAll(testDir)
|
||||||
if err := os.Mkdir(testDir, 0777); err != nil {
|
if err := os.Mkdir(testDir, 0777); err != nil {
|
||||||
log.Printf("\nError occurred: %v", err)
|
log.Printf("\nError occurred: %v", err)
|
||||||
}
|
}
|
||||||
@@ -30,7 +165,7 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
contexts:
|
contexts:
|
||||||
- name: qlik-default
|
- name: qlik-default
|
||||||
crLocation: /root/.qliksense/contexts/qlik-default.yaml
|
crFile: contexts/qlik-default/qlik-default.yaml
|
||||||
currentContext: qlik-default
|
currentContext: qlik-default
|
||||||
`
|
`
|
||||||
configFile := filepath.Join(testDir, "config.yaml")
|
configFile := filepath.Join(testDir, "config.yaml")
|
||||||
@@ -65,6 +200,22 @@ spec:
|
|||||||
return tearDown
|
return tearDown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readCRFile() (*api.QliksenseCR, error) {
|
||||||
|
qlikDefaultContext := "qlik-default"
|
||||||
|
qliksenseCR := &api.QliksenseCR{}
|
||||||
|
contextFileContents, err := ioutil.ReadFile(filepath.Join(testDir, contexts, qlikDefaultContext, qlikDefaultContext+".yaml"))
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
err = fmt.Errorf("Not able to read current context info")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := yaml.Unmarshal(contextFileContents, qliksenseCR); err != nil {
|
||||||
|
err = fmt.Errorf("An error occurred during unmarshalling: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return qliksenseCR, nil
|
||||||
|
}
|
||||||
|
|
||||||
func Test_retrieveCurrentContextInfo(t *testing.T) {
|
func Test_retrieveCurrentContextInfo(t *testing.T) {
|
||||||
|
|
||||||
tearDown := setup()
|
tearDown := setup()
|
||||||
@@ -108,18 +259,23 @@ func TestSetUpQliksenseContext(t *testing.T) {
|
|||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "empty contextname",
|
||||||
|
args: args{
|
||||||
|
qlikSenseHome: testDir,
|
||||||
|
contextName: "",
|
||||||
|
isDefaultContext: false,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
tearDown := setup()
|
tearDown := setup()
|
||||||
defer tearDown()
|
defer tearDown()
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
q, err := New(tt.args.qlikSenseHome)
|
q := New(tt.args.qlikSenseHome)
|
||||||
if err != nil {
|
if err := q.SetUpQliksenseContext(tt.args.contextName); (err != nil) != tt.wantErr {
|
||||||
t.Errorf("unable to create a qliksense instance")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := q.SetUpQliksenseContext(tt.args.contextName, tt.args.isDefaultContext); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("SetUpQliksenseContext() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("SetUpQliksenseContext() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -147,11 +303,7 @@ func TestSetUpQliksenseDefaultContext(t *testing.T) {
|
|||||||
defer tearDown()
|
defer tearDown()
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
q, err := New(tt.args.qlikSenseHome)
|
q := New(tt.args.qlikSenseHome)
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unable to create a qliksense instance")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := q.SetUpQliksenseDefaultContext(); (err != nil) != tt.wantErr {
|
if err := q.SetUpQliksenseDefaultContext(); (err != nil) != tt.wantErr {
|
||||||
t.Errorf("SetUpQliksenseDefaultContext() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("SetUpQliksenseDefaultContext() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
}
|
}
|
||||||
@@ -175,10 +327,30 @@ func TestSetOtherConfigs(t *testing.T) {
|
|||||||
q: &Qliksense{
|
q: &Qliksense{
|
||||||
QliksenseHome: testDir,
|
QliksenseHome: testDir,
|
||||||
},
|
},
|
||||||
args: []string{"profile=minikube", "namespace=qliksense", "storageClassName=efs"},
|
args: []string{"profile=minikube", "rotateKeys=yes", "storageClassName=efs", "gitops.enabled=yes", "gitops.schedule=30 * * * *"},
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "invalid configs",
|
||||||
|
args: args{
|
||||||
|
q: &Qliksense{
|
||||||
|
QliksenseHome: testDir,
|
||||||
|
},
|
||||||
|
args: []string{"someconfig=somevalue, gitops.schedule=bar", "gitops.enabled=bar"},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty configs",
|
||||||
|
args: args{
|
||||||
|
q: &Qliksense{
|
||||||
|
QliksenseHome: testDir,
|
||||||
|
},
|
||||||
|
args: []string{},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
tearDown := setup()
|
tearDown := setup()
|
||||||
defer tearDown()
|
defer tearDown()
|
||||||
@@ -207,7 +379,7 @@ func TestSetConfigs(t *testing.T) {
|
|||||||
q: &Qliksense{
|
q: &Qliksense{
|
||||||
QliksenseHome: testDir,
|
QliksenseHome: testDir,
|
||||||
},
|
},
|
||||||
args: []string{"qliksense[name=acceptEULA]=\"yes\"", "qliksense[name=mongoDbUri]=\"mongo://mongo:3307\""},
|
args: []string{"qliksense.acceptEULA=\"yes\"", "qliksense.mongoDbUri=\"mongo://mongo:3307\""},
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
@@ -225,7 +397,7 @@ func TestSetConfigs(t *testing.T) {
|
|||||||
|
|
||||||
func TestSetImageRegistry(t *testing.T) {
|
func TestSetImageRegistry(t *testing.T) {
|
||||||
getQlikSense := func(tmpQlikSenseHome string) (*Qliksense, error) {
|
getQlikSense := func(tmpQlikSenseHome string) (*Qliksense, error) {
|
||||||
if err := ioutil.WriteFile(path.Join(tmpQlikSenseHome, "config.yaml"), []byte(fmt.Sprintf(`
|
if err := ioutil.WriteFile(path.Join(tmpQlikSenseHome, "config.yaml"), []byte(`
|
||||||
apiVersion: config.qlik.com/v1
|
apiVersion: config.qlik.com/v1
|
||||||
kind: QliksenseConfig
|
kind: QliksenseConfig
|
||||||
metadata:
|
metadata:
|
||||||
@@ -233,9 +405,9 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
contexts:
|
contexts:
|
||||||
- name: qlik-default
|
- name: qlik-default
|
||||||
crFile: %s/contexts/qlik-default/qlik-default.yaml
|
crFile: contexts/qlik-default/qlik-default.yaml
|
||||||
currentContext: qlik-default
|
currentContext: qlik-default
|
||||||
`, tmpQlikSenseHome)), os.ModePerm); err != nil {
|
`), os.ModePerm); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,20 +490,449 @@ spec:
|
|||||||
pushSecret.Username != testCase.pushUsername || pushSecret.Password != testCase.pushPassword {
|
pushSecret.Username != testCase.pushUsername || pushSecret.Password != testCase.pushPassword {
|
||||||
t.Fatalf("unexpected push secret content: %v", pushSecret)
|
t.Fatalf("unexpected push secret content: %v", pushSecret)
|
||||||
}
|
}
|
||||||
if pullSecret, err := qConfig.GetDockerConfigJsonSecret("image-registry-pull-secret.yaml"); err != nil {
|
if pullSecret, err := qConfig.GetPullDockerConfigJsonSecret(); err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
} else if pullSecret.Uri != testCase.registry ||
|
} else if pullSecret.Uri != testCase.registry ||
|
||||||
pullSecret.Name != "artifactory-docker-secret" || pullSecret.Namespace != "some-namespace" ||
|
pullSecret.Name != "artifactory-docker-secret" ||
|
||||||
pullSecret.Username != testCase.pullUsername || pullSecret.Password != testCase.pullPassword {
|
pullSecret.Username != testCase.pullUsername || pullSecret.Password != testCase.pullPassword {
|
||||||
t.Fatalf("unexpected pull secret content: %v", pullSecret)
|
t.Fatalf("unexpected pull secret content: %v", pullSecret)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if _, err := qConfig.GetPushDockerConfigJsonSecret(); err == nil {
|
if _, err := qConfig.GetPushDockerConfigJsonSecret(); err == nil {
|
||||||
t.Fatal("unexpected image-registry-push-secret.yaml")
|
t.Fatal("unexpected image-registry-push-secret.yaml")
|
||||||
} else if _, err := qConfig.GetDockerConfigJsonSecret("image-registry-pull-secret.yaml"); err == nil {
|
} else if _, err := qConfig.GetPullDockerConfigJsonSecret(); err == nil {
|
||||||
t.Fatal("unexpected image-registry-pull-secret.yaml")
|
t.Fatal("unexpected image-registry-pull-secret.yaml")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_PrepareK8sSecret(t *testing.T) {
|
||||||
|
|
||||||
|
type fields struct {
|
||||||
|
QliksenseHome string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
setup func() (string, func())
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid case",
|
||||||
|
fields: fields{
|
||||||
|
QliksenseHome: testDir,
|
||||||
|
},
|
||||||
|
want: fmt.Sprintf(targetFileStringTemplate, base64.StdEncoding.EncodeToString([]byte(decText))),
|
||||||
|
wantErr: false,
|
||||||
|
setup: func() (string, func()) {
|
||||||
|
tearDown := setup()
|
||||||
|
_, _, targetFile, _ := setupTargetFileAndPrivateKey()
|
||||||
|
return targetFile, tearDown
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "private key not supplied should result in decryption error",
|
||||||
|
fields: fields{
|
||||||
|
QliksenseHome: testDir,
|
||||||
|
},
|
||||||
|
want: "",
|
||||||
|
wantErr: true,
|
||||||
|
setup: func() (string, func()) {
|
||||||
|
tearDown := setup()
|
||||||
|
_, _, targetFile, _ := setupTargetFileAndPrivateKey()
|
||||||
|
removePrivateKey()
|
||||||
|
return targetFile, tearDown
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "target file not supplied",
|
||||||
|
fields: fields{
|
||||||
|
QliksenseHome: testDir,
|
||||||
|
},
|
||||||
|
want: "",
|
||||||
|
wantErr: true,
|
||||||
|
setup: func() (string, func()) {
|
||||||
|
tearDown := setup()
|
||||||
|
_, _, _, _ = setupTargetFileAndPrivateKey()
|
||||||
|
removePrivateKey()
|
||||||
|
return "", tearDown
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
targetFile, tearDown := tt.setup()
|
||||||
|
|
||||||
|
q := &Qliksense{
|
||||||
|
QliksenseHome: tt.fields.QliksenseHome,
|
||||||
|
}
|
||||||
|
got, err := q.PrepareK8sSecret(targetFile)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Qliksense.PrepareK8sSecret() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(strings.TrimSpace(got), strings.TrimSpace(tt.want)) {
|
||||||
|
t.Errorf("Qliksense.PrepareK8sSecret() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
tearDown()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ListContextConfigs(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
QliksenseHome string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
wantErr bool
|
||||||
|
setup func() (string, func())
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid case",
|
||||||
|
fields: fields{
|
||||||
|
QliksenseHome: testDir,
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
setup: func() (string, func()) {
|
||||||
|
tearDown := setup()
|
||||||
|
return "", tearDown
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config yaml does not exist",
|
||||||
|
fields: fields{
|
||||||
|
QliksenseHome: testDir,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
setup: func() (string, func()) {
|
||||||
|
return "", func() {}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
_, tearDown := tt.setup()
|
||||||
|
|
||||||
|
q := &Qliksense{
|
||||||
|
QliksenseHome: tt.fields.QliksenseHome,
|
||||||
|
}
|
||||||
|
if err := q.ListContextConfigs(); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("ListContextConfigs() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
tearDown()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_SetSecrets(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
QliksenseHome string
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
args []string
|
||||||
|
isSecretSet bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid secret secrets=false",
|
||||||
|
fields: fields{
|
||||||
|
QliksenseHome: testDir,
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
args: []string{"qliksense.mongoDbUri=\"mongodb://qlik-default-mongodb:27017/qliksense?ssl=false\""},
|
||||||
|
isSecretSet: false,
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test1 valid secret secrets=true",
|
||||||
|
fields: fields{
|
||||||
|
QliksenseHome: testDir,
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
args: []string{"qliksense.mongoDbUri=\"mongo://mongo:3307\""},
|
||||||
|
isSecretSet: true,
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test2 valid secret secrets=true",
|
||||||
|
fields: fields{
|
||||||
|
QliksenseHome: testDir,
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
args: []string{"qliksense.mongoDbUri=\"mongodb://qlik-default-mongodb:27017/qliksense?ssl=false\""},
|
||||||
|
isSecretSet: true,
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid secret secrets=false",
|
||||||
|
fields: fields{
|
||||||
|
QliksenseHome: testDir,
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
args: []string{},
|
||||||
|
isSecretSet: false,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tearDown := setup()
|
||||||
|
_, privateKeyBytes, _, err := setupTargetFileAndPrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
privKey, err := api.DecodeToPrivateKey(privateKeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
q := &Qliksense{
|
||||||
|
QliksenseHome: tt.fields.QliksenseHome,
|
||||||
|
}
|
||||||
|
if err := q.SetSecrets(tt.args.args, tt.args.isSecretSet); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("SetSecrets() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if tt.wantErr || len(tt.args.args) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// VERIFICATION PART BELOW
|
||||||
|
// extract the value for testing
|
||||||
|
testValueArr := strings.SplitN(tt.args.args[0], "=", 2)
|
||||||
|
testValue := strings.ReplaceAll(testValueArr[1], "\"", "")
|
||||||
|
|
||||||
|
qliksenseCR, err := readCRFile()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Not able to read from context file: %v", err)
|
||||||
|
log.Println(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
for svcName := range qliksenseCR.Spec.Secrets { // we are sure we only have one service
|
||||||
|
for _, v := range qliksenseCR.Spec.Secrets {
|
||||||
|
for _, item := range v { // we are sure we only have one entry
|
||||||
|
valToBeEncrypted, err := getValueToBeDecodedForSetSecrets(item, qliksenseCR, svcName)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error occurred while decoding: %v", err)
|
||||||
|
log.Printf("decode error: %v", err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
decodedValue, err := b64.StdEncoding.DecodeString(valToBeEncrypted)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error occurred while decoding: %v", err)
|
||||||
|
log.Printf("decode error: %v", err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
decryptedVal, err := api.Decrypt(decodedValue, privKey)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error occurred while testing decryption: %v", err)
|
||||||
|
log.Printf("No Data in Secret: %v", err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if string(decryptedVal) != testValue {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getValueToBeDecodedForSetSecrets(item config.NameValue, qliksenseCR *api.QliksenseCR, svcName string) (string, error) {
|
||||||
|
if item.ValueFrom != nil && item.ValueFrom.SecretKeyRef != nil {
|
||||||
|
// secret=true
|
||||||
|
secretFilePath := filepath.Join(testDir, contexts, qliksenseCR.GetName(), QliksenseSecretsDir, svcName+".yaml")
|
||||||
|
if api.FileExists(secretFilePath) {
|
||||||
|
secretFileContents, err := ioutil.ReadFile(secretFilePath)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("An error occurred during unmarshalling: %v", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
k8sSecret, err := api.K8sSecretFromYaml(secretFileContents)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("An error occurred during unmarshalling: %v", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if k8sSecret.Data == nil {
|
||||||
|
err = fmt.Errorf("No Data in Secret: %v", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(k8sSecret.Data[item.ValueFrom.SecretKeyRef.Key]), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// secret=false
|
||||||
|
if item.Value != "" {
|
||||||
|
return item.Value, nil
|
||||||
|
}
|
||||||
|
err := fmt.Errorf("Both Value and ValueFrom are empty")
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupDeleteContext() func() {
|
||||||
|
if err := os.Mkdir(testDir, 0777); err != nil {
|
||||||
|
log.Printf("\nError occurred: %v", err)
|
||||||
|
}
|
||||||
|
config :=
|
||||||
|
`
|
||||||
|
apiVersion: config.qlik.com/v1
|
||||||
|
kind: QliksenseConfig
|
||||||
|
metadata:
|
||||||
|
name: qliksenseConfig
|
||||||
|
spec:
|
||||||
|
contexts:
|
||||||
|
- name: qlik-default
|
||||||
|
crFile: contexts/qlik-default.yaml
|
||||||
|
- name: qlik1
|
||||||
|
crFile: contexts/qlik1.yaml
|
||||||
|
- name: qlik2
|
||||||
|
crFile: contexts/qlik2.yaml
|
||||||
|
currentContext: qlik1
|
||||||
|
`
|
||||||
|
configFile := filepath.Join(testDir, "config.yaml")
|
||||||
|
// tests/config.yaml exists
|
||||||
|
ioutil.WriteFile(configFile, []byte(config), 0777)
|
||||||
|
contextYaml :=
|
||||||
|
`
|
||||||
|
apiVersion: qlik.com/v1
|
||||||
|
kind: Qliksense
|
||||||
|
metadata:
|
||||||
|
name: qlik-default
|
||||||
|
spec:
|
||||||
|
profile: docker-desktop
|
||||||
|
rotateKeys: "yes"
|
||||||
|
releaseName: qlik-default
|
||||||
|
`
|
||||||
|
qlikDefaultContext := "qlik-default"
|
||||||
|
// create contexts/qlik-default/ under tests/
|
||||||
|
contexts := "contexts"
|
||||||
|
contextsDir1 := filepath.Join(testDir, contexts, qlikDefaultContext)
|
||||||
|
if err := os.MkdirAll(contextsDir1, 0777); err != nil {
|
||||||
|
err = fmt.Errorf("Not able to create directories")
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
contextYaml1 :=
|
||||||
|
`
|
||||||
|
apiVersion: qlik.com/v1
|
||||||
|
kind: Qliksense
|
||||||
|
metadata:
|
||||||
|
name: qlik1
|
||||||
|
spec:
|
||||||
|
profile: docker-desktop
|
||||||
|
rotateKeys: "yes"
|
||||||
|
releaseName: qlik1`
|
||||||
|
|
||||||
|
contextYaml2 :=
|
||||||
|
`
|
||||||
|
apiVersion: qlik.com/v1
|
||||||
|
kind: Qliksense
|
||||||
|
metadata:
|
||||||
|
name: qlik2
|
||||||
|
spec:
|
||||||
|
profile: docker-desktop
|
||||||
|
rotateKeys: "yes"
|
||||||
|
releaseName: qlik2`
|
||||||
|
|
||||||
|
contextsDir := filepath.Join(testDir, contexts, "qlik1")
|
||||||
|
if err := os.MkdirAll(contextsDir, 0777); err != nil {
|
||||||
|
err = fmt.Errorf("Not able to create directories")
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
contextsDir2 := filepath.Join(testDir, contexts, "qlik2")
|
||||||
|
if err := os.MkdirAll(contextsDir2, 0777); err != nil {
|
||||||
|
err = fmt.Errorf("Not able to create directories")
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
contextFile := filepath.Join(contextsDir, "qlik1.yaml")
|
||||||
|
ioutil.WriteFile(contextFile, []byte(contextYaml1), 0777)
|
||||||
|
|
||||||
|
contextFile2 := filepath.Join(contextsDir2, "qlik2.yaml")
|
||||||
|
ioutil.WriteFile(contextFile2, []byte(contextYaml2), 0777)
|
||||||
|
|
||||||
|
contextFile1 := filepath.Join(contextsDir1, "qlik-default.yaml")
|
||||||
|
ioutil.WriteFile(contextFile1, []byte(contextYaml), 0777)
|
||||||
|
|
||||||
|
tearDown := func() {
|
||||||
|
os.RemoveAll(testDir)
|
||||||
|
}
|
||||||
|
return tearDown
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteContexts(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
qlikSenseHome string
|
||||||
|
contextName string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid context",
|
||||||
|
args: args{
|
||||||
|
qlikSenseHome: testDir,
|
||||||
|
contextName: "qlik2",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default context",
|
||||||
|
args: args{
|
||||||
|
qlikSenseHome: testDir,
|
||||||
|
contextName: "qlik-default",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-existent context",
|
||||||
|
args: args{
|
||||||
|
qlikSenseHome: testDir,
|
||||||
|
contextName: "qlik3",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "current context",
|
||||||
|
args: args{
|
||||||
|
qlikSenseHome: testDir,
|
||||||
|
contextName: "qlik1",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tearDown := setupDeleteContext()
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
q := New(tt.args.qlikSenseHome)
|
||||||
|
var arg []string
|
||||||
|
arg = append(arg, tt.args.contextName)
|
||||||
|
if err := q.DeleteContextConfig(arg, true); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("DeleteContext() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package qliksense
|
package qliksense
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CrdCommandOptions struct {
|
type CrdCommandOptions struct {
|
||||||
@@ -40,11 +40,11 @@ func (q *Qliksense) InstallCrds(opts *CrdCommandOptions) error {
|
|||||||
|
|
||||||
if engineCRD, err := getQliksenseInitCrd(qcr); err != nil {
|
if engineCRD, err := getQliksenseInitCrd(qcr); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if err = qapi.KubectlApply(engineCRD, qcr.Spec.NameSpace); err != nil {
|
} else if err = qapi.KubectlApply(engineCRD, ""); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if opts.All { // install opeartor crd
|
if opts.All { // install opeartor crd
|
||||||
if err := qapi.KubectlApply(q.GetOperatorCRDString(), qcr.Spec.NameSpace); err != nil {
|
if err := qapi.KubectlApply(q.GetOperatorCRDString(), ""); err != nil {
|
||||||
fmt.Println("cannot do kubectl apply on opeartor CRD", err)
|
fmt.Println("cannot do kubectl apply on opeartor CRD", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -53,13 +53,18 @@ func (q *Qliksense) InstallCrds(opts *CrdCommandOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getQliksenseInitCrd(qcr *qapi.QliksenseCR) (string, error) {
|
func getQliksenseInitCrd(qcr *qapi.QliksenseCR) (string, error) {
|
||||||
|
var repoPath string
|
||||||
|
var err error
|
||||||
|
|
||||||
if qcr.Spec.GetManifestsRoot() == "" {
|
if qcr.Spec.GetManifestsRoot() != "" {
|
||||||
return "", errors.New("Cannot find manifests root. Please use `qliksense fetch <version>`")
|
repoPath = qcr.Spec.GetManifestsRoot()
|
||||||
|
} else {
|
||||||
|
if repoPath, err = downloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master"); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qInitMsPath := filepath.Join(qcr.Spec.GetManifestsRoot(), Q_INIT_CRD_PATH)
|
qInitMsPath := filepath.Join(repoPath, Q_INIT_CRD_PATH)
|
||||||
|
|
||||||
qInitByte, err := executeKustomizeBuild(qInitMsPath)
|
qInitByte, err := executeKustomizeBuild(qInitMsPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("cannot generate crds for qliksense-init", err)
|
fmt.Println("cannot generate crds for qliksense-init", err)
|
||||||
|
|||||||
41
pkg/qliksense/crds_test.go
Normal file
41
pkg/qliksense/crds_test.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package qliksense
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
kapi_config "github.com/qlik-oss/k-apis/pkg/config"
|
||||||
|
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetQliksenseInitCrd(t *testing.T) {
|
||||||
|
someTmpRepoPath, err := downloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
crdFromContextConfig, err := getQliksenseInitCrd(&qapi.QliksenseCR{
|
||||||
|
KApiCr: kapi_config.KApiCr{
|
||||||
|
Spec: &kapi_config.CRSpec{
|
||||||
|
ManifestsRoot: someTmpRepoPath,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
crdFromDownloadedConfig, err := getQliksenseInitCrd(&qapi.QliksenseCR{
|
||||||
|
KApiCr: kapi_config.KApiCr{
|
||||||
|
Spec: &kapi_config.CRSpec{
|
||||||
|
ManifestsRoot: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if crdFromContextConfig != crdFromDownloadedConfig {
|
||||||
|
t.Fatalf("expected %v to equal %v, but they didn't", crdFromContextConfig, crdFromDownloadedConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,7 +48,12 @@ func (q *Qliksense) PullImagesForCurrentCR() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, image := range versionOut.Images {
|
images := versionOut.Images
|
||||||
|
if err := q.appendOperatorImages(&images); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, image := range images {
|
||||||
if err := pullImage(image, imagesDir); err != nil {
|
if err := pullImage(image, imagesDir); err != nil {
|
||||||
fmt.Printf("%v\n", err)
|
fmt.Printf("%v\n", err)
|
||||||
return err
|
return err
|
||||||
@@ -64,6 +69,15 @@ func (q *Qliksense) PullImagesForCurrentCR() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *Qliksense) appendOperatorImages(images *[]string) error {
|
||||||
|
if operatorImages, err := getImageList([]byte(q.GetOperatorControllerString())); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
*images = append(*images, operatorImages...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func pullImage(image, imagesDir string) error {
|
func pullImage(image, imagesDir string) error {
|
||||||
srcRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%v", image))
|
srcRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%v", image))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -134,7 +148,12 @@ func (q *Qliksense) PushImagesForCurrentCR() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, image := range versionOut.Images {
|
images := versionOut.Images
|
||||||
|
if err := q.appendOperatorImages(&images); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, image := range images {
|
||||||
if err = pushImage(image, imagesDir, dockerConfigJsonSecret); err != nil {
|
if err = pushImage(image, imagesDir, dockerConfigJsonSecret); err != nil {
|
||||||
fmt.Printf("%v\n", err)
|
fmt.Printf("%v\n", err)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gobuffalo/packr/v2"
|
||||||
|
|
||||||
"github.com/containers/image/v5/copy"
|
"github.com/containers/image/v5/copy"
|
||||||
"github.com/containers/image/v5/signature"
|
"github.com/containers/image/v5/signature"
|
||||||
"github.com/containers/image/v5/transports/alltransports"
|
"github.com/containers/image/v5/transports/alltransports"
|
||||||
@@ -47,7 +49,9 @@ func Test_locateDockerRegistryBinary(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Test_getSelfSignedCertAndKey(t *testing.T) {
|
func Test_getSelfSignedCertAndKey(t *testing.T) {
|
||||||
selfSignedCert, key, err := getSelfSignedCertAndKey()
|
host := "andriy.registry.com"
|
||||||
|
validity := time.Hour * 24 * 365
|
||||||
|
selfSignedCert, key, err := getSelfSignedCertAndKey(host, validity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -125,7 +129,10 @@ func Test_Pull_Push_ImagesForCurrentCR(t *testing.T) {
|
|||||||
if err := setupQlikSenseHome(t, tmpQlikSenseHome, registry, testCase.clientAuth); err != nil {
|
if err := setupQlikSenseHome(t, tmpQlikSenseHome, registry, testCase.clientAuth); err != nil {
|
||||||
t.Fatalf("unexpected error setting up qliksense home: %v", err)
|
t.Fatalf("unexpected error setting up qliksense home: %v", err)
|
||||||
}
|
}
|
||||||
q := &Qliksense{QliksenseHome: tmpQlikSenseHome}
|
q := &Qliksense{
|
||||||
|
QliksenseHome: tmpQlikSenseHome,
|
||||||
|
CrdBox: packr.New("crds", "./crds"),
|
||||||
|
}
|
||||||
var versionOut VersionOutput
|
var versionOut VersionOutput
|
||||||
|
|
||||||
if err := q.PullImagesForCurrentCR(); err != nil {
|
if err := q.PullImagesForCurrentCR(); err != nil {
|
||||||
@@ -135,7 +142,7 @@ func Test_Pull_Push_ImagesForCurrentCR(t *testing.T) {
|
|||||||
} else if err = yaml.Unmarshal(versionOutBytes, &versionOut); err != nil {
|
} else if err = yaml.Unmarshal(versionOutBytes, &versionOut); err != nil {
|
||||||
t.Fatalf("unexpected error unmarshalling version file: %v", err)
|
t.Fatalf("unexpected error unmarshalling version file: %v", err)
|
||||||
} else if len(versionOut.Images) != 1 || versionOut.Images[0] != "alpine:latest" {
|
} else if len(versionOut.Images) != 1 || versionOut.Images[0] != "alpine:latest" {
|
||||||
t.Fatal("did not find alpine:latest in the version file")
|
t.Fatal(`did not find "alpine:latest"" in the version file`)
|
||||||
} else if infos, err := ioutil.ReadDir(path.Join(tmpQlikSenseHome, "images", "index", "alpine", "latest")); err != nil || len(infos) == 0 {
|
} else if infos, err := ioutil.ReadDir(path.Join(tmpQlikSenseHome, "images", "index", "alpine", "latest")); err != nil || len(infos) == 0 {
|
||||||
t.Fatal("expected images/index/alpine/latest directory to be non-empty")
|
t.Fatal("expected images/index/alpine/latest directory to be non-empty")
|
||||||
} else if blobInfos, err := ioutil.ReadDir(path.Join(tmpQlikSenseHome, "images", "blobs", "sha256")); err != nil || len(blobInfos) == 0 {
|
} else if blobInfos, err := ioutil.ReadDir(path.Join(tmpQlikSenseHome, "images", "blobs", "sha256")); err != nil || len(blobInfos) == 0 {
|
||||||
@@ -164,7 +171,7 @@ func Test_Pull_Push_ImagesForCurrentCR(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setupQlikSenseHome(t *testing.T, tmpQlikSenseHome string, registry *testRegistryV2, clientAuth clientAuthType) error {
|
func setupQlikSenseHome(t *testing.T, tmpQlikSenseHome string, registry *testRegistryV2, clientAuth clientAuthType) error {
|
||||||
if err := ioutil.WriteFile(path.Join(tmpQlikSenseHome, "config.yaml"), []byte(fmt.Sprintf(`
|
if err := ioutil.WriteFile(path.Join(tmpQlikSenseHome, "config.yaml"), []byte(`
|
||||||
apiVersion: config.qlik.com/v1
|
apiVersion: config.qlik.com/v1
|
||||||
kind: QliksenseConfig
|
kind: QliksenseConfig
|
||||||
metadata:
|
metadata:
|
||||||
@@ -172,9 +179,9 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
contexts:
|
contexts:
|
||||||
- name: qlik-default
|
- name: qlik-default
|
||||||
crFile: %s/contexts/qlik-default/qlik-default.yaml
|
crFile: contexts/qlik-default/qlik-default.yaml
|
||||||
currentContext: qlik-default
|
currentContext: qlik-default
|
||||||
`, tmpQlikSenseHome)), os.ModePerm); err != nil {
|
`), os.ModePerm); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,7 +352,7 @@ http:
|
|||||||
)
|
)
|
||||||
var env []string
|
var env []string
|
||||||
if auth {
|
if auth {
|
||||||
if certificate, key, err := getSelfSignedCertAndKey(); err != nil {
|
if certificate, key, err := getSelfSignedCertAndKey("localhost", time.Hour*24); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
certPath := filepath.Join(tmp, "domain.crt")
|
certPath := filepath.Join(tmp, "domain.crt")
|
||||||
@@ -464,7 +471,7 @@ func consumeAndLogOutputs(id string, cmd *exec.Cmd) (*bytes.Buffer, *bytes.Buffe
|
|||||||
return consumeAndLogOutputStream(id+" stdout", stdout), consumeAndLogOutputStream(id+" stderr", stderr), nil
|
return consumeAndLogOutputStream(id+" stdout", stdout), consumeAndLogOutputStream(id+" stderr", stderr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSelfSignedCertAndKey() (certificate, key []byte, err error) {
|
func getSelfSignedCertAndKey(hostname string, validity time.Duration) (certificate, key []byte, err error) {
|
||||||
priv, err := rsa.GenerateKey(rand.Reader, 4096)
|
priv, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -477,13 +484,14 @@ func getSelfSignedCertAndKey() (certificate, key []byte, err error) {
|
|||||||
template := x509.Certificate{
|
template := x509.Certificate{
|
||||||
SerialNumber: serialNumber,
|
SerialNumber: serialNumber,
|
||||||
Subject: pkix.Name{
|
Subject: pkix.Name{
|
||||||
Organization: []string{"Acme Co"},
|
Organization: []string{"self-signed"},
|
||||||
},
|
},
|
||||||
NotBefore: time.Now(),
|
NotBefore: time.Now(),
|
||||||
NotAfter: time.Now().Add(time.Hour * 24),
|
NotAfter: time.Now().Add(validity),
|
||||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
|
DNSNames: []string{hostname},
|
||||||
}
|
}
|
||||||
|
|
||||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||||
|
|||||||
35
pkg/qliksense/fetch_test.go
Normal file
35
pkg/qliksense/fetch_test.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package qliksense
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFetchAndUpdateCR(t *testing.T) {
|
||||||
|
tempHome, _ := ioutil.TempDir("", "")
|
||||||
|
|
||||||
|
q := &Qliksense{
|
||||||
|
QliksenseHome: tempHome,
|
||||||
|
}
|
||||||
|
q.SetUpQliksenseContext("test1")
|
||||||
|
qConfig := qapi.NewQConfig(tempHome)
|
||||||
|
if err := fetchAndUpdateCR(qConfig, "v0.0.2"); err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
actualCrFile := filepath.Join(tempHome, "contexts", "test1", "test1.yaml")
|
||||||
|
cr := &qapi.QliksenseCR{}
|
||||||
|
if err := qapi.ReadFromFile(cr, actualCrFile); err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if cr.Spec.ManifestsRoot != "contexts/test1/qlik-k8s/v0.0.2" {
|
||||||
|
t.Log("actual path: " + cr.Spec.ManifestsRoot + ", expected path: contexts/test1/qlik-k8s/v0.0.2")
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
88
pkg/qliksense/get_installable_versions.go
Normal file
88
pkg/qliksense/get_installable_versions.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package qliksense
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qlik-oss/k-apis/pkg/git"
|
||||||
|
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LsRemoteCmdOptions struct {
|
||||||
|
IncludeBranches bool
|
||||||
|
Limit int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Qliksense) GetInstallableVersions(opts *LsRemoteCmdOptions) error {
|
||||||
|
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||||
|
qcr, err := qConfig.GetCurrentCR()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var repoPath string
|
||||||
|
if qcr.Spec.GetManifestsRoot() != "" {
|
||||||
|
repoPath = qcr.Spec.GetManifestsRoot()
|
||||||
|
} else {
|
||||||
|
repoPath, err = downloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master")
|
||||||
|
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: opts.IncludeBranches,
|
||||||
|
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) > opts.Limit {
|
||||||
|
tags = tags[:opts.Limit]
|
||||||
|
}
|
||||||
|
fmt.Print("Versions:\n")
|
||||||
|
for _, tag := range tags {
|
||||||
|
fmt.Printf(" %s\n", tag)
|
||||||
|
}
|
||||||
|
if opts.IncludeBranches {
|
||||||
|
branches := originRemoteRefs.Branches
|
||||||
|
if len(branches) > opts.Limit {
|
||||||
|
branches = branches[:opts.Limit]
|
||||||
|
}
|
||||||
|
fmt.Print("Branches:\n")
|
||||||
|
for _, branch := range branches {
|
||||||
|
fmt.Printf(" %s\n", branch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -3,19 +3,21 @@ package qliksense
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/qlik-oss/k-apis/pkg/config"
|
||||||
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
|
|
||||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InstallCommandOptions struct {
|
type InstallCommandOptions struct {
|
||||||
AcceptEULA string
|
|
||||||
Namespace string
|
|
||||||
StorageClass string
|
StorageClass string
|
||||||
MongoDbUri string
|
MongoDbUri string
|
||||||
RotateKeys string
|
RotateKeys string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) error {
|
func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, keepPatchFiles bool) error {
|
||||||
|
|
||||||
// step1: fetch 1.0.0 # pull down qliksense-k8s@1.0.0
|
// step1: fetch 1.0.0 # pull down qliksense-k8s@1.0.0
|
||||||
// step2: operator view | kubectl apply -f # operator manifest (CRD)
|
// step2: operator view | kubectl apply -f # operator manifest (CRD)
|
||||||
@@ -24,6 +26,13 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) err
|
|||||||
|
|
||||||
// fetch the version
|
// fetch the version
|
||||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||||
|
if !keepPatchFiles {
|
||||||
|
defer func() {
|
||||||
|
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
|
||||||
|
fmt.Printf("error removing temporary changes to the config: %v\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
qcr, err := qConfig.GetCurrentCR()
|
qcr, err := qConfig.GetCurrentCR()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -31,34 +40,59 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) err
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.AcceptEULA != "" {
|
qcr.SetEULA("yes")
|
||||||
qcr.Spec.AddToConfigs("qliksense", "acceptEULA", opts.AcceptEULA)
|
|
||||||
}
|
|
||||||
if opts.MongoDbUri != "" {
|
if opts.MongoDbUri != "" {
|
||||||
qcr.Spec.AddToSecrets("qliksense", "mongoDbUri", opts.MongoDbUri, "")
|
qcr.Spec.AddToSecrets("qliksense", "mongoDbUri", opts.MongoDbUri, "")
|
||||||
}
|
}
|
||||||
if opts.StorageClass != "" {
|
if opts.StorageClass != "" {
|
||||||
qcr.Spec.StorageClassName = opts.StorageClass
|
qcr.Spec.StorageClassName = opts.StorageClass
|
||||||
}
|
}
|
||||||
if opts.Namespace != "" {
|
|
||||||
qcr.Spec.NameSpace = opts.Namespace
|
|
||||||
}
|
|
||||||
if opts.RotateKeys != "" {
|
if opts.RotateKeys != "" {
|
||||||
qcr.Spec.RotateKeys = opts.RotateKeys
|
qcr.Spec.RotateKeys = opts.RotateKeys
|
||||||
}
|
}
|
||||||
qConfig.WriteCurrentContextCR(qcr)
|
qConfig.WriteCurrentContextCR(qcr)
|
||||||
|
|
||||||
|
//if the docker pull secret exists on disk, install it in the cluster
|
||||||
|
//if it doesn't exist on disk, remove it in the cluster
|
||||||
|
if err := installOrRemoveImagePullSecret(qConfig); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if acceptEULA is yes or not
|
||||||
|
if !qcr.IsEULA() {
|
||||||
|
return errors.New(agreementTempalte + "\n Please do $ qliksense install --acceptEULA=yes\n")
|
||||||
|
}
|
||||||
|
|
||||||
//CRD will be installed outside of operator
|
//CRD will be installed outside of operator
|
||||||
//install operator controller into the namespace
|
//install operator controller into the namespace
|
||||||
fmt.Println("Installing operator controller")
|
fmt.Println("Installing operator controller")
|
||||||
if err := qapi.KubectlApply(q.GetOperatorControllerString(), qcr.Spec.NameSpace); err != nil {
|
operatorControllerString := q.GetOperatorControllerString()
|
||||||
|
if imageRegistry := qcr.GetImageRegistry(); imageRegistry != "" {
|
||||||
|
operatorControllerString, err = kustomizeForImageRegistry(operatorControllerString, pullSecretName,
|
||||||
|
"qlik/qliksense-operator", fmt.Sprintf("%v/qliksense-operator", imageRegistry))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := qapi.KubectlApply(operatorControllerString, ""); err != nil {
|
||||||
fmt.Println("cannot do kubectl apply on opeartor controller", err)
|
fmt.Println("cannot do kubectl apply on opeartor controller", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if qcr.Spec.Git.Repository != "" {
|
// create patch dependent resoruces
|
||||||
|
fmt.Println("Installing resoruces used kuztomize patch")
|
||||||
|
if err := q.createK8sResoruceBeforePatch(qcr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if qcr.Spec.Git != nil && qcr.Spec.Git.Repository != "" {
|
||||||
// fetching and applying manifest will be in the operator controller
|
// fetching and applying manifest will be in the operator controller
|
||||||
return q.applyCR(qcr.Spec.NameSpace)
|
// get decrypted cr
|
||||||
|
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
return q.applyCR(dcr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if version != "" { // no need to fetch manifest root already set by some other way
|
if version != "" { // no need to fetch manifest root already set by some other way
|
||||||
if err := fetchAndUpdateCR(qConfig, version); err != nil {
|
if err := fetchAndUpdateCR(qConfig, version); err != nil {
|
||||||
@@ -76,25 +110,100 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) err
|
|||||||
|
|
||||||
// install generated manifests into cluster
|
// install generated manifests into cluster
|
||||||
fmt.Println("Installing generated manifests into cluster")
|
fmt.Println("Installing generated manifests into cluster")
|
||||||
if err := q.applyConfigToK8s(qcr); err != nil {
|
|
||||||
|
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
|
||||||
|
return err
|
||||||
|
} else if err := q.applyConfigToK8s(dcr); err != nil {
|
||||||
fmt.Println("cannot do kubectl apply on manifests")
|
fmt.Println("cannot do kubectl apply on manifests")
|
||||||
return err
|
return err
|
||||||
|
} else {
|
||||||
|
return q.applyCR(dcr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return q.applyCR(qcr.Spec.NameSpace)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Qliksense) applyCR(ns string) error {
|
func installOrRemoveImagePullSecret(qConfig *qapi.QliksenseConfig) error {
|
||||||
|
if pullDockerConfigJsonSecret, err := qConfig.GetPullDockerConfigJsonSecret(); err == nil {
|
||||||
|
if dockerConfigJsonSecretYaml, err := pullDockerConfigJsonSecret.ToYaml(nil); err != nil {
|
||||||
|
return err
|
||||||
|
} else if err := qapi.KubectlApply(string(dockerConfigJsonSecretYaml), ""); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deleteDockerConfigJsonSecret := qapi.DockerConfigJsonSecret{
|
||||||
|
Name: pullSecretName,
|
||||||
|
}
|
||||||
|
if deleteDockerConfigJsonSecretYaml, err := deleteDockerConfigJsonSecret.ToYaml(nil); err != nil {
|
||||||
|
return err
|
||||||
|
} else if err := qapi.KubectlDelete(string(deleteDockerConfigJsonSecretYaml), ""); err != nil {
|
||||||
|
qapi.LogDebugMessage("failed deleting %v, error: %v\n", pullSecretName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func kustomizeForImageRegistry(resources, dockerConfigJsonSecretName, name, newName string) (string, error) {
|
||||||
|
fSys := filesys.MakeFsInMemory()
|
||||||
|
if err := fSys.WriteFile("/resources.yaml", []byte(resources)); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if err := fSys.WriteFile("/addImagePullSecrets.yaml", []byte(fmt.Sprintf(`
|
||||||
|
apiVersion: builtin
|
||||||
|
kind: PatchTransformer
|
||||||
|
metadata:
|
||||||
|
name: notImportantHere
|
||||||
|
patch: '[{"op": "add", "path": "/spec/template/spec/imagePullSecrets", "value": [{"name": "%v"}]}]'
|
||||||
|
target:
|
||||||
|
name: .*-operator
|
||||||
|
kind: Deployment
|
||||||
|
`, dockerConfigJsonSecretName))); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if err := fSys.WriteFile("/kustomization.yaml", []byte(fmt.Sprintf(`
|
||||||
|
resources:
|
||||||
|
- resources.yaml
|
||||||
|
transformers:
|
||||||
|
- addImagePullSecrets.yaml
|
||||||
|
images:
|
||||||
|
- name: %s
|
||||||
|
newName: %s
|
||||||
|
`, name, newName))); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if out, err := executeKustomizeBuildForFileSystem("/", fSys); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else {
|
||||||
|
return string(out), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Qliksense) applyCR(cr *qapi.QliksenseCR) error {
|
||||||
// install operator cr into cluster
|
// install operator cr into cluster
|
||||||
//get the current context cr
|
//get the current context cr
|
||||||
fmt.Println("Install operator CR into cluster")
|
fmt.Println("Install operator CR into cluster")
|
||||||
r, err := q.getCurrentCRString()
|
r, err := cr.GetString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := qapi.KubectlApply(r, ns); err != nil {
|
if err := qapi.KubectlApply(r, ""); err != nil {
|
||||||
fmt.Println("cannot do kubectl apply on operator CR")
|
fmt.Println("cannot do kubectl apply on operator CR")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *Qliksense) createK8sResoruceBeforePatch(qcr *qapi.QliksenseCR) error {
|
||||||
|
for svc, nvs := range qcr.Spec.Secrets {
|
||||||
|
for _, nv := range nvs {
|
||||||
|
if isK8sSecretNeedToCreate(nv) {
|
||||||
|
fmt.Println(filepath.Join(qcr.GetK8sSecretsFolder(q.QliksenseHome), svc+".yaml"))
|
||||||
|
if secS, err := q.PrepareK8sSecret(filepath.Join(qcr.GetK8sSecretsFolder(q.QliksenseHome), svc+".yaml")); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
return qapi.KubectlApply(secS, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isK8sSecretNeedToCreate(nv config.NameValue) bool {
|
||||||
|
return nv.ValueFrom != nil
|
||||||
|
}
|
||||||
|
|||||||
65
pkg/qliksense/install_test.go
Normal file
65
pkg/qliksense/install_test.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package qliksense
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateK8sResoruceBeforePatch(t *testing.T) {
|
||||||
|
td := setup()
|
||||||
|
sampleCr := `
|
||||||
|
apiVersion: qlik.com/v1
|
||||||
|
kind: Qliksense
|
||||||
|
metadata:
|
||||||
|
name: qlik-test3
|
||||||
|
labels:
|
||||||
|
version: v0.0.2
|
||||||
|
spec:
|
||||||
|
git:
|
||||||
|
repository: https://github.com/ffoysal/qliksense-k8s
|
||||||
|
accessToken: abababababababaab
|
||||||
|
userName: "blblbl"
|
||||||
|
gitOps:
|
||||||
|
enabled: "no"
|
||||||
|
schedule: "*/1 * * * *"
|
||||||
|
watchBranch: pr-branch-db1d26d6
|
||||||
|
image: qlik-docker-oss.bintray.io/qliksense-repo-watcher
|
||||||
|
configs:
|
||||||
|
qliksense:
|
||||||
|
- name: acceptEULA
|
||||||
|
value: "yes"
|
||||||
|
secrets:
|
||||||
|
qliksense:
|
||||||
|
- name: mongoDbUri
|
||||||
|
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
|
||||||
|
profile: docker-desktop
|
||||||
|
rotateKeys: "yes"`
|
||||||
|
|
||||||
|
crFile := filepath.Join(testDir, "install_test.yaml")
|
||||||
|
ioutil.WriteFile(crFile, []byte(sampleCr), 0644)
|
||||||
|
q := New(testDir)
|
||||||
|
file, e := os.Open(crFile)
|
||||||
|
if e != nil {
|
||||||
|
t.Log(e)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if err := q.LoadCr(file, false); err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
qConfig := qapi.NewQConfig(testDir)
|
||||||
|
cr, err := qConfig.GetCR("qlik-test3")
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if err = q.createK8sResoruceBeforePatch(cr); err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
td()
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
|
||||||
|
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // don't delete this line ref: https://github.com/kubernetes/client-go/issues/242
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
"sigs.k8s.io/kustomize/api/konfig"
|
||||||
"sigs.k8s.io/kustomize/api/krusty"
|
"sigs.k8s.io/kustomize/api/krusty"
|
||||||
@@ -13,12 +14,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func executeKustomizeBuild(directory string) ([]byte, error) {
|
func executeKustomizeBuild(directory string) ([]byte, error) {
|
||||||
|
return executeKustomizeBuildForFileSystem(directory, filesys.MakeFsOnDisk())
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeKustomizeBuildForFileSystem(directory string, fSys filesys.FileSystem) ([]byte, error) {
|
||||||
log.SetOutput(&nullWriter{})
|
log.SetOutput(&nullWriter{})
|
||||||
defer func() {
|
defer func() {
|
||||||
log.SetOutput(os.Stderr)
|
log.SetOutput(os.Stderr)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
fSys := filesys.MakeFsOnDisk()
|
|
||||||
options := &krusty.Options{
|
options := &krusty.Options{
|
||||||
DoLegacyResourceSort: false,
|
DoLegacyResourceSort: false,
|
||||||
LoadRestrictions: types.LoadRestrictionsNone,
|
LoadRestrictions: types.LoadRestrictionsNone,
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
|
|||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
configPath := path.Join(tmpDir, "config")
|
configPath := path.Join(tmpDir, "config")
|
||||||
if repo, err := kapis_git.CloneRepository(configPath, defaultGitUrl, nil); err != nil {
|
if repo, err := kapis_git.CloneRepository(configPath, defaultConfigRepoGitUrl, nil); err != nil {
|
||||||
t.Fatalf("unexpected error: %v\n", err)
|
t.Fatalf("unexpected error: %v\n", err)
|
||||||
} else if err := kapis_git.Checkout(repo, "v1.21.23-edge", "", nil); err != nil {
|
} else if err := kapis_git.Checkout(repo, "v1.21.23-edge", "", nil); err != nil {
|
||||||
t.Fatalf("unexpected error: %v\n", err)
|
t.Fatalf("unexpected error: %v\n", err)
|
||||||
|
|||||||
81
pkg/qliksense/load_cr.go
Normal file
81
pkg/qliksense/load_cr.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package qliksense
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (q *Qliksense) LoadCr(reader io.Reader, overwriteExistingContext bool) error {
|
||||||
|
if crBytes, err := ioutil.ReadAll(reader); err != nil {
|
||||||
|
return err
|
||||||
|
} else if crName, err := q.loadCrStringIntoFileSystem(string(crBytes), overwriteExistingContext); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
fmt.Println("cr name: [ " + crName + " ] has been loaded")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Qliksense) IsEulaAcceptedInCrFile(reader io.Reader) (bool, error) {
|
||||||
|
if crBytes, err := ioutil.ReadAll(reader); err != nil {
|
||||||
|
return false, err
|
||||||
|
} else if cr, err := qapi.CreateCRObjectFromString(string(crBytes)); err != nil {
|
||||||
|
return false, err
|
||||||
|
} else {
|
||||||
|
return cr.IsEULA(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Qliksense) loadCrStringIntoFileSystem(crstr string, overwriteExistingContext bool) (string, error) {
|
||||||
|
cr, err := qapi.CreateCRObjectFromString(crstr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||||
|
if qConfig.IsContextExist(cr.GetName()) {
|
||||||
|
if !overwriteExistingContext {
|
||||||
|
return "", errors.New("Context with name: " + cr.GetName() + " already exists. " +
|
||||||
|
"Please delete the existing context first using the delete-context command or specify the --overwrite flag.")
|
||||||
|
} else if err := os.RemoveAll(qConfig.GetContextPath(cr.GetName())); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := qConfig.CreateContextDirs(cr.GetName()); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// encrypt the secrets and do base64 then update the CR
|
||||||
|
rsaPublicKey, _, err := qConfig.GetContextEncryptionKeyPair(cr.GetName())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for svc, nvs := range cr.Spec.Secrets {
|
||||||
|
for _, nv := range nvs {
|
||||||
|
if nv.ValueFrom == nil {
|
||||||
|
skv := &qapi.ServiceKeyValue{
|
||||||
|
Key: nv.Name,
|
||||||
|
Value: nv.Value,
|
||||||
|
SvcName: svc,
|
||||||
|
}
|
||||||
|
if err := q.processSecret(skv, rsaPublicKey, cr, false); err != nil {
|
||||||
|
return cr.GetName(), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write to disk
|
||||||
|
|
||||||
|
if err = qConfig.CreateOrWriteCrAndContext(cr); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
qConfig.SetCurrentContextName(cr.GetName())
|
||||||
|
qConfig.Write()
|
||||||
|
|
||||||
|
return cr.GetName(), nil
|
||||||
|
}
|
||||||
140
pkg/qliksense/load_cr_test.go
Normal file
140
pkg/qliksense/load_cr_test.go
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
package qliksense
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadCrFile(t *testing.T) {
|
||||||
|
td := setup()
|
||||||
|
setup()
|
||||||
|
sampleCr1 := `
|
||||||
|
apiVersion: qlik.com/v1
|
||||||
|
kind: Qliksense
|
||||||
|
metadata:
|
||||||
|
name: qlik-test
|
||||||
|
labels:
|
||||||
|
version: v0.0.2
|
||||||
|
spec:
|
||||||
|
git:
|
||||||
|
repository: https://github.com/ffoysal/qliksense-k8s
|
||||||
|
accessToken: abababababababaab
|
||||||
|
userName: "blblbl"
|
||||||
|
gitOps:
|
||||||
|
enabled: "no"
|
||||||
|
schedule: "*/1 * * * *"
|
||||||
|
watchBranch: pr-branch-db1d26d6
|
||||||
|
image: qlik-docker-oss.bintray.io/qliksense-repo-watcher
|
||||||
|
configs:
|
||||||
|
qliksense:
|
||||||
|
- name: acceptEULA
|
||||||
|
value: "yes"
|
||||||
|
secrets:
|
||||||
|
qliksense:
|
||||||
|
- name: mongoDbUri
|
||||||
|
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
|
||||||
|
profile: docker-desktop
|
||||||
|
rotateKeys: "yes"`
|
||||||
|
sampleCr2 := `
|
||||||
|
apiVersion: qlik.com/v1
|
||||||
|
kind: Qliksense
|
||||||
|
metadata:
|
||||||
|
name: qlik-test3
|
||||||
|
labels:
|
||||||
|
version: v0.0.2
|
||||||
|
spec:
|
||||||
|
git:
|
||||||
|
repository: https://github.com/ffoysal/qliksense-k8s
|
||||||
|
accessToken: abababababababaab
|
||||||
|
userName: "blblbl"
|
||||||
|
gitOps:
|
||||||
|
enabled: "no"
|
||||||
|
schedule: "*/1 * * * *"
|
||||||
|
watchBranch: pr-branch-db1d26d6
|
||||||
|
image: qlik-docker-oss.bintray.io/qliksense-repo-watcher
|
||||||
|
configs:
|
||||||
|
qliksense:
|
||||||
|
- name: acceptEULA
|
||||||
|
value: "yes"
|
||||||
|
secrets:
|
||||||
|
qliksense:
|
||||||
|
- name: mongoDbUri
|
||||||
|
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
|
||||||
|
profile: docker-desktop
|
||||||
|
rotateKeys: "yes"`
|
||||||
|
|
||||||
|
duplicateCr := `
|
||||||
|
apiVersion: qlik.com/v1
|
||||||
|
kind: Qliksense
|
||||||
|
metadata:
|
||||||
|
name: qlik-default
|
||||||
|
labels:
|
||||||
|
version: v0.0.2
|
||||||
|
spec:
|
||||||
|
git:
|
||||||
|
repository: https://github.com/ffoysal/qliksense-k8s
|
||||||
|
accessToken: abababababababaab
|
||||||
|
userName: "blblbl"`
|
||||||
|
crFile1 := filepath.Join(testDir, "testcr1.yaml")
|
||||||
|
ioutil.WriteFile(crFile1, []byte(sampleCr1), 0644)
|
||||||
|
crFile2 := filepath.Join(testDir, "testcr2.yaml")
|
||||||
|
ioutil.WriteFile(crFile2, []byte(sampleCr2), 0644)
|
||||||
|
|
||||||
|
dupCrFile := filepath.Join(testDir, "dupcr.yaml")
|
||||||
|
ioutil.WriteFile(dupCrFile, []byte(duplicateCr), 0644)
|
||||||
|
|
||||||
|
q := New(testDir)
|
||||||
|
file1, e := os.Open(crFile1)
|
||||||
|
if e != nil {
|
||||||
|
t.Log(e)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if err := q.LoadCr(file1, false); err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
file2, e := os.Open(crFile2)
|
||||||
|
if e != nil {
|
||||||
|
t.Log(e)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if err := q.LoadCr(file2, false); err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
qConfig := qapi.NewQConfig(testDir)
|
||||||
|
cr, err := qConfig.GetCR("qlik-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if cr.GetName() != "qlik-test" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
cr, err = qConfig.GetCR("qlik-test3")
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if cr.GetName() != "qlik-test3" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if qConfig.Spec.CurrentContext != "qlik-test3" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
file, e := os.Open(dupCrFile)
|
||||||
|
if e != nil {
|
||||||
|
t.Log(e)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if err := q.LoadCr(file, false); err == nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
td()
|
||||||
|
}
|
||||||
@@ -2,29 +2,21 @@
|
|||||||
package qliksense
|
package qliksense
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/gobuffalo/packr/v2"
|
"github.com/gobuffalo/packr/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Qliksense is the logic behind the qliksense client
|
// Qliksense is the logic behind the qliksense client
|
||||||
type Qliksense struct {
|
type Qliksense struct {
|
||||||
QliksenseHome string
|
QliksenseHome string
|
||||||
QliksenseEjsonKeyDir string
|
CrdBox *packr.Box ``
|
||||||
CrdBox *packr.Box ``
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New qliksense client, initialized with useful defaults.
|
// New qliksense client, initialized with useful defaults.
|
||||||
func New(qliksenseHome string) (*Qliksense, error) {
|
func New(qliksenseHome string) *Qliksense {
|
||||||
qliksenseClient := &Qliksense{
|
qliksenseClient := &Qliksense{
|
||||||
QliksenseHome: qliksenseHome,
|
QliksenseHome: qliksenseHome,
|
||||||
CrdBox: packr.New("crds", "./crds"),
|
CrdBox: packr.New("crds", "./crds"),
|
||||||
}
|
}
|
||||||
|
|
||||||
qliksenseClient.QliksenseEjsonKeyDir = path.Join(qliksenseHome, "ejson", "keys")
|
return qliksenseClient
|
||||||
if err := os.MkdirAll(qliksenseClient.QliksenseEjsonKeyDir, os.ModePerm); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return qliksenseClient, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
23
pkg/qliksense/repo.go
Normal file
23
pkg/qliksense/repo.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package qliksense
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
kapis_git "github.com/qlik-oss/k-apis/pkg/git"
|
||||||
|
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (q *Qliksense) DiscardAllUnstagedChangesFromGitRepo(qConfig *qapi.QliksenseConfig) error {
|
||||||
|
if qcr, err := qConfig.GetCurrentCR(); err != nil {
|
||||||
|
return err
|
||||||
|
} else if version := qcr.GetLabelFromCr("version"); version == "" {
|
||||||
|
return errors.New("version label is not set in CR")
|
||||||
|
} else if qcr.Spec.ManifestsRoot == qConfig.BuildRepoPath(version) {
|
||||||
|
if repo, err := kapis_git.OpenRepository(qcr.Spec.ManifestsRoot); err != nil {
|
||||||
|
return err
|
||||||
|
} else if err = kapis_git.DiscardAllUnstagedChanges(repo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -13,13 +13,9 @@ func (q *Qliksense) UninstallQK8s(contextName string) error {
|
|||||||
} else if !qConfig.IsContextExist(contextName) {
|
} else if !qConfig.IsContextExist(contextName) {
|
||||||
return errors.New("context name [ " + contextName + " ] not found")
|
return errors.New("context name [ " + contextName + " ] not found")
|
||||||
}
|
}
|
||||||
cr, err := qConfig.GetCurrentCR()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
str, err := q.getCRString(contextName)
|
str, err := q.getCRString(contextName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return qapi.KubectlDelete(str, cr.Spec.NameSpace)
|
return qapi.KubectlDelete(str, "")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (q *Qliksense) UpgradeQK8s() error {
|
func (q *Qliksense) UpgradeQK8s(keepPatchFiles bool) error {
|
||||||
|
|
||||||
// step1: get CR
|
// step1: get CR
|
||||||
// step2: run kustomize
|
// step2: run kustomize
|
||||||
@@ -14,6 +14,13 @@ func (q *Qliksense) UpgradeQK8s() error {
|
|||||||
|
|
||||||
// fetch the version
|
// fetch the version
|
||||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||||
|
if !keepPatchFiles {
|
||||||
|
defer func() {
|
||||||
|
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
|
||||||
|
fmt.Printf("error removing temporary changes to the config: %v\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
qcr, err := qConfig.GetCurrentCR()
|
qcr, err := qConfig.GetCurrentCR()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -21,7 +28,9 @@ func (q *Qliksense) UpgradeQK8s() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
qcr.Spec.RotateKeys = "no"
|
qcr.Spec.RotateKeys = "no"
|
||||||
if err := q.applyConfigToK8s(qcr); err != nil {
|
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
|
||||||
|
return err
|
||||||
|
} else if err := q.applyConfigToK8s(dcr); err != nil {
|
||||||
fmt.Println("cannot do kubectl apply on manifests")
|
fmt.Println("cannot do kubectl apply on manifests")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -31,7 +40,7 @@ func (q *Qliksense) UpgradeQK8s() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := qapi.KubectlApply(r, qcr.Spec.NameSpace); err != nil {
|
if err := qapi.KubectlApply(r, ""); err != nil {
|
||||||
fmt.Println("cannot do kubectl apply on operator CR")
|
fmt.Println("cannot do kubectl apply on operator CR")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user