Compare commits
118 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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/qliksense-packr.go
|
||||
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
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
test: clean generate
|
||||
ifeq ($(shell ${WHICH} docker-registry 2>${DEVNUL}),)
|
||||
$(eval TMP := $(shell mktemp -d))
|
||||
git clone https://github.com/docker/distribution.git $(TMP)/docker-distribution
|
||||
cd $(TMP)/docker-distribution; git checkout -b v2.7.1; make
|
||||
cp $(TMP)/docker-distribution/bin/registry pkg/qliksense/docker-registry
|
||||
$(eval TMP-docker-distribution := $(shell mktemp -d))
|
||||
git clone https://github.com/docker/distribution.git $(TMP-docker-distribution)/docker-distribution
|
||||
cd $(TMP-docker-distribution)/docker-distribution; git checkout -b v2.7.1; make
|
||||
cp $(TMP-docker-distribution)/docker-distribution/bin/registry pkg/qliksense/docker-registry
|
||||
-rm -rf $(TMP-docker-distribution)
|
||||
endif
|
||||
go test -short -count=1 -tags "$(BUILDTAGS)" -v ./...
|
||||
$(MAKE) clean
|
||||
|
||||
xbuild-all: clean generate
|
||||
$(foreach OS, $(SUPPORTED_PLATFORMS), \
|
||||
$(foreach ARCH, $(SUPPORTED_ARCHES), \
|
||||
$(MAKE) $(MAKE_OPTS) CLIENT_PLATFORM=$(OS) CLIENT_ARCH=$(ARCH) MIXIN=$(MIXIN) xbuild; \
|
||||
))
|
||||
$(foreach ARCH, $(SUPPORTED_ARCHES), \
|
||||
$(MAKE) $(MAKE_OPTS) CLIENT_PLATFORM=$(OS) CLIENT_ARCH=$(ARCH) MIXIN=$(MIXIN) xbuild; \
|
||||
))
|
||||
|
||||
$(MAKE) clean
|
||||
|
||||
xbuild: $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
||||
|
||||
$(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT):
|
||||
mkdir -p $(dir $@)
|
||||
GOOS=$(CLIENT_PLATFORM) GOARCH=$(CLIENT_ARCH) $(XBUILD) -o $@ ./cmd/$(MIXIN)
|
||||
tar -czvf $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).tar.gz -C $(BINDIR)/$(VERSION)/ $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
||||
#tar -C $(BINDIR)/$(VERSION)/ -cvf $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).tar.gz $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
||||
|
||||
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
|
||||
go generate ./...
|
||||
|
||||
HAS_PACKR2 := $(shell packr2)
|
||||
packr2:
|
||||
ifndef HAS_PACKR2
|
||||
go get -u github.com/gobuffalo/packr/v2/packr2
|
||||
ifeq ($(shell ${WHICH} packr2 2>${DEVNUL}),)
|
||||
go get -u github.com/gobuffalo/packr/v2/packr2@v2.7.1
|
||||
endif
|
||||
|
||||
clean: clean-packr
|
||||
-rm -rf /tmp/operator
|
||||
-rm -fr pkg/qliksense/crds
|
||||
|
||||
clean-packr: packr2
|
||||
cd pkg/qliksense && packr2 clean
|
||||
|
||||
get-crds:
|
||||
$(eval TMP := $(shell mktemp -d))
|
||||
git clone git@github.com:qlik-oss/qliksense-operator.git -b ms-3 $(TMP)/operator
|
||||
ifeq ($(QLIKSENSE_OPERATOR_DIR),)
|
||||
$(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/crd
|
||||
mkdir -p pkg/qliksense/crds/crd-deploy
|
||||
cp $(TMP)/operator/deploy/*.yaml pkg/qliksense/crds/crd-deploy
|
||||
cp $(TMP)/operator/deploy/crds/*_crd.yaml pkg/qliksense/crds/crd
|
||||
cp $(TMP)/operator/deploy/crds/*_cr.yaml pkg/qliksense/crds/cr
|
||||
cp $(QLIKSENSE_OPERATOR_DIR)/deploy/*.yaml pkg/qliksense/crds/crd-deploy
|
||||
cp $(QLIKSENSE_OPERATOR_DIR)/deploy/crds/*_crd.yaml pkg/qliksense/crds/crd
|
||||
cp $(QLIKSENSE_OPERATOR_DIR)/deploy/crds/*_cr.yaml pkg/qliksense/crds/cr
|
||||
endif
|
||||
|
||||
143
README.md
143
README.md
@@ -1,150 +1,25 @@
|
||||
# (WIP) Qlik Sense installation and operations CLI
|
||||
|
||||
- [Qlik Sense installation and operations CLI](#qlik-sense-installation-and-operations-cli)
|
||||
- [About](#about)
|
||||
- [Future Direction](#future-direction)
|
||||
- [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)
|
||||
|
||||
## Documentation
|
||||
|
||||
To learn more about Qlik Sense installer go to https://qlik-oss.github.io/sense-installer/
|
||||
|
||||
## 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 qliksense operator to manage QSEoK
|
||||
- air gapped installation of QSEoK
|
||||
|
||||
This is a technology preview that uses qlik modified [kustomize](https://github.com/qlik-oss/kustomize) to kubernetes manifests of the versions of the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository.
|
||||
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
|
||||
|
||||
- More operations:
|
||||
- Expanded preflight checks
|
||||
- Expand preflight checks
|
||||
- backup/restore operations
|
||||
- fully support airgap installation of QSEoK
|
||||
- restore unwanted deletion of kubernetes resoureces
|
||||
|
||||
## 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
|
||||
- restore unwanted deletion of kubernetes resources
|
||||
|
||||
@@ -7,10 +7,13 @@ 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-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` - set secrets configurations into the qliksense context
|
||||
- `qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=false` - set secrets configurations into the qliksense context as key-value pairs and show encrypted value as part of CR
|
||||
- `qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=true` - set secrets configurations into the qliksense context as key-value pairs and show a key reference to the created Kubernetes secret resource as part of the CR
|
||||
- `qliksense config view` - view the qliksense operator CR
|
||||
- `qliksense config 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
|
||||
|
||||
39
cmd/qliksense/apply.go
Normal file
39
cmd/qliksense/apply.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func applyCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
|
||||
filePath := ""
|
||||
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 {
|
||||
if filePath == "-" {
|
||||
if !isInputFromPipe() {
|
||||
return errors.New("No input pipe present")
|
||||
}
|
||||
return q.ApplyCRFromReader(os.Stdin)
|
||||
}
|
||||
file, e := os.Open(filePath)
|
||||
if e != nil {
|
||||
return errors.Wrapf(e,
|
||||
"unable to read the file %s", filePath)
|
||||
}
|
||||
return q.ApplyCRFromReader(file)
|
||||
},
|
||||
}
|
||||
|
||||
f := c.Flags()
|
||||
f.StringVarP(&filePath, "file", "f", "", "Install from a CR file")
|
||||
c.MarkFlagRequired("file")
|
||||
return c
|
||||
}
|
||||
@@ -5,16 +5,22 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "do operations on/around CR",
|
||||
Long: `do operations on/around CR`,
|
||||
func configCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
var configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
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 {
|
||||
c := &cobra.Command{
|
||||
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`,
|
||||
Example: `qliksense config apply`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -2,6 +2,9 @@ package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -13,9 +16,12 @@ func setContextConfigCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
)
|
||||
|
||||
cmd = &cobra.Command{
|
||||
Use: "set-context",
|
||||
Short: "Sets the context in which the Kubernetes cluster and resources live in",
|
||||
Example: `qliksense config set-context <context_name>`,
|
||||
Use: "set-context",
|
||||
Short: "Sets the context in which the Kubernetes cluster and resources live in",
|
||||
Example: `
|
||||
qliksense config set-context <context_name>
|
||||
- The above configuration will be displayed in the CR
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.SetContextConfig(args)
|
||||
},
|
||||
@@ -45,9 +51,12 @@ func setOtherConfigsCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
)
|
||||
|
||||
cmd = &cobra.Command{
|
||||
Use: "set",
|
||||
Short: "configure a key value pair into the current context",
|
||||
Example: `qliksense config set <key>=<value>`,
|
||||
Use: "set",
|
||||
Short: "configure a key value pair into the current context",
|
||||
Example: `
|
||||
qliksense config set <key>=<value>
|
||||
- The above configuration will be displayed in the CR
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.SetOtherConfigs(args)
|
||||
},
|
||||
@@ -61,9 +70,12 @@ func setConfigsCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
)
|
||||
|
||||
cmd = &cobra.Command{
|
||||
Use: "set-configs",
|
||||
Short: "set configurations into the qliksense context",
|
||||
Example: `qliksense config set-configs <key>=<value>`,
|
||||
Use: "set-configs",
|
||||
Short: "set configurations into the qliksense context as key-value pairs",
|
||||
Example: `
|
||||
qliksense config set-configs <service_name>.<attribute>="<value>"
|
||||
- The above configuration will be displayed in the CR
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.SetConfigs(args)
|
||||
},
|
||||
@@ -78,9 +90,18 @@ func setSecretsCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
)
|
||||
|
||||
cmd = &cobra.Command{
|
||||
Use: "set-secrets",
|
||||
Short: "set secrets configurations into the qliksense context",
|
||||
Example: `qliksense config set-secrets <key>=<value> --secret=true`,
|
||||
Use: "set-secrets",
|
||||
Short: "set secrets configurations into the qliksense context as key-value pairs",
|
||||
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 {
|
||||
return q.SetSecrets(args, secret)
|
||||
},
|
||||
@@ -90,6 +111,22 @@ func setSecretsCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func deleteContextConfigCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
var (
|
||||
cmd *cobra.Command
|
||||
)
|
||||
|
||||
cmd = &cobra.Command{
|
||||
Use: "delete-context",
|
||||
Short: "deletes a specific context locally (not in-cluster)",
|
||||
Example: `qliksense config delete-contexts <context_name>`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.DeleteContextConfig(args)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func setImageRegistryCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
var (
|
||||
cmd *cobra.Command
|
||||
@@ -140,3 +177,19 @@ qliksense config set-image-registry https://your.private.registry.example.com:50
|
||||
f.StringVar(&password, "password", "", "Password used for both pushing and pulling images")
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
47
cmd/qliksense/eula.go
Normal file
47
cmd/qliksense/eula.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
)
|
||||
|
||||
var eulaEnforced = false
|
||||
var eulaText = "EULA text goes here..."
|
||||
var eulaPrompt = "Do you accept our EULA? (y/n): "
|
||||
var eulaErrorInstruction = "You must enter y/yes to continue"
|
||||
|
||||
func isEulaEnforced() bool {
|
||||
return eulaEnforced
|
||||
}
|
||||
|
||||
func enforceEula(q *qliksense.Qliksense) {
|
||||
if isEulaEnforced() {
|
||||
if qConfig, err := qapi.NewQConfigE(q.QliksenseHome); err != nil {
|
||||
doEnforceEula()
|
||||
} else if qcr, err := qConfig.GetCurrentCR(); err != nil || !qcr.IsEULA() {
|
||||
doEnforceEula()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func doEnforceEula() {
|
||||
fmt.Println(eulaText)
|
||||
fmt.Print(eulaPrompt)
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanSuccess := scanner.Scan()
|
||||
if !scanSuccess {
|
||||
fmt.Println(eulaErrorInstruction)
|
||||
os.Exit(1)
|
||||
}
|
||||
line := scanner.Text()
|
||||
answer := strings.ToLower(strings.TrimSpace(line))
|
||||
if answer != "y" && answer != "yes" {
|
||||
fmt.Println(eulaErrorInstruction)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
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,25 @@ import (
|
||||
|
||||
func installCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
opts := &qliksense.InstallCommandOptions{}
|
||||
keepPatchFiles := false
|
||||
c := &cobra.Command{
|
||||
Use: "install",
|
||||
Short: "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 {
|
||||
if len(args) == 0 {
|
||||
return q.InstallQK8s("", opts)
|
||||
return q.InstallQK8s("", opts, keepPatchFiles)
|
||||
}
|
||||
return q.InstallQK8s(args[0], opts)
|
||||
return q.InstallQK8s(args[0], opts, keepPatchFiles)
|
||||
},
|
||||
}
|
||||
|
||||
f := c.Flags()
|
||||
f.StringVarP(&opts.AcceptEULA, "acceptEULA", "a", "", "AcceptEULA for qliksense")
|
||||
f.StringVarP(&opts.Namespace, "namespace", "n", "", "Namespace where to install the qliksense")
|
||||
f.StringVarP(&opts.StorageClass, "storageClass", "s", "", "Storage class for qliksense")
|
||||
f.StringVarP(&opts.MongoDbUri, "mongoDbUri", "m", "", "mongoDbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
|
||||
f.StringVarP(&opts.RotateKeys, "rotateKeys", "r", "", "Rotate JWT keys for qliksense (yes:rotate keys/ no:use exising keys from cluster/ None: use default EJSON_KEY from env")
|
||||
f.BoolVar(&keepPatchFiles, keepPatchFilesFlagName, keepPatchFiles, keepPatchFilesFlagUsage)
|
||||
return c
|
||||
}
|
||||
|
||||
41
cmd/qliksense/load.go
Normal file
41
cmd/qliksense/load.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"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 := ""
|
||||
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`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if filePath == "-" {
|
||||
if !isInputFromPipe() {
|
||||
return errors.New("No input pipe present")
|
||||
}
|
||||
return q.LoadCr(os.Stdin)
|
||||
}
|
||||
file, e := os.Open(filePath)
|
||||
if e != nil {
|
||||
return errors.Wrapf(e,
|
||||
"unable to read the file %s", filePath)
|
||||
}
|
||||
return q.LoadCr(file)
|
||||
},
|
||||
}
|
||||
f := c.Flags()
|
||||
f.StringVarP(&filePath, "file", "f", "", "File to laod CR from")
|
||||
c.MarkFlagRequired("file")
|
||||
return c
|
||||
}
|
||||
|
||||
func isInputFromPipe() bool {
|
||||
fileInfo, _ := os.Stdin.Stat()
|
||||
return fileInfo.Mode()&os.ModeCharDevice == 0
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
if version == "" {
|
||||
if qcr, err := qConfig.GetCurrentCR(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
version = qcr.GetLabelFromCr("version")
|
||||
}
|
||||
}
|
||||
|
||||
if version != "" {
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
if !qConfig.IsRepoExistForCurrent(version) {
|
||||
if err := q.FetchQK8s(version); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := qConfig.SwitchCurrentCRToVersionAndProfile(version, opts.Profile); err != nil {
|
||||
}
|
||||
if err := qConfig.SwitchCurrentCRToVersionAndProfile(version, opts.Profile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
ansi "github.com/mattn/go-colorable"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/qlik-oss/sense-installer/pkg"
|
||||
@@ -10,21 +16,17 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"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
|
||||
// qliksense <command>
|
||||
|
||||
const (
|
||||
qlikSenseHomeVar = "QLIKSENSE_HOME"
|
||||
qlikSenseDirVar = ".qliksense"
|
||||
qlikSenseHomeVar = "QLIKSENSE_HOME"
|
||||
qlikSenseDirVar = ".qliksense"
|
||||
keepPatchFilesFlagName = "keep-config-repo-patches"
|
||||
keepPatchFilesFlagUsage = "Keep config repo patch files (for debugging)"
|
||||
)
|
||||
|
||||
func initAndExecute() error {
|
||||
@@ -39,18 +41,14 @@ func initAndExecute() error {
|
||||
// create dirs and appropriate files for setting up contexts
|
||||
api.LogDebugMessage("QliksenseHomeDir: %s", qlikSenseHome)
|
||||
|
||||
qliksenseClient, err := qliksense.New(qlikSenseHome)
|
||||
if err != nil {
|
||||
qliksenseClient := qliksense.New(qlikSenseHome)
|
||||
cmd := rootCmd(qliksenseClient)
|
||||
if err := cmd.Execute(); err != nil {
|
||||
//levenstein checks (auto-suggestions)
|
||||
levenstein(cmd)
|
||||
return err
|
||||
}
|
||||
qliksenseClient.SetUpQliksenseDefaultContext()
|
||||
cmd := rootCmd(qliksenseClient)
|
||||
//levenstein checks
|
||||
if levenstein(cmd) == false {
|
||||
if err := cmd.Execute(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -86,16 +84,37 @@ var versionCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||
var (
|
||||
cmd *cobra.Command
|
||||
)
|
||||
func commandUsesContext(command string) bool {
|
||||
return command != "" && command != "help" && command != "version"
|
||||
}
|
||||
|
||||
cmd = &cobra.Command{
|
||||
func globalPreRun(cmd *cobra.Command, p *qliksense.Qliksense) {
|
||||
if command := cmd.CalledAs(); commandUsesContext(command) {
|
||||
if isEulaEnforced() {
|
||||
enforceEula(p)
|
||||
}
|
||||
|
||||
if err := p.SetUpQliksenseDefaultContext(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if isEulaEnforced() {
|
||||
if err := p.SetEulaAccepted(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "qliksense",
|
||||
Short: "Qliksense cli tool",
|
||||
Long: `qliksense cli tool provides functionality to perform operations on qliksense-k8s, qliksense operator, and kubernetes cluster`,
|
||||
Args: cobra.ArbitraryArgs,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
globalPreRun(cmd, p)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().SetInterspersed(false)
|
||||
@@ -104,6 +123,7 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||
|
||||
// For qliksense overrides/commands
|
||||
|
||||
cmd.AddCommand(getInstallableVersionsCmd(p))
|
||||
cmd.AddCommand(pullQliksenseImages(p))
|
||||
cmd.AddCommand(pushQliksenseImages(p))
|
||||
cmd.AddCommand(about(p))
|
||||
@@ -123,6 +143,7 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||
cmd.AddCommand(installCmd(p))
|
||||
|
||||
// add config command
|
||||
configCmd := configCmd(p)
|
||||
cmd.AddCommand(configCmd)
|
||||
configCmd.AddCommand(configApplyCmd(p))
|
||||
configCmd.AddCommand(configViewCmd(p))
|
||||
@@ -147,9 +168,15 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||
// add the list config command as a sub-command to the app config sub-command
|
||||
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
|
||||
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
|
||||
cmd.AddCommand(uninstallCmd(p))
|
||||
|
||||
@@ -157,6 +184,18 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||
cmd.AddCommand(crdsCmd)
|
||||
crdsCmd.AddCommand(crdsViewCmd(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
|
||||
}
|
||||
|
||||
@@ -165,32 +204,6 @@ func initConfig() {
|
||||
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) {
|
||||
var (
|
||||
source, destination *os.File
|
||||
@@ -219,28 +232,21 @@ func copy(src, dst string) (int64, error) {
|
||||
return nBytes, err
|
||||
}
|
||||
|
||||
func levenstein(cmd *cobra.Command) bool {
|
||||
cmd.SuggestionsMinimumDistance = 4
|
||||
func levenstein(cmd *cobra.Command) {
|
||||
cmd.SuggestionsMinimumDistance = 2
|
||||
if len(os.Args) > 1 {
|
||||
args := os.Args[1]
|
||||
for _, ctx := range cmd.Commands() {
|
||||
val := *ctx
|
||||
if args == val.Name() {
|
||||
//found command
|
||||
return false
|
||||
}
|
||||
}
|
||||
suggest := cmd.SuggestionsFor(os.Args[1])
|
||||
suggest := cmd.SuggestionsFor(args)
|
||||
if len(suggest) > 0 {
|
||||
arg := []string{}
|
||||
for _, cm := range os.Args {
|
||||
arg = append(arg, cm)
|
||||
}
|
||||
arg[1] = suggest[0]
|
||||
out := ansi.NewColorableStdout()
|
||||
fmt.Fprintln(out, chalk.Green.Color("Did you mean: "), chalk.Bold.TextStyle(strings.Join(arg, " ")), "?")
|
||||
return true
|
||||
if !strings.EqualFold(arg[1], suggest[0]) {
|
||||
arg[1] = suggest[0]
|
||||
out := ansi.NewColorableStdout()
|
||||
fmt.Fprintln(out, chalk.Green.Color("Did you mean: "), chalk.Bold.TextStyle(strings.Join(arg, " ")), "?")
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -9,7 +8,7 @@ import (
|
||||
func uninstallCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "uninstall",
|
||||
Short: "Uninstall the deployed qliksense with release name [ " + qapi.NewQConfig(q.QliksenseHome).Spec.CurrentContext + " ]",
|
||||
Short: "Uninstall the deployed qliksense.",
|
||||
Long: `Uninstall the deployed qliksense. By default uninstall the current context`,
|
||||
Example: `qliksense uninstall <context-name>`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -6,15 +6,18 @@ import (
|
||||
)
|
||||
|
||||
func upgradeCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
keepPatchFiles := false
|
||||
c := &cobra.Command{
|
||||
Use: "upgrade",
|
||||
Short: "upgrade qliksense release",
|
||||
Long: `upgrade qliksense release`,
|
||||
Example: `qliksense upgrade <version>`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.UpgradeQK8s()
|
||||
return q.UpgradeQK8s(keepPatchFiles)
|
||||
},
|
||||
}
|
||||
|
||||
f := c.Flags()
|
||||
f.BoolVar(&keepPatchFiles, keepPatchFilesFlagName, keepPatchFiles, keepPatchFilesFlagUsage)
|
||||
return c
|
||||
}
|
||||
|
||||
0
docs/air_gap.md
Normal file
0
docs/air_gap.md
Normal file
41
docs/command_reference.md
Normal file
41
docs/command_reference.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# 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"
|
||||
```
|
||||
|
||||
after doing one of the above commands, cli will set the current context to the cr name and install the qliksense into the cluster. so make sure you dont have a context (cr name = context name) with the same name already. It will though error if it same context name already exist. It will encrypt the secrets from CR while writing CR into the disk.
|
||||
|
||||
## 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 -`
|
||||
|
||||
It will load the cr into `~/.qliksense` folder and create context strcture with for the CR and set the current context with the cr name. In case of multiple documents in a single stream/file it will set the current context to the last one. It will encrypt the secrets from CR while writing the CR into the disk.
|
||||
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
|
||||
```
|
||||
14
go.mod
14
go.mod
@@ -33,29 +33,31 @@ require (
|
||||
github.com/golang/protobuf v1.3.3 // indirect
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gorilla/mux v1.7.3 // indirect
|
||||
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.4
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
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/spf13/cobra v0.0.6
|
||||
github.com/spf13/viper v1.6.1
|
||||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31
|
||||
golang.org/x/crypto v0.0.0-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/net v0.0.0-20200202094626-16171245cfb2
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae // indirect
|
||||
golang.org/x/tools v0.0.0-20200228135638-5c7c66ced534 // indirect
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect
|
||||
golang.org/x/tools v0.0.0-20200312194400-c312e98713c2 // indirect
|
||||
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b // indirect
|
||||
google.golang.org/grpc v1.27.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
gopkg.in/yaml.v3 v3.0.0-20190924164351-c8b7dadae555
|
||||
k8s.io/api v0.17.0
|
||||
k8s.io/apimachinery v0.17.0
|
||||
k8s.io/client-go v11.0.0+incompatible
|
||||
sigs.k8s.io/kustomize/api v0.3.2
|
||||
sigs.k8s.io/yaml v1.1.0
|
||||
)
|
||||
|
||||
28
go.sum
28
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 v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM=
|
||||
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/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
@@ -844,14 +843,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/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/qlik-oss/k-apis v0.0.11 h1:dhbcH1+8r14OMlTSrP2RAlr+707QXcrj1iVnA/y9r5I=
|
||||
github.com/qlik-oss/k-apis v0.0.11/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE=
|
||||
github.com/qlik-oss/k-apis v0.0.22 h1:tntQEeRqDYkBi2Ku5+xt7ABGMeFPck7+DOKrHUnpzwI=
|
||||
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/go.mod h1:OCt7FTrRVHj4kmR2xLJJUIqu00BTr6GeF09hSmM17Kw=
|
||||
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
|
||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
|
||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
|
||||
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/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
@@ -1041,8 +1042,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-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-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA=
|
||||
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
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=
|
||||
@@ -1077,6 +1078,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.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.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-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1117,6 +1120,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-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1194,21 +1199,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-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-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-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-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-20200224181240-023911ca70b2 h1:L/G4KZvrQn7FWLN/LlulBtBzrLUhqjiGfTWWDmrh+IQ=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
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/tools v0.0.0-20200312194400-c312e98713c2 h1:6TB4+MaZlkcSsJDu+BS5yxSEuZIYhjWz+jhbSLEZylI=
|
||||
golang.org/x/tools v0.0.0-20200312194400-c312e98713c2/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
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=
|
||||
|
||||
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.
311
pkg/api/apis.go
311
pkg/api/apis.go
@@ -8,9 +8,13 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/qlik-oss/k-apis/pkg/config"
|
||||
|
||||
b64 "encoding/base64"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -18,38 +22,44 @@ const (
|
||||
pullSecretFileName = "image-registry-pull-secret.yaml"
|
||||
qliksenseContextsDirName = "contexts"
|
||||
qliksenseSecretsDirName = "secrets"
|
||||
qliksenseEjsonDirName = "ejson"
|
||||
)
|
||||
|
||||
// NewQConfig create QliksenseConfig object from file ~/.qliksense/config.yaml
|
||||
func NewQConfig(qsHome string) *QliksenseConfig {
|
||||
configFile := filepath.Join(qsHome, "config.yaml")
|
||||
data, err := ioutil.ReadFile(configFile)
|
||||
if err != nil {
|
||||
fmt.Println("Cannot read config file from: "+configFile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
qc := &QliksenseConfig{}
|
||||
err = yaml.Unmarshal(data, qc)
|
||||
qc, err := NewQConfigE(qsHome)
|
||||
if err != nil {
|
||||
fmt.Println("yaml unmarshalling error ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
qc.QliksenseHomePath = qsHome
|
||||
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
|
||||
// from file ~/.qliksense/contexts/<contx-name>/<contx-name>.yaml
|
||||
func (qc *QliksenseConfig) GetCR(contextName string) (*QliksenseCR, error) {
|
||||
crFilePath := qc.getCRFilePath(contextName)
|
||||
crFilePath := qc.GetCRFilePath(contextName)
|
||||
if crFilePath == "" {
|
||||
return nil, errors.New("context name " + contextName + " not found")
|
||||
}
|
||||
return getCRObject(crFilePath)
|
||||
}
|
||||
|
||||
func getUnencryptedCR() {
|
||||
|
||||
return qc.GetAndTransformCrObject(crFilePath)
|
||||
}
|
||||
|
||||
// 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
|
||||
func (qc *QliksenseConfig) SetCrLocation(contextName, filepath string) (*QliksenseConfig, error) {
|
||||
func (qc *QliksenseConfig) SetCrLocation(contextName, filePath string) (*QliksenseConfig, error) {
|
||||
tempQc := &QliksenseConfig{}
|
||||
copier.Copy(tempQc, qc)
|
||||
found := false
|
||||
tempQc.Spec.Contexts = []Context{}
|
||||
for _, c := range qc.Spec.Contexts {
|
||||
if c.Name == contextName {
|
||||
c.CrFile = filepath
|
||||
c.CrFile = filePath
|
||||
found = true
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
func getCRObject(crfile string) (*QliksenseCR, error) {
|
||||
data, err := ioutil.ReadFile(crfile)
|
||||
// GetCRObject create a qliksense CR object from file
|
||||
func GetCRObject(crfile string) (*QliksenseCR, error) {
|
||||
cr := &QliksenseCR{}
|
||||
err := ReadFromFile(cr, crfile)
|
||||
if err != nil {
|
||||
fmt.Println("Cannot read config file from: "+crfile, err)
|
||||
fmt.Println("cannot unmarshal cr ", 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{}
|
||||
err = yaml.Unmarshal(data, cr)
|
||||
err := ReadFromStream(cr, strings.NewReader(crContent))
|
||||
if err != nil {
|
||||
fmt.Println("cannot unmarshal cr ", err)
|
||||
return nil, err
|
||||
@@ -91,11 +123,11 @@ func getCRObject(crfile string) (*QliksenseCR, error) {
|
||||
return cr, nil
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) getCRFilePath(contextName string) string {
|
||||
func (qc *QliksenseConfig) GetCRFilePath(contextName string) string {
|
||||
crFilePath := ""
|
||||
for _, ctx := range qc.Spec.Contexts {
|
||||
if ctx.Name == contextName {
|
||||
crFilePath = ctx.CrFile
|
||||
crFilePath = filepath.Join(qc.QliksenseHomePath, ctx.CrFile)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -128,19 +160,59 @@ func (qc *QliksenseConfig) BuildCurrentManifestsRoot(version string) string {
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) WriteCR(cr *QliksenseCR, contextName string) error {
|
||||
crf := qc.getCRFilePath(contextName)
|
||||
crf := qc.GetCRFilePath(contextName)
|
||||
if crf == "" {
|
||||
return errors.New("context name " + contextName + " not found")
|
||||
}
|
||||
by, err := yaml.Marshal(cr)
|
||||
if err != nil {
|
||||
fmt.Println("cannot marshal cr ", err)
|
||||
return err
|
||||
}
|
||||
ioutil.WriteFile(crf, by, 0644)
|
||||
return nil
|
||||
|
||||
return qc.TransformAndWriteCr(cr, crf)
|
||||
}
|
||||
|
||||
//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 := WriteToFile(qc, filepath.Join(qc.QliksenseHomePath, "config.yaml")); 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 {
|
||||
return qc.WriteCR(cr, qc.Spec.CurrentContext)
|
||||
}
|
||||
@@ -158,7 +230,7 @@ func (qc *QliksenseConfig) GetCurrentContextDir() (string, error) {
|
||||
if qcr, err := qc.GetCurrentCR(); err != nil {
|
||||
return "", err
|
||||
} 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) {
|
||||
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{}
|
||||
if secretsDir, err := qc.GetCurrentContextSecretsDir(); err != nil {
|
||||
return nil, err
|
||||
@@ -211,6 +303,15 @@ func (qc *QliksenseConfig) GetDockerConfigJsonSecret(name string) (*DockerConfig
|
||||
}
|
||||
|
||||
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
|
||||
var secretKeyPairLocation string
|
||||
if os.Getenv("QLIKSENSE_KEY_LOCATION") != "" {
|
||||
@@ -219,25 +320,40 @@ func (qc *QliksenseConfig) getCurrentContextEncryptionKeyPairLocation() (string,
|
||||
} else {
|
||||
// QLIKSENSE_KEY_LOCATION has not been set, hence storing key pair in default location:
|
||||
// /.qliksense/secrets/contexts/<current-context>/secrets/
|
||||
if qcr, err := qc.GetCurrentCR(); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
secretKeyPairLocation = filepath.Join(qc.QliksenseHomePath, qliksenseSecretsDirName, qliksenseContextsDirName, qcr.Metadata.Name, qliksenseSecretsDirName)
|
||||
}
|
||||
secretKeyPairLocation = filepath.Join(qc.QliksenseHomePath, qliksenseSecretsDirName, qliksenseContextsDirName, contextName, qliksenseSecretsDirName)
|
||||
|
||||
}
|
||||
LogDebugMessage("SecretKeyLocation to store key pair: %s", secretKeyPairLocation)
|
||||
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) {
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
publicKeyFilePath := filepath.Join(secretKeyPairLocation, QliksensePublicKey)
|
||||
privateKeyFilePath := filepath.Join(secretKeyPairLocation, QliksensePrivateKey)
|
||||
|
||||
// try to create the dir if it doesn't exist
|
||||
if !FileExists(publicKeyFilePath) || !FileExists(privateKeyFilePath) {
|
||||
LogDebugMessage("Qliksense secretKeyLocation dir does not exist, creating it now: %s", secretKeyPairLocation)
|
||||
@@ -271,22 +387,20 @@ func (qc *QliksenseConfig) GetCurrentContextEncryptionKeyPair() (*rsa.PublicKey,
|
||||
}
|
||||
|
||||
func (cr *QliksenseCR) AddLabelToCr(key, value string) {
|
||||
if cr.Metadata.Labels == nil {
|
||||
cr.Metadata.Labels = make(map[string]string)
|
||||
m := cr.GetObjectMeta().GetLabels()
|
||||
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 {
|
||||
val := ""
|
||||
if cr.Metadata.Labels != nil {
|
||||
val = cr.Metadata.Labels[key]
|
||||
}
|
||||
return val
|
||||
return cr.GetObjectMeta().GetLabels()[key]
|
||||
}
|
||||
|
||||
func (cr *QliksenseCR) GetString() (string, error) {
|
||||
out, err := yaml.Marshal(cr)
|
||||
out, err := K8sToYaml(cr)
|
||||
if err != nil {
|
||||
fmt.Println("cannot unmarshal cr ", err)
|
||||
return "", err
|
||||
@@ -304,3 +418,96 @@ func (cr *QliksenseCR) GetImageRegistry() string {
|
||||
}
|
||||
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) {
|
||||
contexPath := filepath.Join(qc.QliksenseHomePath, qliksenseContextsDirName, contextName)
|
||||
os.MkdirAll(contexPath, os.ModePerm)
|
||||
}
|
||||
|
||||
//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.QliksenseHomePath, qliksenseContextsDirName, 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(qliksenseContextsDirName, 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -45,12 +47,17 @@ metadata:
|
||||
spec:
|
||||
profile: docker-desktop
|
||||
manifestsRoot: /Users/mqb/.qliksense/contexts/contx1/qlik-k8s/v0.0.1/manifests
|
||||
namespace: myqliksense
|
||||
storageClassName: efs
|
||||
configs:
|
||||
qliksense:
|
||||
- name: acceptEULA
|
||||
value: "yes"
|
||||
secrets:
|
||||
qliksense:
|
||||
- name: mongoDbUri
|
||||
# this is rsa encrypted value, the pub and pri keys are in setuPublicAndPrivateKey() method
|
||||
# actual value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
|
||||
value: n/pDi7Z/A3i16cAHFFwMp19/egNKc8WZxm6MKHLT/B1DMv3U6pDXWyXT5fYYDV1wDTO3Vk43yECST1UgZYmMpgUOwgSfGgqTVi2VqS0JQsnwI+Twwhnvha8RJANX8b/XIoSFVWaOgy7+RP35ZkvOqHdCfC2aT8JMIHgBQqqCbsNgimCuRSxi0klR000ic/Tp5PYSz5mD+WLrkPw2FbS0OVBsQ/hIp5GZrmVpvEOZdbT63Sz+n/G4Br6GTv2LkZcU7JBuKQm2wfB+mRjJmJnNrPawLfn2UZ89Rz0BLwIy+6b24/RoIUgoNowfGkJreGiwItGK8fjCcx11oavK/yAo6pYZXCcru46pmHbxxle1OlkdTKkG6EVtJuKjSZXtVmBHZYRFzsR7HnAiXnL7QzSEcS7ieZlQvTmNLfpidJhK199oSbyKREqXGl2S8DzPKM9RLccVbQTy6X8qWimP3MYCnO4K0KoQnNQAgfuV8ZxnvdDecByLDPIpmFMGy0Xm9pUZWxmSoDBq+p5WBI2HdCX2gCYVv5yxS2iBqO5SMKo8iOglHtPI9NIMvloERdN1vZtxSRkY5uDEfrU9ysYwfayEXxvXmdWv0HxlotcgUinP02j7k+OfIapTmY/jGfvF4euyCGRKuJ9JlSD9pIiRdAcekjL6hCxXLJLdajCV4sL/YDo=
|
||||
`
|
||||
ctx1Dir := filepath.Join(homeDir, "contexts", "contx1")
|
||||
crFile := filepath.Join(ctx1Dir, "contx1.yaml")
|
||||
@@ -67,7 +74,7 @@ func TestGetCR(t *testing.T) {
|
||||
// create CR
|
||||
createCRFile(dir)
|
||||
|
||||
crFile := filepath.Join(dir, "contexts", "contx1", "contx1.yaml")
|
||||
crFile := filepath.Join("contexts", "contx1", "contx1.yaml")
|
||||
qct, e := qc.SetCrLocation("contx1", crFile)
|
||||
if e != nil {
|
||||
t.Fail()
|
||||
@@ -83,3 +90,136 @@ func TestGetCR(t *testing.T) {
|
||||
}
|
||||
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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"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 (
|
||||
QliksenseConfigApiVersion = "config.qlik.com/v1"
|
||||
QliksenseConfigKind = "QliksenseConfig"
|
||||
QliksenseContextApiVersion = "qlik.com/v1"
|
||||
QliksenseContextKind = "Qliksense"
|
||||
QliksenseDefaultProfile = "docker-desktop"
|
||||
DefaultRotateKeys = "yes"
|
||||
QliksenseMetadataName = "QliksenseConfigMetadata"
|
||||
DefaultMongoDbUri = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
|
||||
DefaultMongoDbUriKey = "mongoDbUri"
|
||||
QliksenseConfigApiVersion = "v1"
|
||||
QliksenseConfigApiGroup = "config.qlik.com"
|
||||
QliksenseConfigKind = "QliksenseConfig"
|
||||
|
||||
QliksenseApiVersion = "v1"
|
||||
QliksenseKind = "Qliksense"
|
||||
QliksenseGroup = "qlik.com"
|
||||
QliksenseDefaultProfile = "docker-desktop"
|
||||
DefaultRotateKeys = "yes"
|
||||
QliksenseMetadataName = "QliksenseConfigMetadata"
|
||||
DefaultMongoDbUri = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
|
||||
DefaultMongoDbUriKey = "mongoDbUri"
|
||||
)
|
||||
|
||||
// AddCommonConfig adds common configs into CRs
|
||||
func (qliksenseCR *QliksenseCR) AddCommonConfig(contextName string) {
|
||||
qliksenseCR.ApiVersion = QliksenseContextApiVersion
|
||||
qliksenseCR.Kind = QliksenseContextKind
|
||||
if qliksenseCR.Metadata == nil {
|
||||
qliksenseCR.Metadata = &Metadata{}
|
||||
qliksenseCR.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Group: QliksenseGroup,
|
||||
Kind: QliksenseKind,
|
||||
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, "")
|
||||
}
|
||||
|
||||
// AddBaseQliksenseConfigs adds configs into config.yaml
|
||||
func (qliksenseConfig *QliksenseConfig) AddBaseQliksenseConfigs(defaultQliksenseContext string) {
|
||||
qliksenseConfig.ApiVersion = QliksenseConfigApiVersion
|
||||
qliksenseConfig.Kind = QliksenseConfigKind
|
||||
if qliksenseConfig.Metadata == nil {
|
||||
qliksenseConfig.Metadata = &Metadata{}
|
||||
}
|
||||
qliksenseConfig.Metadata.Name = QliksenseMetadataName
|
||||
qliksenseConfig.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Group: QliksenseConfigApiGroup,
|
||||
Kind: QliksenseConfigKind,
|
||||
Version: QliksenseConfigApiVersion,
|
||||
})
|
||||
qliksenseConfig.SetName(QliksenseMetadataName)
|
||||
if defaultQliksenseContext != "" {
|
||||
if qliksenseConfig.Spec == nil {
|
||||
qliksenseConfig.Spec = &ContextSpec{}
|
||||
@@ -79,13 +84,12 @@ func WriteToFile(content interface{}, targetFile string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
x, err := yaml.Marshal(content)
|
||||
x, err := K8sToYaml(content)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("An error occurred during marshalling CR: %v", err)
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Writing content
|
||||
err = ioutil.WriteFile(targetFile, x, 0644)
|
||||
if err != nil {
|
||||
@@ -101,14 +105,22 @@ func ReadFromFile(content interface{}, sourceFile string) error {
|
||||
if content == nil || sourceFile == "" {
|
||||
return nil
|
||||
}
|
||||
contents, err := ioutil.ReadFile(sourceFile)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("There was an error reading from file: %s, %v", sourceFile, err)
|
||||
return err
|
||||
file, e := os.Open(sourceFile)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
if err := yaml.Unmarshal(contents, content); err != nil {
|
||||
err = fmt.Errorf("An error occurred during unmarshalling: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return ReadFromStream(content, file)
|
||||
}
|
||||
|
||||
// ReadFromStream reads from input stream and creat yaml struct of type content
|
||||
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"
|
||||
|
||||
"github.com/qlik-oss/k-apis/pkg/config"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -12,6 +13,26 @@ var (
|
||||
)
|
||||
|
||||
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 {
|
||||
qliksenseCR *QliksenseCR
|
||||
contextName string
|
||||
@@ -27,27 +48,7 @@ func TestAddCommonConfig(t *testing.T) {
|
||||
qliksenseCR: &QliksenseCR{},
|
||||
contextName: "myqliksense",
|
||||
},
|
||||
want: &QliksenseCR{
|
||||
CommonConfig: CommonConfig{
|
||||
ApiVersion: QliksenseContextApiVersion,
|
||||
Kind: QliksenseContextKind,
|
||||
Metadata: &Metadata{
|
||||
Name: "myqliksense",
|
||||
},
|
||||
},
|
||||
Spec: &config.CRSpec{
|
||||
Profile: QliksenseDefaultProfile,
|
||||
ReleaseName: "myqliksense",
|
||||
RotateKeys: DefaultRotateKeys,
|
||||
Secrets: map[string]config.NameValues{
|
||||
"qliksense": []config.NameValue{{
|
||||
Name: DefaultMongoDbUriKey,
|
||||
Value: DefaultMongoDbUri,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: q,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -61,6 +62,18 @@ func TestAddCommonConfig(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 {
|
||||
qliksenseConfig *QliksenseConfig
|
||||
defaultQliksenseContext string
|
||||
@@ -76,18 +89,7 @@ func TestAddBaseQliksenseConfigs(t *testing.T) {
|
||||
qliksenseConfig: &QliksenseConfig{},
|
||||
defaultQliksenseContext: "qlik-default",
|
||||
},
|
||||
want: &QliksenseConfig{
|
||||
CommonConfig: CommonConfig{
|
||||
ApiVersion: QliksenseConfigApiVersion,
|
||||
Kind: QliksenseConfigKind,
|
||||
Metadata: &Metadata{
|
||||
Name: QliksenseMetadataName,
|
||||
},
|
||||
},
|
||||
Spec: &ContextSpec{
|
||||
CurrentContext: "qlik-default",
|
||||
},
|
||||
},
|
||||
want: qc,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -28,7 +28,6 @@ func (kdcjt *k8sDockerConfigJsonType) GenerateAuth() {
|
||||
|
||||
type DockerConfigJsonSecret struct {
|
||||
Name string
|
||||
Namespace string
|
||||
Uri string
|
||||
Username string
|
||||
Password string
|
||||
@@ -51,9 +50,13 @@ func (d *DockerConfigJsonSecret) ToYaml(encryptionKey *rsa.PublicKey) ([]byte, e
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k8sDockerConfigJsonMapEncryptedBytes, err := Encrypt(k8sDockerConfigJsonMapBytes, encryptionKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var k8sDockerConfigJsonMapMaybeEncryptedBytes []byte
|
||||
if encryptionKey != nil {
|
||||
if k8sDockerConfigJsonMapMaybeEncryptedBytes, err = Encrypt(k8sDockerConfigJsonMapBytes, encryptionKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
k8sDockerConfigJsonMapMaybeEncryptedBytes = k8sDockerConfigJsonMapBytes
|
||||
}
|
||||
|
||||
k8sSecret := v1.Secret{
|
||||
@@ -63,11 +66,10 @@ func (d *DockerConfigJsonSecret) ToYaml(encryptionKey *rsa.PublicKey) ([]byte, e
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: d.Name,
|
||||
Namespace: d.Namespace,
|
||||
},
|
||||
Type: v1.SecretTypeDockerConfigJson,
|
||||
Data: map[string][]byte{
|
||||
".dockerconfigjson": k8sDockerConfigJsonMapEncryptedBytes,
|
||||
".dockerconfigjson": k8sDockerConfigJsonMapMaybeEncryptedBytes,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -90,7 +92,6 @@ func (d *DockerConfigJsonSecret) FromYaml(secretBytes []byte, decryptionKey *rsa
|
||||
return err
|
||||
} else {
|
||||
d.Name = k8sSecret.ObjectMeta.Name
|
||||
d.Namespace = k8sSecret.ObjectMeta.Namespace
|
||||
for registry, k8sDockerConfigJson := range k8sDockerConfigJsonMap.Auths {
|
||||
d.Uri = registry
|
||||
d.Username = k8sDockerConfigJson.Username
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
func TestDockerConfigJsonSecret(t *testing.T) {
|
||||
dockerConfigJsonSecret := DockerConfigJsonSecret{
|
||||
Name: "some-name",
|
||||
Namespace: "some-namespace",
|
||||
Uri: "some-uri",
|
||||
Username: "some-username",
|
||||
Password: "some-password",
|
||||
@@ -38,7 +37,6 @@ func TestDockerConfigJsonSecret(t *testing.T) {
|
||||
} else if validYamlMap["apiVersion"] != "v1" ||
|
||||
validYamlMap["kind"] != "Secret" ||
|
||||
validYamlMap["metadata"].(map[string]interface{})["name"] != dockerConfigJsonSecret.Name ||
|
||||
validYamlMap["metadata"].(map[string]interface{})["namespace"] != dockerConfigJsonSecret.Namespace ||
|
||||
validYamlMap["type"] != "kubernetes.io/dockerconfigjson" {
|
||||
t.Fatalf("error verifying validity of secret yaml: %v", string(dockerConfigJsonSecretYamlBytes))
|
||||
} else if dockerConfigJsonBytesBase64, ok := validYamlMap["data"].(map[string]interface{})[".dockerconfigjson"]; !ok {
|
||||
|
||||
@@ -67,8 +67,8 @@ func writeContentToFile(keyData []byte, fileName string) error {
|
||||
|
||||
// Encrypt encrypts data with public key
|
||||
func Encrypt(pt []byte, pub *rsa.PublicKey) ([]byte, error) {
|
||||
// hash := sha512.New()
|
||||
// ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, pub, msg, nil)
|
||||
//hash := sha512.New()
|
||||
//ct, err := rsa.EncryptOAEP(hash, rand.Reader, pub, pt, nil)
|
||||
ct, err := rsa.EncryptPKCS1v15(rand.Reader, pub, pt)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
|
||||
@@ -114,7 +114,7 @@ MFxk1pS9LMa/WnzvFr0gWakCAwEAAQ==
|
||||
}
|
||||
|
||||
encDataStr := base64.StdEncoding.EncodeToString(encData)
|
||||
log.Printf("encrypted data: %s\n", encDataStr)
|
||||
log.Println("Encoded text:", encDataStr)
|
||||
dec, _ := base64.StdEncoding.DecodeString(encDataStr)
|
||||
data, err := Decrypt(dec, privKey)
|
||||
if err != nil {
|
||||
@@ -125,5 +125,4 @@ MFxk1pS9LMa/WnzvFr0gWakCAwEAAQ==
|
||||
t.Error("original string and decrypted string don't match")
|
||||
t.FailNow()
|
||||
}
|
||||
log.Printf("decrypted data: %s\n", data)
|
||||
}
|
||||
|
||||
@@ -6,15 +6,7 @@ import (
|
||||
)
|
||||
|
||||
func K8sSecretToYaml(k8sSecret v1.Secret) ([]byte, error) {
|
||||
k8sSecretYamlMap := map[string]interface{}{}
|
||||
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)
|
||||
}
|
||||
return K8sToYaml(k8sSecret)
|
||||
}
|
||||
|
||||
func K8sSecretFromYaml(k8sSecretBytes []byte) (v1.Secret, error) {
|
||||
@@ -24,3 +16,15 @@ func K8sSecretFromYaml(k8sSecretBytes []byte) (v1.Secret, error) {
|
||||
}
|
||||
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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"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 {
|
||||
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 {
|
||||
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 {
|
||||
tempYaml, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
@@ -42,14 +87,31 @@ func kubectlOperation(manifests string, oprName string, namespace string) error
|
||||
cmd = exec.Command("kubectl", arguments...)
|
||||
}
|
||||
|
||||
sterrBuffer := &bytes.Buffer{}
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stderr = sterrBuffer
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
fmt.Printf("kubectl apply failed with %s\n", err)
|
||||
fmt.Println("temp CRD file: " + tempYaml.Name())
|
||||
return err
|
||||
return fmt.Errorf("kubectl %v failed with: %v, %v, temp k8s yaml file:%v\n", oprName, err, sterrBuffer.String(), tempYaml.Name())
|
||||
}
|
||||
os.Remove(tempYaml.Name())
|
||||
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
|
||||
|
||||
import "github.com/qlik-oss/k-apis/pkg/config"
|
||||
|
||||
// CommonConfig is exported
|
||||
type CommonConfig struct {
|
||||
ApiVersion string `json:"apiVersion" yaml:"apiVersion"`
|
||||
Kind string `json:"kind" yaml:"kind"`
|
||||
Metadata *Metadata `json:"metadata" yaml:"metadata"`
|
||||
}
|
||||
import (
|
||||
kapi_config "github.com/qlik-oss/k-apis/pkg/config"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// QliksenseConfig is exported
|
||||
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"`
|
||||
QliksenseHomePath string `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
/*type CommonConfig struct {
|
||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
|
||||
}
|
||||
*/
|
||||
// QliksenseCR is exported
|
||||
type QliksenseCR struct {
|
||||
CommonConfig `json:",inline" yaml:",inline"`
|
||||
Spec *config.CRSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
|
||||
kapi_config.KApiCr `json:",inline" yaml:",inline"`
|
||||
}
|
||||
|
||||
// ContextSpec is exported
|
||||
|
||||
162
pkg/api/utils.go
162
pkg/api/utils.go
@@ -1,13 +1,21 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func checkExists(filename string) os.FileInfo {
|
||||
@@ -59,24 +67,22 @@ func ProcessConfigArgs(args []string) ([]*ServiceKeyValue, error) {
|
||||
// split args[0] into key and value
|
||||
if len(args) == 0 {
|
||||
err := fmt.Errorf("No args were provided. Please provide args to configure the current context")
|
||||
log.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
resultSvcKV := make([]*ServiceKeyValue, len(args))
|
||||
re1 := regexp.MustCompile(`(\w{1,}).(\w{1,})=("*[\w\-_/:0-9]+"*)`)
|
||||
re1 := regexp.MustCompile(`(\w{1,}).(\w{1,})=("*[\w\-?=_/:0-9]+"*)`)
|
||||
for i, arg := range args {
|
||||
LogDebugMessage("Arg received: %s", arg)
|
||||
result := re1.FindStringSubmatch(arg)
|
||||
// check if result array's length is == 4 (index 0 - is the full match & indices 1,2,3- are the fields we need)
|
||||
if len(result) != 4 {
|
||||
err := fmt.Errorf("Please provide valid args for this command")
|
||||
log.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
resultSvcKV[i] = &ServiceKeyValue{
|
||||
SvcName: result[1],
|
||||
Key: result[2],
|
||||
Value: result[3],
|
||||
Value: strings.ReplaceAll(result[3], `"`, ""),
|
||||
}
|
||||
}
|
||||
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 (
|
||||
defaultProfile = "docker-desktop"
|
||||
defaultGitUrl = "https://github.com/qlik-oss/qliksense-k8s"
|
||||
defaultProfile = "docker-desktop"
|
||||
defaultConfigRepoGitUrl = "https://github.com/qlik-oss/qliksense-k8s"
|
||||
)
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
if isTemporary {
|
||||
} else if isTemporary {
|
||||
defer os.RemoveAll(configDirectory)
|
||||
}
|
||||
|
||||
return q.AboutDir(configDirectory, profile)
|
||||
}
|
||||
|
||||
func (q *Qliksense) AboutDir(configDirectory, profile string) (*VersionOutput, error) {
|
||||
chartVersion, err := getChartVersion(filepath.Join(configDirectory, "transformers", "qseokversion.yaml"), "qliksense")
|
||||
if err != nil {
|
||||
if chartVersion, err := getChartVersion(filepath.Join(configDirectory, "transformers", "qseokversion.yaml"), "qliksense"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kuzManifest, err := executeKustomizeBuildWithStdoutProgress(filepath.Join(configDirectory, "manifests", profile))
|
||||
if err != nil {
|
||||
} else if kuzManifest, err := executeKustomizeBuildWithStdoutProgress(filepath.Join(configDirectory, "manifests", profile)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
images, err := getImageList(kuzManifest)
|
||||
if err != nil {
|
||||
} else if images, err := getImageList(kuzManifest); err != nil {
|
||||
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) {
|
||||
|
||||
23
pkg/qliksense/apply.go
Normal file
23
pkg/qliksense/apply.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
func (q *Qliksense) ApplyCRFromReader(r io.Reader) error {
|
||||
if err := q.LoadCr(r); err != nil {
|
||||
return err
|
||||
}
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
cr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts := &InstallCommandOptions{}
|
||||
if err := q.InstallQK8s(cr.GetLabelFromCr("version"), opts, true); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -9,7 +10,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/qlik-oss/k-apis/pkg/cr"
|
||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||
@@ -17,7 +17,11 @@ import (
|
||||
)
|
||||
|
||||
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 {
|
||||
@@ -29,22 +33,47 @@ func (q *Qliksense) ConfigApplyQK8s() error {
|
||||
fmt.Println("cannot get the current-context cr", 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 != "" {
|
||||
// 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 {
|
||||
if qcr.Spec.RotateKeys != "None" {
|
||||
if err := os.Unsetenv("EJSON_KEY"); err != nil {
|
||||
fmt.Printf("error unsetting EJSON_KEY environment variable: %v\n", err)
|
||||
return err
|
||||
}
|
||||
if err := os.Setenv("EJSON_KEYDIR", q.QliksenseEjsonKeyDir); err != nil {
|
||||
fmt.Printf("error setting EJSON_KEYDIR environment variable: %v\n", err)
|
||||
if err := q.configEjson(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -54,8 +83,9 @@ func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Manifests root: " + qcr.Spec.GetManifestsRoot())
|
||||
qcr.SetNamespace(qapi.GetKubectlNamespace())
|
||||
// generate patches
|
||||
cr.GeneratePatches(qcr.Spec, path.Join(userHomeDir, ".kube", "config"))
|
||||
cr.GeneratePatches(&qcr.KApiCr, path.Join(userHomeDir, ".kube", "config"))
|
||||
// apply generated manifests
|
||||
profilePath := filepath.Join(qcr.Spec.GetManifestsRoot(), qcr.Spec.GetProfileDir())
|
||||
mByte, err := executeKustomizeBuild(profilePath)
|
||||
@@ -63,7 +93,7 @@ func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error {
|
||||
fmt.Println("cannot generate manifests for "+profilePath, 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
|
||||
}
|
||||
|
||||
@@ -76,7 +106,11 @@ func (q *Qliksense) ConfigViewCR() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(r)
|
||||
oth, err := q.getCurrentCrDependentResourceAsString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(r + "\n" + oth)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -92,18 +126,28 @@ func (q *Qliksense) getCRString(contextName string) (string, error) {
|
||||
fmt.Println("cannot get the context cr", err)
|
||||
return "", err
|
||||
}
|
||||
out, err := yaml.Marshal(qcr)
|
||||
out, err := qapi.K8sToYaml(qcr)
|
||||
if err != nil {
|
||||
fmt.Println("cannot unmarshal cr ", 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
|
||||
crString.Write(out)
|
||||
|
||||
for svcName, v := range qcr.Spec.Secrets {
|
||||
for _, item := range v {
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -10,25 +10,27 @@ import (
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"encoding/base64"
|
||||
"github.com/robfig/cron/v3"
|
||||
|
||||
"github.com/qlik-oss/k-apis/pkg/config"
|
||||
|
||||
b64 "encoding/base64"
|
||||
|
||||
ansi "github.com/mattn/go-colorable"
|
||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||
"github.com/ttacon/chalk"
|
||||
_ "gopkg.in/yaml.v2"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
// Below are some constants to support qliksense context setup
|
||||
QliksenseConfigHome = "/.qliksense"
|
||||
QliksenseConfigContextHome = "/.qliksense/contexts"
|
||||
QliksenseConfigFile = "config.yaml"
|
||||
QliksenseContextsDir = "contexts"
|
||||
DefaultQliksenseContext = "qlik-default"
|
||||
MaxContextNameLength = 17
|
||||
QliksenseSecretsDir = "secrets"
|
||||
QliksenseConfigFile = "config.yaml"
|
||||
QliksenseContextsDir = "contexts"
|
||||
DefaultQliksenseContext = "qlik-default"
|
||||
MaxContextNameLength = 17
|
||||
QliksenseSecretsDir = "secrets"
|
||||
|
||||
imageRegistryConfigKey = "imageRegistry"
|
||||
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
|
||||
api.LogDebugMessage("Current context: %s", qliksenseCR.Metadata.Name)
|
||||
api.LogDebugMessage("Current context: %s", qliksenseCR.GetName())
|
||||
rsaPublicKey, _, err := qConfig.GetCurrentContextEncryptionKeyPair()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resultArgs, err := api.ProcessConfigArgs(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, ra := range resultArgs {
|
||||
api.LogDebugMessage("value args to be encrypted: %s", ra.Value)
|
||||
if err := q.processSecret(ra, rsaPublicKey, qliksenseCR, isSecretSet); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -64,7 +66,7 @@ func (q *Qliksense) SetSecrets(args []string, isSecretSet bool) error {
|
||||
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
|
||||
valueBytes := []byte(ra.Value)
|
||||
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)
|
||||
secretName := ""
|
||||
if isSecretSet {
|
||||
secretFolder := filepath.Join(q.QliksenseHome, QliksenseContextsDir, qliksenseCR.Metadata.Name, QliksenseSecretsDir)
|
||||
secretFolder := qliksenseCR.GetK8sSecretsFolder(q.QliksenseHome)
|
||||
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)
|
||||
|
||||
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")
|
||||
return err
|
||||
}
|
||||
// api.WriteToFile(&k8sSecret, secretFileName)
|
||||
api.LogDebugMessage("Created a Kubernetes secret")
|
||||
|
||||
// Prepare args to update CR in the next step
|
||||
@@ -148,13 +149,12 @@ func (q *Qliksense) SetConfigs(args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func retrieveCurrentContextInfo(q *Qliksense) (api.QliksenseCR, string, error) {
|
||||
func retrieveCurrentContextInfo(q *Qliksense) (*api.QliksenseCR, string, error) {
|
||||
var qliksenseConfig api.QliksenseConfig
|
||||
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
|
||||
|
||||
if err := api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile); err != nil {
|
||||
log.Println(err)
|
||||
return api.QliksenseCR{}, "", err
|
||||
return nil, "", err
|
||||
}
|
||||
currentContext := qliksenseConfig.Spec.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
|
||||
err := fmt.Errorf(`Please run the "qliksense config set-context <context-name>" first before viewing the current context info`)
|
||||
log.Println(err)
|
||||
return api.QliksenseCR{}, "", err
|
||||
return nil, "", err
|
||||
}
|
||||
// read the context.yaml file
|
||||
var qliksenseCR api.QliksenseCR
|
||||
qliksenseCR := &api.QliksenseCR{}
|
||||
if currentContext == "" {
|
||||
// current-context is empty
|
||||
err := fmt.Errorf(`Please run the "qliksense config set-context <context-name>" first before viewing the current context info`)
|
||||
log.Println(err)
|
||||
return api.QliksenseCR{}, "", err
|
||||
return nil, "", err
|
||||
}
|
||||
qliksenseContextsFile := filepath.Join(q.QliksenseHome, QliksenseContextsDir, currentContext, currentContext+".yaml")
|
||||
if !api.FileExists(qliksenseContextsFile) {
|
||||
err := fmt.Errorf("Context file does not exist.\nPlease try re-running `qliksense config set-context <context-name>` and then `qliksense config view` again")
|
||||
log.Println(err)
|
||||
return api.QliksenseCR{}, "", err
|
||||
return nil, "", err
|
||||
}
|
||||
if err := api.ReadFromFile(&qliksenseCR, qliksenseContextsFile); err != nil {
|
||||
if err := api.ReadFromFile(qliksenseCR, qliksenseContextsFile); err != nil {
|
||||
log.Println(err)
|
||||
return api.QliksenseCR{}, "", err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
api.LogDebugMessage("Read context file: %s, Read QliksenseCR: %v", qliksenseContextsFile, qliksenseCR)
|
||||
|
||||
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 {
|
||||
// retieve current context from config.yaml
|
||||
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q)
|
||||
@@ -208,10 +209,10 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
|
||||
case "profile":
|
||||
qliksenseCR.Spec.Profile = argsString[1]
|
||||
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":
|
||||
if qliksenseCR.Spec.Git == nil {
|
||||
qliksenseCR.Spec.Git = &config.Repo{}
|
||||
}
|
||||
qliksenseCR.Spec.Git.Repository = argsString[1]
|
||||
api.LogDebugMessage("Current git repository after modification: %s ", qliksenseCR.Spec.Git.Repository)
|
||||
case "storageClassName":
|
||||
@@ -226,8 +227,44 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
|
||||
}
|
||||
qliksenseCR.Spec.RotateKeys = 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:
|
||||
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
|
||||
@@ -239,7 +276,10 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
|
||||
// SetContextConfig - set the context for qliksense kubernetes resources to live in
|
||||
func (q *Qliksense) SetContextConfig(args []string) error {
|
||||
if len(args) == 1 {
|
||||
q.SetUpQliksenseContext(args[0], false)
|
||||
err := q.SetUpQliksenseContext(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := fmt.Errorf("Please provide a name to configure the context with")
|
||||
log.Println(err)
|
||||
@@ -261,7 +301,7 @@ func (q *Qliksense) ListContextConfigs() error {
|
||||
w.Flush()
|
||||
if len(qliksenseConfig.Spec.Contexts) > 0 {
|
||||
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()
|
||||
fmt.Fprintln(out, "")
|
||||
@@ -272,13 +312,78 @@ func (q *Qliksense) ListContextConfigs() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) DeleteContextConfig(args []string) error {
|
||||
if len(args) == 1 {
|
||||
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
|
||||
var qliksenseConfig api.QliksenseConfig
|
||||
api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile)
|
||||
out := ansi.NewColorableStdout()
|
||||
switch args[0] {
|
||||
case qliksenseConfig.Spec.CurrentContext:
|
||||
fmt.Fprintln(out, chalk.Yellow.Color("Please switch contexts to be able to delete this context."))
|
||||
err := fmt.Errorf(chalk.Red.Color("Cannot delete current context - %s"), chalk.White.Color(chalk.Bold.TextStyle(qliksenseConfig.Spec.CurrentContext)))
|
||||
return err
|
||||
case DefaultQliksenseContext:
|
||||
err := fmt.Errorf(chalk.Red.Color("Cannot delete default qliksense context"))
|
||||
return err
|
||||
default:
|
||||
qliksenseContextsDir1 := filepath.Join(q.QliksenseHome, QliksenseContextsDir)
|
||||
qliksenseContextFile := filepath.Join(qliksenseContextsDir1, args[0])
|
||||
qliksenseSecretsDir1 := filepath.Join(q.QliksenseHome, QliksenseSecretsDir, QliksenseContextsDir)
|
||||
qliksenseSecretsFile := filepath.Join(qliksenseSecretsDir1, args[0])
|
||||
if err := os.RemoveAll(qliksenseContextFile); err != nil {
|
||||
err = fmt.Errorf("Not able to delete %s dir: %v", qliksenseContextsDir1, err)
|
||||
log.Println(err)
|
||||
return err
|
||||
} else if err := os.RemoveAll(qliksenseSecretsFile); err != nil {
|
||||
err = fmt.Errorf("No Secrets Folder Detected")
|
||||
log.Println(err)
|
||||
return err
|
||||
} else {
|
||||
currentLength := len(qliksenseConfig.Spec.Contexts)
|
||||
if currentLength > 0 {
|
||||
temp := qliksenseConfig.Spec.Contexts
|
||||
qliksenseConfig.Spec.Contexts = nil
|
||||
for _, ctx := range temp {
|
||||
if ctx.Name != args[0] {
|
||||
qliksenseConfig.Spec.Contexts = append(qliksenseConfig.Spec.Contexts, api.Context{
|
||||
Name: ctx.Name,
|
||||
CrFile: ctx.CrFile,
|
||||
})
|
||||
}
|
||||
}
|
||||
newLength := len(qliksenseConfig.Spec.Contexts)
|
||||
if currentLength != newLength {
|
||||
api.WriteToFile(&qliksenseConfig, qliksenseConfigFile)
|
||||
fmt.Fprintln(out, chalk.Yellow.Color(chalk.Underline.TextStyle("Warning: Active resources may still be running in-cluster")))
|
||||
fmt.Fprintln(out, chalk.Green.Color("Successfully deleted context: "), chalk.Bold.TextStyle(args[0]))
|
||||
} else {
|
||||
err := fmt.Errorf(chalk.Red.Color("Context not found"))
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err := fmt.Errorf("Please provide a context as an argument to delete")
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetUpQliksenseDefaultContext - to setup dir structure for default qliksense context
|
||||
func (q *Qliksense) SetUpQliksenseDefaultContext() error {
|
||||
return q.SetUpQliksenseContext(DefaultQliksenseContext, true)
|
||||
return q.SetUpQliksenseContext(DefaultQliksenseContext)
|
||||
}
|
||||
|
||||
// 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
|
||||
if len(contextName) > MaxContextNameLength {
|
||||
err := fmt.Errorf("Please enter a context-name with utmost 17 characters")
|
||||
@@ -287,78 +392,27 @@ func (q *Qliksense) SetUpQliksenseContext(contextName string, isDefaultContext b
|
||||
}
|
||||
|
||||
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
|
||||
var qliksenseConfig api.QliksenseConfig
|
||||
configFileTrack := false
|
||||
qliksenseConfig := api.NewQConfigEmpty(q.QliksenseHome)
|
||||
|
||||
if !api.FileExists(qliksenseConfigFile) {
|
||||
qliksenseConfig.AddBaseQliksenseConfigs(contextName)
|
||||
} else {
|
||||
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 {
|
||||
if err := api.ReadFromFile(qliksenseConfig, qliksenseConfigFile); err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
api.WriteToFile(&qliksenseCR, qliksenseContextFile)
|
||||
ctxTrack := false
|
||||
if len(qliksenseConfig.Spec.Contexts) > 0 {
|
||||
for _, ctx := range qliksenseConfig.Spec.Contexts {
|
||||
if ctx.Name == contextName {
|
||||
ctx.CrFile = qliksenseContextFile
|
||||
ctxTrack = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if qliksenseConfig.IsContextExist(contextName) {
|
||||
return nil
|
||||
}
|
||||
if !ctxTrack {
|
||||
qliksenseConfig.Spec.Contexts = append(qliksenseConfig.Spec.Contexts, api.Context{
|
||||
Name: contextName,
|
||||
CrFile: qliksenseContextFile,
|
||||
})
|
||||
qliksenseCR := &api.QliksenseCR{}
|
||||
qliksenseCR.AddCommonConfig(contextName)
|
||||
if err := qliksenseConfig.CreateOrWriteCrAndContext(qliksenseCR); err != nil {
|
||||
return err
|
||||
}
|
||||
qliksenseConfig.Spec.CurrentContext = contextName
|
||||
if !configFileTrack {
|
||||
api.WriteToFile(&qliksenseConfig, qliksenseConfigFile)
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -380,19 +434,19 @@ func validateInput(input string) (string, error) {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
qConfig := api.NewQConfig(q.QliksenseHome)
|
||||
_, rsaPrivateKey, err := qConfig.GetCurrentContextEncryptionKeyPair()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
func (q *Qliksense) PrepareK8sSecret(targetFile string) (string, error) {
|
||||
// check if targetFile exists
|
||||
if !api.FileExists(targetFile) {
|
||||
err := fmt.Errorf("Target file does not exist in the path provided")
|
||||
log.Println(err)
|
||||
return "", err
|
||||
}
|
||||
qConfig := api.NewQConfig(q.QliksenseHome)
|
||||
_, rsaPrivateKey, err := qConfig.GetCurrentContextEncryptionKeyPair()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// read the target file
|
||||
k8sSecret, err := readTargetfile(targetFile)
|
||||
if err != nil {
|
||||
@@ -403,34 +457,28 @@ func (q *Qliksense) PrepareK8sSecret(qliksenseCR api.QliksenseCR, targetFile str
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dataMap := k8sSecret1.Data
|
||||
var base64EncodedSecret string
|
||||
var resultMap = make(map[string][]byte)
|
||||
for k, v := range dataMap {
|
||||
// base64 decode every value
|
||||
decodedStr, _ := base64.StdEncoding.DecodeString(string(v))
|
||||
|
||||
decryptedString, err := api.Decrypt(decodedStr, rsaPrivateKey)
|
||||
ba, err := b64.StdEncoding.DecodeString(string(v))
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Not able to decrypt message")
|
||||
err := fmt.Errorf("Not able to decode message: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// base64 encode the values
|
||||
base64EncodedSecret = b64.StdEncoding.EncodeToString(decryptedString)
|
||||
resultMap[k] = []byte(base64EncodedSecret)
|
||||
decryptedString, err := api.Decrypt(ba, rsaPrivateKey)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Not able to decrypt message: %v", err)
|
||||
return "", err
|
||||
}
|
||||
resultMap[k] = decryptedString
|
||||
}
|
||||
api.LogDebugMessage("B64 encoded Map: %v\n", resultMap)
|
||||
|
||||
// putting the above map back into the k8sSecret struct
|
||||
k8sSecret1.Data = resultMap
|
||||
k8sSecretBytes, err := api.K8sSecretToYaml(k8sSecret1)
|
||||
api.LogDebugMessage("Final Yaml: %v\n", string(k8sSecretBytes))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(k8sSecretBytes), nil
|
||||
}
|
||||
|
||||
@@ -458,16 +506,30 @@ func (q *Qliksense) SetImageRegistry(registry, pushUsername, pushPassword, pullU
|
||||
}); err != nil {
|
||||
return err
|
||||
} else if err := qConfig.SetPullDockerConfigJsonSecret(&api.DockerConfigJsonSecret{
|
||||
Name: pullSecretName,
|
||||
Namespace: qliksenseCR.Spec.NameSpace,
|
||||
Uri: registry,
|
||||
Username: pullUsername,
|
||||
Password: pullPassword,
|
||||
Email: pullUsername,
|
||||
Name: pullSecretName,
|
||||
Uri: registry,
|
||||
Username: pullUsername,
|
||||
Password: pullPassword,
|
||||
Email: pullUsername,
|
||||
}); err != nil {
|
||||
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)
|
||||
return api.WriteToFile(&qliksenseCR, qliksenseContextsFile)
|
||||
}
|
||||
|
||||
func (q *Qliksense) SetEulaAccepted() error {
|
||||
qConfig := api.NewQConfig(q.QliksenseHome)
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
qcr.SetEULA("yes")
|
||||
return qConfig.WriteCurrentContextCR(qcr)
|
||||
}
|
||||
|
||||
@@ -1,23 +1,158 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
b64 "encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/qlik-oss/k-apis/pkg/config"
|
||||
|
||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
testDir = "./tests"
|
||||
const (
|
||||
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() {
|
||||
// create tests dir
|
||||
os.RemoveAll(testDir)
|
||||
if err := os.Mkdir(testDir, 0777); err != nil {
|
||||
log.Printf("\nError occurred: %v", err)
|
||||
}
|
||||
@@ -30,7 +165,7 @@ metadata:
|
||||
spec:
|
||||
contexts:
|
||||
- name: qlik-default
|
||||
crLocation: /root/.qliksense/contexts/qlik-default.yaml
|
||||
crFile: contexts/qlik-default/qlik-default.yaml
|
||||
currentContext: qlik-default
|
||||
`
|
||||
configFile := filepath.Join(testDir, "config.yaml")
|
||||
@@ -65,6 +200,22 @@ spec:
|
||||
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) {
|
||||
|
||||
tearDown := setup()
|
||||
@@ -108,18 +259,23 @@ func TestSetUpQliksenseContext(t *testing.T) {
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty contextname",
|
||||
args: args{
|
||||
qlikSenseHome: testDir,
|
||||
contextName: "",
|
||||
isDefaultContext: false,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
tearDown := setup()
|
||||
defer tearDown()
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
q, err := New(tt.args.qlikSenseHome)
|
||||
if err != nil {
|
||||
t.Errorf("unable to create a qliksense instance")
|
||||
return
|
||||
}
|
||||
if err := q.SetUpQliksenseContext(tt.args.contextName, tt.args.isDefaultContext); (err != nil) != tt.wantErr {
|
||||
q := New(tt.args.qlikSenseHome)
|
||||
if err := q.SetUpQliksenseContext(tt.args.contextName); (err != nil) != tt.wantErr {
|
||||
t.Errorf("SetUpQliksenseContext() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
@@ -147,11 +303,7 @@ func TestSetUpQliksenseDefaultContext(t *testing.T) {
|
||||
defer tearDown()
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
q, err := New(tt.args.qlikSenseHome)
|
||||
if err != nil {
|
||||
t.Errorf("unable to create a qliksense instance")
|
||||
return
|
||||
}
|
||||
q := New(tt.args.qlikSenseHome)
|
||||
if err := q.SetUpQliksenseDefaultContext(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("SetUpQliksenseDefaultContext() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
@@ -175,10 +327,30 @@ func TestSetOtherConfigs(t *testing.T) {
|
||||
q: &Qliksense{
|
||||
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,
|
||||
},
|
||||
{
|
||||
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()
|
||||
defer tearDown()
|
||||
@@ -207,7 +379,7 @@ func TestSetConfigs(t *testing.T) {
|
||||
q: &Qliksense{
|
||||
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,
|
||||
},
|
||||
@@ -225,7 +397,7 @@ func TestSetConfigs(t *testing.T) {
|
||||
|
||||
func TestSetImageRegistry(t *testing.T) {
|
||||
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
|
||||
kind: QliksenseConfig
|
||||
metadata:
|
||||
@@ -233,9 +405,9 @@ metadata:
|
||||
spec:
|
||||
contexts:
|
||||
- name: qlik-default
|
||||
crFile: %s/contexts/qlik-default/qlik-default.yaml
|
||||
crFile: contexts/qlik-default/qlik-default.yaml
|
||||
currentContext: qlik-default
|
||||
`, tmpQlikSenseHome)), os.ModePerm); err != nil {
|
||||
`), os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -318,20 +490,448 @@ spec:
|
||||
pushSecret.Username != testCase.pushUsername || pushSecret.Password != testCase.pushPassword {
|
||||
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)
|
||||
} 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 {
|
||||
t.Fatalf("unexpected pull secret content: %v", pullSecret)
|
||||
}
|
||||
} else {
|
||||
if _, err := qConfig.GetPushDockerConfigJsonSecret(); err == nil {
|
||||
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")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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); (err != nil) != tt.wantErr {
|
||||
t.Errorf("DeleteContext() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
"path/filepath"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
type CrdCommandOptions struct {
|
||||
@@ -40,11 +40,11 @@ func (q *Qliksense) InstallCrds(opts *CrdCommandOptions) error {
|
||||
|
||||
if engineCRD, err := getQliksenseInitCrd(qcr); err != nil {
|
||||
return err
|
||||
} else if err = qapi.KubectlApply(engineCRD, qcr.Spec.NameSpace); err != nil {
|
||||
} else if err = qapi.KubectlApply(engineCRD, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
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)
|
||||
return err
|
||||
}
|
||||
@@ -53,13 +53,18 @@ func (q *Qliksense) InstallCrds(opts *CrdCommandOptions) error {
|
||||
}
|
||||
|
||||
func getQliksenseInitCrd(qcr *qapi.QliksenseCR) (string, error) {
|
||||
var repoPath string
|
||||
var err error
|
||||
|
||||
if qcr.Spec.GetManifestsRoot() == "" {
|
||||
return "", errors.New("Cannot find manifests root. Please use `qliksense fetch <version>`")
|
||||
if qcr.Spec.GetManifestsRoot() != "" {
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
fmt.Printf("%v\n", err)
|
||||
return err
|
||||
@@ -64,6 +69,15 @@ func (q *Qliksense) PullImagesForCurrentCR() error {
|
||||
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 {
|
||||
srcRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%v", image))
|
||||
if err != nil {
|
||||
@@ -134,7 +148,12 @@ func (q *Qliksense) PushImagesForCurrentCR() error {
|
||||
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 {
|
||||
fmt.Printf("%v\n", err)
|
||||
return err
|
||||
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
|
||||
"github.com/containers/image/v5/copy"
|
||||
"github.com/containers/image/v5/signature"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
@@ -47,7 +49,9 @@ func Test_locateDockerRegistryBinary(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 {
|
||||
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 {
|
||||
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
|
||||
|
||||
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 {
|
||||
t.Fatalf("unexpected error unmarshalling version file: %v", err)
|
||||
} 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 {
|
||||
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 {
|
||||
@@ -164,7 +171,7 @@ func Test_Pull_Push_ImagesForCurrentCR(t *testing.T) {
|
||||
}
|
||||
|
||||
func setupQlikSenseHome(t *testing.T, tmpQlikSenseHome string, registry *testRegistryV2, clientAuth clientAuthType) error {
|
||||
if err := ioutil.WriteFile(path.Join(tmpQlikSenseHome, "config.yaml"), []byte(fmt.Sprintf(`
|
||||
if err := ioutil.WriteFile(path.Join(tmpQlikSenseHome, "config.yaml"), []byte(`
|
||||
apiVersion: config.qlik.com/v1
|
||||
kind: QliksenseConfig
|
||||
metadata:
|
||||
@@ -172,9 +179,9 @@ metadata:
|
||||
spec:
|
||||
contexts:
|
||||
- name: qlik-default
|
||||
crFile: %s/contexts/qlik-default/qlik-default.yaml
|
||||
crFile: contexts/qlik-default/qlik-default.yaml
|
||||
currentContext: qlik-default
|
||||
`, tmpQlikSenseHome)), os.ModePerm); err != nil {
|
||||
`), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -345,7 +352,7 @@ http:
|
||||
)
|
||||
var env []string
|
||||
if auth {
|
||||
if certificate, key, err := getSelfSignedCertAndKey(); err != nil {
|
||||
if certificate, key, err := getSelfSignedCertAndKey("localhost", time.Hour*24); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -477,13 +484,14 @@ func getSelfSignedCertAndKey() (certificate, key []byte, err error) {
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Acme Co"},
|
||||
Organization: []string{"self-signed"},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(time.Hour * 24),
|
||||
NotAfter: time.Now().Add(validity),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
DNSNames: []string{hostname},
|
||||
}
|
||||
|
||||
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,22 @@ package qliksense
|
||||
import (
|
||||
"errors"
|
||||
"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"
|
||||
)
|
||||
|
||||
type InstallCommandOptions struct {
|
||||
AcceptEULA string
|
||||
Namespace string
|
||||
StorageClass string
|
||||
MongoDbUri 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
|
||||
// step2: operator view | kubectl apply -f # operator manifest (CRD)
|
||||
@@ -24,6 +27,13 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) err
|
||||
|
||||
// fetch the version
|
||||
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()
|
||||
if err != nil {
|
||||
@@ -32,7 +42,7 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) err
|
||||
}
|
||||
|
||||
if opts.AcceptEULA != "" {
|
||||
qcr.Spec.AddToConfigs("qliksense", "acceptEULA", opts.AcceptEULA)
|
||||
qcr.SetEULA(opts.AcceptEULA)
|
||||
}
|
||||
if opts.MongoDbUri != "" {
|
||||
qcr.Spec.AddToSecrets("qliksense", "mongoDbUri", opts.MongoDbUri, "")
|
||||
@@ -40,25 +50,52 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) err
|
||||
if opts.StorageClass != "" {
|
||||
qcr.Spec.StorageClassName = opts.StorageClass
|
||||
}
|
||||
if opts.Namespace != "" {
|
||||
qcr.Spec.NameSpace = opts.Namespace
|
||||
}
|
||||
if opts.RotateKeys != "" {
|
||||
qcr.Spec.RotateKeys = opts.RotateKeys
|
||||
}
|
||||
qConfig.WriteCurrentContextCR(qcr)
|
||||
|
||||
//if 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
|
||||
//install operator controller into the namespace
|
||||
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)
|
||||
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
|
||||
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 err := fetchAndUpdateCR(qConfig, version); err != nil {
|
||||
@@ -76,25 +113,100 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) err
|
||||
|
||||
// install 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")
|
||||
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
|
||||
//get the current context cr
|
||||
fmt.Println("Install operator CR into cluster")
|
||||
r, err := q.getCurrentCRString()
|
||||
r, err := cr.GetString()
|
||||
if err != nil {
|
||||
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")
|
||||
return err
|
||||
}
|
||||
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); 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"
|
||||
|
||||
_ "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/konfig"
|
||||
"sigs.k8s.io/kustomize/api/krusty"
|
||||
@@ -13,12 +14,15 @@ import (
|
||||
)
|
||||
|
||||
func executeKustomizeBuild(directory string) ([]byte, error) {
|
||||
return executeKustomizeBuildForFileSystem(directory, filesys.MakeFsOnDisk())
|
||||
}
|
||||
|
||||
func executeKustomizeBuildForFileSystem(directory string, fSys filesys.FileSystem) ([]byte, error) {
|
||||
log.SetOutput(&nullWriter{})
|
||||
defer func() {
|
||||
log.SetOutput(os.Stderr)
|
||||
}()
|
||||
|
||||
fSys := filesys.MakeFsOnDisk()
|
||||
options := &krusty.Options{
|
||||
DoLegacyResourceSort: false,
|
||||
LoadRestrictions: types.LoadRestrictionsNone,
|
||||
|
||||
@@ -66,7 +66,7 @@ func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
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)
|
||||
} else if err := kapis_git.Checkout(repo, "v1.21.23-edge", "", nil); err != nil {
|
||||
t.Fatalf("unexpected error: %v\n", err)
|
||||
|
||||
84
pkg/qliksense/load_cr.go
Normal file
84
pkg/qliksense/load_cr.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
//
|
||||
func (q *Qliksense) LoadCr(reader io.Reader) error {
|
||||
for _, doc := range readMultipleYamlFromReader(reader) {
|
||||
if crName, err := q.loadCrStringIntoFileSystem(doc); err != nil {
|
||||
return err
|
||||
} else {
|
||||
fmt.Println("cr name: [ " + crName + " ] has been loaded")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) loadCrStringIntoFileSystem(crstr string) (string, error) {
|
||||
cr, err := qapi.CreateCRObjectFromString(crstr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
if qConfig.IsContextExist(cr.GetName()) {
|
||||
return "", errors.New("Context Name: " + cr.GetName() + " already exist. please delete the existing context first using delete-context command")
|
||||
}
|
||||
qConfig.CreateContextDirs(cr.GetName())
|
||||
|
||||
// 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.AddToContextsRaw(cr.GetName(), qConfig.BuildCrFilePath(cr.GetName()))
|
||||
qConfig.SetCurrentContextName(cr.GetName())
|
||||
qConfig.Write()
|
||||
|
||||
return cr.GetName(), nil
|
||||
}
|
||||
|
||||
func readMultipleYamlFromReader(reader io.Reader) []string {
|
||||
docs := make([]string, 0)
|
||||
scanner := bufio.NewScanner(bufio.NewReader(reader))
|
||||
adoc := ""
|
||||
for scanner.Scan() {
|
||||
s := scanner.Text()
|
||||
if s == "---" {
|
||||
docs = append(docs, adoc)
|
||||
adoc = ""
|
||||
s = ""
|
||||
}
|
||||
adoc = adoc + "\n" + s
|
||||
}
|
||||
if adoc != "" {
|
||||
docs = append(docs, adoc)
|
||||
}
|
||||
return docs
|
||||
}
|
||||
129
pkg/qliksense/load_cr_test.go
Normal file
129
pkg/qliksense/load_cr_test.go
Normal file
@@ -0,0 +1,129 @@
|
||||
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()
|
||||
sampleCr := `
|
||||
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"
|
||||
---
|
||||
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"`
|
||||
crFile := filepath.Join(testDir, "testcr.yaml")
|
||||
ioutil.WriteFile(crFile, []byte(sampleCr), 0644)
|
||||
|
||||
dupCrFile := filepath.Join(testDir, "dupcr.yaml")
|
||||
ioutil.WriteFile(dupCrFile, []byte(duplicateCr), 0644)
|
||||
|
||||
q := New(testDir)
|
||||
file, e := os.Open(crFile)
|
||||
if e != nil {
|
||||
t.Log(e)
|
||||
t.FailNow()
|
||||
}
|
||||
if err := q.LoadCr(file); 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); err == nil {
|
||||
t.FailNow()
|
||||
}
|
||||
td()
|
||||
}
|
||||
@@ -2,29 +2,21 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
)
|
||||
|
||||
// Qliksense is the logic behind the qliksense client
|
||||
type Qliksense struct {
|
||||
QliksenseHome string
|
||||
QliksenseEjsonKeyDir string
|
||||
CrdBox *packr.Box ``
|
||||
QliksenseHome string
|
||||
CrdBox *packr.Box ``
|
||||
}
|
||||
|
||||
// New qliksense client, initialized with useful defaults.
|
||||
func New(qliksenseHome string) (*Qliksense, error) {
|
||||
func New(qliksenseHome string) *Qliksense {
|
||||
qliksenseClient := &Qliksense{
|
||||
QliksenseHome: qliksenseHome,
|
||||
CrdBox: packr.New("crds", "./crds"),
|
||||
}
|
||||
|
||||
qliksenseClient.QliksenseEjsonKeyDir = path.Join(qliksenseHome, "ejson", "keys")
|
||||
if err := os.MkdirAll(qliksenseClient.QliksenseEjsonKeyDir, os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return qliksenseClient, nil
|
||||
return qliksenseClient
|
||||
}
|
||||
|
||||
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) {
|
||||
return errors.New("context name [ " + contextName + " ] not found")
|
||||
}
|
||||
cr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
str, err := q.getCRString(contextName)
|
||||
if err != nil {
|
||||
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"
|
||||
)
|
||||
|
||||
func (q *Qliksense) UpgradeQK8s() error {
|
||||
func (q *Qliksense) UpgradeQK8s(keepPatchFiles bool) error {
|
||||
|
||||
// step1: get CR
|
||||
// step2: run kustomize
|
||||
@@ -14,6 +14,13 @@ func (q *Qliksense) UpgradeQK8s() error {
|
||||
|
||||
// fetch the version
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
if !keepPatchFiles {
|
||||
defer func() {
|
||||
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
|
||||
fmt.Printf("error removing temporary changes to the config: %v\n", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
@@ -21,7 +28,9 @@ func (q *Qliksense) UpgradeQK8s() error {
|
||||
return err
|
||||
}
|
||||
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")
|
||||
return err
|
||||
}
|
||||
@@ -31,7 +40,7 @@ func (q *Qliksense) UpgradeQK8s() error {
|
||||
if err != nil {
|
||||
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")
|
||||
}
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user