Compare commits
1 Commits
gitops_spe
...
testing-ou
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3a913f242 |
@@ -9,7 +9,6 @@ jobs:
|
|||||||
working_directory: /go/src/github.com/qlik-oss/sense-installer
|
working_directory: /go/src/github.com/qlik-oss/sense-installer
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run: make test
|
|
||||||
- run: make build
|
- run: make build
|
||||||
build_release:
|
build_release:
|
||||||
docker:
|
docker:
|
||||||
@@ -17,27 +16,40 @@ jobs:
|
|||||||
working_directory: /go/src/github.com/qlik-oss/sense-installer
|
working_directory: /go/src/github.com/qlik-oss/sense-installer
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run: make test
|
|
||||||
- run: make xbuild-all
|
- run: make xbuild-all
|
||||||
- run:
|
- run:
|
||||||
name: "Publish Release on GitHub"
|
name: "Publish Release on GitHub"
|
||||||
command: |
|
command: |
|
||||||
go get github.com/tcnksm/ghr
|
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}/
|
# 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:
|
workflows:
|
||||||
version: 2
|
version: 2
|
||||||
commit:
|
commit:
|
||||||
jobs:
|
jobs:
|
||||||
- build:
|
- build:
|
||||||
filters:
|
filters:
|
||||||
tags:
|
branches:
|
||||||
ignore: /^v.*/
|
ignore: master
|
||||||
|
|
||||||
|
- build_release:
|
||||||
|
requires:
|
||||||
|
- build
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
ignore: master
|
||||||
build_release:
|
build_release:
|
||||||
jobs:
|
jobs:
|
||||||
- build_release:
|
- build:
|
||||||
filters:
|
filters:
|
||||||
tags:
|
|
||||||
only: /^v.*/
|
|
||||||
branches:
|
branches:
|
||||||
ignore: /.*/
|
ignore: /.*/
|
||||||
|
tags:
|
||||||
|
only: /v.*/
|
||||||
|
- build_release:
|
||||||
|
requires:
|
||||||
|
- build
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
ignore: /.*/
|
||||||
|
tags:
|
||||||
|
only: /v.*/
|
||||||
|
|||||||
5
.gitattributes
vendored
5
.gitattributes
vendored
@@ -1,5 +0,0 @@
|
|||||||
# Ignore all files and folders that start with .; .circleci, .github, .git, etc.
|
|
||||||
# Warning! This will ignore files in subfolders as well.
|
|
||||||
# If you needs files starting with . then change condition below to be specific
|
|
||||||
# for each file and folder that needs to be ignored
|
|
||||||
.* export-ignore
|
|
||||||
21
.github/workflows/mkdocs.yml
vendored
21
.github/workflows/mkdocs.yml
vendored
@@ -1,21 +0,0 @@
|
|||||||
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 }}
|
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,8 +1,2 @@
|
|||||||
bin
|
bin
|
||||||
.vscode
|
.vscode
|
||||||
cmd/qliksense/__debug_bin
|
|
||||||
pkg/qliksense/crds
|
|
||||||
pkg/qliksense/packrd
|
|
||||||
pkg/qliksense/qliksense-packr.go
|
|
||||||
pkg/qliksense/docker-registry
|
|
||||||
/pkg/qliksense/tests
|
|
||||||
|
|||||||
628
.qliksense/porter-runtime
Normal file
628
.qliksense/porter-runtime
Normal file
File diff suppressed because one or more lines are too long
628
.qliksense/porter.exe
Normal file
628
.qliksense/porter.exe
Normal file
File diff suppressed because one or more lines are too long
23
MKDOCS.md
23
MKDOCS.md
@@ -1,23 +0,0 @@
|
|||||||
# 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
|
|
||||||
```
|
|
||||||
57
Makefile
57
Makefile
@@ -4,15 +4,13 @@ PKG = github.com/qlik-oss/sense-installer
|
|||||||
MAKE_OPTS ?= --no-print-directory
|
MAKE_OPTS ?= --no-print-directory
|
||||||
|
|
||||||
LDFLAGS = -w -X $(PKG)/pkg.Version=$(VERSION) -X $(PKG)/pkg.Commit=$(COMMIT) -X "$(PKG)/pkg.CommitDate=$(COMMIT_DATE)"
|
LDFLAGS = -w -X $(PKG)/pkg.Version=$(VERSION) -X $(PKG)/pkg.Commit=$(COMMIT) -X "$(PKG)/pkg.CommitDate=$(COMMIT_DATE)"
|
||||||
XBUILD = CGO_ENABLED=0 go build -a -tags "$(BUILDTAGS)" -ldflags '$(LDFLAGS)'
|
XBUILD = CGO_ENABLED=0 go build -a -tags netgo -ldflags '$(LDFLAGS)'
|
||||||
BINDIR = bin
|
BINDIR = bin
|
||||||
|
|
||||||
COMMIT ?= $(shell git rev-parse --short HEAD)
|
COMMIT ?= $(shell git rev-parse --short HEAD)
|
||||||
COMMIT_DATE ?= $(shell git show --no-patch --no-notes --pretty='%cd' $(COMMIT) --date=iso)
|
COMMIT_DATE ?= $(shell git show --no-patch --no-notes --pretty='%cd' $(COMMIT) --date=iso)
|
||||||
VERSION ?= $(shell git describe --tags 2> /dev/null || echo v0)
|
VERSION ?= $(shell git describe --tags 2> /dev/null || echo v0)
|
||||||
PERMALINK ?= $(shell git describe --tags --exact-match &> /dev/null && echo latest || echo canary)
|
PERMALINK ?= $(shell git describe --tags --exact-match &> /dev/null && echo latest || echo canary)
|
||||||
BUILDTAGS = netgo containers_image_ostree_stub exclude_graphdriver_devicemapper exclude_graphdriver_btrfs containers_image_openpgp
|
|
||||||
|
|
||||||
|
|
||||||
CLIENT_PLATFORM ?= $(shell go env GOOS)
|
CLIENT_PLATFORM ?= $(shell go env GOOS)
|
||||||
CLIENT_ARCH ?= $(shell go env GOARCH)
|
CLIENT_ARCH ?= $(shell go env GOARCH)
|
||||||
@@ -24,13 +22,8 @@ SUPPORTED_ARCHES = amd64
|
|||||||
|
|
||||||
MIXIN = qliksense
|
MIXIN = qliksense
|
||||||
|
|
||||||
DEVNUL := /dev/null
|
|
||||||
WHICH := which
|
|
||||||
|
|
||||||
ifeq ($(CLIENT_PLATFORM),windows)
|
ifeq ($(CLIENT_PLATFORM),windows)
|
||||||
FILE_EXT=.exe
|
FILE_EXT=.exe
|
||||||
DEVNUL := NUL
|
|
||||||
WHICH := where
|
|
||||||
else ifeq ($(RUNTIME_PLATFORM),windows)
|
else ifeq ($(RUNTIME_PLATFORM),windows)
|
||||||
FILE_EXT=.exe
|
FILE_EXT=.exe
|
||||||
else
|
else
|
||||||
@@ -38,58 +31,18 @@ FILE_EXT=
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: clean generate
|
build:
|
||||||
mkdir -p $(BINDIR)
|
mkdir -p $(BINDIR)
|
||||||
go build -ldflags '$(LDFLAGS)' -tags "$(BUILDTAGS)" -o $(BINDIR)/$(MIXIN)$(FILE_EXT) ./cmd/$(MIXIN)
|
go build -ldflags '$(LDFLAGS)' -o $(BINDIR)/$(MIXIN)$(FILE_EXT) ./cmd/$(MIXIN)
|
||||||
$(MAKE) clean
|
|
||||||
|
|
||||||
.PHONY: test
|
xbuild-all:
|
||||||
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
|
|
||||||
-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 OS, $(SUPPORTED_PLATFORMS), \
|
||||||
$(foreach ARCH, $(SUPPORTED_ARCHES), \
|
$(foreach ARCH, $(SUPPORTED_ARCHES), \
|
||||||
$(MAKE) $(MAKE_OPTS) CLIENT_PLATFORM=$(OS) CLIENT_ARCH=$(ARCH) MIXIN=$(MIXIN) xbuild; \
|
$(MAKE) $(MAKE_OPTS) CLIENT_PLATFORM=$(OS) CLIENT_ARCH=$(ARCH) MIXIN=$(MIXIN) xbuild; \
|
||||||
))
|
))
|
||||||
$(MAKE) clean
|
|
||||||
xbuild: $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
xbuild: $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
||||||
$(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT):
|
$(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT):
|
||||||
mkdir -p $(dir $@)
|
mkdir -p $(dir $@)
|
||||||
GOOS=$(CLIENT_PLATFORM) GOARCH=$(CLIENT_ARCH) $(XBUILD) -o $@ ./cmd/$(MIXIN)
|
GOOS=$(CLIENT_PLATFORM) GOARCH=$(CLIENT_ARCH) $(XBUILD) -o $@ ./cmd/$(MIXIN)
|
||||||
tar -czvf $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).tar.gz -C $(BINDIR)/$(VERSION)/ $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
|
||||||
#tar -C $(BINDIR)/$(VERSION)/ -cvf $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).tar.gz $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
|
||||||
|
|
||||||
|
|
||||||
generate: get-crds packr2
|
|
||||||
go generate ./...
|
|
||||||
|
|
||||||
packr2:
|
|
||||||
ifeq ($(shell ${WHICH} packr2 2>${DEVNUL}),)
|
|
||||||
go get -u github.com/gobuffalo/packr/v2/packr2@v2.7.1
|
|
||||||
endif
|
|
||||||
|
|
||||||
clean: clean-packr
|
|
||||||
-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
|
|
||||||
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
|
|
||||||
-rm -rf $(TMP)/operator
|
|
||||||
|
|||||||
230
README.md
230
README.md
@@ -1,4 +1,4 @@
|
|||||||
# (WIP) Qlik Sense installation and operations CLI
|
# Qlik Sense installation and operations CLI
|
||||||
|
|
||||||
- [Qlik Sense installation and operations CLI](#qlik-sense-installation-and-operations-cli)
|
- [Qlik Sense installation and operations CLI](#qlik-sense-installation-and-operations-cli)
|
||||||
- [About](#about)
|
- [About](#about)
|
||||||
@@ -6,145 +6,157 @@
|
|||||||
- [Getting Started](#getting-started)
|
- [Getting Started](#getting-started)
|
||||||
- [Requirements](#requirements)
|
- [Requirements](#requirements)
|
||||||
- [Download](#download)
|
- [Download](#download)
|
||||||
- [TL;DR](#TL;DR)
|
- [Porter CLI](#porter-cli)
|
||||||
- [How qliksense CLI works](#how-qliksense-cli-works)
|
- [Generate Credentials from published bundle](#generate-credentials-from-published-bundle)
|
||||||
- [Witout Git Repo](#Without-git-repo)
|
- [Qlik Sense version and image list](#qliksense-version-and-image-list)
|
||||||
- [With Git Repo](#With-a-git-repo)
|
- [Optional: Pulling images in manifest locally, "air gap"](#optional-pulling-images-in-manifest-locally-%22air-gap%22)
|
||||||
- [Air Gapped](#air-gaped)
|
- [Running Preflight checks](#running-preflight-checks)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Supported Parameters during install](#supported-parameters-during-install)
|
||||||
|
- [How To Add Identity Provider Config](#how-to-add-identity-provider-config)
|
||||||
|
- [Packaging a Custom bundle](#packaging-a-custom-bundle)
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
The Qlik Sense installer CLI (qliksense) provides an imperative interface to many of the configurations that need to be applied against the declarative structure described in [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s). This cli facilitates:
|
The Qlik Sense installer CLI (sense-installer) 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).
|
||||||
|
|
||||||
- installation of QSEoK
|
This is a technology preview that uses [porter](https://porter.sh) to execute "actions" (operations) and bundle versions of the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository.
|
||||||
- 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.
|
These bundles are posted to [docker hub](https://hub.docker.com/) at the following location: [qliksense-cnab-bundle](https://hub.docker.com/r/qlik/qliksense-cnab-bundle/tags).
|
||||||
|
|
||||||
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)
|
For each version of a qliksense sense edge build there should be a corresponding release current posted on docker hub. ex. `qlik/qliksense-cnab-bundle:v1.21.23-edge` for `v1.21.23` edge release of qliksense. The latest version posted will also be labelled as `latest`
|
||||||
|
|
||||||
### Future Direction
|
### Future Direction
|
||||||
|
|
||||||
|
- Porter is currently used as a core technology for the CLI. In the future Porter will be moved "up the stack" to allow the CLI to perform the current and expanded operations independently and encapsulate core functionality currently provided by Porter and other dependent tooling.
|
||||||
- More operations:
|
- More operations:
|
||||||
- Expand preflight checks
|
- Expanded preflight checks
|
||||||
- backup/restore operations
|
- backup/restore operations
|
||||||
- fully support airgap installation of QSEoK
|
|
||||||
- restore unwanted deletion of kubernetes resources
|
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
### Requirements
|
### 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 Client connected to a docker engine into which images can built, pulled and pushed.
|
||||||
- (Docker Desktop setup tested for these instructions)
|
- (Docker Desktop setup tested for these instructions)
|
||||||
|
|
||||||
### Download
|
### 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`.
|
- Download the appropriate executable for your platform from the [releases page](https://github.com/qlik-oss/sense-installer/releases).
|
||||||
|
- To allow the CLI to download and initialize dependencies (including porter and it's associated mixins), simply execute `qliksense` with no arguments
|
||||||
|
- `qliksense`
|
||||||
|
|
||||||
### TL;DR
|
#### Porter CLI
|
||||||
|
- *Optional*: If wanting to use porter CLI directly, two environment variables will need to be set so as not to conflict with an existing porter installation:
|
||||||
|
- _Bash_
|
||||||
|
```shell
|
||||||
|
bash# export PORTER_HOME="$HOME\.qliksense"
|
||||||
|
bash# export PATH="$HOME\.qliksense;$PATH"
|
||||||
|
```
|
||||||
|
|
||||||
- To download the version `v0.0.2` from qliksense-k8s [releases](https://github.com/qlik-oss/qliksense-k8s/releases).
|
- _PowerShell_
|
||||||
|
```shell
|
||||||
|
PS> $Env:PORTER_HOME="$Env:USERPROFILE\.qliksense"
|
||||||
|
PS> $Env:PATH="$Env:USERPROFILE\.qliksense;$Env:PATH"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generate Credentials from published bundle
|
||||||
|
|
||||||
|
- Ensure connectivity to the target cluster create a kubeconfig credential for a target bundle.
|
||||||
|
- generating a file as follows, replace `<credential_name>` with a name of your choosing.
|
||||||
|
- _Bash_
|
||||||
|
```shell
|
||||||
|
bash# CREDENTIAL_NAME=<credential_name>
|
||||||
|
bash# cat <<EOF > $HOME/.qliksense/credentials/$CREDENTIAL_NAME.yaml
|
||||||
|
name: $CREDENTIAL_NAME
|
||||||
|
credentials:
|
||||||
|
- name: kubeconfig
|
||||||
|
source:
|
||||||
|
path: $HOME/.kube/config
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
- _PowerShell_
|
||||||
|
```shell
|
||||||
|
PS> $CREDENTIAL_NAME="<credential_name>"
|
||||||
|
PS> Add-Content -Value @"
|
||||||
|
name: $CREDENTIAL_NAME
|
||||||
|
credentials:
|
||||||
|
- name: kubeconfig
|
||||||
|
source:
|
||||||
|
path: $Env:USERPROFILE\.kube\config
|
||||||
|
"@ -Path $Env:USERPROFILE\.qliksense\credentials\$CREDENTIAL_NAME".yaml"
|
||||||
|
```
|
||||||
|
- credentials can also be created using the [porter](https://porter.sh) CLI *(the correct environmental variable need to have been set up as shown in [Porter CLI](#porter-cli) above)*
|
||||||
|
- `porter cred generate <credential_name> --tag qlik/qliksense-cnab-bundle:v1.21.23-edge`, replace `<credential_name>` with a name of your choosing.
|
||||||
|
- Select `file path` and specify full path to a kube config file ex. _Bash_: `/home/user/.kube/config` or _PowerShell_ `C:\Users\user\.kube\config`
|
||||||
|
|
||||||
|
### Qlik Sense version and image list
|
||||||
|
|
||||||
|
It is possible verify the version of the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository bundled into the `qlik/qliksense-cnab-bundle` image and retreive the list of images included in that release. (This operation can take a minute or so)<https://github.com/qlik-oss/kustomize/issues/13> as the entire manifests needs to be rendered:
|
||||||
|
|
||||||
|
- `qliksense about --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
|
||||||
|
|
||||||
|
### Optional: Pulling images in manifest locally, "air gap"
|
||||||
|
|
||||||
|
If the `dockerRegistry` parameter is specified as the private docker registry to be used by the kubernetes cluster hosting qliksense, it is possible to pull images to the local docker engine for an eventual push during a `qliksense install` or `qliksense upgrade`
|
||||||
|
|
||||||
|
- `qliksense pull --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
|
||||||
|
|
||||||
|
### Running Preflight checks
|
||||||
|
|
||||||
|
You can run preflight checks to ensure that the cluster is in a healthy state before installing Qliksense.
|
||||||
|
|
||||||
|
- `qliksense preflight -c <credential_name> --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
|
||||||
|
|
||||||
|
The above command runs the checks in the default namespace. If you want to specify the namespace to run preflight checks on:
|
||||||
|
|
||||||
|
- `qliksense preflight --param namespace=<value> -c <credential_name> --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
- Install the bundle : `qliksense install --param acceptEULA=yes -c <credential_name> --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
|
||||||
|
|
||||||
|
#### Supported Parameters during install
|
||||||
|
|
||||||
|
| Name | Descriptions | Default |
|
||||||
|
| ------------- |:-------------:| -----:|
|
||||||
|
| profile | select a profile i.e docker-desktop, aws-eks, gke | docker-desktop |
|
||||||
|
| acceptEULA | yes | has to be yes |
|
||||||
|
| namespace | any kubernetes namespace | default |
|
||||||
|
| dockerRegistry | A private docker image regitry for pods | not specified (public) |
|
||||||
|
| rotateKeys | regenerate application PKI keys on upgrade (yes/no) | no |
|
||||||
|
| mongoDbUri | the mongodb URI to use | URI of development mongodb |
|
||||||
|
| scName | storage class name | none |
|
||||||
|
|
||||||
|
#### How To Add Identity Provider Config
|
||||||
|
|
||||||
|
Since idp configs are usually multiline configs it is not conventional to pass to porter during install as a `param`. Rather put the configs in a file and refer to that file during `porter install` command. For example to add `keycloak` IDP create file named `idpconfigs.txt` and put
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$qliksense fetch v0.0.2
|
idpConfigs=[{"discoveryUrl":"http://keycloak-insecure:8089/keycloak/realms/master22/.well-known/openid-configuration","clientId":"edge-auth","clientSecret":"e15b5075-9399-4b20-a95e-023022aa4aed","realm":"master","hostname":"elastic.example","claimsMapping":{"sub":["sub","client_id"],"name":["name","given_name","family_name","preferred_username"]}}]
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
- To install CRDs for QSEoK and qliksense operator into the kubernetes cluster.
|
Then pass that file during install command like this
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$qliksense crds install --all
|
qliksense install --param acceptEULA=yes -c <credential_name> --param-file idpconfigs.txt --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
|
||||||
```
|
```
|
||||||
|
|
||||||
- To install QSEoK into a namespace in the kubernetes cluster where `kubectl` is pointing to.
|
## Packaging a Custom bundle
|
||||||
|
|
||||||
|
If files need to be added to the [qliksense-k8s repository](https://github.com/qlik-oss/qliksense-k8s) in order to perform advanced configuration outside the scope of the what the operator provides, a custom bundle needs to be built.
|
||||||
|
|
||||||
|
Packaging of Qlik Sense on Kubernetes is done through a [Porter](https://porter.sh/) definition in the [Qlik Sense on Kubernetes configuration repository](https://github.com/qlik-oss/qliksense-k8s/blob/master/porter.yaml), the resulting bundle published on DockerHub as a [Cloud Natvie Application Bundle](https://cnab.io/) called [qliksense-cnab-bundle](https://hub.docker.com/r/qlik/qliksense-cnab-bundle)
|
||||||
|
|
||||||
|
To start, clone [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and modify the repo as desired, once finished make sure to be in the `qliksense-k8s` directory from which the porter bundle can be built:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$qliksense install --acceptEULA="yes"
|
git clone git@github.com:qlik-oss/qliksense-k8s.git
|
||||||
|
|
||||||
|
cd qliksense-k8s
|
||||||
|
|
||||||
|
qliksense build
|
||||||
```
|
```
|
||||||
|
|
||||||
## How qliksense cli works
|
Once built, all of the `porter` command that were used with `--tag` can be now be used without this flag provided that porter is executed with the `qliksense-k8s` directory. `porter` will automatically use the qliksense-k8s (and the porter.yaml) in the current directory.
|
||||||
|
|
||||||
At the initialization `qliksense` cli create few files in the director `~/.qliksene` and it contains following files
|
|
||||||
|
|
||||||
```console
|
|
||||||
.qliksense
|
|
||||||
├── config.yaml
|
|
||||||
├── contexts
|
|
||||||
│ └── qlik-default
|
|
||||||
│ └── qlik-default.yaml
|
|
||||||
└── ejson
|
|
||||||
└── keys
|
|
||||||
```
|
|
||||||
|
|
||||||
`qlik-default.yaml` is a default CR has been created with some default values like this
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
apiVersion: qlik.com/v1
|
|
||||||
kind: Qliksense
|
|
||||||
metadata:
|
|
||||||
name: qlik-default
|
|
||||||
spec:
|
|
||||||
profile: docker-desktop
|
|
||||||
secrets:
|
|
||||||
qliksense:
|
|
||||||
- name: mongoDbUri
|
|
||||||
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
|
|
||||||
rotateKeys: "yes"
|
|
||||||
releaseName: qlik-default
|
|
||||||
```
|
|
||||||
|
|
||||||
The `qliksense` cli creates a default qliksense context (different from kubectl context) named `qlik-default` which will be the prefix for all kubernetes resources created by the cli under this context latter on. New context and configuration can be created by the cli.
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ qliksense config -h
|
|
||||||
do operations on/around CR
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
qliksense config [command]
|
|
||||||
|
|
||||||
Available Commands:
|
|
||||||
apply generate the patchs and apply manifests to k8s
|
|
||||||
list-contexts retrieves the contexts and lists them
|
|
||||||
set configure a key value pair into the current context
|
|
||||||
set-configs set configurations into the qliksense context as key-value pairs
|
|
||||||
set-context Sets the context in which the Kubernetes cluster and resources live in
|
|
||||||
set-secrets set secrets configurations into the qliksense context as key-value pairs
|
|
||||||
view view the qliksense operator CR
|
|
||||||
|
|
||||||
Flags:
|
|
||||||
-h, --help help for config
|
|
||||||
|
|
||||||
Use "qliksense config [command] --help" for more information about a command.
|
|
||||||
```
|
|
||||||
|
|
||||||
`qliksense` cli works in two modes
|
|
||||||
|
|
||||||
- with a git repo fork/clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s)
|
|
||||||
- without git repo
|
|
||||||
|
|
||||||
### Without git repo
|
|
||||||
|
|
||||||
In this mode `qliksense` CLI download the specified version from [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and put it into folder `~/.qliksense/contexts/<context-name>/qlik-k8s`.
|
|
||||||
|
|
||||||
The qliksense cli create a CR for the qliksense operator and all the config operations are peformed to edit the CR. So when `qliksense install` or `qliksense config apply` both generate patches in local file system (i.e `~/.qliksense/contexts/<context-name>/qlik-k8s`) and install those manifests into the cluster and create a custom resoruce (CR) for the `qliksene operator` then the operator make association to the isntalled resoruces so that when `qliksenes uninstall` is performed the operator can delete all those kubernetes resources related to QSEoK for the current context.
|
|
||||||
|
|
||||||
### With a git repo
|
|
||||||
|
|
||||||
User has to create fork or clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and push it to their own git server. When user perform `qliksense install` or `qliksene config apply` the qliksense operator do these tasks
|
|
||||||
|
|
||||||
- downloads the corresponding version of manifests from the user's git repo.
|
|
||||||
- generate kustomize patches
|
|
||||||
- install kubernetes resoruces
|
|
||||||
- push those generated patches into a new branch in the provided git repo. so that user user can merge those patches into their master branch.
|
|
||||||
- spinup a cornjob to monitor master branch. If user modifies anything in the master branch those changes will be applied into the cluster. This is a light weight `git-ops` model
|
|
||||||
|
|
||||||
This is how repo info is provided into the CR
|
|
||||||
|
|
||||||
```console
|
|
||||||
qliksense config set git.repository="https://github.com/my-org/qliksense-k8s"
|
|
||||||
|
|
||||||
qliksense config set git.accessToken=blablalaala
|
|
||||||
```
|
|
||||||
|
|
||||||
## Air gaped
|
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
# qliksense about
|
|
||||||
|
|
||||||
About action will display inside information regarding [qliksense-k8](https://github.com/qlik-oss/qliksense-k8s) release.
|
|
||||||
|
|
||||||
it will support following flags
|
|
||||||
|
|
||||||
- `qliksense about 1.0.0` display default profile for tag `1.0.0`.
|
|
||||||
- `qliksense about 1.0.0 --profile=docker-desktop`
|
|
||||||
- `qliksense about`
|
|
||||||
- assuming current directory has `manifests/docker-desktop`
|
|
||||||
- or get version information from pull of `qliksense-k8s` `master`
|
|
||||||
|
|
||||||
using other supported commands user might have built the CR into the location `~/.qliksense/myqliksense.yaml`
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
apiVersion: qlik.com/v1
|
|
||||||
kind: QlikSense
|
|
||||||
metadata:
|
|
||||||
name: myqliksense
|
|
||||||
spec:
|
|
||||||
profile: docker-desktop
|
|
||||||
manifestsRoot: /Usr/ddd/my-k8-repo/manifests
|
|
||||||
namespace: myqliksense
|
|
||||||
storageClassName: efs
|
|
||||||
configs:
|
|
||||||
qliksense:
|
|
||||||
- name: acceptEULA
|
|
||||||
value: "yes"
|
|
||||||
secrets:
|
|
||||||
qliksense:
|
|
||||||
- name: mongoDbUri
|
|
||||||
value: "mongo://mongo:3307"
|
|
||||||
- name: messagingPassword
|
|
||||||
valueFromKey: messagingPassword
|
|
||||||
```
|
|
||||||
|
|
||||||
In that case the command would be
|
|
||||||
|
|
||||||
- `qliksense about`
|
|
||||||
- display from `/Usr/ddd/my-k8-repo/manifests/docker-desktop` location
|
|
||||||
- pull from `master` if directory invalid/empty
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# qliksense config
|
|
||||||
|
|
||||||
Config action will perform operations on configurations and contexts regarding the [qliksense-k8](https://github.com/qlik-oss/qliksense-k8s) release.
|
|
||||||
|
|
||||||
it will support following commands:
|
|
||||||
|
|
||||||
- `qliksense config apply` - generate the patchs and apply manifests to k8s
|
|
||||||
- `qliksense config list-contexts` - retrieves the contexts and lists them
|
|
||||||
- `qliksense config set` - configure a key value pair into the current context
|
|
||||||
- `qliksense config set-configs` - set configurations into the qliksense context as key-value pairs
|
|
||||||
- `qliksense config set-context` - sets the context in which the Kubernetes cluster and resources live in
|
|
||||||
- `qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=false` - set secrets configurations into the qliksense context as key-value pairs and show encrypted value as part of CR
|
|
||||||
- `qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=true` - set secrets configurations into the qliksense context as key-value pairs and show a key reference to the created Kubernetes secret resource as part of the CR
|
|
||||||
- `qliksense config view` - view the qliksense operator CR
|
|
||||||
- `qliksense config delete-context` - deletes a specific context locally (not in-cluster). Deletes context in spec of `config.yaml` and locally deletes entire folder of specified context (does not delete in-cluster secrets)
|
|
||||||
|
|
||||||
|
|
||||||
the global file that abstracts all the contexts is `config.yaml`, located at: `~/.qliksense/config.yaml`:
|
|
||||||
```yaml
|
|
||||||
apiVersion: config.qlik.com/v1
|
|
||||||
kind: QliksenseConfig
|
|
||||||
metadata:
|
|
||||||
name: QliksenseConfigMetadata
|
|
||||||
spec:
|
|
||||||
contexts:
|
|
||||||
- name: qlik-default
|
|
||||||
crFile: /Users/fff/.qliksense/contexts/qlik-default/qlik-default.yaml
|
|
||||||
- name: myqliksense
|
|
||||||
crFile: /Users/fff/.qliksense/contexts/myqliksense/myqliksense.yaml
|
|
||||||
- name: hello
|
|
||||||
crFile: /Users/fff/.qliksense/contexts/hello/hello.yaml
|
|
||||||
currentContext: hello
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type aboutCommandOptions struct {
|
|
||||||
Profile string
|
|
||||||
}
|
|
||||||
|
|
||||||
func about(q *qliksense.Qliksense) *cobra.Command {
|
|
||||||
opts := &aboutCommandOptions{}
|
|
||||||
|
|
||||||
c := &cobra.Command{
|
|
||||||
Use: "about ref",
|
|
||||||
Short: "Displays information pertaining to Qliksense on Kubernetes",
|
|
||||||
Long: "Gives the version of QLik Sense on Kubernetes and versions of images.",
|
|
||||||
Example: `
|
|
||||||
qliksense about 1.0.0
|
|
||||||
- display default profile (docker-desktop) for Git ref 1.0.0 in the qliksense-k8s repo
|
|
||||||
qliksense about 1.0.0 --profile=docker-desktop
|
|
||||||
- specifying profile
|
|
||||||
qliksense about
|
|
||||||
qliksense about --profile=test
|
|
||||||
- if no Git ref is provided, then get version information from the configuration on disk:
|
|
||||||
- if user's current directory has a subdirectory "manifests/${profile}",
|
|
||||||
then get version information from that
|
|
||||||
- if using other supported commands the user has built a CR in ~/.qliksense,
|
|
||||||
then get version information based on the path derived like so:
|
|
||||||
- ${spec.manifestsRoot}/${spec.profile} # if no profile flag provided
|
|
||||||
- ${spec.manifestsRoot}/${profile} # if profile is provided using the --profile command flag
|
|
||||||
- if no config found on disk in locations described above,
|
|
||||||
then get version information based on the default profile in the qliksense-k8s repo master
|
|
||||||
`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
if gitRef, err := getAboutCommandGitRef(args); err != nil {
|
|
||||||
return err
|
|
||||||
} else if vout, err := q.About(gitRef, opts.Profile); err != nil {
|
|
||||||
return err
|
|
||||||
} else if out, err := yaml.Marshal(vout); err != nil {
|
|
||||||
return err
|
|
||||||
} else if _, err := fmt.Println(string(out)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
f := c.Flags()
|
|
||||||
f.StringVar(&opts.Profile, "profile", "", "Configuration profile")
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAboutCommandGitRef(args []string) (string, error) {
|
|
||||||
if len(args) > 1 {
|
|
||||||
return "", errors.New("too many arguments, only 1 expected")
|
|
||||||
} else if len(args) == 1 {
|
|
||||||
return strings.TrimSpace(args[0]), nil
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
423
cmd/qliksense/aliases.go
Normal file
423
cmd/qliksense/aliases.go
Normal file
@@ -0,0 +1,423 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildAliasCommands(porterCmd *cobra.Command, q *qliksense.Qliksense) []*cobra.Command {
|
||||||
|
|
||||||
|
return []*cobra.Command{
|
||||||
|
buildBuildAlias(porterCmd, q),
|
||||||
|
buildInstallAlias(porterCmd, q),
|
||||||
|
buildUpgradeAlias(porterCmd, q),
|
||||||
|
buildAboutAlias(porterCmd, q),
|
||||||
|
buildPreflightAlias(porterCmd, q),
|
||||||
|
buildUninstallAlias(porterCmd, q),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildBuildAlias(porterCmd *cobra.Command, q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
var (
|
||||||
|
c *cobra.Command
|
||||||
|
)
|
||||||
|
c = &cobra.Command{
|
||||||
|
Use: "build",
|
||||||
|
Short: "Build a bundle",
|
||||||
|
Long: "Builds the bundle in the current directory by generating a Dockerfile and a CNAB bundle.json, and then building the invocation image.",
|
||||||
|
DisableFlagParsing: true,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return porterCmd.RunE(porterCmd, append([]string{"build"}, args...))
|
||||||
|
},
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"group": "alias",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
type paramOptions struct {
|
||||||
|
aboutOptions
|
||||||
|
Params []string
|
||||||
|
ParamFiles []string
|
||||||
|
Name string
|
||||||
|
InsecureRegistry bool
|
||||||
|
|
||||||
|
// CredentialIdentifiers is a list of credential names or paths to make available to the bundle.
|
||||||
|
CredentialIdentifiers []string
|
||||||
|
Driver string
|
||||||
|
Force bool
|
||||||
|
Insecure bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildInstallAlias(porterCmd *cobra.Command, q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
var (
|
||||||
|
c *cobra.Command
|
||||||
|
opts *paramOptions
|
||||||
|
registry *string
|
||||||
|
)
|
||||||
|
|
||||||
|
opts = ¶mOptions{}
|
||||||
|
|
||||||
|
c = &cobra.Command{
|
||||||
|
Use: "install [INSTANCE]",
|
||||||
|
Short: "Install qliksense",
|
||||||
|
Long: `Install a new instance of a bundle.
|
||||||
|
|
||||||
|
The first argument is the bundle instance name to create for the installation. This defaults to the name of the bundle.
|
||||||
|
|
||||||
|
Porter uses the Docker driver as the default runtime for executing a bundle's invocation image, but an alternate driver may be supplied via '--driver/-d'.
|
||||||
|
For example, the 'debug' driver may be specified, which simply logs the info given to it and then exits.`,
|
||||||
|
Example: ` qliksense install
|
||||||
|
qliksense install --version v1.0.0
|
||||||
|
qliksense install --insecure
|
||||||
|
qliksense install qliksense --file qliksense/bundle.json
|
||||||
|
qliksense install --param-file base-values.txt --param-file dev-values.txt --param test-mode=true --param header-color=blue
|
||||||
|
qliksense install --cred kubernetes
|
||||||
|
qliksense install --driver debug
|
||||||
|
qliksense install MyAppFromTag --tag qlik/qliksense-cnab-bundle:v1.0.0
|
||||||
|
`,
|
||||||
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
checkMinVersion(opts.Tag, q)
|
||||||
|
},
|
||||||
|
//DisableFlagParsing: true,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
// Push images here.
|
||||||
|
// TODO: Need to get the private reg from params
|
||||||
|
args = append(os.Args[2:], opts.getTagValue(args)...)
|
||||||
|
if registry = opts.findKey("dockerRegistry"); registry != nil {
|
||||||
|
if len(*registry) > 0 {
|
||||||
|
q.TagAndPushImages(*registry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return porterCmd.RunE(porterCmd, append([]string{"install"}, args...))
|
||||||
|
},
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"group": "alias",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
f := c.Flags()
|
||||||
|
f.StringVarP(&opts.Version, "version", "v", "latest",
|
||||||
|
"Version of Qlik Sense to install")
|
||||||
|
f.BoolVar(&opts.Insecure, "insecure", true,
|
||||||
|
"Allow working with untrusted bundles")
|
||||||
|
f.StringVarP(&opts.File, "file", "f", "",
|
||||||
|
"Path to the porter manifest file. Defaults to the bundle in the current directory.")
|
||||||
|
f.StringVar(&opts.CNABFile, "cnab-file", "",
|
||||||
|
"Path to the CNAB bundle.json file.")
|
||||||
|
f.StringSliceVar(&opts.ParamFiles, "param-file", nil,
|
||||||
|
"Path to a parameters definition file for the bundle, each line in the form of NAME=VALUE. May be specified multiple times.")
|
||||||
|
f.StringSliceVar(&opts.Params, "param", nil,
|
||||||
|
"Define an individual parameter in the form NAME=VALUE. Overrides parameters set with the same name using --param-file. May be specified multiple times.")
|
||||||
|
f.StringSliceVarP(&opts.CredentialIdentifiers, "cred", "c", nil,
|
||||||
|
"Credential to use when installing the bundle. May be either a named set of credentials or a filepath, and specified multiple times.")
|
||||||
|
f.StringVarP(&opts.Driver, "driver", "d", "docker",
|
||||||
|
"Specify a driver to use. Allowed values: docker, debug")
|
||||||
|
f.StringVarP(&opts.Tag, "tag", "t", "",
|
||||||
|
"Use a bundle in an OCI registry specified by the given tag")
|
||||||
|
f.BoolVar(&opts.InsecureRegistry, "insecure-registry", false,
|
||||||
|
"Don't require TLS for the registry")
|
||||||
|
f.BoolVar(&opts.Force, "force", false,
|
||||||
|
"Force a fresh pull of the bundle and all dependencies")
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildUpgradeAlias(porterCmd *cobra.Command, q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
var (
|
||||||
|
c *cobra.Command
|
||||||
|
opts *paramOptions
|
||||||
|
registry *string
|
||||||
|
)
|
||||||
|
|
||||||
|
opts = ¶mOptions{}
|
||||||
|
|
||||||
|
c = &cobra.Command{
|
||||||
|
Use: "upgrade [INSTANCE]",
|
||||||
|
Short: "Upgrade qliksense",
|
||||||
|
Long: `Upgrade to a new instance of a bundle.
|
||||||
|
|
||||||
|
The first argument is the bundle instance name to upgrade for the installation. This defaults to the name of the bundle.
|
||||||
|
|
||||||
|
Porter uses the Docker driver as the default runtime for executing a bundle's invocation image, but an alternate driver may be supplied via '--driver/-d'.
|
||||||
|
For example, the 'debug' driver may be specified, which simply logs the info given to it and then exits.`,
|
||||||
|
Example: ` qliksense upgrade
|
||||||
|
qliksense upgrade --version v1.0.0
|
||||||
|
qliksense upgrade --insecure
|
||||||
|
qliksense upgrade qliksense --file qliksense/bundle.json
|
||||||
|
qliksense upgrade --param-file base-values.txt --param-file dev-values.txt --param test-mode=true --param header-color=blue
|
||||||
|
qliksense upgrade --cred kubernetes
|
||||||
|
qliksense upgrade --driver debug
|
||||||
|
qliksense upgrade MyAppFromTag --tag qlik/qliksense-cnab-bundle:v1.0.0
|
||||||
|
`,
|
||||||
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
checkMinVersion(opts.Tag, q)
|
||||||
|
},
|
||||||
|
//DisableFlagParsing: true,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
// Push images here.
|
||||||
|
// TODO: Need to get the private reg from params
|
||||||
|
args = append(os.Args[2:], opts.getTagValue(args)...)
|
||||||
|
if registry = opts.findKey("dockerRegistry"); registry != nil {
|
||||||
|
if len(*registry) > 0 {
|
||||||
|
q.TagAndPushImages(*registry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return porterCmd.RunE(porterCmd, append([]string{"upgrade"}, args...))
|
||||||
|
},
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"group": "alias",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
f := c.Flags()
|
||||||
|
f.StringVarP(&opts.Version, "version", "v", "latest",
|
||||||
|
"Version of Qlik Sense to upgrade to")
|
||||||
|
f.BoolVar(&opts.Insecure, "insecure", true,
|
||||||
|
"Allow working with untrusted bundles")
|
||||||
|
f.StringVarP(&opts.File, "file", "f", "",
|
||||||
|
"Path to the porter manifest file. Defaults to the bundle in the current directory.")
|
||||||
|
f.StringVar(&opts.CNABFile, "cnab-file", "",
|
||||||
|
"Path to the CNAB bundle.json file.")
|
||||||
|
f.StringSliceVar(&opts.ParamFiles, "param-file", nil,
|
||||||
|
"Path to a parameters definition file for the bundle, each line in the form of NAME=VALUE. May be specified multiple times.")
|
||||||
|
f.StringSliceVar(&opts.Params, "param", nil,
|
||||||
|
"Define an individual parameter in the form NAME=VALUE. Overrides parameters set with the same name using --param-file. May be specified multiple times.")
|
||||||
|
f.StringSliceVarP(&opts.CredentialIdentifiers, "cred", "c", nil,
|
||||||
|
"Credential to use when installing the bundle. May be either a named set of credentials or a filepath, and specified multiple times.")
|
||||||
|
f.StringVarP(&opts.Driver, "driver", "d", "docker",
|
||||||
|
"Specify a driver to use. Allowed values: docker, debug")
|
||||||
|
f.StringVarP(&opts.Tag, "tag", "t", "",
|
||||||
|
"Use a bundle in an OCI registry specified by the given tag")
|
||||||
|
f.BoolVar(&opts.InsecureRegistry, "insecure-registry", false,
|
||||||
|
"Don't require TLS for the registry")
|
||||||
|
f.BoolVar(&opts.Force, "force", false,
|
||||||
|
"Force a fresh pull of the bundle and all dependencies")
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildUninstallAlias(porterCmd *cobra.Command, q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
var (
|
||||||
|
c *cobra.Command
|
||||||
|
opts *paramOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
opts = ¶mOptions{}
|
||||||
|
|
||||||
|
c = &cobra.Command{
|
||||||
|
Use: "uninstall [INSTANCE]",
|
||||||
|
Short: "Uninstall a bundle instance",
|
||||||
|
Long: `Uninstall a bundle instance
|
||||||
|
The first argument is the bundle instance name to uninstall. This defaults to the name of the bundle.
|
||||||
|
Porter uses the Docker driver as the default runtime for executing a bundle's invocation image, but an alternate driver may be supplied via '--driver/-d'.
|
||||||
|
For example, the 'debug' driver may be specified, which simply logs the info given to it and then exits.`,
|
||||||
|
Example: ` qliksense uninstall
|
||||||
|
qliksense uninstall --insecure
|
||||||
|
qliksense uninstall MyAppInDev --file myapp/bundle.json
|
||||||
|
qliksense uninstall --param-file base-values.txt --param-file dev-values.txt --param test-mode=true --param header-color=blue
|
||||||
|
qliksense uninstall --cred azure --cred kubernetes
|
||||||
|
qliksense uninstall --driver debug
|
||||||
|
qliksense uninstall MyAppFromTag --tag deislabs/porter-kube-bundle:v1.0
|
||||||
|
`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return porterCmd.RunE(porterCmd, append([]string{"uninstall"}, os.Args[2:]...))
|
||||||
|
},
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"group": "alias",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
f := c.Flags()
|
||||||
|
f.BoolVar(&opts.Insecure, "insecure", true,
|
||||||
|
"Allow working with untrusted bundles")
|
||||||
|
f.StringVarP(&opts.File, "file", "f", "",
|
||||||
|
"Path to the porter manifest file. Defaults to the bundle in the current directory. Optional unless a newer version of the bundle should be used to uninstall the bundle.")
|
||||||
|
f.StringVar(&opts.CNABFile, "cnab-file", "",
|
||||||
|
"Path to the CNAB bundle.json file.")
|
||||||
|
f.StringSliceVar(&opts.ParamFiles, "param-file", nil,
|
||||||
|
"Path to a parameters definition file for the bundle, each line in the form of NAME=VALUE. May be specified multiple times.")
|
||||||
|
f.StringSliceVar(&opts.Params, "param", nil,
|
||||||
|
"Define an individual parameter in the form NAME=VALUE. Overrides parameters set with the same name using --param-file. May be specified multiple times.")
|
||||||
|
f.StringSliceVarP(&opts.CredentialIdentifiers, "cred", "c", nil,
|
||||||
|
"Credential to use when uninstalling the bundle. May be either a named set of credentials or a filepath, and specified multiple times.")
|
||||||
|
f.StringVarP(&opts.Driver, "driver", "d", "docker",
|
||||||
|
"Specify a driver to use. Allowed values: docker, debug")
|
||||||
|
f.StringVarP(&opts.Tag, "tag", "t", "",
|
||||||
|
"Use a bundle in an OCI registry specified by the given tag")
|
||||||
|
f.BoolVar(&opts.InsecureRegistry, "insecure-registry", false,
|
||||||
|
"Don't require TLS for the registry")
|
||||||
|
f.BoolVar(&opts.Force, "force", false,
|
||||||
|
"Force a fresh pull of the bundle and all dependencies")
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
func (o *aboutOptions) getTagDefaults(args []string) []string {
|
||||||
|
args = append(args, o.getTagValue(args)...)
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *aboutOptions) getTagValue(args []string) []string {
|
||||||
|
tagArr := []string{}
|
||||||
|
if len(o.Tag) > 1 {
|
||||||
|
tagArr = []string{"--tag", o.Tag}
|
||||||
|
}
|
||||||
|
if len(o.Tag) <= 0 && len(o.File) <= 0 && len(o.CNABFile) <= 0 {
|
||||||
|
if _, err := os.Stat("porter.yaml"); err != nil {
|
||||||
|
tagArr = []string{"--tag", "qlik/qliksense-cnab-bundle:" + o.Version}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tagArr
|
||||||
|
}
|
||||||
|
|
||||||
|
type aboutOptions struct {
|
||||||
|
Version string
|
||||||
|
Tag string
|
||||||
|
File string
|
||||||
|
CNABFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildAboutAlias(porterCmd *cobra.Command, q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
var (
|
||||||
|
c *cobra.Command
|
||||||
|
opts *aboutOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
opts = &aboutOptions{}
|
||||||
|
|
||||||
|
c = &cobra.Command{
|
||||||
|
Use: "about",
|
||||||
|
Short: "About Qlik Sense",
|
||||||
|
Long: "Gives the verion of QLik Sense on Kuberntetes and versions of images.",
|
||||||
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
checkMinVersion(opts.Tag, q)
|
||||||
|
},
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
args = opts.getTagDefaults(args)
|
||||||
|
return porterCmd.RunE(porterCmd, append([]string{"invoke", "--action", "about"}, args...))
|
||||||
|
},
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"group": "alias",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
f := c.Flags()
|
||||||
|
f.StringVarP(&opts.Version, "version", "v", "latest",
|
||||||
|
"Version of Qlik Sense to install")
|
||||||
|
f.StringVarP(&opts.Tag, "tag", "t", "",
|
||||||
|
"Use a bundle in an OCI registry specified by the given tag")
|
||||||
|
f.StringVarP(&opts.File, "file", "f", "",
|
||||||
|
"Path to the porter manifest file. Defaults to the bundle in the current directory.")
|
||||||
|
f.StringVar(&opts.CNABFile, "cnab-file", "",
|
||||||
|
"Path to the CNAB bundle.json file.")
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildPreflightAlias(porterCmd *cobra.Command, q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
var (
|
||||||
|
c *cobra.Command
|
||||||
|
opts *paramOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
opts = ¶mOptions{}
|
||||||
|
|
||||||
|
c = &cobra.Command{
|
||||||
|
Use: "preflight",
|
||||||
|
Short: "Preflight Checks",
|
||||||
|
Long: "Perform Preflight Checks",
|
||||||
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
checkMinVersion(opts.Tag, q)
|
||||||
|
},
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
args = append(os.Args[1:], opts.getTagDefaults(args)...)
|
||||||
|
return porterCmd.RunE(porterCmd, append([]string{"invoke", "--action", "preflight"}, args...))
|
||||||
|
},
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"group": "alias",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
f := c.Flags()
|
||||||
|
f.StringSliceVar(&opts.Params, "param", nil,
|
||||||
|
"Define an individual parameter in the form NAME=VALUE. Overrides parameters set with the same name using --param-file. May be specified multiple times.")
|
||||||
|
f.StringSliceVar(&opts.ParamFiles, "param-file", nil,
|
||||||
|
"Path to a parameters definition file for the bundle, each line in the form of NAME=VALUE. May be specified multiple times.")
|
||||||
|
f.StringVarP(&opts.Tag, "tag", "t", "",
|
||||||
|
"Use a bundle in an OCI registry specified by the given tag")
|
||||||
|
f.StringVarP(&opts.Version, "version", "v", "latest",
|
||||||
|
"Version of Qlik Sense to install")
|
||||||
|
f.StringSliceVarP(&opts.CredentialIdentifiers, "cred", "c", nil,
|
||||||
|
"Credential to use when installing the bundle. May be either a named set of credentials or a filepath, and specified multiple times.")
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *paramOptions) findKey(param string) *string {
|
||||||
|
var (
|
||||||
|
value *string
|
||||||
|
)
|
||||||
|
if value = o.findParams(param); value != nil {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
if value = o.findParamFiles(param); value != nil {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsedParams parses the variable assignments in Params.
|
||||||
|
func (o *paramOptions) findParams(param string) *string {
|
||||||
|
return o.findVariableKey(param, o.Params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseParamFiles parses the variable assignments in ParamFiles.
|
||||||
|
func (o *paramOptions) findParamFiles(param string) *string {
|
||||||
|
var (
|
||||||
|
path string
|
||||||
|
retStr *string
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, path = range o.ParamFiles {
|
||||||
|
retStr = o.findParamFile(param, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return retStr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *paramOptions) findParamFile(param string, path string) *string {
|
||||||
|
var (
|
||||||
|
f *os.File
|
||||||
|
err error
|
||||||
|
scanner *bufio.Scanner
|
||||||
|
lines []string
|
||||||
|
retStr *string
|
||||||
|
)
|
||||||
|
if f, err = os.Open(path); err == nil {
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
scanner = bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
lines = append(lines, scanner.Text())
|
||||||
|
}
|
||||||
|
|
||||||
|
retStr = o.findVariableKey(param, lines)
|
||||||
|
}
|
||||||
|
return retStr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *paramOptions) findVariableKey(param string, params []string) *string {
|
||||||
|
var (
|
||||||
|
variable, value string
|
||||||
|
)
|
||||||
|
for _, p := range params {
|
||||||
|
parts := strings.SplitN(p, "=", 2)
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
variable = strings.TrimSpace(parts[0])
|
||||||
|
if variable == param {
|
||||||
|
value = strings.TrimSpace(parts[1])
|
||||||
|
return &value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
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",
|
|
||||||
Long: `generate patches based on CR and apply manifests to k8s`,
|
|
||||||
Example: `qliksense config apply`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return q.ConfigApplyQK8s()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func configViewCmd(q *qliksense.Qliksense) *cobra.Command {
|
|
||||||
c := &cobra.Command{
|
|
||||||
Use: "view",
|
|
||||||
Short: "view the qliksense operator CR",
|
|
||||||
Long: `display the operator CR, that has been created for the current context`,
|
|
||||||
Example: `qliksense config view`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return q.ConfigViewCR()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setContextConfigCmd(q *qliksense.Qliksense) *cobra.Command {
|
|
||||||
var (
|
|
||||||
cmd *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>
|
|
||||||
- The above configuration will be displayed in the CR
|
|
||||||
`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return q.SetContextConfig(args)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func listContextConfigCmd(q *qliksense.Qliksense) *cobra.Command {
|
|
||||||
var (
|
|
||||||
cmd *cobra.Command
|
|
||||||
)
|
|
||||||
|
|
||||||
cmd = &cobra.Command{
|
|
||||||
Use: "list-contexts",
|
|
||||||
Short: "retrieves the contexts and lists them",
|
|
||||||
Example: `qliksense config list-contexts`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return q.ListContextConfigs()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func setOtherConfigsCmd(q *qliksense.Qliksense) *cobra.Command {
|
|
||||||
var (
|
|
||||||
cmd *cobra.Command
|
|
||||||
)
|
|
||||||
|
|
||||||
cmd = &cobra.Command{
|
|
||||||
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)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func setConfigsCmd(q *qliksense.Qliksense) *cobra.Command {
|
|
||||||
var (
|
|
||||||
cmd *cobra.Command
|
|
||||||
)
|
|
||||||
|
|
||||||
cmd = &cobra.Command{
|
|
||||||
Use: "set-configs",
|
|
||||||
Short: "set configurations into the qliksense context as key-value pairs",
|
|
||||||
Example: `
|
|
||||||
qliksense config set-configs <service_name>.<attribute>="<value>"
|
|
||||||
- The above configuration will be displayed in the CR
|
|
||||||
`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return q.SetConfigs(args)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func setSecretsCmd(q *qliksense.Qliksense) *cobra.Command {
|
|
||||||
var (
|
|
||||||
cmd *cobra.Command
|
|
||||||
secret bool
|
|
||||||
)
|
|
||||||
|
|
||||||
cmd = &cobra.Command{
|
|
||||||
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)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
f := cmd.Flags()
|
|
||||||
f.BoolVar(&secret, "secret", false, "Whether secrets should be encrypted as a Kubernetes Secret resource")
|
|
||||||
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
|
|
||||||
pushUsername string
|
|
||||||
pushPassword string
|
|
||||||
pullUsername string
|
|
||||||
pullPassword string
|
|
||||||
username string
|
|
||||||
password string
|
|
||||||
)
|
|
||||||
|
|
||||||
cmd = &cobra.Command{
|
|
||||||
Use: "set-image-registry",
|
|
||||||
Short: "set private image registry",
|
|
||||||
Example: `
|
|
||||||
qliksense config set-image-registry https://your.private.registry.example.com:5000 --push-username foo1 --push-password bar1 --pull-username foo2 --pull-password bar2
|
|
||||||
qliksense config set-image-registry https://your.private.registry.example.com:5000 --username foo --password bar
|
|
||||||
`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
if len(args) != 1 {
|
|
||||||
return errors.New("private docker image registry FQDN is required")
|
|
||||||
}
|
|
||||||
registry := args[0]
|
|
||||||
|
|
||||||
if username != "" {
|
|
||||||
pullUsername = username
|
|
||||||
pushUsername = username
|
|
||||||
}
|
|
||||||
if password != "" {
|
|
||||||
pullPassword = password
|
|
||||||
pushPassword = password
|
|
||||||
}
|
|
||||||
if (pullUsername != "" && pushUsername == "") || (pullUsername == "" && pushUsername != "") {
|
|
||||||
return errors.New("if you specify pull credentials, you must specify push credentials as well and vise versa")
|
|
||||||
}
|
|
||||||
if (pullUsername == "" && pullPassword != "") || (pushUsername == "" && pushPassword != "") {
|
|
||||||
return errors.New("if you specify passwords, you must specify usernames as well")
|
|
||||||
}
|
|
||||||
return q.SetImageRegistry(registry, pushUsername, pushPassword, pullUsername, pullPassword)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
f := cmd.Flags()
|
|
||||||
f.StringVar(&pushUsername, "push-username", "", "Username used for pushing images")
|
|
||||||
f.StringVar(&pushPassword, "push-password", "", "Password used for pushing images")
|
|
||||||
f.StringVar(&pullUsername, "pull-username", "", "Username used for pulling images")
|
|
||||||
f.StringVar(&pullPassword, "pull-password", "", "Password used for pulling images")
|
|
||||||
f.StringVar(&username, "username", "", "Username used for both pushing and pulling images")
|
|
||||||
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
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var crdsCmd = &cobra.Command{
|
|
||||||
Use: "crds",
|
|
||||||
Short: "crds for qliksense and operators",
|
|
||||||
Long: `crds for qliksense and operators`,
|
|
||||||
}
|
|
||||||
|
|
||||||
func crdsViewCmd(q *qliksense.Qliksense) *cobra.Command {
|
|
||||||
opts := &qliksense.CrdCommandOptions{}
|
|
||||||
c := &cobra.Command{
|
|
||||||
Use: "view",
|
|
||||||
Short: "View CRDs for qliksense application. use view --all to see opearator crd as well ",
|
|
||||||
Long: `View CRDs for qliksense application. use view --all to see opearator crd as well`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return q.ViewCrds(opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
f := c.Flags()
|
|
||||||
f.BoolVarP(&opts.All, "all", "a", false, "Include All CRDs")
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func crdsInstallCmd(q *qliksense.Qliksense) *cobra.Command {
|
|
||||||
opts := &qliksense.CrdCommandOptions{}
|
|
||||||
c := &cobra.Command{
|
|
||||||
Use: "install",
|
|
||||||
Short: "Install CRDs fro Qliksense application. Use install --all to include operator crd",
|
|
||||||
Long: `Install CRDs fro Qliksense application. Use install --all to include operator crd`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return q.InstallCrds(opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
f := c.Flags()
|
|
||||||
f.BoolVarP(&opts.All, "all", "a", false, "Include All CRDs")
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func fetchCmd(q *qliksense.Qliksense) *cobra.Command {
|
|
||||||
c := &cobra.Command{
|
|
||||||
Use: "fetch",
|
|
||||||
Short: "fetch a release from qliksense-k8s repo",
|
|
||||||
Long: `fetch a release from qliksense-k8s repo`,
|
|
||||||
Example: `qliksense fetch <version>`,
|
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
|
||||||
if len(args) != 1 {
|
|
||||||
return errors.New("requires a version (i.e. v1.0.0)")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return q.FetchQK8s(args[0])
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
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> #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, keepPatchFiles)
|
|
||||||
}
|
|
||||||
return q.InstallQK8s(args[0], opts, keepPatchFiles)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
f := c.Flags()
|
|
||||||
f.StringVarP(&opts.AcceptEULA, "acceptEULA", "a", "", "AcceptEULA for qliksense")
|
|
||||||
f.StringVarP(&opts.StorageClass, "storageClass", "s", "", "Storage class for qliksense")
|
|
||||||
f.StringVarP(&opts.MongoDbUri, "mongoDbUri", "m", "", "mongoDbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
|
|
||||||
f.StringVarP(&opts.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
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var operatorCmd = &cobra.Command{
|
|
||||||
Use: "operator",
|
|
||||||
Short: "Configuration for operator",
|
|
||||||
Long: `Configuration for operator`,
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
func operatorViewCmd(q *qliksense.Qliksense) *cobra.Command {
|
|
||||||
c := &cobra.Command{
|
|
||||||
Use: "view",
|
|
||||||
Short: "View CRD for operator",
|
|
||||||
Long: `View CRD for operator`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return q.ViewOperator()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func operatorCrdCmd(q *qliksense.Qliksense) *cobra.Command {
|
|
||||||
c := &cobra.Command{
|
|
||||||
Use: "crd",
|
|
||||||
Short: "View CRD for operator",
|
|
||||||
Long: `View CRD for operator`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return q.ViewOperator()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func operatorControllerCmd(q *qliksense.Qliksense) *cobra.Command {
|
|
||||||
c := &cobra.Command{
|
|
||||||
Use: "controller",
|
|
||||||
Short: "View manifests for operator controller",
|
|
||||||
Long: `View manifests for operator controller`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return q.ViewOperatorController()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
31
cmd/qliksense/porter.go
Normal file
31
cmd/qliksense/porter.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func porter(q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "porter",
|
||||||
|
Short: "Execute a porter command",
|
||||||
|
DisableFlagParsing: true,
|
||||||
|
RunE: func(cobCmd *cobra.Command, args []string) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if _, err = q.CallPorter(args,
|
||||||
|
func(x string) (out *string) {
|
||||||
|
out = new(string)
|
||||||
|
*out = strings.ReplaceAll(x, "porter", "qliksense porter")
|
||||||
|
fmt.Println(*out)
|
||||||
|
return
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
33
cmd/qliksense/pull.go
Normal file
33
cmd/qliksense/pull.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func pullQliksenseImages(q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
var (
|
||||||
|
cmd *cobra.Command
|
||||||
|
opts *aboutOptions
|
||||||
|
)
|
||||||
|
opts = &aboutOptions{}
|
||||||
|
|
||||||
|
cmd = &cobra.Command{
|
||||||
|
Use: "pull",
|
||||||
|
Short: "Pull docke images for offline install",
|
||||||
|
Example: `qliksense pull`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return q.PullImages(opts.getTagDefaults(args))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
f := cmd.Flags()
|
||||||
|
f.StringVarP(&opts.Version, "version", "v", "latest",
|
||||||
|
"From version of Qlik Sense The images will be pulled")
|
||||||
|
f.StringVarP(&opts.Tag, "tag", "t", "",
|
||||||
|
"Use a bundle in an OCI registry specified by the given tag")
|
||||||
|
f.StringVarP(&opts.File, "file", "f", "",
|
||||||
|
"Path to the porter manifest file. Defaults to the bundle in the current directory.")
|
||||||
|
f.StringVar(&opts.CNABFile, "cnab-file", "",
|
||||||
|
"Path to the CNAB bundle.json file.")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
|
||||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func pullQliksenseImages(q *qliksense.Qliksense) *cobra.Command {
|
|
||||||
opts := &aboutCommandOptions{}
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "pull",
|
|
||||||
Short: "Pull docker images for offline install",
|
|
||||||
Example: `qliksense pull`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
version, err := getAboutCommandGitRef(args)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
|
||||||
if version == "" {
|
|
||||||
if qcr, err := qConfig.GetCurrentCR(); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
version = qcr.GetLabelFromCr("version")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if version != "" {
|
|
||||||
if !qConfig.IsRepoExistForCurrent(version) {
|
|
||||||
if err := q.FetchQK8s(version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := qConfig.SwitchCurrentCRToVersionAndProfile(version, opts.Profile); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return q.PullImagesForCurrentCR()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
f := cmd.Flags()
|
|
||||||
f.StringVar(&opts.Profile, "profile", "", "Configuration profile")
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func pushQliksenseImages(q *qliksense.Qliksense) *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "push",
|
|
||||||
Short: "Push docker images for offline install",
|
|
||||||
Example: `qliksense push`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
|
||||||
if qcr, err := qConfig.GetCurrentCR(); err != nil {
|
|
||||||
return err
|
|
||||||
} else if registry := qcr.GetImageRegistry(); registry == "" {
|
|
||||||
return errors.New("no image registry in config")
|
|
||||||
} else {
|
|
||||||
return q.PushImagesForCurrentCR()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
@@ -1,22 +1,23 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
ansi "github.com/mattn/go-colorable"
|
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
"github.com/qlik-oss/sense-installer/pkg"
|
"github.com/qlik-oss/sense-installer/pkg"
|
||||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
|
||||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/ttacon/chalk"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// To run this project in ddebug mode, run:
|
// To run this project in ddebug mode, run:
|
||||||
@@ -24,57 +25,88 @@ import (
|
|||||||
// qliksense <command>
|
// qliksense <command>
|
||||||
|
|
||||||
const (
|
const (
|
||||||
qlikSenseHomeVar = "QLIKSENSE_HOME"
|
winOS = "porter-windows-amd64.exe"
|
||||||
qlikSenseDirVar = ".qliksense"
|
linuxOS = "porter-linux-amd64"
|
||||||
keepPatchFilesFlagName = "keep-config-repo-patches"
|
macOS = "porter-darwin-amd64"
|
||||||
keepPatchFilesFlagUsage = "Keep config repo patch files (for debugging)"
|
porterHomeVar = "PORTER_HOME"
|
||||||
|
qlikSenseHomeVar = "QLIKSENSE_HOME"
|
||||||
|
qlikSenseDirVar = ".qliksense"
|
||||||
|
mixinDirVar = "mixins"
|
||||||
|
porterRuntime = "porter-runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initAndExecute() error {
|
func initAndExecute() error {
|
||||||
var (
|
var (
|
||||||
qlikSenseHome string
|
porterExe, qlikSenseHome string
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
qlikSenseHome, err = setUpPaths()
|
|
||||||
|
porterExe, qlikSenseHome, err = setUpPaths()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
// create dirs and appropriate files for setting up contexts
|
|
||||||
api.LogDebugMessage("QliksenseHomeDir: %s", qlikSenseHome)
|
|
||||||
|
|
||||||
qliksenseClient := qliksense.New(qlikSenseHome)
|
if err = rootCmd(qliksense.New(porterExe, qlikSenseHome)).Execute(); err != nil {
|
||||||
qliksenseClient.SetUpQliksenseDefaultContext()
|
return err
|
||||||
cmd := rootCmd(qliksenseClient)
|
|
||||||
//levenstein checks
|
|
||||||
if levenstein(cmd) == false {
|
|
||||||
if err := cmd.Execute(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setUpPaths() (string, error) {
|
func setUpPaths() (string, string, error) {
|
||||||
var (
|
var (
|
||||||
homeDir, qlikSenseHome string
|
porterExe, homeDir, qlikSenseHome string
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
porterExe = "porter"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
porterExe = porterExe + ".exe"
|
||||||
|
}
|
||||||
if qlikSenseHome = os.Getenv(qlikSenseHomeVar); qlikSenseHome == "" {
|
if qlikSenseHome = os.Getenv(qlikSenseHomeVar); qlikSenseHome == "" {
|
||||||
if homeDir, err = homedir.Dir(); err != nil {
|
if homeDir, err = homedir.Dir(); err != nil {
|
||||||
return "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
if homeDir, err = homedir.Expand(homeDir); err != nil {
|
if homeDir, err = homedir.Expand(homeDir); err != nil {
|
||||||
return "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
qlikSenseHome = filepath.Join(homeDir, qlikSenseDirVar)
|
qlikSenseHome = filepath.Join(homeDir, qlikSenseDirVar)
|
||||||
}
|
}
|
||||||
|
os.Setenv(porterHomeVar, qlikSenseHome)
|
||||||
|
|
||||||
if err := os.MkdirAll(qlikSenseHome, os.ModePerm); err != nil {
|
porterExe = filepath.Join(qlikSenseHome, porterExe)
|
||||||
return "", err
|
return porterExe, qlikSenseHome, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func installPorter(qlikSenseHome, porterExe string) (string, error) {
|
||||||
|
var (
|
||||||
|
destination string
|
||||||
|
// downloadPorter = true
|
||||||
|
porterDownloadURL string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
// if downloadPorter {
|
||||||
|
os.Mkdir(qlikSenseHome, os.ModePerm)
|
||||||
|
destination = filepath.Join(qlikSenseHome, porterRuntime)
|
||||||
|
// construct url to download porter from
|
||||||
|
porterDownloadURL = constructPorterURL(runtime.GOOS)
|
||||||
|
|
||||||
|
if (runtime.GOOS == "linux" && runtime.GOARCH == "amd64") || runtime.GOOS == "darwin" {
|
||||||
|
if err = downloadFile(porterDownloadURL, destination); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
os.Chmod(destination, 0755)
|
||||||
|
if _, err = copy(destination, porterExe); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
os.Chmod(porterExe, 0755)
|
||||||
|
} else if runtime.GOOS == "windows" {
|
||||||
|
if err = downloadFile(porterDownloadURL, porterExe); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
os.Chmod(porterExe, 0755)
|
||||||
}
|
}
|
||||||
|
return porterExe, nil
|
||||||
return qlikSenseHome, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var versionCmd = &cobra.Command{
|
var versionCmd = &cobra.Command{
|
||||||
@@ -86,16 +118,132 @@ var versionCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func constructPorterURL(runtimeOS string) string {
|
||||||
|
// FYI: Porter does not support other architectures other than amd64
|
||||||
|
const (
|
||||||
|
porterURLBase = "https://cdn.deislabs.io/porter/"
|
||||||
|
winOS = "porter-windows-amd64.exe"
|
||||||
|
linuxOS = "porter-linux-amd64"
|
||||||
|
macOS = "porter-darwin-amd64"
|
||||||
|
)
|
||||||
|
var url, version string
|
||||||
|
version = retrievePorterVersion()
|
||||||
|
if runtimeOS == "linux" {
|
||||||
|
url = porterURLBase + version + "/" + linuxOS
|
||||||
|
} else if runtimeOS == "windows" {
|
||||||
|
url = porterURLBase + version + "/" + winOS
|
||||||
|
} else if runtimeOS == "darwin" {
|
||||||
|
url = porterURLBase + version + "/" + macOS
|
||||||
|
}
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrievePorterVersion() string {
|
||||||
|
type apiInfo struct {
|
||||||
|
TagName string `json:"tag_name,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
const porterRepoURL = "https://api.github.com/repos/deislabs/porter/releases/latest"
|
||||||
|
|
||||||
|
resp, err := http.Get(porterRepoURL)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error occurred while retrieving porter version info: %v\n", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
fmt.Printf("response status was not OK while retrieving porter version info\n")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error occurred while reading porter version info: %v\n", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
result := &apiInfo{}
|
||||||
|
err = json.Unmarshal(body, result)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error occurred while unmarshalling porter version info: %v\n", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
fmt.Printf("Porter Version: %s\n", result.Name)
|
||||||
|
return result.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func installMixins(porterExe, qlikSenseHome string) (string, error) {
|
||||||
|
var (
|
||||||
|
mixin, mixinOpts string
|
||||||
|
mixinsVar = map[string]string{
|
||||||
|
"kustomize": "-v 0.2-beta-3-0e19ca4 --url https://github.com/donmstewart/porter-kustomize/releases/download",
|
||||||
|
"qliksense": "-v v0.16.0 --url https://github.com/qlik-oss/porter-qliksense/releases/download",
|
||||||
|
"exec": "-v latest",
|
||||||
|
"kubernetes": "-v latest",
|
||||||
|
"helm": "-v latest",
|
||||||
|
"azure": "-v latest",
|
||||||
|
"terraform": "-v latest",
|
||||||
|
"az": "-v latest",
|
||||||
|
"aws": "-v latest",
|
||||||
|
"gcloud": "-v latest",
|
||||||
|
}
|
||||||
|
downloadMixins map[string]string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if _, err = os.Stat(filepath.Join(qlikSenseHome, mixinDirVar)); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
downloadMixins = mixinsVar
|
||||||
|
} else {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
downloadMixins = make(map[string]string)
|
||||||
|
for mixin, mixinOpts = range mixinsVar {
|
||||||
|
if _, err = os.Stat(filepath.Join(qlikSenseHome, mixinDirVar, mixin)); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
downloadMixins[mixin] = mixinOpts
|
||||||
|
} else {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for mixin, mixinOpts = range downloadMixins {
|
||||||
|
if _, err = installMixin(porterExe, mixin, mixinOpts); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
func installMixin(porterExe, mixin, mixinOpts string) (string, error) {
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
|
||||||
|
args := []string{"mixin", "install", mixin}
|
||||||
|
if mixinOpts != "" {
|
||||||
|
args = append(args, strings.Fields(mixinOpts)...)
|
||||||
|
}
|
||||||
|
cmd = exec.Command(porterExe, args...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||||
var (
|
var (
|
||||||
cmd *cobra.Command
|
cmd, porterCmd, alias *cobra.Command
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd = &cobra.Command{
|
cmd = &cobra.Command{
|
||||||
Use: "qliksense",
|
Use: "qliksense",
|
||||||
Short: "Qliksense cli tool",
|
Short: "Qliksense cli tool",
|
||||||
Long: `qliksense cli tool provides functionality to perform operations on qliksense-k8s, qliksense operator, and kubernetes cluster`,
|
Long: `qliksense cli tool provides a wrapper around the porter api as well as
|
||||||
Args: cobra.ArbitraryArgs,
|
provides addition functionality`,
|
||||||
|
Args: cobra.ArbitraryArgs,
|
||||||
|
SilenceUsage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().SetInterspersed(false)
|
cmd.Flags().SetInterspersed(false)
|
||||||
@@ -105,65 +253,15 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
|||||||
// For qliksense overrides/commands
|
// For qliksense overrides/commands
|
||||||
|
|
||||||
cmd.AddCommand(pullQliksenseImages(p))
|
cmd.AddCommand(pullQliksenseImages(p))
|
||||||
cmd.AddCommand(pushQliksenseImages(p))
|
porterCmd = porter(p)
|
||||||
cmd.AddCommand(about(p))
|
cmd.AddCommand(porterCmd)
|
||||||
|
for _, alias = range buildAliasCommands(porterCmd, p) {
|
||||||
|
cmd.AddCommand(alias)
|
||||||
|
}
|
||||||
// add version command
|
// add version command
|
||||||
cmd.AddCommand(versionCmd)
|
cmd.AddCommand(versionCmd)
|
||||||
|
|
||||||
// add operator command
|
|
||||||
cmd.AddCommand(operatorCmd)
|
|
||||||
//operatorCmd.AddCommand(operatorViewCmd(p))
|
|
||||||
operatorCmd.AddCommand(operatorCrdCmd(p))
|
|
||||||
operatorCmd.AddCommand(operatorControllerCmd(p))
|
|
||||||
|
|
||||||
//add fetch command
|
|
||||||
cmd.AddCommand(fetchCmd(p))
|
|
||||||
|
|
||||||
// add install command
|
|
||||||
cmd.AddCommand(installCmd(p))
|
|
||||||
|
|
||||||
// add config command
|
|
||||||
configCmd := configCmd(p)
|
|
||||||
cmd.AddCommand(configCmd)
|
|
||||||
configCmd.AddCommand(configApplyCmd(p))
|
|
||||||
configCmd.AddCommand(configViewCmd(p))
|
|
||||||
|
|
||||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||||
|
|
||||||
//add upgrade command
|
|
||||||
cmd.AddCommand(upgradeCmd(p))
|
|
||||||
|
|
||||||
// add the set-context config command as a sub-command to the app config command
|
|
||||||
configCmd.AddCommand(setContextConfigCmd(p))
|
|
||||||
|
|
||||||
// add the set profile/namespace/storageClassName/git-repository config command as a sub-command to the app config command
|
|
||||||
configCmd.AddCommand(setOtherConfigsCmd(p))
|
|
||||||
|
|
||||||
// add the set ### config command as a sub-command to the app config sub-command
|
|
||||||
configCmd.AddCommand(setConfigsCmd(p))
|
|
||||||
|
|
||||||
// add the set ### config command as a sub-command to the app config sub-command
|
|
||||||
configCmd.AddCommand(setSecretsCmd(p))
|
|
||||||
|
|
||||||
// add the list config command as a sub-command to the app config sub-command
|
|
||||||
configCmd.AddCommand(listContextConfigCmd(p))
|
|
||||||
|
|
||||||
// add 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))
|
|
||||||
|
|
||||||
// add crds
|
|
||||||
cmd.AddCommand(crdsCmd)
|
|
||||||
crdsCmd.AddCommand(crdsViewCmd(p))
|
|
||||||
crdsCmd.AddCommand(crdsInstallCmd(p))
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,6 +276,7 @@ func downloadFile(url string, filepath string) error {
|
|||||||
err error
|
err error
|
||||||
resp *http.Response
|
resp *http.Response
|
||||||
)
|
)
|
||||||
|
logDebugMessage("Porter download link: %s\n", url)
|
||||||
// Create the file
|
// Create the file
|
||||||
if out, err = os.Create(filepath); err != nil {
|
if out, err = os.Create(filepath); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -225,29 +324,3 @@ func copy(src, dst string) (int64, error) {
|
|||||||
nBytes, err = io.Copy(destination, source)
|
nBytes, err = io.Copy(destination, source)
|
||||||
return nBytes, err
|
return nBytes, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func levenstein(cmd *cobra.Command) bool {
|
|
||||||
cmd.SuggestionsMinimumDistance = 4
|
|
||||||
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])
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
|
||||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func uninstallCmd(q *qliksense.Qliksense) *cobra.Command {
|
|
||||||
c := &cobra.Command{
|
|
||||||
Use: "uninstall",
|
|
||||||
Short: "Uninstall the deployed qliksense with release name [ " + qapi.NewQConfig(q.QliksenseHome).Spec.CurrentContext + " ]",
|
|
||||||
Long: `Uninstall the deployed qliksense. By default uninstall the current context`,
|
|
||||||
Example: `qliksense uninstall <context-name>`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
if len(args) > 0 {
|
|
||||||
return q.UninstallQK8s(args[0])
|
|
||||||
}
|
|
||||||
return q.UninstallQK8s("")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func upgradeCmd(q *qliksense.Qliksense) *cobra.Command {
|
|
||||||
keepPatchFiles := false
|
|
||||||
c := &cobra.Command{
|
|
||||||
Use: "upgrade",
|
|
||||||
Short: "upgrade qliksense release",
|
|
||||||
Long: `upgrade qliksense release`,
|
|
||||||
Example: `qliksense upgrade <version>`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return q.UpgradeQK8s(keepPatchFiles)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
f := c.Flags()
|
|
||||||
f.BoolVar(&keepPatchFiles, keepPatchFilesFlagName, keepPatchFiles, keepPatchFilesFlagUsage)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
292
cmd/qliksense/version_checks.go
Normal file
292
cmd/qliksense/version_checks.go
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
"github.com/qlik-oss/sense-installer/pkg"
|
||||||
|
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dependenciesFile = "dependencies.yaml"
|
||||||
|
updateMixin, updateComponent bool
|
||||||
|
currentPorterVersion string
|
||||||
|
mixinURLs = map[string]string{
|
||||||
|
"qliksense": "--url https://github.com/qlik-oss/porter-qliksense/releases/download",
|
||||||
|
"kustomize": "--url https://github.com/donmstewart/porter-kustomize/releases/",
|
||||||
|
}
|
||||||
|
mixinsVar = map[string]string{
|
||||||
|
"kustomize": "-v 0.2-beta-3-0e19ca4 --url https://github.com/donmstewart/porter-kustomize/releases/download",
|
||||||
|
"qliksense": "-v v0.14.0 --url https://github.com/qlik-oss/porter-qliksense/releases/download",
|
||||||
|
"exec": "-v latest",
|
||||||
|
"kubernetes": "-v latest",
|
||||||
|
"helm": "-v latest",
|
||||||
|
"azure": "-v latest",
|
||||||
|
"terraform": "-v latest",
|
||||||
|
"az": "-v latest",
|
||||||
|
"aws": "-v latest",
|
||||||
|
"gcloud": "-v latest",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkMinVersion(tag string, q *qliksense.Qliksense) {
|
||||||
|
|
||||||
|
logDebugMessage("Starting version checks\n")
|
||||||
|
|
||||||
|
dependencies := map[string]string{}
|
||||||
|
// check if tag is empty or not
|
||||||
|
var err error
|
||||||
|
if len(strings.TrimSpace(tag)) > 0 {
|
||||||
|
// --tag exists
|
||||||
|
logDebugMessage("Input tag: %s, %s\n", tag, strings.Replace(tag, "bundle", "invocation", 1))
|
||||||
|
// Pull the image and store labels in a map
|
||||||
|
dependencies, err = q.PullImage(strings.Replace(tag, "bundle", "invocation", 1))
|
||||||
|
logDebugMessage("\nDependencies map from the inspected image: %v\n", dependencies)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to pull the requested image: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Tag is empty, hence looking for dependenciesFile.yaml, exit if this file is not present
|
||||||
|
if fileExists(dependenciesFile) {
|
||||||
|
// read the dependencies.yaml and store into a map
|
||||||
|
yamlFile, err := ioutil.ReadFile(dependenciesFile)
|
||||||
|
if err != nil {
|
||||||
|
logDebugMessage("Exit: CheckMinVersion()\n")
|
||||||
|
log.Fatalf("Error reading from source: %s\n", err)
|
||||||
|
}
|
||||||
|
err = yaml.Unmarshal(yamlFile, &dependencies)
|
||||||
|
if err != nil {
|
||||||
|
logDebugMessage("Exit: CheckMinVersion()\n")
|
||||||
|
log.Fatalf("Error when parsing from source: %s\n", err)
|
||||||
|
}
|
||||||
|
logDebugMessage("Dependencies map from the given yaml: %v\n", dependencies)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(dependencies) > 0 {
|
||||||
|
for k, v := range dependencies {
|
||||||
|
if strings.Contains(k, ".mixin.") {
|
||||||
|
dependencies[k] = fmt.Sprintf("-v %s", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// CLI check
|
||||||
|
checkCLIVersion(dependencies)
|
||||||
|
// Porter check
|
||||||
|
checkPorterVersion(dependencies, q)
|
||||||
|
// Mixins check
|
||||||
|
checkMixinVersion(dependencies, q)
|
||||||
|
} else {
|
||||||
|
log.Fatalf("Not able to infer dependencies, hence exiting")
|
||||||
|
}
|
||||||
|
logDebugMessage("Completed version checks\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkMixinVersion(dependencies map[string]string, q *qliksense.Qliksense) {
|
||||||
|
var tmp string
|
||||||
|
logDebugMessage("------------ Mixins version check -----------")
|
||||||
|
currentMixinVersions, err := retrieveCurrentInstalledMixinVersions(q)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for k := range mixinsVar {
|
||||||
|
tmp, _ = dependencies[fmt.Sprintf("org.qlik.operator.mixin.%s.version.min", k)]
|
||||||
|
if tmp == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
shouldUpdateMixin := false
|
||||||
|
mixinVersion, ok := currentMixinVersions[k]
|
||||||
|
if !ok {
|
||||||
|
shouldUpdateMixin = true
|
||||||
|
} else {
|
||||||
|
shouldUpdateMixin = versionCheck(fmt.Sprintf("Mixin %s", k), mixinVersion, tmp)
|
||||||
|
}
|
||||||
|
// if tmp is not empty and mixin requires download and install
|
||||||
|
if shouldUpdateMixin {
|
||||||
|
fmt.Println("Downloading a newer version of mixin:", k)
|
||||||
|
// download and install the new mixin
|
||||||
|
mURL, ok := mixinURLs[k]
|
||||||
|
if ok {
|
||||||
|
tmp = fmt.Sprintf("%s %s", tmp, mURL)
|
||||||
|
}
|
||||||
|
if _, err = installMixin(q.PorterExe, k, tmp); err != nil {
|
||||||
|
logDebugMessage("Completed version checks\n")
|
||||||
|
log.Fatalf("Error installing mixin %s: %s\n", k, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPorterVersion(dependencies map[string]string, q *qliksense.Qliksense) {
|
||||||
|
// Infer info about the min porter version
|
||||||
|
var porterVersionFromDependencies, tmp string
|
||||||
|
var err error
|
||||||
|
logDebugMessage("------------ Porter version check -----------")
|
||||||
|
tmp, _ = dependencies["org.qlik.operator.cli.porter.version.min"]
|
||||||
|
if len(tmp) != 0 {
|
||||||
|
porterVersionFromDependencies = tmp
|
||||||
|
}
|
||||||
|
logDebugMessage("Porter version from dependencies map: %v\n", porterVersionFromDependencies)
|
||||||
|
|
||||||
|
// check porter version
|
||||||
|
currentPorterVersion, err = determineCurrentPorterVersion(q)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("warning:", err)
|
||||||
|
}
|
||||||
|
logDebugMessage("Current Porter version: %v\n", currentPorterVersion)
|
||||||
|
updateComponent = true
|
||||||
|
if currentPorterVersion != "" {
|
||||||
|
updateComponent = versionCheck("Porter", currentPorterVersion, porterVersionFromDependencies)
|
||||||
|
}
|
||||||
|
if updateComponent {
|
||||||
|
fmt.Println("Downloading a newer version of Porter")
|
||||||
|
// Download and install newer version of porter and mixins
|
||||||
|
q.PorterExe, err = installPorter(q.QliksenseHome, q.PorterExe)
|
||||||
|
if err != nil {
|
||||||
|
logDebugMessage("Completed version checks")
|
||||||
|
log.Fatalf("error installing porter: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = installMixins(q.PorterExe, q.QliksenseHome); err != nil {
|
||||||
|
logDebugMessage("Completed version checks")
|
||||||
|
log.Fatalf("error installing mixin: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkCLIVersion(dependencies map[string]string) {
|
||||||
|
// Infer info about the minimum cli version
|
||||||
|
var cliVersionFromDependencies, tmp string
|
||||||
|
logDebugMessage("\n------------ CLI version check -----------\n")
|
||||||
|
tmp, _ = dependencies["org.qlik.operator.cli.sense-installer.version.min"]
|
||||||
|
if len(tmp) != 0 {
|
||||||
|
cliVersionFromDependencies = tmp
|
||||||
|
}
|
||||||
|
logDebugMessage("\nCLI version from dependencies map: %v\n", cliVersionFromDependencies)
|
||||||
|
|
||||||
|
// Checking version below
|
||||||
|
updateComponent = versionCheck("CLI", pkg.Version, cliVersionFromDependencies)
|
||||||
|
if updateComponent {
|
||||||
|
log.Fatalf("Please download a newer version of CLI and retry the operation, exiting now.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieveCurrentInstalledMixinVersions(q *qliksense.Qliksense) (map[string]string, error) {
|
||||||
|
if _, err := os.Stat(filepath.Join(q.QliksenseHome, mixinDirVar)); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
// if path doesnt exist, return empty map, and let porter take care of the rest
|
||||||
|
return map[string]string{}, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := map[string]string{}
|
||||||
|
currentInstalledMixinVersions, err := q.CallPorter([]string{"mixins", "list"}, func(x string) (out *string) {
|
||||||
|
out = new(string)
|
||||||
|
*out = strings.ReplaceAll(x, "porter", "qliksense porter")
|
||||||
|
logDebugMessage("%s\n", *out)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error occurred when retrieving mixins list: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
currentInstalledMixinVersionsArr := strings.Split(currentInstalledMixinVersions, "\n")
|
||||||
|
for _, mix := range currentInstalledMixinVersionsArr {
|
||||||
|
mixRow := strings.Fields(mix)
|
||||||
|
mixRowLen := len(mixRow)
|
||||||
|
if mixRowLen > 0 && mixRow[0] == "Name" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// we handle the case of mixins like `kustomize` where version and author could be empty
|
||||||
|
if mixRowLen >= 2 {
|
||||||
|
_, err := semver.NewVersion(mixRow[1])
|
||||||
|
if err == nil {
|
||||||
|
result[mixRow[0]] = mixRow[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func determineVersion(versionString string) (string, error) {
|
||||||
|
|
||||||
|
versionSlice := strings.Fields(versionString)
|
||||||
|
|
||||||
|
var currentComponentVersionNumber *semver.Version
|
||||||
|
var err error
|
||||||
|
for _, value := range versionSlice {
|
||||||
|
currentComponentVersionNumber, err = semver.NewVersion(value)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logDebugMessage("Version string: %v\n", currentComponentVersionNumber)
|
||||||
|
if currentComponentVersionNumber != nil {
|
||||||
|
return currentComponentVersionNumber.String(), nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("unable to extract version information")
|
||||||
|
}
|
||||||
|
|
||||||
|
func determineCurrentPorterVersion(q *qliksense.Qliksense) (string, error) {
|
||||||
|
// determine current porter version
|
||||||
|
currentPorterVersion, err := q.CallPorter([]string{"version"}, func(x string) (out *string) {
|
||||||
|
out = new(string)
|
||||||
|
*out = strings.ReplaceAll(x, "porter", "qliksense porter")
|
||||||
|
logDebugMessage(*out)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error occurred during porter call: %v", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return determineVersion(currentPorterVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func versionCheck(component string, currentVersion string, versionFromSourceOfTruth string) bool {
|
||||||
|
|
||||||
|
if strings.HasPrefix(versionFromSourceOfTruth, "-v ") {
|
||||||
|
versionFromSourceOfTruth = strings.Replace(versionFromSourceOfTruth, "-v ", "", 1)
|
||||||
|
}
|
||||||
|
componentVersionFromDependenciesYaml, err := semver.NewVersion(versionFromSourceOfTruth)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("There has been an error parsing version from source of truth: %s\n", err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
logDebugMessage("%s version from source of truth: %s\n", component, componentVersionFromDependenciesYaml)
|
||||||
|
|
||||||
|
currentComponentVersion, err := semver.NewVersion(currentVersion)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("There has been an error parsing version from the derived current version: %s\n", err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
logDebugMessage("\nCurrently installed %s version: %v\n", component, currentComponentVersion)
|
||||||
|
|
||||||
|
// check component version
|
||||||
|
if currentComponentVersion.LessThan(componentVersionFromDependenciesYaml) {
|
||||||
|
fmt.Printf("\n\nCurrent %s Component version: %s is less than minimum required version:%s\n", component, currentComponentVersion, componentVersionFromDependenciesYaml)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
logDebugMessage("Current %s version is greater than version from dependencies, upgrade not necessary.\n\n", component)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func logDebugMessage(strMessage string, args ...interface{}) {
|
||||||
|
if os.Getenv("QLIKSENSE_DEBUG") == "true" {
|
||||||
|
fmt.Printf(strMessage, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileExists(filename string) bool {
|
||||||
|
info, err := os.Stat(filename)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !info.IsDir()
|
||||||
|
}
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
# How qliksense cli works
|
|
||||||
|
|
||||||
At the initialization `qliksense` cli create few files in the director `~/.qliksene` and it contains following files
|
|
||||||
|
|
||||||
```console
|
|
||||||
.qliksense
|
|
||||||
├── config.yaml
|
|
||||||
├── contexts
|
|
||||||
│ └── qlik-default
|
|
||||||
│ └── qlik-default.yaml
|
|
||||||
└── ejson
|
|
||||||
└── keys
|
|
||||||
```
|
|
||||||
|
|
||||||
`qlik-default.yaml` is a default CR has been created with some default values like this
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
apiVersion: qlik.com/v1
|
|
||||||
kind: Qliksense
|
|
||||||
metadata:
|
|
||||||
name: qlik-default
|
|
||||||
spec:
|
|
||||||
profile: docker-desktop
|
|
||||||
secrets:
|
|
||||||
qliksense:
|
|
||||||
- name: mongoDbUri
|
|
||||||
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
|
|
||||||
rotateKeys: "yes"
|
|
||||||
releaseName: qlik-default
|
|
||||||
```
|
|
||||||
|
|
||||||
The `qliksense` cli creates a default qliksense context (different from kubectl context) named `qlik-default` which will be the prefix for all kubernetes resources created by the cli under this context latter on. New context and configuration can be created by the cli.
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ qliksense config -h
|
|
||||||
do operations on/around CR
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
qliksense config [command]
|
|
||||||
|
|
||||||
Available Commands:
|
|
||||||
apply generate the patchs and apply manifests to k8s
|
|
||||||
list-contexts retrieves the contexts and lists them
|
|
||||||
set configure a key value pair into the current context
|
|
||||||
set-configs set configurations into the qliksense context as key-value pairs
|
|
||||||
set-context Sets the context in which the Kubernetes cluster and resources live in
|
|
||||||
set-secrets set secrets configurations into the qliksense context as key-value pairs
|
|
||||||
view view the qliksense operator CR
|
|
||||||
|
|
||||||
Flags:
|
|
||||||
-h, --help help for config
|
|
||||||
|
|
||||||
Use "qliksense config [command] --help" for more information about a command.
|
|
||||||
```
|
|
||||||
|
|
||||||
`qliksense` cli works in two modes
|
|
||||||
|
|
||||||
- with a git repo fork/clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s)
|
|
||||||
- without git repo
|
|
||||||
|
|
||||||
## Without git repo
|
|
||||||
|
|
||||||
In this mode `qliksense` CLI download the specified version from [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and put it into folder `~/.qliksense/contexts/<context-name>/qlik-k8s`.
|
|
||||||
|
|
||||||
The qliksense cli create a CR for the qliksense operator and all the config operations are peformed to edit the CR. So when `qliksense install` or `qliksense config apply` both generate patches in local file system (i.e `~/.qliksense/contexts/<context-name>/qlik-k8s`) and install those manifests into the cluster and create a custom resoruce (CR) for the `qliksene operator` then the operator make association to the isntalled resoruces so that when `qliksenes uninstall` is performed the operator can delete all those kubernetes resources related to QSEoK for the current context.
|
|
||||||
|
|
||||||
## With a git repo
|
|
||||||
|
|
||||||
User has to create fork or clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and push it to their own git server. When user perform `qliksense install` or `qliksene config apply` the qliksense operator do these tasks
|
|
||||||
|
|
||||||
- downloads the corresponding version of manifests from the user's git repo.
|
|
||||||
- generate kustomize patches
|
|
||||||
- install kubernetes resoruces
|
|
||||||
- push those generated patches into a new branch in the provided git repo. so that user user can merge those patches into their master branch.
|
|
||||||
- spinup a cornjob to monitor master branch. If user modifies anything in the master branch those changes will be applied into the cluster. This is a light weight `git-ops` model
|
|
||||||
|
|
||||||
This is how repo info is provided into the CR
|
|
||||||
|
|
||||||
```console
|
|
||||||
qliksense config set git.repository="https://github.com/my-org/qliksense-k8s"
|
|
||||||
|
|
||||||
qliksense config set git.accessToken=blablalaala
|
|
||||||
```
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# Getting started
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
- `kubectl` need to be installed and configured properly so that `kubectl` can connect to the kubernetes cluser. The `qliksense` CLI uses `kubectl` under the hood to perform operations on cluster
|
|
||||||
- (Docker Desktop setup tested for these instructions)
|
|
||||||
|
|
||||||
## Download
|
|
||||||
|
|
||||||
- Download the appropriate executable for your platform from the [releases page](https://github.com/qlik-oss/sense-installer/releases) and rename it to `qliksense`. All the examplease down below uses `qliksense`.
|
|
||||||
|
|
||||||
## Quick start
|
|
||||||
|
|
||||||
- To download the version `v0.0.2` from qliksense-k8s [releases](https://github.com/qlik-oss/qliksense-k8s/releases).
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$qliksense fetch v0.0.2
|
|
||||||
```
|
|
||||||
|
|
||||||
- To install CRDs for QSEoK and qliksense operator into the kubernetes cluster.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$qliksense crds install --all
|
|
||||||
```
|
|
||||||
|
|
||||||
- To install QSEoK into a namespace in the kubernetes cluster where `kubectl` is pointing to.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$qliksense install --acceptEULA="yes"
|
|
||||||
```
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# Overview
|
|
||||||
|
|
||||||
The Qlik Sense installer CLI (qliksense) provides an imperative interface to many of the configurations that need to be applied against the declarative structure described in [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s). This cli facilitates:
|
|
||||||
|
|
||||||
- installation of QSEoK
|
|
||||||
- installation of qliksense operator to manage QSEoK
|
|
||||||
- air gapped installation of QSEoK
|
|
||||||
|
|
||||||
This is a technology preview that uses Qlik modified [kustomize](https://github.com/qlik-oss/kustomize) to kubernetes manifests of the versions of the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository.
|
|
||||||
|
|
||||||
For each version of a qliksense edge build there should be a corresponding release in [qliksense-k8s] repository under [releases](https://github.com/qlik-oss/qliksense-k8s/releases)
|
|
||||||
|
|
||||||
## Future Direction
|
|
||||||
|
|
||||||
- More operations:
|
|
||||||
- Expand preflight checks
|
|
||||||
- backup/restore operations
|
|
||||||
- fully support airgap installation of QSEoK
|
|
||||||
- restore unwanted deletion of kubernetes resources
|
|
||||||
92
go.mod
92
go.mod
@@ -3,60 +3,68 @@ module github.com/qlik-oss/sense-installer
|
|||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
|
github.com/Sirupsen/logrus v1.0.5 => github.com/sirupsen/logrus v1.0.5
|
||||||
|
github.com/Sirupsen/logrus v1.3.0 => github.com/Sirupsen/logrus v1.0.6
|
||||||
|
github.com/Sirupsen/logrus v1.4.0 => github.com/sirupsen/logrus v1.0.6
|
||||||
|
// github.com/containerd/containerd v1.3.0-0.20190507210959-7c1e88399ec0 => github.com/containerd/containerd v1.3.2
|
||||||
|
|
||||||
github.com/docker/docker => github.com/moby/moby v0.7.3-0.20190826074503-38ab9da00309
|
github.com/docker/docker => github.com/moby/moby v0.7.3-0.20190826074503-38ab9da00309
|
||||||
|
// github.com/jaguilar/vt100 => github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305
|
||||||
|
// golang.org/x/crypto v0.0.0-20190129210102-0709b304e793 => golang.org/x/crypto v0.0.0-20180904163835-0709b304e793
|
||||||
golang.org/x/sys => golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a
|
golang.org/x/sys => golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a
|
||||||
|
|
||||||
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8
|
|
||||||
k8s.io/client-go => k8s.io/client-go v0.0.0-20191016111102-bec269661e48
|
|
||||||
k8s.io/kubectl => k8s.io/kubectl v0.0.0-20191016120415-2ed914427d51
|
|
||||||
|
|
||||||
sigs.k8s.io/kustomize/api => github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.52.0 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||||
cloud.google.com/go/storage v1.5.0 // indirect
|
github.com/Masterminds/semver/v3 v3.0.3
|
||||||
github.com/Shopify/ejson v1.2.1
|
github.com/Microsoft/hcsshim v0.8.7 // indirect
|
||||||
github.com/aws/aws-sdk-go v1.28.9 // indirect
|
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
|
||||||
|
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
|
||||||
|
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 // indirect
|
||||||
|
github.com/bitly/go-simplejson v0.5.0 // indirect
|
||||||
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
||||||
github.com/bugsnag/bugsnag-go v1.5.3 // indirect
|
github.com/bugsnag/bugsnag-go v1.5.3 // indirect
|
||||||
|
github.com/bugsnag/panicwrap v1.2.0 // indirect
|
||||||
|
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
||||||
|
github.com/cloudflare/cfssl v1.4.1 // indirect
|
||||||
github.com/containerd/containerd v1.3.2 // indirect
|
github.com/containerd/containerd v1.3.2 // indirect
|
||||||
github.com/containerd/continuity v0.0.0-20191214063359-1097c8bae83b // indirect
|
github.com/containerd/continuity v0.0.0-20191214063359-1097c8bae83b // indirect
|
||||||
github.com/containers/image/v5 v5.1.0
|
github.com/docker/cli v0.0.0-20191212191748-ebca1413117a
|
||||||
github.com/docker/cli v0.0.0-20191212191748-ebca1413117a // indirect
|
github.com/docker/distribution v2.7.1+incompatible
|
||||||
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7 // indirect
|
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7
|
||||||
|
github.com/docker/docker-credential-helpers v0.6.3 // indirect
|
||||||
|
github.com/docker/go v1.5.1-1 // indirect
|
||||||
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
github.com/docker/go-metrics v0.0.1 // indirect
|
github.com/docker/go-metrics v0.0.1 // indirect
|
||||||
github.com/gobuffalo/envy v1.9.0 // indirect
|
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
|
||||||
github.com/gobuffalo/logger v1.0.3 // indirect
|
github.com/gofrs/uuid v3.2.0+incompatible // indirect
|
||||||
github.com/gobuffalo/packd v1.0.0 // indirect
|
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d // indirect
|
||||||
github.com/gobuffalo/packr/v2 v2.7.1
|
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
|
||||||
github.com/golang/protobuf v1.3.3 // indirect
|
|
||||||
github.com/google/uuid v1.1.1
|
|
||||||
github.com/gorilla/mux v1.7.3 // indirect
|
github.com/gorilla/mux v1.7.3 // indirect
|
||||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
|
||||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
github.com/jinzhu/gorm v1.9.11 // indirect
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.4
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||||
|
github.com/lib/pq v1.2.0 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v2.0.1+incompatible // indirect
|
||||||
|
github.com/miekg/pkcs11 v1.0.3 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/qlik-oss/k-apis v0.0.19
|
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.5.2 // indirect
|
github.com/opencontainers/runc v0.1.1 // indirect
|
||||||
github.com/spf13/cobra v0.0.6
|
github.com/sirupsen/logrus v1.4.2 // indirect
|
||||||
|
github.com/spf13/cobra v0.0.5
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/spf13/viper v1.6.1
|
github.com/spf13/viper v1.6.1
|
||||||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31
|
github.com/theupdateframework/notary v0.6.1 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 // indirect
|
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 // indirect
|
||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a // indirect
|
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e // indirect
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553
|
||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect
|
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect
|
||||||
golang.org/x/tools v0.0.0-20200309202150-20ab64c0d93f // indirect
|
google.golang.org/grpc v1.24.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b // indirect
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||||
google.golang.org/grpc v1.27.0 // indirect
|
gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect
|
||||||
gopkg.in/yaml.v2 v2.2.8
|
gopkg.in/fatih/pool.v2 v2.0.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20190924164351-c8b7dadae555
|
gopkg.in/gorethink/gorethink.v3 v3.0.5 // indirect
|
||||||
k8s.io/api v0.17.0
|
gopkg.in/yaml.v2 v2.2.7
|
||||||
k8s.io/apimachinery v0.17.0
|
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 // indirect
|
||||||
sigs.k8s.io/kustomize/api v0.3.2
|
|
||||||
sigs.k8s.io/yaml v1.1.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
exclude github.com/Azure/go-autorest v12.0.0+incompatible
|
|
||||||
|
|||||||
18
mkdocs.yml
18
mkdocs.yml
@@ -1,18 +0,0 @@
|
|||||||
site_name: Qlik Sense installer
|
|
||||||
repo_url: 'https://github.com/qlik-oss/sense-installer'
|
|
||||||
strict: true
|
|
||||||
theme:
|
|
||||||
name: "material"
|
|
||||||
palette:
|
|
||||||
primary: 'green'
|
|
||||||
accent: 'indigo'
|
|
||||||
markdown_extensions:
|
|
||||||
- toc:
|
|
||||||
permalink: true
|
|
||||||
- admonition
|
|
||||||
nav:
|
|
||||||
- Overview: index.md
|
|
||||||
- getting_started.md
|
|
||||||
- concepts.md
|
|
||||||
- air_gap.md
|
|
||||||
- Releases ⧉: https://github.com/qlik-oss/sense-installer/releases
|
|
||||||
376
pkg/api/apis.go
376
pkg/api/apis.go
@@ -1,376 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rsa"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qlik-oss/k-apis/pkg/config"
|
|
||||||
|
|
||||||
b64 "encoding/base64"
|
|
||||||
|
|
||||||
"github.com/jinzhu/copier"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
pushSecretFileName = "image-registry-push-secret.yaml"
|
|
||||||
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")
|
|
||||||
qc := &QliksenseConfig{}
|
|
||||||
|
|
||||||
err := ReadFromFile(qc, configFile)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("yaml unmarshalling error ", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
qc.QliksenseHomePath = qsHome
|
|
||||||
return qc
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
if crFilePath == "" {
|
|
||||||
return nil, errors.New("context name " + contextName + " not found")
|
|
||||||
}
|
|
||||||
return getCRObject(crFilePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUnencryptedCR() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCurrentCR create a QliksenseCR object for current context
|
|
||||||
func (qc *QliksenseConfig) GetCurrentCR() (*QliksenseCR, error) {
|
|
||||||
return qc.GetCR(qc.Spec.CurrentContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCrLocation sets the CR location for a context. Helpful during test
|
|
||||||
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
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
tempQc.Spec.Contexts = append(tempQc.Spec.Contexts, []Context{c}...)
|
|
||||||
}
|
|
||||||
if found {
|
|
||||||
return tempQc, nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("cannot find the context")
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCRObject(crfile string) (*QliksenseCR, error) {
|
|
||||||
cr := &QliksenseCR{}
|
|
||||||
err := ReadFromFile(cr, crfile)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("cannot unmarshal cr ", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qc *QliksenseConfig) getCRFilePath(contextName string) string {
|
|
||||||
crFilePath := ""
|
|
||||||
for _, ctx := range qc.Spec.Contexts {
|
|
||||||
if ctx.Name == contextName {
|
|
||||||
crFilePath = ctx.CrFile
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return crFilePath
|
|
||||||
}
|
|
||||||
func (qc *QliksenseConfig) IsRepoExist(contextName, version string) bool {
|
|
||||||
if _, err := os.Lstat(qc.BuildRepoPathForContext(contextName, version)); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qc *QliksenseConfig) IsRepoExistForCurrent(version string) bool {
|
|
||||||
if _, err := os.Lstat(qc.BuildRepoPath(version)); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qc *QliksenseConfig) BuildRepoPath(version string) string {
|
|
||||||
return qc.BuildRepoPathForContext(qc.Spec.CurrentContext, version)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qc *QliksenseConfig) BuildRepoPathForContext(contextName, version string) string {
|
|
||||||
return filepath.Join(qc.QliksenseHomePath, qliksenseContextsDirName, contextName, "qlik-k8s", version)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qc *QliksenseConfig) BuildCurrentManifestsRoot(version string) string {
|
|
||||||
return qc.BuildRepoPath(version)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qc *QliksenseConfig) WriteCR(cr *QliksenseCR, contextName string) error {
|
|
||||||
crf := qc.getCRFilePath(contextName)
|
|
||||||
if crf == "" {
|
|
||||||
return errors.New("context name " + contextName + " not found")
|
|
||||||
}
|
|
||||||
return WriteToFile(cr, crf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qc *QliksenseConfig) WriteCurrentContextCR(cr *QliksenseCR) error {
|
|
||||||
return qc.WriteCR(cr, qc.Spec.CurrentContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qc *QliksenseConfig) IsContextExist(ctxName string) bool {
|
|
||||||
for _, ct := range qc.Spec.Contexts {
|
|
||||||
if ct.Name == ctxName {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qc *QliksenseConfig) GetCurrentContextDir() (string, error) {
|
|
||||||
if qcr, err := qc.GetCurrentCR(); err != nil {
|
|
||||||
return "", err
|
|
||||||
} else {
|
|
||||||
return filepath.Join(qc.QliksenseHomePath, qliksenseContextsDirName, qcr.GetObjectMeta().GetName()), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qc *QliksenseConfig) GetCurrentContextSecretsDir() (string, error) {
|
|
||||||
if currentContextDir, err := qc.GetCurrentContextDir(); err != nil {
|
|
||||||
return "", err
|
|
||||||
} else {
|
|
||||||
return filepath.Join(currentContextDir, qliksenseSecretsDirName), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qc *QliksenseConfig) setDockerConfigJsonSecret(filename string, dockerConfigJsonSecret *DockerConfigJsonSecret) error {
|
|
||||||
if secretsDir, err := qc.GetCurrentContextSecretsDir(); err != nil {
|
|
||||||
return err
|
|
||||||
} else if publicKey, _, err := qc.GetCurrentContextEncryptionKeyPair(); err != nil {
|
|
||||||
return err
|
|
||||||
} else if dockerConfigJsonSecretYaml, err := dockerConfigJsonSecret.ToYaml(publicKey); err != nil {
|
|
||||||
return err
|
|
||||||
} else if err := os.MkdirAll(secretsDir, os.ModePerm); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
return ioutil.WriteFile(filepath.Join(secretsDir, filename), dockerConfigJsonSecretYaml, os.ModePerm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qc *QliksenseConfig) SetPushDockerConfigJsonSecret(dockerConfigJsonSecret *DockerConfigJsonSecret) error {
|
|
||||||
return qc.setDockerConfigJsonSecret(pushSecretFileName, dockerConfigJsonSecret)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qc *QliksenseConfig) SetPullDockerConfigJsonSecret(dockerConfigJsonSecret *DockerConfigJsonSecret) error {
|
|
||||||
return qc.setDockerConfigJsonSecret(pullSecretFileName, dockerConfigJsonSecret)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qc *QliksenseConfig) GetPushDockerConfigJsonSecret() (*DockerConfigJsonSecret, error) {
|
|
||||||
return qc.getDockerConfigJsonSecret(pushSecretFileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
} else if dockerConfigJsonSecretYaml, err := ioutil.ReadFile(filepath.Join(secretsDir, name)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if _, privateKey, err := qc.GetCurrentContextEncryptionKeyPair(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if err := dockerConfigJsonSecret.FromYaml(dockerConfigJsonSecretYaml, privateKey); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return dockerConfigJsonSecret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qc *QliksenseConfig) getCurrentContextEncryptionKeyPairLocation() (string, error) {
|
|
||||||
// Check env var: QLIKSENSE_KEY_LOCATION to determine location to store keypair
|
|
||||||
var secretKeyPairLocation string
|
|
||||||
if os.Getenv("QLIKSENSE_KEY_LOCATION") != "" {
|
|
||||||
LogDebugMessage("Env variable: QLIKSENSE_KEY_LOCATION= %s", os.Getenv("QLIKSENSE_KEY_LOCATION"))
|
|
||||||
secretKeyPairLocation = os.Getenv("QLIKSENSE_KEY_LOCATION")
|
|
||||||
} 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.GetObjectMeta().GetName(), 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 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)
|
|
||||||
if err := os.MkdirAll(secretKeyPairLocation, os.ModePerm); err != nil {
|
|
||||||
err = fmt.Errorf("Not able to create %s dir: %v", secretKeyPairLocation, err)
|
|
||||||
log.Println(err)
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
// generating and storing key-pair
|
|
||||||
err1 := GenerateAndStoreSecretKeypair(secretKeyPairLocation)
|
|
||||||
if err1 != nil {
|
|
||||||
err1 = fmt.Errorf("Not able to generate and store key pair for encryption")
|
|
||||||
log.Println(err1)
|
|
||||||
return nil, nil, err1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if publicKeyBytes, err := ReadKeys(publicKeyFilePath); err != nil {
|
|
||||||
LogDebugMessage("Not able to read public key")
|
|
||||||
return nil, nil, err
|
|
||||||
} else if privateKeyBytes, err := ReadKeys(privateKeyFilePath); err != nil {
|
|
||||||
LogDebugMessage("Not able to read private key")
|
|
||||||
return nil, nil, err
|
|
||||||
} else if rsaPublicKey, err := DecodeToPublicKey(publicKeyBytes); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
} else if rsaPrivateKey, err := DecodeToPrivateKey(privateKeyBytes); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
} else {
|
|
||||||
return rsaPublicKey, rsaPrivateKey, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cr *QliksenseCR) AddLabelToCr(key, value string) {
|
|
||||||
m := cr.GetObjectMeta().GetLabels()
|
|
||||||
if m == nil {
|
|
||||||
m = make(map[string]string)
|
|
||||||
}
|
|
||||||
m[key] = value
|
|
||||||
cr.GetObjectMeta().SetLabels(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cr *QliksenseCR) GetLabelFromCr(key string) string {
|
|
||||||
return cr.GetObjectMeta().GetLabels()[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cr *QliksenseCR) GetString() (string, error) {
|
|
||||||
out, err := K8sToYaml(cr)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("cannot unmarshal cr ", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(out), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cr *QliksenseCR) GetImageRegistry() string {
|
|
||||||
for _, nameValues := range cr.Spec.Configs {
|
|
||||||
for _, nameValue := range nameValues {
|
|
||||||
if nameValue.Name == "imageRegistry" {
|
|
||||||
return nameValue.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cr *QliksenseCR) GetK8sSecretsFolder(qlikSenseHomeDir string) string {
|
|
||||||
return filepath.Join(qlikSenseHomeDir, qliksenseContextsDirName, cr.GetName(), qliksenseSecretsDirName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cr *QliksenseCR) IsEULA() bool {
|
|
||||||
for k, nvs := range cr.Spec.Configs {
|
|
||||||
if k == "qliksense" {
|
|
||||||
for _, nv := range nvs {
|
|
||||||
if nv.Name == "acceptEULA" {
|
|
||||||
return nv.Value == "yes"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDecryptedCr it decrypts all the encrypted value and return a new CR
|
|
||||||
func (qc *QliksenseConfig) GetDecryptedCr(cr *QliksenseCR) (*QliksenseCR, error) {
|
|
||||||
newCr := &QliksenseCR{}
|
|
||||||
copier.Copy(newCr, cr)
|
|
||||||
_, rsaPrivateKey, err := qc.GetCurrentContextEncryptionKeyPair()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
finalSecrets := map[string]config.NameValues{}
|
|
||||||
for k, nvs := range newCr.Spec.Secrets {
|
|
||||||
newNvs := config.NameValues{}
|
|
||||||
for _, nv := range nvs {
|
|
||||||
if nv.Value != "" {
|
|
||||||
b, err := b64.StdEncoding.DecodeString(strings.TrimSpace(nv.Value))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
db, err := Decrypt(b, rsaPrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
newNvs = append(newNvs, config.NameValue{
|
|
||||||
Name: nv.Name,
|
|
||||||
Value: string(db),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finalSecrets[k] = newNvs
|
|
||||||
}
|
|
||||||
newCr.Spec.Secrets = finalSecrets
|
|
||||||
return newCr, nil
|
|
||||||
}
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
const tempPermissionCode os.FileMode = 0777
|
|
||||||
|
|
||||||
func setup() (func(), string) {
|
|
||||||
dir, _ := ioutil.TempDir("", "testing_path")
|
|
||||||
config :=
|
|
||||||
`
|
|
||||||
apiVersion: config.qlik.com/v1
|
|
||||||
kind: QliksenseConfig
|
|
||||||
metadata:
|
|
||||||
name: whatever
|
|
||||||
spec:
|
|
||||||
contexts:
|
|
||||||
- name: contx1
|
|
||||||
crLocation: /Users/mqb/.qliksense/contexts/contx1
|
|
||||||
- name: cotx2
|
|
||||||
crLocation: /root/.qliksense/contexts/cotx2.yaml
|
|
||||||
currentContext: contx1
|
|
||||||
`
|
|
||||||
configFile := filepath.Join(dir, "config.yaml")
|
|
||||||
ioutil.WriteFile(configFile, []byte(config), tempPermissionCode)
|
|
||||||
tearDown := func() {
|
|
||||||
os.RemoveAll(dir)
|
|
||||||
}
|
|
||||||
return tearDown, dir
|
|
||||||
}
|
|
||||||
|
|
||||||
func createCRFile(homeDir string) {
|
|
||||||
cr :=
|
|
||||||
`
|
|
||||||
apiVersion: qlik.com/v1
|
|
||||||
kind: QlikSense
|
|
||||||
metadata:
|
|
||||||
name: contx1
|
|
||||||
labels:
|
|
||||||
version: v1.0.0
|
|
||||||
spec:
|
|
||||||
profile: docker-desktop
|
|
||||||
manifestsRoot: /Users/mqb/.qliksense/contexts/contx1/qlik-k8s/v0.0.1/manifests
|
|
||||||
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")
|
|
||||||
os.MkdirAll(ctx1Dir, tempPermissionCode)
|
|
||||||
ioutil.WriteFile(crFile, []byte(cr), tempPermissionCode)
|
|
||||||
|
|
||||||
}
|
|
||||||
func TestGetCR(t *testing.T) {
|
|
||||||
td, dir := setup()
|
|
||||||
qc := NewQConfig(dir)
|
|
||||||
if qc.Spec.CurrentContext != "contx1" {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
// create CR
|
|
||||||
createCRFile(dir)
|
|
||||||
|
|
||||||
crFile := filepath.Join(dir, "contexts", "contx1", "contx1.yaml")
|
|
||||||
qct, e := qc.SetCrLocation("contx1", crFile)
|
|
||||||
if e != nil {
|
|
||||||
t.Fail()
|
|
||||||
t.Log(e)
|
|
||||||
}
|
|
||||||
qcr, err := qct.GetCurrentCR()
|
|
||||||
if err != nil {
|
|
||||||
t.Fail()
|
|
||||||
t.Log(err)
|
|
||||||
}
|
|
||||||
if qcr.Spec.Profile != "docker-desktop" {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
td()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetDecryptedCr(t *testing.T) {
|
|
||||||
td, dir := setup()
|
|
||||||
qc := NewQConfig(dir)
|
|
||||||
if qc.Spec.CurrentContext != "contx1" {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
// create CR
|
|
||||||
createCRFile(dir)
|
|
||||||
|
|
||||||
crFile := filepath.Join(dir, "contexts", "contx1", "contx1.yaml")
|
|
||||||
qct, e := qc.SetCrLocation("contx1", crFile)
|
|
||||||
if e != nil {
|
|
||||||
t.Fail()
|
|
||||||
t.Log(e)
|
|
||||||
}
|
|
||||||
qcr, err := qct.GetCurrentCR()
|
|
||||||
if err != nil {
|
|
||||||
t.Fail()
|
|
||||||
t.Log(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
setuPublicAndPrivateKey(dir)
|
|
||||||
newCr, err := qct.GetDecryptedCr(qcr)
|
|
||||||
if err != nil {
|
|
||||||
t.Fail()
|
|
||||||
t.Log(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
decryptedValue := newCr.Spec.GetFromSecrets("qliksense", "mongoDbUri")
|
|
||||||
orignalValue := qcr.Spec.GetFromSecrets("qliksense", "mongoDbUri")
|
|
||||||
if decryptedValue != "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false" {
|
|
||||||
t.Fail()
|
|
||||||
b, _ := K8sToYaml(newCr)
|
|
||||||
t.Log(b)
|
|
||||||
}
|
|
||||||
if decryptedValue == orignalValue {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
td()
|
|
||||||
}
|
|
||||||
func setuPublicAndPrivateKey(homeDir string) ([]byte, []byte, error) {
|
|
||||||
privKeyBytes := []byte(`-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIJJwIBAAKCAgEAwUCimKCidbF3UxEHPy8K+hvhklRB9JYhj5sJy0if4lTVibkK
|
|
||||||
1MrYCykOnmC40pPU9GLY1b8HxAg9tvyRn0YHUxOra6vVQaVcOVJhTM8D18d+lSr3
|
|
||||||
Lp1yiX+UGT4nzWI9+R1CCbwXrqeQVoZs6QZKynEXMkFI9/wNMOwPOvQFOSTuoEoC
|
|
||||||
O+zyTyUWEkNbUq825ELUQdIsjgmlWUOONudxsAr7ESRXW9QTHVh6uWmr3VRKZHby
|
|
||||||
1JdU3I/wjdlGg5M2dDuXy5nQO9w/nYLjJXiw+zzOetZ/+t7/VOkOpNTeJQhwTM1W
|
|
||||||
F7Y2VLetbi9FHgyzHatrduh07+XEiTbgDf3GIx2bp2p6oh0G3N2zpiLcK/aZj8ro
|
|
||||||
uWWydfFfsU3MZ4FfJDP8I6b9awxjmKYqIr6hiPQCJaLBED8mwK+I5evIbnKv6E6u
|
|
||||||
K+BApWA/R7ElragoFYbqQ1VpvntVMtJt9Dy5ZrI+IQARdXD3bb34oh0IPBhClnvv
|
|
||||||
MUc1cWxDoXEX6oJ4I+LzxE87Zkwnan9qOwengolMVKFwPx1o37qrbmrXID21kKt7
|
|
||||||
FL6xN4HxHLkItr1fKzdyWDFRHgASTAWfx5BIwvPuUW0vZHkvO80VyV2L63whVhPn
|
|
||||||
PASmFkbviomrBttYfpr2aGQqF/qR1Nlxe834MFxk1pS9LMa/WnzvFr0gWakCAwEA
|
|
||||||
AQKCAgARSp9B2N2wejibDiL/3E23I1eDqFZedDB8kPrHXbAwqDaTJCN79spt9TaB
|
|
||||||
pVXkQaYEV/Pe7EDdoX8kKGU/QxzUqiXkdHOYdBtUZbKfFMbbP9ZrsnR7j0r4UpoF
|
|
||||||
yDH3hprU93E5PcNAtW2M0GpeT1nR01yn+n908PCdOAIE3GC7RDq1zOl2QzVLL55R
|
|
||||||
9ATv2Q2oTvJ/ETc7XlGVMx4+e2cIwXLFjeLjLI6pSYlxnarrGuetJZeEviWxto9n
|
|
||||||
odFVZI6yx8JFTXX8ZTCr/1IjwDDVyhMPmrHI2Lsv9cqBpSpbVe32cUkKxhsGaYjz
|
|
||||||
GvesQKamOPhco2ATNxPm0yopFlPsGKMfVl0BK0J6BqFh1BvU/SYJmXfnFuUNO3vV
|
|
||||||
4u2Saa0q1iddxV0rXDwIqUfn+S6rwzK0G7y8bH2yvpB2VwiG3TFPnULep4wsefNq
|
|
||||||
Fj92kqFBjacGpQLEEslUY0CMgeZ2+NuBQSUTscP3wBRsottMR6YXJtINdvfHBx+e
|
|
||||||
EcN71z8D00w3mYqIQ7qb4Ml6HOqknunn58g43L9sACMUMTlEBXa9pUnScNYgWBAz
|
|
||||||
W2q2mH37cIydM2JRZPpA8B4yTHt5ugJmChwyNFM7941arjKrebH+6AzLkofGedOP
|
|
||||||
zg+vZQuPEXWs+3MBBnkWoyJW3Y0fbQdjsuQTtnd+7iyoxoBroQKCAQEA4dIiFlIS
|
|
||||||
MDfRhQQWSiDvaw9aneDEJ3uo63ZRH5tm/IynLgtjYgEm/ZxlBCQgqRKLYELBxhu8
|
|
||||||
SaF0uPK8pmpFJt0mIwSlsdeVhuE2obQeKUCczaqrKeaHS3PdWLjTlwph81BGRkHy
|
|
||||||
qfqtNylyyMxrdEbnR51EtsWgFq6anTUAui1Q09JMuMNZRMOzDs1F4gExgD22rc0V
|
|
||||||
c9YQ+jHJRxBGtNKMpMEqc8cvaxBidbItrN9SMTSWog7uYPBuEuaJ6K9vpgyJMOzJ
|
|
||||||
SYcQEFGqgIqIDCg+ABE4d/4YROMKZ1DV/bJCind9brUHSx6XALsF0nC5c1Q9TnUL
|
|
||||||
qI2khOwts4KYKwKCAQEA2xRC6Az97Vkdzu7BjLJ1FKmx4S2nEEgVS12ds82U+5Xf
|
|
||||||
BHKAJnjqlqmmpzzJG+d77IYktz0+mey1QCNkqlm2fhuKs8LZMnpZRf0l8VcoBsUP
|
|
||||||
/xKz7wfiE7RRFZtLJhPp4hhe43GzX5/JFMWMnC6UykwQbj4t1E/GNM/Suqwvg12M
|
|
||||||
wktAJ6nqLgfhjQSO4xWo+nPzcbX+fNtrPCZVrBhYXihhcwRRNImWUCGJ6J4LMdPY
|
|
||||||
Y9Z59qhOvE9cReH/Xw1av46omyiSyAqlgPyZ/kzA2IJSqYCjiQR/2+RD/g13jpcJ
|
|
||||||
jatXLVZ8MJSL5OTS40G/HHTNNpNHbKKh0GOyxBA3ewKCAQBAn8UXhCcmW2L/YPsL
|
|
||||||
/b7mcX9qPP+FmRLvR23R0MQ5M/tH5wRq8I969n3GIJykJeVzB8eybQ+GNslTgEvS
|
|
||||||
iAkAJTubu+G7MkndTqg2wHf9MDtvdA8Fr646Po8yq7oJuHPtkKR7yLWsRUu6xIbP
|
|
||||||
xgheP0hCq1QVxhqZQyCGKrvpi7xc0gsYuPbcAfFFJCOCmPrUi1SzCkTAYJt9LjA+
|
|
||||||
wP6rErIjGBCRD4iXaBn1OqdtmH9KC5WsDP/VCBlIGWeQCly2NVIxiSHVg+xp7yUP
|
|
||||||
IhXq/L05gbQaSsIhPKQmivCiaJg4The8TdwneDqYf+0bmxzHT203/bD3bImPbJNr
|
|
||||||
ksz/AoIBAEwu4Y1cZzkQUmNRd5D7xecnk6ngfEYXKwCIT3zlMrfCSEl9n77BMaKu
|
|
||||||
4Dsr0iuX9eosQ7xM2eYhAG6LYEg05lc4MKWOToVVMpI6E+W3Dz47bPKgiF3I+f8s
|
|
||||||
Jz5CQIG/TwfGvciOE3hfUkec4ua09BzdEqGjkcBQ9XYMBxXPJr6h2379OBQS7FKR
|
|
||||||
fwfQ2/dv4tElXTTfut2kV8gU9Jnh5Wjo1epvR+XjKpg28YQo4W+0YX1magcyRB8L
|
|
||||||
4eSTUIC3XiVa8Jr0IwbZXPBb5xkdi7o+p4w2JahSHjxTRqmj+T1mnHXdbXVgq9Mg
|
|
||||||
9Pzl7cgFZvX4UBx4XtASRf73jITNtt0CggEADH9K+O7FrIOSQly0sMvsRCMtejp3
|
|
||||||
o+MDh1Q+vEg2kEgNXjS4ZFVljUpM2kg1OdUz7feS4dLXUJiIQ8ZWtZPedcq7wjHd
|
|
||||||
02he5+s06l0jPifN3tX1ADfXGpXg5R2fbkrIzakkPP5/RO/aDxIUo7qhklNsVTXO
|
|
||||||
VlGGfWLdk0ekA4upKm02Q1+YOlbIcAicEYYY8K7IffUwnohzKwL9yfuGi1VKTXpE
|
|
||||||
4fzdegsHI03FSqR7V+LvtBpIupQ7RO4kuBmCEyI4E9FVknchg4te4gO3qwd9y0rJ
|
|
||||||
Gu7HNIOrwOHzviI7J6Nd/l9MmeKqklHSgJvko/f5TmiXuQQ8xDZf84rcjQ==
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
||||||
`)
|
|
||||||
|
|
||||||
publicKeyBytes := []byte(`-----BEGIN RSA PUBLIC KEY-----
|
|
||||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwUCimKCidbF3UxEHPy8K
|
|
||||||
+hvhklRB9JYhj5sJy0if4lTVibkK1MrYCykOnmC40pPU9GLY1b8HxAg9tvyRn0YH
|
|
||||||
UxOra6vVQaVcOVJhTM8D18d+lSr3Lp1yiX+UGT4nzWI9+R1CCbwXrqeQVoZs6QZK
|
|
||||||
ynEXMkFI9/wNMOwPOvQFOSTuoEoCO+zyTyUWEkNbUq825ELUQdIsjgmlWUOONudx
|
|
||||||
sAr7ESRXW9QTHVh6uWmr3VRKZHby1JdU3I/wjdlGg5M2dDuXy5nQO9w/nYLjJXiw
|
|
||||||
+zzOetZ/+t7/VOkOpNTeJQhwTM1WF7Y2VLetbi9FHgyzHatrduh07+XEiTbgDf3G
|
|
||||||
Ix2bp2p6oh0G3N2zpiLcK/aZj8rouWWydfFfsU3MZ4FfJDP8I6b9awxjmKYqIr6h
|
|
||||||
iPQCJaLBED8mwK+I5evIbnKv6E6uK+BApWA/R7ElragoFYbqQ1VpvntVMtJt9Dy5
|
|
||||||
ZrI+IQARdXD3bb34oh0IPBhClnvvMUc1cWxDoXEX6oJ4I+LzxE87Zkwnan9qOwen
|
|
||||||
golMVKFwPx1o37qrbmrXID21kKt7FL6xN4HxHLkItr1fKzdyWDFRHgASTAWfx5BI
|
|
||||||
wvPuUW0vZHkvO80VyV2L63whVhPnPASmFkbviomrBttYfpr2aGQqF/qR1Nlxe834
|
|
||||||
MFxk1pS9LMa/WnzvFr0gWakCAwEAAQ==
|
|
||||||
-----END RSA PUBLIC KEY-----
|
|
||||||
`)
|
|
||||||
secretKeyPairDir := filepath.Join(homeDir, "secrets", "contexts", "contx1", "secrets")
|
|
||||||
if err := os.MkdirAll(secretKeyPairDir, 0777); err != nil {
|
|
||||||
err = fmt.Errorf("Not able to create directories")
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
os.Setenv("QLIKSENSE_KEY_LOCATION", secretKeyPairDir)
|
|
||||||
|
|
||||||
privKeyFile := filepath.Join(secretKeyPairDir, "qliksensePriv")
|
|
||||||
// construct and write priv key file into secretsDir location
|
|
||||||
err := ioutil.WriteFile(privKeyFile, privKeyBytes, 0777)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error while creating file: %v", err)
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
pubKeyFile := filepath.Join(secretKeyPairDir, "qliksensePub")
|
|
||||||
// construct and write pub key file into secretsDir location
|
|
||||||
err = ioutil.WriteFile(pubKeyFile, publicKeyBytes, 0777)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error while creating file: %v", err)
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return publicKeyBytes, privKeyBytes, nil
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/qlik-oss/k-apis/pkg/config"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
machine_yaml "k8s.io/apimachinery/pkg/util/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
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.SetGroupVersionKind(schema.GroupVersionKind{
|
|
||||||
Group: QliksenseGroup,
|
|
||||||
Kind: QliksenseKind,
|
|
||||||
Version: QliksenseApiVersion,
|
|
||||||
})
|
|
||||||
qliksenseCR.SetName(contextName)
|
|
||||||
qliksenseCR.Spec = &config.CRSpec{
|
|
||||||
Profile: QliksenseDefaultProfile,
|
|
||||||
RotateKeys: DefaultRotateKeys,
|
|
||||||
}
|
|
||||||
qliksenseCR.Spec.AddToSecrets("qliksense", DefaultMongoDbUriKey, DefaultMongoDbUri, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddBaseQliksenseConfigs adds configs into config.yaml
|
|
||||||
func (qliksenseConfig *QliksenseConfig) AddBaseQliksenseConfigs(defaultQliksenseContext string) {
|
|
||||||
qliksenseConfig.SetGroupVersionKind(schema.GroupVersionKind{
|
|
||||||
Group: QliksenseConfigApiGroup,
|
|
||||||
Kind: QliksenseConfigKind,
|
|
||||||
Version: QliksenseConfigApiVersion,
|
|
||||||
})
|
|
||||||
qliksenseConfig.SetName(QliksenseMetadataName)
|
|
||||||
if defaultQliksenseContext != "" {
|
|
||||||
if qliksenseConfig.Spec == nil {
|
|
||||||
qliksenseConfig.Spec = &ContextSpec{}
|
|
||||||
}
|
|
||||||
qliksenseConfig.Spec.CurrentContext = defaultQliksenseContext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qliksenseConfig *QliksenseConfig) SwitchCurrentCRToVersionAndProfile(version, profile string) error {
|
|
||||||
if qcr, err := qliksenseConfig.GetCurrentCR(); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
versionManifestRoot := qliksenseConfig.BuildCurrentManifestsRoot(version)
|
|
||||||
if (qcr.Spec.ManifestsRoot != versionManifestRoot) || (profile != "" && qcr.Spec.Profile != profile) || (qcr.GetLabelFromCr("version") != version) {
|
|
||||||
qcr.Spec.ManifestsRoot = versionManifestRoot
|
|
||||||
if profile != "" {
|
|
||||||
qcr.Spec.Profile = profile
|
|
||||||
}
|
|
||||||
qcr.AddLabelToCr("version", version)
|
|
||||||
if err := qliksenseConfig.WriteCurrentContextCR(qcr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteToFile (content, targetFile) writes content into specified file
|
|
||||||
func WriteToFile(content interface{}, targetFile string) error {
|
|
||||||
if content == nil || targetFile == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
LogDebugMessage("Wrote content into %s", targetFile)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFromFile (content, targetFile) reads content from specified sourcefile
|
|
||||||
func ReadFromFile(content interface{}, sourceFile string) error {
|
|
||||||
if content == nil || sourceFile == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
contents, err := ioutil.ReadFile(sourceFile)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("There was an error reading from file: %s, %v", sourceFile, err)
|
|
||||||
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)
|
|
||||||
dec.Decode(content)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/qlik-oss/k-apis/pkg/config"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
testDir = "./tests"
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want *QliksenseCR
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "valid case",
|
|
||||||
args: args{
|
|
||||||
qliksenseCR: &QliksenseCR{},
|
|
||||||
contextName: "myqliksense",
|
|
||||||
},
|
|
||||||
want: q,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
tt.args.qliksenseCR.AddCommonConfig(tt.args.contextName)
|
|
||||||
if !reflect.DeepEqual(tt.args.qliksenseCR, tt.want) {
|
|
||||||
t.Errorf("AddCommonConfig() = %+v, want %+v", tt.args.qliksenseCR, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddBaseQliksenseConfigs(t *testing.T) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want *QliksenseConfig
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "valid case",
|
|
||||||
args: args{
|
|
||||||
qliksenseConfig: &QliksenseConfig{},
|
|
||||||
defaultQliksenseContext: "qlik-default",
|
|
||||||
},
|
|
||||||
want: qc,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
tt.args.qliksenseConfig.AddBaseQliksenseConfigs(tt.args.defaultQliksenseContext)
|
|
||||||
if !reflect.DeepEqual(tt.args.qliksenseConfig, tt.want) {
|
|
||||||
t.Errorf("AddBaseQliksenseConfigs() = %+v, want %+v", tt.args.qliksenseConfig, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rsa"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
type k8sDockerConfigJsonMapType struct {
|
|
||||||
Auths map[string]k8sDockerConfigJsonType `json:"auths"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type k8sDockerConfigJsonType struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
Email string `json:"email,omitempty"`
|
|
||||||
Auth string `json:"auth"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kdcjt *k8sDockerConfigJsonType) GenerateAuth() {
|
|
||||||
kdcjt.Auth = base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", kdcjt.Username, kdcjt.Password)))
|
|
||||||
}
|
|
||||||
|
|
||||||
type DockerConfigJsonSecret struct {
|
|
||||||
Name string
|
|
||||||
Uri string
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
Email string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DockerConfigJsonSecret) ToYaml(encryptionKey *rsa.PublicKey) ([]byte, error) {
|
|
||||||
k8sDockerConfigJson := k8sDockerConfigJsonType{
|
|
||||||
Username: d.Username,
|
|
||||||
Password: d.Password,
|
|
||||||
Email: d.Email,
|
|
||||||
}
|
|
||||||
k8sDockerConfigJson.GenerateAuth()
|
|
||||||
k8sDockerConfigJsonMap := k8sDockerConfigJsonMapType{
|
|
||||||
Auths: map[string]k8sDockerConfigJsonType{
|
|
||||||
d.Uri: k8sDockerConfigJson,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
k8sDockerConfigJsonMapBytes, err := json.Marshal(k8sDockerConfigJsonMap)
|
|
||||||
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{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
APIVersion: "v1",
|
|
||||||
Kind: "Secret",
|
|
||||||
},
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: d.Name,
|
|
||||||
},
|
|
||||||
Type: v1.SecretTypeDockerConfigJson,
|
|
||||||
Data: map[string][]byte{
|
|
||||||
".dockerconfigjson": k8sDockerConfigJsonMapMaybeEncryptedBytes,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return K8sSecretToYaml(k8sSecret)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DockerConfigJsonSecret) FromYaml(secretBytes []byte, decryptionKey *rsa.PrivateKey) error {
|
|
||||||
k8sDockerConfigJsonMap := k8sDockerConfigJsonMapType{}
|
|
||||||
if k8sSecret, err := K8sSecretFromYaml(secretBytes); err != nil {
|
|
||||||
return err
|
|
||||||
} else if k8sSecret.TypeMeta.Kind != "Secret" {
|
|
||||||
return errors.New("not a Secret kind")
|
|
||||||
} else if k8sSecret.Type != v1.SecretTypeDockerConfigJson {
|
|
||||||
return errors.New("not a kubernetes.io/dockerconfigjson type")
|
|
||||||
} else if k8sDockerConfigJsonMapEncryptedBytes, ok := k8sSecret.Data[".dockerconfigjson"]; !ok {
|
|
||||||
return errors.New("secret data is missing a value for the .dockerconfigjson key")
|
|
||||||
} else if k8sDockerConfigJsonMapBytes, err := Decrypt(k8sDockerConfigJsonMapEncryptedBytes, decryptionKey); err != nil {
|
|
||||||
return errors.New("secret data is missing a value for the .dockerconfigjson key")
|
|
||||||
} else if err := json.Unmarshal(k8sDockerConfigJsonMapBytes, &k8sDockerConfigJsonMap); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
d.Name = k8sSecret.ObjectMeta.Name
|
|
||||||
for registry, k8sDockerConfigJson := range k8sDockerConfigJsonMap.Auths {
|
|
||||||
d.Uri = registry
|
|
||||||
d.Username = k8sDockerConfigJson.Username
|
|
||||||
d.Password = k8sDockerConfigJson.Password
|
|
||||||
d.Email = k8sDockerConfigJson.Email
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDockerConfigJsonSecret(t *testing.T) {
|
|
||||||
dockerConfigJsonSecret := DockerConfigJsonSecret{
|
|
||||||
Name: "some-name",
|
|
||||||
Uri: "some-uri",
|
|
||||||
Username: "some-username",
|
|
||||||
Password: "some-password",
|
|
||||||
Email: "some-email",
|
|
||||||
}
|
|
||||||
dockerConfigJsonSecretFromYaml := DockerConfigJsonSecret{}
|
|
||||||
validYamlMap := map[string]interface{}{}
|
|
||||||
|
|
||||||
privateKey, err := rsa.GenerateKey(rand.Reader, RSA_KEY_LENGTH)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error generating RSA private key: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dockerConfigJsonSecretYamlBytes, err := dockerConfigJsonSecret.ToYaml(&privateKey.PublicKey)
|
|
||||||
dockerConfigJsonMap := map[string]interface{}{}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error converting secret to yaml: %v", err)
|
|
||||||
} else if err := yaml.Unmarshal(dockerConfigJsonSecretYamlBytes, &validYamlMap); err != nil {
|
|
||||||
t.Fatalf("error unmarshalling yaml string: %v, error: %v", string(dockerConfigJsonSecretYamlBytes), err)
|
|
||||||
} else if validYamlMap["apiVersion"] != "v1" ||
|
|
||||||
validYamlMap["kind"] != "Secret" ||
|
|
||||||
validYamlMap["metadata"].(map[string]interface{})["name"] != dockerConfigJsonSecret.Name ||
|
|
||||||
validYamlMap["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 {
|
|
||||||
t.Fatalf("no .dockerconfigjson data key in the secret yaml: %v", string(dockerConfigJsonSecretYamlBytes))
|
|
||||||
} else if dockerConfigJsonEncryptedBytes, err := base64.StdEncoding.DecodeString(dockerConfigJsonBytesBase64.(string)); err != nil {
|
|
||||||
t.Fatalf("error decoding dockerConfigJsonBytes from base64: %v", err)
|
|
||||||
} else if dockerConfigJsonBytes, err := Decrypt(dockerConfigJsonEncryptedBytes, privateKey); err != nil {
|
|
||||||
t.Fatalf("error decrypting dockerConfigJsonBytes: %v", err)
|
|
||||||
} else if err := json.Unmarshal(dockerConfigJsonBytes, &dockerConfigJsonMap); err != nil {
|
|
||||||
t.Fatalf("error unmarshalling dockerConfigJson from json: %v", err)
|
|
||||||
} else if dockerConfigJson, ok := dockerConfigJsonMap["auths"].(map[string]interface{})[dockerConfigJsonSecret.Uri]; !ok {
|
|
||||||
t.Fatalf("dockerConfigJson map does not contain data for the registry: %v", dockerConfigJsonSecret.Uri)
|
|
||||||
} else if dockerConfigJson.(map[string]interface{})["username"] != dockerConfigJsonSecret.Username ||
|
|
||||||
dockerConfigJson.(map[string]interface{})["password"] != dockerConfigJsonSecret.Password ||
|
|
||||||
dockerConfigJson.(map[string]interface{})["email"] != dockerConfigJsonSecret.Email {
|
|
||||||
t.Fatal("dockerConfigJson map does not contain expected values")
|
|
||||||
} else {
|
|
||||||
authBase64 := dockerConfigJson.(map[string]interface{})["auth"]
|
|
||||||
if auth, err := base64.StdEncoding.DecodeString(authBase64.(string)); err != nil {
|
|
||||||
t.Fatal("error base64 decoding auth value")
|
|
||||||
} else if string(auth) != fmt.Sprintf("%s:%s", dockerConfigJsonSecret.Username, dockerConfigJsonSecret.Password) {
|
|
||||||
t.Fatal("auth value was not what we expected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("dockerConfigJsonSecretYaml: \n%v\n", string(dockerConfigJsonSecretYamlBytes))
|
|
||||||
if err := dockerConfigJsonSecretFromYaml.FromYaml(dockerConfigJsonSecretYamlBytes, privateKey); err != nil {
|
|
||||||
t.Fatalf("error reading secret in from yaml: %v", err)
|
|
||||||
} else if !reflect.DeepEqual(dockerConfigJsonSecret, dockerConfigJsonSecretFromYaml) {
|
|
||||||
t.Fatalf("secret: %v does not equal secret: %v", dockerConfigJsonSecret, dockerConfigJsonSecretFromYaml)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
RSA_KEY_LENGTH = 4096
|
|
||||||
|
|
||||||
QliksensePublicKey = "qliksensePub"
|
|
||||||
QliksensePrivateKey = "qliksensePriv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GenerateAndStoreSecretKeypair generates and stores key pairs
|
|
||||||
func GenerateAndStoreSecretKeypair(secretsPath string) error {
|
|
||||||
LogDebugMessage("%s exists", secretsPath)
|
|
||||||
// creating contexts/qlik-default/secrets/qliksensePub and contexts/qlik-default/secrets/qliksensePriv files
|
|
||||||
publicKeyFilePath := filepath.Join(secretsPath, QliksensePublicKey)
|
|
||||||
privateKeyFilePath := filepath.Join(secretsPath, QliksensePrivateKey)
|
|
||||||
LogDebugMessage("Generating public-private key pair.....")
|
|
||||||
GenerateRSAEncryptionKeys(publicKeyFilePath, privateKeyFilePath)
|
|
||||||
LogDebugMessage("Generated public-private key pairs")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateRSAEncryptionKeys is used to generate a new public-private key pair
|
|
||||||
func GenerateRSAEncryptionKeys(publicKeyFilePath, privateKeyFilePath string) error {
|
|
||||||
LogDebugMessage("Generating new RSA key pair")
|
|
||||||
privateKey, err := rsa.GenerateKey(rand.Reader, RSA_KEY_LENGTH)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("error generating RSA private key: %v\n", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
privateKeyPEM := EncodePrivateKey(privateKey)
|
|
||||||
if err := writeContentToFile(privateKeyPEM, privateKeyFilePath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pubKeyPEM, err2 := EncodePublicKey(&privateKey.PublicKey)
|
|
||||||
if err2 != nil {
|
|
||||||
log.Printf("error occurred when encoding public key: %v\n", err2)
|
|
||||||
return err2
|
|
||||||
}
|
|
||||||
if err := writeContentToFile(pubKeyPEM, publicKeyFilePath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeContentToFile writes keys to a file
|
|
||||||
func writeContentToFile(keyData []byte, fileName string) error {
|
|
||||||
err := ioutil.WriteFile(fileName, keyData, 0600)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("error writing to file (%s): %v", fileName, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt encrypts data with public key
|
|
||||||
func Encrypt(pt []byte, pub *rsa.PublicKey) ([]byte, error) {
|
|
||||||
//hash := sha512.New()
|
|
||||||
//ct, err := rsa.EncryptOAEP(hash, rand.Reader, pub, pt, nil)
|
|
||||||
ct, err := rsa.EncryptPKCS1v15(rand.Reader, pub, pt)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return ct, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt decrypts data with private key
|
|
||||||
func Decrypt(ct []byte, priv *rsa.PrivateKey) ([]byte, error) {
|
|
||||||
// hash := sha512.New()
|
|
||||||
// plaintext, err := rsa.DecryptOAEP(hash, rand.Reader, priv, ciphertext, nil)
|
|
||||||
pt, err := rsa.DecryptPKCS1v15(rand.Reader, priv, ct)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return pt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodePrivateKey private key to bytes
|
|
||||||
func EncodePrivateKey(priv *rsa.PrivateKey) []byte {
|
|
||||||
privBytes := pem.EncodeToMemory(
|
|
||||||
&pem.Block{
|
|
||||||
Type: "RSA PRIVATE KEY",
|
|
||||||
Bytes: x509.MarshalPKCS1PrivateKey(priv),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return privBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodePublicKey public key to bytes
|
|
||||||
func EncodePublicKey(pub *rsa.PublicKey) ([]byte, error) {
|
|
||||||
pubASN1, err := x509.MarshalPKIXPublicKey(pub)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pubBytes := pem.EncodeToMemory(&pem.Block{
|
|
||||||
Type: "RSA PUBLIC KEY",
|
|
||||||
Bytes: pubASN1,
|
|
||||||
})
|
|
||||||
|
|
||||||
return pubBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeToPrivateKey bytes to private key
|
|
||||||
func DecodeToPrivateKey(priv []byte) (*rsa.PrivateKey, error) {
|
|
||||||
block, _ := pem.Decode(priv)
|
|
||||||
enc := x509.IsEncryptedPEMBlock(block)
|
|
||||||
b := block.Bytes
|
|
||||||
var err error
|
|
||||||
if enc {
|
|
||||||
log.Println("is encrypted pem block")
|
|
||||||
b, err = x509.DecryptPEMBlock(block, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
key, err := x509.ParsePKCS1PrivateKey(b)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeToPublicKey bytes to public key
|
|
||||||
func DecodeToPublicKey(pub []byte) (*rsa.PublicKey, error) {
|
|
||||||
block, _ := pem.Decode(pub)
|
|
||||||
enc := x509.IsEncryptedPEMBlock(block)
|
|
||||||
b := block.Bytes
|
|
||||||
var err error
|
|
||||||
if enc {
|
|
||||||
log.Println("is encrypted pem block")
|
|
||||||
b, err = x509.DecryptPEMBlock(block, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
iface, err := x509.ParsePKIXPublicKey(b)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
key, ok := iface.(*rsa.PublicKey)
|
|
||||||
if !ok {
|
|
||||||
err := fmt.Errorf("Unable to decode public key")
|
|
||||||
log.Println(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_generateRSAEncryptionKeys(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "valid case",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if err := GenerateAndStoreSecretKeypair(os.TempDir()); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("generateRSAEncryptionKeys() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_encryption_decryption(t *testing.T) {
|
|
||||||
privKeyBytes := []byte(`-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIJJwIBAAKCAgEAwUCimKCidbF3UxEHPy8K+hvhklRB9JYhj5sJy0if4lTVibkK
|
|
||||||
1MrYCykOnmC40pPU9GLY1b8HxAg9tvyRn0YHUxOra6vVQaVcOVJhTM8D18d+lSr3
|
|
||||||
Lp1yiX+UGT4nzWI9+R1CCbwXrqeQVoZs6QZKynEXMkFI9/wNMOwPOvQFOSTuoEoC
|
|
||||||
O+zyTyUWEkNbUq825ELUQdIsjgmlWUOONudxsAr7ESRXW9QTHVh6uWmr3VRKZHby
|
|
||||||
1JdU3I/wjdlGg5M2dDuXy5nQO9w/nYLjJXiw+zzOetZ/+t7/VOkOpNTeJQhwTM1W
|
|
||||||
F7Y2VLetbi9FHgyzHatrduh07+XEiTbgDf3GIx2bp2p6oh0G3N2zpiLcK/aZj8ro
|
|
||||||
uWWydfFfsU3MZ4FfJDP8I6b9awxjmKYqIr6hiPQCJaLBED8mwK+I5evIbnKv6E6u
|
|
||||||
K+BApWA/R7ElragoFYbqQ1VpvntVMtJt9Dy5ZrI+IQARdXD3bb34oh0IPBhClnvv
|
|
||||||
MUc1cWxDoXEX6oJ4I+LzxE87Zkwnan9qOwengolMVKFwPx1o37qrbmrXID21kKt7
|
|
||||||
FL6xN4HxHLkItr1fKzdyWDFRHgASTAWfx5BIwvPuUW0vZHkvO80VyV2L63whVhPn
|
|
||||||
PASmFkbviomrBttYfpr2aGQqF/qR1Nlxe834MFxk1pS9LMa/WnzvFr0gWakCAwEA
|
|
||||||
AQKCAgARSp9B2N2wejibDiL/3E23I1eDqFZedDB8kPrHXbAwqDaTJCN79spt9TaB
|
|
||||||
pVXkQaYEV/Pe7EDdoX8kKGU/QxzUqiXkdHOYdBtUZbKfFMbbP9ZrsnR7j0r4UpoF
|
|
||||||
yDH3hprU93E5PcNAtW2M0GpeT1nR01yn+n908PCdOAIE3GC7RDq1zOl2QzVLL55R
|
|
||||||
9ATv2Q2oTvJ/ETc7XlGVMx4+e2cIwXLFjeLjLI6pSYlxnarrGuetJZeEviWxto9n
|
|
||||||
odFVZI6yx8JFTXX8ZTCr/1IjwDDVyhMPmrHI2Lsv9cqBpSpbVe32cUkKxhsGaYjz
|
|
||||||
GvesQKamOPhco2ATNxPm0yopFlPsGKMfVl0BK0J6BqFh1BvU/SYJmXfnFuUNO3vV
|
|
||||||
4u2Saa0q1iddxV0rXDwIqUfn+S6rwzK0G7y8bH2yvpB2VwiG3TFPnULep4wsefNq
|
|
||||||
Fj92kqFBjacGpQLEEslUY0CMgeZ2+NuBQSUTscP3wBRsottMR6YXJtINdvfHBx+e
|
|
||||||
EcN71z8D00w3mYqIQ7qb4Ml6HOqknunn58g43L9sACMUMTlEBXa9pUnScNYgWBAz
|
|
||||||
W2q2mH37cIydM2JRZPpA8B4yTHt5ugJmChwyNFM7941arjKrebH+6AzLkofGedOP
|
|
||||||
zg+vZQuPEXWs+3MBBnkWoyJW3Y0fbQdjsuQTtnd+7iyoxoBroQKCAQEA4dIiFlIS
|
|
||||||
MDfRhQQWSiDvaw9aneDEJ3uo63ZRH5tm/IynLgtjYgEm/ZxlBCQgqRKLYELBxhu8
|
|
||||||
SaF0uPK8pmpFJt0mIwSlsdeVhuE2obQeKUCczaqrKeaHS3PdWLjTlwph81BGRkHy
|
|
||||||
qfqtNylyyMxrdEbnR51EtsWgFq6anTUAui1Q09JMuMNZRMOzDs1F4gExgD22rc0V
|
|
||||||
c9YQ+jHJRxBGtNKMpMEqc8cvaxBidbItrN9SMTSWog7uYPBuEuaJ6K9vpgyJMOzJ
|
|
||||||
SYcQEFGqgIqIDCg+ABE4d/4YROMKZ1DV/bJCind9brUHSx6XALsF0nC5c1Q9TnUL
|
|
||||||
qI2khOwts4KYKwKCAQEA2xRC6Az97Vkdzu7BjLJ1FKmx4S2nEEgVS12ds82U+5Xf
|
|
||||||
BHKAJnjqlqmmpzzJG+d77IYktz0+mey1QCNkqlm2fhuKs8LZMnpZRf0l8VcoBsUP
|
|
||||||
/xKz7wfiE7RRFZtLJhPp4hhe43GzX5/JFMWMnC6UykwQbj4t1E/GNM/Suqwvg12M
|
|
||||||
wktAJ6nqLgfhjQSO4xWo+nPzcbX+fNtrPCZVrBhYXihhcwRRNImWUCGJ6J4LMdPY
|
|
||||||
Y9Z59qhOvE9cReH/Xw1av46omyiSyAqlgPyZ/kzA2IJSqYCjiQR/2+RD/g13jpcJ
|
|
||||||
jatXLVZ8MJSL5OTS40G/HHTNNpNHbKKh0GOyxBA3ewKCAQBAn8UXhCcmW2L/YPsL
|
|
||||||
/b7mcX9qPP+FmRLvR23R0MQ5M/tH5wRq8I969n3GIJykJeVzB8eybQ+GNslTgEvS
|
|
||||||
iAkAJTubu+G7MkndTqg2wHf9MDtvdA8Fr646Po8yq7oJuHPtkKR7yLWsRUu6xIbP
|
|
||||||
xgheP0hCq1QVxhqZQyCGKrvpi7xc0gsYuPbcAfFFJCOCmPrUi1SzCkTAYJt9LjA+
|
|
||||||
wP6rErIjGBCRD4iXaBn1OqdtmH9KC5WsDP/VCBlIGWeQCly2NVIxiSHVg+xp7yUP
|
|
||||||
IhXq/L05gbQaSsIhPKQmivCiaJg4The8TdwneDqYf+0bmxzHT203/bD3bImPbJNr
|
|
||||||
ksz/AoIBAEwu4Y1cZzkQUmNRd5D7xecnk6ngfEYXKwCIT3zlMrfCSEl9n77BMaKu
|
|
||||||
4Dsr0iuX9eosQ7xM2eYhAG6LYEg05lc4MKWOToVVMpI6E+W3Dz47bPKgiF3I+f8s
|
|
||||||
Jz5CQIG/TwfGvciOE3hfUkec4ua09BzdEqGjkcBQ9XYMBxXPJr6h2379OBQS7FKR
|
|
||||||
fwfQ2/dv4tElXTTfut2kV8gU9Jnh5Wjo1epvR+XjKpg28YQo4W+0YX1magcyRB8L
|
|
||||||
4eSTUIC3XiVa8Jr0IwbZXPBb5xkdi7o+p4w2JahSHjxTRqmj+T1mnHXdbXVgq9Mg
|
|
||||||
9Pzl7cgFZvX4UBx4XtASRf73jITNtt0CggEADH9K+O7FrIOSQly0sMvsRCMtejp3
|
|
||||||
o+MDh1Q+vEg2kEgNXjS4ZFVljUpM2kg1OdUz7feS4dLXUJiIQ8ZWtZPedcq7wjHd
|
|
||||||
02he5+s06l0jPifN3tX1ADfXGpXg5R2fbkrIzakkPP5/RO/aDxIUo7qhklNsVTXO
|
|
||||||
VlGGfWLdk0ekA4upKm02Q1+YOlbIcAicEYYY8K7IffUwnohzKwL9yfuGi1VKTXpE
|
|
||||||
4fzdegsHI03FSqR7V+LvtBpIupQ7RO4kuBmCEyI4E9FVknchg4te4gO3qwd9y0rJ
|
|
||||||
Gu7HNIOrwOHzviI7J6Nd/l9MmeKqklHSgJvko/f5TmiXuQQ8xDZf84rcjQ==
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
||||||
`)
|
|
||||||
|
|
||||||
publicKeyBytes := []byte(`-----BEGIN RSA PUBLIC KEY-----
|
|
||||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwUCimKCidbF3UxEHPy8K
|
|
||||||
+hvhklRB9JYhj5sJy0if4lTVibkK1MrYCykOnmC40pPU9GLY1b8HxAg9tvyRn0YH
|
|
||||||
UxOra6vVQaVcOVJhTM8D18d+lSr3Lp1yiX+UGT4nzWI9+R1CCbwXrqeQVoZs6QZK
|
|
||||||
ynEXMkFI9/wNMOwPOvQFOSTuoEoCO+zyTyUWEkNbUq825ELUQdIsjgmlWUOONudx
|
|
||||||
sAr7ESRXW9QTHVh6uWmr3VRKZHby1JdU3I/wjdlGg5M2dDuXy5nQO9w/nYLjJXiw
|
|
||||||
+zzOetZ/+t7/VOkOpNTeJQhwTM1WF7Y2VLetbi9FHgyzHatrduh07+XEiTbgDf3G
|
|
||||||
Ix2bp2p6oh0G3N2zpiLcK/aZj8rouWWydfFfsU3MZ4FfJDP8I6b9awxjmKYqIr6h
|
|
||||||
iPQCJaLBED8mwK+I5evIbnKv6E6uK+BApWA/R7ElragoFYbqQ1VpvntVMtJt9Dy5
|
|
||||||
ZrI+IQARdXD3bb34oh0IPBhClnvvMUc1cWxDoXEX6oJ4I+LzxE87Zkwnan9qOwen
|
|
||||||
golMVKFwPx1o37qrbmrXID21kKt7FL6xN4HxHLkItr1fKzdyWDFRHgASTAWfx5BI
|
|
||||||
wvPuUW0vZHkvO80VyV2L63whVhPnPASmFkbviomrBttYfpr2aGQqF/qR1Nlxe834
|
|
||||||
MFxk1pS9LMa/WnzvFr0gWakCAwEAAQ==
|
|
||||||
-----END RSA PUBLIC KEY-----
|
|
||||||
`)
|
|
||||||
origStr := "Value1234"
|
|
||||||
|
|
||||||
pubKey, err := DecodeToPublicKey(publicKeyBytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
privKey, err := DecodeToPrivateKey(privKeyBytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
encData, err := Encrypt([]byte(origStr), pubKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
encDataStr := base64.StdEncoding.EncodeToString(encData)
|
|
||||||
log.Println("Encoded text:", encDataStr)
|
|
||||||
dec, _ := base64.StdEncoding.DecodeString(encDataStr)
|
|
||||||
data, err := Decrypt(dec, privKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if string(data) != origStr {
|
|
||||||
t.Error("original string and decrypted string don't match")
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
func K8sSecretToYaml(k8sSecret v1.Secret) ([]byte, error) {
|
|
||||||
return K8sToYaml(k8sSecret)
|
|
||||||
}
|
|
||||||
|
|
||||||
func K8sSecretFromYaml(k8sSecretBytes []byte) (v1.Secret, error) {
|
|
||||||
k8sSecret := v1.Secret{}
|
|
||||||
if err := yaml.UnmarshalStrict(k8sSecretBytes, &k8sSecret); err != nil {
|
|
||||||
return k8sSecret, err
|
|
||||||
}
|
|
||||||
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,99 +0,0 @@
|
|||||||
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 {
|
|
||||||
fmt.Println("cannot create file ", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tempYaml.WriteString(manifests)
|
|
||||||
|
|
||||||
arguments := make([]string, 0)
|
|
||||||
arguments = append(arguments, oprName)
|
|
||||||
arguments = append(arguments, "-f")
|
|
||||||
arguments = append(arguments, tempYaml.Name())
|
|
||||||
|
|
||||||
if oprName == "apply" {
|
|
||||||
arguments = append(arguments, "--validate=false")
|
|
||||||
}
|
|
||||||
if namespace != "" {
|
|
||||||
arguments = append(arguments, "-n")
|
|
||||||
arguments = append(arguments, namespace)
|
|
||||||
}
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
if oprName == "apply" {
|
|
||||||
cmd = exec.Command("kubectl", arguments...)
|
|
||||||
} else {
|
|
||||||
cmd = exec.Command("kubectl", arguments...)
|
|
||||||
}
|
|
||||||
|
|
||||||
sterrBuffer := &bytes.Buffer{}
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = sterrBuffer
|
|
||||||
err = cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("kubectl %v failed with: %v, %v, temp k8s yaml file:%v\n", oprName, err, sterrBuffer.String(), tempYaml.Name())
|
|
||||||
}
|
|
||||||
os.Remove(tempYaml.Name())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetKubectlNamespace(t *testing.T) {
|
|
||||||
t.Skip()
|
|
||||||
ns := GetKubectlNamespace()
|
|
||||||
SetKubectlNamespace("tada")
|
|
||||||
got := GetKubectlNamespace()
|
|
||||||
if got != "tada" {
|
|
||||||
t.Log(got)
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
SetKubectlNamespace(ns)
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
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 {
|
|
||||||
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 {
|
|
||||||
kapi_config.KApiCr `json:",inline" yaml:",inline"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContextSpec is exported
|
|
||||||
type ContextSpec struct {
|
|
||||||
Contexts []Context `json:"contexts" yaml:"contexts"`
|
|
||||||
CurrentContext string `json:"currentContext" yaml:"currentContext"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Context is exported
|
|
||||||
type Context struct {
|
|
||||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
|
||||||
CrFile string `json:"crFile,omitempty" yaml:"crFile,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metadata is exported
|
|
||||||
type Metadata struct {
|
|
||||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
|
||||||
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceKeyValue holds the combination of service, key and value
|
|
||||||
type ServiceKeyValue struct {
|
|
||||||
SvcName string
|
|
||||||
Key string
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
111
pkg/api/utils.go
111
pkg/api/utils.go
@@ -1,111 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkExists(filename string) os.FileInfo {
|
|
||||||
info, err := os.Stat(filename)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
LogDebugMessage("File exists")
|
|
||||||
return info
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileExists checks if a file exists
|
|
||||||
func FileExists(filename string) bool {
|
|
||||||
if fe := checkExists(filename); fe != nil && !fe.IsDir() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// DirExists checks if a directory exists
|
|
||||||
func DirExists(dirname string) bool {
|
|
||||||
if fe := checkExists(dirname); fe != nil && fe.IsDir() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// LogDebugMessage logs a debug message
|
|
||||||
func LogDebugMessage(strMessage string, args ...interface{}) {
|
|
||||||
if os.Getenv("QLIKSENSE_DEBUG") == "true" {
|
|
||||||
log.Printf(strMessage, args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadKeys reads key file from disk
|
|
||||||
func ReadKeys(keyFile string) ([]byte, error) {
|
|
||||||
keybyteArray, err := ioutil.ReadFile(keyFile)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("There was an error reading from file: %s, %v", keyFile, err)
|
|
||||||
log.Println(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return keybyteArray, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessConfigArgs processes args and returns an service, key, value slice
|
|
||||||
func ProcessConfigArgs(args []string) ([]*ServiceKeyValue, error) {
|
|
||||||
// prepare received args
|
|
||||||
// split args[0] into key and value
|
|
||||||
if len(args) == 0 {
|
|
||||||
err := fmt.Errorf("No args were provided. Please provide args to configure the current context")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resultSvcKV := make([]*ServiceKeyValue, len(args))
|
|
||||||
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")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resultSvcKV[i] = &ServiceKeyValue{
|
|
||||||
SvcName: result[1],
|
|
||||||
Key: result[2],
|
|
||||||
Value: strings.ReplaceAll(result[3], `"`, ""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resultSvcKV, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExecuteTaskWithBlinkingStdoutFeedback(task func() (interface{}, error), feedback string) (result interface{}, err error) {
|
|
||||||
taskDone := make(chan bool)
|
|
||||||
go func() {
|
|
||||||
result, err = task()
|
|
||||||
taskDone <- true
|
|
||||||
}()
|
|
||||||
progressOnTicker := time.NewTicker(500 * time.Millisecond)
|
|
||||||
progressOffTicker := time.NewTicker(1000 * time.Millisecond)
|
|
||||||
printProgress := func(on bool) {
|
|
||||||
if on {
|
|
||||||
fmt.Printf("%s\r", feedback)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("%s\r", strings.Repeat(" ", len(feedback)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-taskDone:
|
|
||||||
progressOnTicker.Stop()
|
|
||||||
progressOffTicker.Stop()
|
|
||||||
printProgress(false)
|
|
||||||
return result, err
|
|
||||||
case <-progressOnTicker.C:
|
|
||||||
printProgress(true)
|
|
||||||
case <-progressOffTicker.C:
|
|
||||||
printProgress(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,241 +0,0 @@
|
|||||||
package qliksense
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
kapis_git "github.com/qlik-oss/k-apis/pkg/git"
|
|
||||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type patch struct {
|
|
||||||
Target struct {
|
|
||||||
Kind string `yaml:"kind"`
|
|
||||||
LabelSelector string `yaml:"labelSelector"`
|
|
||||||
} `yaml:"target"`
|
|
||||||
Patch string `yaml:"patch"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type selectivePatch struct {
|
|
||||||
APIVersion string `yaml:"apiVersion"`
|
|
||||||
Metadata struct {
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
} `yaml:"metadata"`
|
|
||||||
Enabled bool `yaml:"enabled"`
|
|
||||||
Patches []patch `yaml:"patches"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type helmChart struct {
|
|
||||||
APIVersion string `yaml:"apiVersion"`
|
|
||||||
Kind string `yaml:"kind"`
|
|
||||||
Metadata struct {
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
} `yaml:"metadata"`
|
|
||||||
ReleaseNamespace string `yaml:"releaseNamespace"`
|
|
||||||
ChartHome string `yaml:"chartHome"`
|
|
||||||
ChartRepo string `yaml:"chartRepo"`
|
|
||||||
ChartName string `yaml:"chartName"`
|
|
||||||
ChartVersion string `yaml:"chartVersion"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type VersionOutput struct {
|
|
||||||
QliksenseVersion string `yaml:"qlikSenseVersion"`
|
|
||||||
Images []string `yaml:"images"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type nullWriter struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nw *nullWriter) Write(p []byte) (n int, err error) {
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
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(defaultConfigRepoGitUrl, gitRef, profile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if isTemporary {
|
|
||||||
defer os.RemoveAll(configDirectory)
|
|
||||||
}
|
|
||||||
return q.AboutDir(configDirectory, profile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Qliksense) AboutDir(configDirectory, profile string) (*VersionOutput, error) {
|
|
||||||
if chartVersion, err := getChartVersion(filepath.Join(configDirectory, "transformers", "qseokversion.yaml"), "qliksense"); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if kuzManifest, err := executeKustomizeBuildWithStdoutProgress(filepath.Join(configDirectory, "manifests", profile)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if images, err := getImageList(kuzManifest); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return &VersionOutput{
|
|
||||||
QliksenseVersion: chartVersion,
|
|
||||||
Images: images,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Qliksense) getConfigDirectory(gitUrl, gitRef, profileEntered string) (dir string, isTemporary bool, profile string, err error) {
|
|
||||||
profile = profileEntered
|
|
||||||
if profile == "" {
|
|
||||||
profile = defaultProfile
|
|
||||||
}
|
|
||||||
|
|
||||||
if gitRef != "" {
|
|
||||||
if dir, err = downloadFromGitRepoToTmpDir(gitUrl, gitRef); err != nil {
|
|
||||||
return "", false, "", err
|
|
||||||
} else {
|
|
||||||
return dir, true, profile, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var exists bool
|
|
||||||
exists, dir, err = configExistsInCurrentDirectory(profile)
|
|
||||||
if err != nil {
|
|
||||||
return "", false, "", err
|
|
||||||
} else if exists {
|
|
||||||
return dir, false, profile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var profileFromCurrentContext string
|
|
||||||
exists, dir, profileFromCurrentContext, err = q.configExistsInCurrentContext()
|
|
||||||
if err != nil {
|
|
||||||
return "", false, "", err
|
|
||||||
} else if exists {
|
|
||||||
if profileEntered == "" {
|
|
||||||
profile = profileFromCurrentContext
|
|
||||||
}
|
|
||||||
return dir, false, profile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if dir, err = downloadFromGitRepoToTmpDir(gitUrl, "master"); err != nil {
|
|
||||||
return "", false, "", err
|
|
||||||
} else {
|
|
||||||
return dir, true, profile, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadFromGitRepoToTmpDir(gitUrl, gitRef string) (string, error) {
|
|
||||||
if tmpDir, err := ioutil.TempDir("", ""); err != nil {
|
|
||||||
return "", err
|
|
||||||
} else {
|
|
||||||
downloadPath := path.Join(tmpDir, "repo")
|
|
||||||
if err := downloadFromGitRepo(gitUrl, gitRef, downloadPath); err != nil {
|
|
||||||
_ = os.RemoveAll(tmpDir)
|
|
||||||
return "", err
|
|
||||||
} else {
|
|
||||||
return downloadPath, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadFromGitRepo(gitUrl, gitRef, destDir string) error {
|
|
||||||
if repo, err := kapis_git.CloneRepository(destDir, gitUrl, nil); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
return kapis_git.Checkout(repo, gitRef, "", nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func configExistsInCurrentDirectory(profile string) (exists bool, currentDirectory string, err error) {
|
|
||||||
currentDirectory, err = os.Getwd()
|
|
||||||
if err == nil {
|
|
||||||
info, err := os.Stat(path.Join(currentDirectory, "manifests", profile))
|
|
||||||
if err == nil && info.IsDir() {
|
|
||||||
exists = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return exists, currentDirectory, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Qliksense) configExistsInCurrentContext() (exists bool, directory string, profile string, err error) {
|
|
||||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
|
||||||
if currentCr, err := qConfig.GetCurrentCR(); err != nil {
|
|
||||||
return false, "", "", err
|
|
||||||
} else if currentCr.Spec.ManifestsRoot == "" {
|
|
||||||
return false, "", "", nil
|
|
||||||
} else {
|
|
||||||
return true, currentCr.Spec.GetManifestsRoot(), currentCr.Spec.Profile, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getImageList(yamlContent []byte) ([]string, error) {
|
|
||||||
decoder := yaml.NewDecoder(bytes.NewReader(yamlContent))
|
|
||||||
var resource map[string]interface{}
|
|
||||||
imageMap := make(map[string]bool)
|
|
||||||
for {
|
|
||||||
err := decoder.Decode(&resource)
|
|
||||||
if err != nil {
|
|
||||||
if err != io.EOF {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
traverseYamlDecodedMapRecursively(reflect.ValueOf(resource), []string{}, func(path []string, val interface{}) {
|
|
||||||
if len(path) >= 2 && path[len(path)-1] == "image" &&
|
|
||||||
(path[len(path)-2] == "containers" || path[len(path)-2] == "initContainers") {
|
|
||||||
if image, ok := val.(string); ok {
|
|
||||||
imageMap[image] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
var sortedImageList []string
|
|
||||||
for image, _ := range imageMap {
|
|
||||||
sortedImageList = append(sortedImageList, image)
|
|
||||||
}
|
|
||||||
sort.Strings(sortedImageList)
|
|
||||||
return sortedImageList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func traverseYamlDecodedMapRecursively(val reflect.Value, path []string, visitorFunc func(path []string, val interface{})) {
|
|
||||||
kind := val.Kind()
|
|
||||||
switch kind {
|
|
||||||
case reflect.Interface:
|
|
||||||
traverseYamlDecodedMapRecursively(val.Elem(), path, visitorFunc)
|
|
||||||
case reflect.Slice:
|
|
||||||
for i := 0; i < val.Len(); i++ {
|
|
||||||
traverseYamlDecodedMapRecursively(val.Index(i), path, visitorFunc)
|
|
||||||
}
|
|
||||||
case reflect.Map:
|
|
||||||
for _, key := range val.MapKeys() {
|
|
||||||
traverseYamlDecodedMapRecursively(val.MapIndex(key), append(path, key.Interface().(string)), visitorFunc)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if kind != reflect.Invalid {
|
|
||||||
visitorFunc(path, val.Interface())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getChartVersion(versionFile, chartName string) (string, error) {
|
|
||||||
var patchInst patch
|
|
||||||
var selPatch selectivePatch
|
|
||||||
var chart helmChart
|
|
||||||
|
|
||||||
if bytes, err := ioutil.ReadFile(versionFile); err != nil {
|
|
||||||
return "", err
|
|
||||||
} else if err = yaml.Unmarshal(bytes, &selPatch); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
for _, patchInst = range selPatch.Patches {
|
|
||||||
if err := yaml.Unmarshal([]byte(patchInst.Patch), &chart); err == nil {
|
|
||||||
if chart.ChartName == chartName {
|
|
||||||
return chart.ChartVersion, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
@@ -1,579 +0,0 @@
|
|||||||
package qliksense
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_About_getImageList(t *testing.T) {
|
|
||||||
var testCases = []struct {
|
|
||||||
name string
|
|
||||||
k8sYaml string
|
|
||||||
expectedImages []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "base",
|
|
||||||
k8sYaml: `
|
|
||||||
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: nginx-deployment-1
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: nginx
|
|
||||||
replicas: 2 # tells deployment to run 2 pods matching the template
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: nginx
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: nginx
|
|
||||||
image: nginx:1.7.9
|
|
||||||
ports:
|
|
||||||
- containerPort: 80
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
creationTimestamp: 2018-11-15T20:46:46Z
|
|
||||||
name: mysecret
|
|
||||||
namespace: default
|
|
||||||
resourceVersion: "7579"
|
|
||||||
uid: 91460ecb-e917-11e8-98f2-025000000001
|
|
||||||
type: Opaque
|
|
||||||
data:
|
|
||||||
username: YWRtaW5pc3RyYXRvcg==
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: nginx-deployment-2
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: nginx
|
|
||||||
replicas: 2 # tells deployment to run 2 pods matching the template
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: nginx
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: nginx
|
|
||||||
image: nginx:1.7.9
|
|
||||||
ports:
|
|
||||||
- containerPort: 80
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: nginx
|
|
||||||
labels:
|
|
||||||
app: nginx
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
- port: 80
|
|
||||||
name: web
|
|
||||||
clusterIP: None
|
|
||||||
selector:
|
|
||||||
app: nginx
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: StatefulSet
|
|
||||||
metadata:
|
|
||||||
name: web
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: nginx # has to match .spec.template.metadata.labels
|
|
||||||
serviceName: "nginx"
|
|
||||||
replicas: 3 # by default is 1
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: nginx # has to match .spec.selector.matchLabels
|
|
||||||
spec:
|
|
||||||
terminationGracePeriodSeconds: 10
|
|
||||||
containers:
|
|
||||||
- name: nginx
|
|
||||||
image: k8s.gcr.io/nginx-slim:0.8
|
|
||||||
ports:
|
|
||||||
- containerPort: 80
|
|
||||||
name: web
|
|
||||||
volumeMounts:
|
|
||||||
- name: www
|
|
||||||
mountPath: /usr/share/nginx/html
|
|
||||||
volumeClaimTemplates:
|
|
||||||
- metadata:
|
|
||||||
name: www
|
|
||||||
spec:
|
|
||||||
accessModes: [ "ReadWriteOnce" ]
|
|
||||||
storageClassName: "my-storage-class"
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 1Gi
|
|
||||||
---
|
|
||||||
apiVersion: batch/v1
|
|
||||||
kind: Job
|
|
||||||
metadata:
|
|
||||||
name: pi
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: pi
|
|
||||||
image: perl
|
|
||||||
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
|
|
||||||
restartPolicy: Never
|
|
||||||
backoffLimit: 4
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Pod
|
|
||||||
metadata:
|
|
||||||
name: init-demo
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: nginx
|
|
||||||
image: nginx
|
|
||||||
env:
|
|
||||||
- name: FOO
|
|
||||||
value: null
|
|
||||||
ports:
|
|
||||||
- containerPort: 80
|
|
||||||
volumeMounts:
|
|
||||||
- name: workdir
|
|
||||||
mountPath: /usr/share/nginx/html
|
|
||||||
# These containers are run during pod initialization
|
|
||||||
initContainers:
|
|
||||||
- name: install
|
|
||||||
image: busybox
|
|
||||||
command:
|
|
||||||
- wget
|
|
||||||
- "-O"
|
|
||||||
- "/work-dir/index.html"
|
|
||||||
- http://kubernetes.io
|
|
||||||
volumeMounts:
|
|
||||||
- name: workdir
|
|
||||||
mountPath: "/work-dir"
|
|
||||||
dnsPolicy: Default
|
|
||||||
volumes:
|
|
||||||
- name: workdir
|
|
||||||
emptyDir: {}
|
|
||||||
`,
|
|
||||||
expectedImages: []string{"busybox", "k8s.gcr.io/nginx-slim:0.8", "nginx", "nginx:1.7.9", "perl"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "works for custom resources and CronJobs",
|
|
||||||
k8sYaml: `
|
|
||||||
apiVersion: "qixmanager.qlik.com/v1"
|
|
||||||
kind: "Engine"
|
|
||||||
metadata:
|
|
||||||
name: release-name-engine-reload
|
|
||||||
spec:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
qix-engine: qix-engine
|
|
||||||
annotations:
|
|
||||||
prometheus.io/scrape: "true"
|
|
||||||
workloadType: "reload"
|
|
||||||
podSpec:
|
|
||||||
imagePullSecrets:
|
|
||||||
- name: artifactory-docker-secret
|
|
||||||
dnsConfig:
|
|
||||||
options:
|
|
||||||
- name: timeout
|
|
||||||
value: "1"
|
|
||||||
- name: single-request-reopen
|
|
||||||
containers:
|
|
||||||
- name: engine-reload
|
|
||||||
image: another-engine
|
|
||||||
---
|
|
||||||
apiVersion: batch/v1beta1
|
|
||||||
kind: CronJob
|
|
||||||
metadata:
|
|
||||||
name: hello
|
|
||||||
spec:
|
|
||||||
schedule: "*/1 * * * *"
|
|
||||||
jobTemplate:
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: hello
|
|
||||||
image: busybox2
|
|
||||||
args:
|
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- date; echo Hello from the Kubernetes cluster
|
|
||||||
restartPolicy: OnFailure
|
|
||||||
`,
|
|
||||||
expectedImages: []string{"another-engine", "busybox2"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
|
||||||
images, err := getImageList([]byte(testCase.k8sYaml))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v\n", err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(images, testCase.expectedImages) {
|
|
||||||
t.Fatalf("expected %v, but got: %v\n", testCase.expectedImages, images)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_About_getConfigDirectory(t *testing.T) {
|
|
||||||
verifyAsdBranch := func(configDir string) (ok bool, reason string) {
|
|
||||||
tmpDir := os.TempDir()
|
|
||||||
|
|
||||||
if (path.Clean(path.Dir(path.Dir(configDir))) != path.Clean(tmpDir)) || (path.Base(configDir) != "repo") {
|
|
||||||
return false, fmt.Sprintf("expected config directory path: %v to be under: %v and terminate with repo", configDir, tmpDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
if info, err := os.Stat(path.Join(configDir, "asdczxc")); err != nil || !info.Mode().IsRegular() {
|
|
||||||
return false, fmt.Sprintf(`expected to find file: "asdczxc" under directory: %v`, configDir)
|
|
||||||
}
|
|
||||||
return true, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
verifyMasterBranch := func(configDir string) (ok bool, reason string) {
|
|
||||||
tmpDir := os.TempDir()
|
|
||||||
|
|
||||||
if (path.Clean(path.Dir(path.Dir(configDir))) != path.Clean(tmpDir)) || (path.Base(configDir) != "repo") {
|
|
||||||
return false, fmt.Sprintf("expected config directory path: %v to be under: %v and terminate with repo", configDir, tmpDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(path.Join(configDir, "asdczxc")); err == nil || !os.IsNotExist(err) {
|
|
||||||
return false, fmt.Sprintf(`expected to NOT find file: "asdczxc"" under directory: %v`, configDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
if info, err := os.Stat(path.Join(configDir, "sad")); err != nil || !info.Mode().IsRegular() {
|
|
||||||
return false, fmt.Sprintf(`expected to find file: "sad"" under directory: %v`, configDir)
|
|
||||||
}
|
|
||||||
return true, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var testCases = []struct {
|
|
||||||
name string
|
|
||||||
setup func(t *testing.T) (q *Qliksense, gitUrl, gitRef, profileEntered string)
|
|
||||||
verify func(q *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error)
|
|
||||||
cleanup func(q *Qliksense, configDir string) error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "config in current directory and default profile",
|
|
||||||
setup: func(t *testing.T) (q *Qliksense, gitUrl, gitRef, profileEntered string) {
|
|
||||||
currentDirectory, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error obtaining current directory: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultProfilePath := path.Join(currentDirectory, "manifests", "docker-desktop")
|
|
||||||
err = os.MkdirAll(defaultProfilePath, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error making path: %v, err: %v\n", defaultProfilePath, err)
|
|
||||||
}
|
|
||||||
return &Qliksense{}, "no-clone-for-you", "", ""
|
|
||||||
},
|
|
||||||
verify: func(_ *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
|
|
||||||
currentDirectory, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return false, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if configDir != currentDirectory {
|
|
||||||
return false, fmt.Sprintf("expected config directory: %v to equal current directory: %v", configDir, currentDirectory), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if isTemporary {
|
|
||||||
return false, "expected isTemporary to be false", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if profile != "docker-desktop" {
|
|
||||||
return false, fmt.Sprintf("expected profile to be: docker-desktop, but it was: %v", profile), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, "", nil
|
|
||||||
},
|
|
||||||
cleanup: func(_ *Qliksense, configDir string) error {
|
|
||||||
if currentDirectory, err := os.Getwd(); err != nil {
|
|
||||||
return err
|
|
||||||
} else if err := os.RemoveAll(path.Join(currentDirectory, "manifests")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "config in current directory and profile specified",
|
|
||||||
setup: func(t *testing.T) (q *Qliksense, gitUrl, gitRef, profileEntered string) {
|
|
||||||
currentDirectory, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error obtaining current directory: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
profileEntered = "foo"
|
|
||||||
defaultProfilePath := path.Join(currentDirectory, "manifests", profileEntered)
|
|
||||||
err = os.MkdirAll(defaultProfilePath, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error making path: %v, err: %v\n", defaultProfilePath, err)
|
|
||||||
}
|
|
||||||
return &Qliksense{}, "no-clone-for-you", "", profileEntered
|
|
||||||
},
|
|
||||||
verify: func(_ *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
|
|
||||||
currentDirectory, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return false, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if configDir != currentDirectory {
|
|
||||||
return false, fmt.Sprintf("expected config directory: %v to equal current directory: %v", configDir, currentDirectory), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if isTemporary {
|
|
||||||
return false, "expected isTemporary to be false", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if profile != "foo" {
|
|
||||||
return false, fmt.Sprintf("expected profile to be: foo, but it was: %v", profile), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, "", nil
|
|
||||||
},
|
|
||||||
cleanup: func(_ *Qliksense, configDir string) error {
|
|
||||||
if currentDirectory, err := os.Getwd(); err != nil {
|
|
||||||
return err
|
|
||||||
} else if err := os.RemoveAll(path.Join(currentDirectory, "manifests")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "config downloaded from git based on specific git ref and default profile used",
|
|
||||||
setup: func(t *testing.T) (q *Qliksense, gitUrl, gitRef, profileEntered string) {
|
|
||||||
return &Qliksense{}, "https://github.com/test/HelloWorld", "asd", ""
|
|
||||||
},
|
|
||||||
verify: func(_ *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
|
|
||||||
ok, reason = verifyAsdBranch(configDir)
|
|
||||||
if !ok {
|
|
||||||
return ok, reason, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isTemporary {
|
|
||||||
return false, "expected isTemporary to be true", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if profile != "docker-desktop" {
|
|
||||||
return false, fmt.Sprintf("expected profile to be: docker-desktop, but it was: %v", profile), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, "", nil
|
|
||||||
},
|
|
||||||
cleanup: func(_ *Qliksense, configDir string) error {
|
|
||||||
tmpDir := os.TempDir()
|
|
||||||
|
|
||||||
if path.Clean(path.Dir(path.Dir(configDir))) == path.Clean(tmpDir) && path.Base(configDir) == "repo" {
|
|
||||||
tmpTmpDir := path.Dir(configDir)
|
|
||||||
if err := os.RemoveAll(tmpTmpDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "config downloaded from git based on specific git ref and profile specified",
|
|
||||||
setup: func(t *testing.T) (q *Qliksense, gitUrl, gitRef, profileEntered string) {
|
|
||||||
return &Qliksense{}, "https://github.com/test/HelloWorld", "asd", "foo"
|
|
||||||
},
|
|
||||||
verify: func(_ *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
|
|
||||||
ok, reason = verifyAsdBranch(configDir)
|
|
||||||
if !ok {
|
|
||||||
return ok, reason, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isTemporary {
|
|
||||||
return false, "expected isTemporary to be true", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if profile != "foo" {
|
|
||||||
return false, fmt.Sprintf("expected profile to be: foo, but it was: %v", profile), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, "", nil
|
|
||||||
},
|
|
||||||
cleanup: func(_ *Qliksense, configDir string) error {
|
|
||||||
tmpDir := os.TempDir()
|
|
||||||
|
|
||||||
if path.Clean(path.Dir(path.Dir(configDir))) == path.Clean(tmpDir) && path.Base(configDir) == "repo" {
|
|
||||||
tmpTmpDir := path.Dir(configDir)
|
|
||||||
if err := os.RemoveAll(tmpTmpDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "config downloaded from git from master branch and default profile used",
|
|
||||||
setup: func(t *testing.T) (q *Qliksense, gitUrl, gitRef, profileEntered string) {
|
|
||||||
if qliksenseHome, err := ioutil.TempDir("", ""); err != nil {
|
|
||||||
t.Fatalf("error creating tmp qliksenseHome directory: %v\n", err)
|
|
||||||
return nil, "", "", ""
|
|
||||||
} else {
|
|
||||||
q := &Qliksense{QliksenseHome: qliksenseHome}
|
|
||||||
if err := q.SetUpQliksenseDefaultContext(); err != nil {
|
|
||||||
t.Fatalf("error setting up default context in the tmp dir: %v\n", err)
|
|
||||||
return nil, "", "", ""
|
|
||||||
} else {
|
|
||||||
return q, "https://github.com/test/HelloWorld", "", ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
verify: func(_ *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
|
|
||||||
ok, reason = verifyMasterBranch(configDir)
|
|
||||||
if !ok {
|
|
||||||
return ok, reason, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isTemporary {
|
|
||||||
return false, "expected isTemporary to be true", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if profile != "docker-desktop" {
|
|
||||||
return false, fmt.Sprintf("expected profile to be: docker-desktop, but it was: %v", profile), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, "", nil
|
|
||||||
},
|
|
||||||
cleanup: func(q *Qliksense, configDir string) error {
|
|
||||||
tmpDir := os.TempDir()
|
|
||||||
if path.Clean(path.Dir(path.Dir(configDir))) == path.Clean(tmpDir) && path.Base(configDir) == "repo" {
|
|
||||||
tmpTmpDir := path.Dir(configDir)
|
|
||||||
if err := os.RemoveAll(tmpTmpDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := os.RemoveAll(q.QliksenseHome); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "config downloaded from git from master branch and profile specified",
|
|
||||||
setup: func(t *testing.T) (q *Qliksense, gitUrl, gitRef, profileEntered string) {
|
|
||||||
if qliksenseHome, err := ioutil.TempDir("", ""); err != nil {
|
|
||||||
t.Fatalf("error creating tmp qliksenseHome directory: %v\n", err)
|
|
||||||
return nil, "", "", ""
|
|
||||||
} else {
|
|
||||||
q := &Qliksense{QliksenseHome: qliksenseHome}
|
|
||||||
if err := q.SetUpQliksenseDefaultContext(); err != nil {
|
|
||||||
t.Fatalf("error setting up default context in the tmp dir: %v\n", err)
|
|
||||||
return nil, "", "", ""
|
|
||||||
} else {
|
|
||||||
return q, "https://github.com/test/HelloWorld", "", "foo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
verify: func(_ *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
|
|
||||||
ok, reason = verifyMasterBranch(configDir)
|
|
||||||
if !ok {
|
|
||||||
return ok, reason, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isTemporary {
|
|
||||||
return false, "expected isTemporary to be true", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if profile != "foo" {
|
|
||||||
return false, fmt.Sprintf("expected profile to be: foo, but it was: %v", profile), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, "", nil
|
|
||||||
},
|
|
||||||
cleanup: func(q *Qliksense, configDir string) error {
|
|
||||||
tmpDir := os.TempDir()
|
|
||||||
if path.Clean(path.Dir(path.Dir(configDir))) == path.Clean(tmpDir) && path.Base(configDir) == "repo" {
|
|
||||||
tmpTmpDir := path.Dir(configDir)
|
|
||||||
if err := os.RemoveAll(tmpTmpDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := os.RemoveAll(q.QliksenseHome); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "config loaded from current context",
|
|
||||||
setup: func(t *testing.T) (q *Qliksense, gitUrl, gitRef, profileEntered string) {
|
|
||||||
if qliksenseHome, err := ioutil.TempDir("", ""); err != nil {
|
|
||||||
t.Fatalf("error creating tmp qliksenseHome directory: %v\n", err)
|
|
||||||
return nil, "", "", ""
|
|
||||||
} else {
|
|
||||||
q := &Qliksense{QliksenseHome: qliksenseHome}
|
|
||||||
if err := q.SetUpQliksenseDefaultContext(); err != nil {
|
|
||||||
t.Fatalf("error setting up default context in the tmp dir: %v\n", err)
|
|
||||||
return nil, "", "", ""
|
|
||||||
} else if err := q.FetchQK8s("master"); err != nil {
|
|
||||||
t.Fatalf("error fetching master config to the tmp dir: %v\n", err)
|
|
||||||
return nil, "", "", ""
|
|
||||||
} else {
|
|
||||||
return q, "no-git-clone-for-you", "", ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
verify: func(q *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
|
|
||||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
|
||||||
expectedConfigDir := qConfig.BuildRepoPath("master")
|
|
||||||
|
|
||||||
if configDir != expectedConfigDir {
|
|
||||||
return false, fmt.Sprintf("expected configDir to be %v", expectedConfigDir), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if isTemporary {
|
|
||||||
return false, "expected isTemporary to be false", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if profile != "docker-desktop" {
|
|
||||||
return false, fmt.Sprintf("expected profile to be: docker-desktop, but it was: %v", profile), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, "", nil
|
|
||||||
},
|
|
||||||
cleanup: func(q *Qliksense, configDir string) error {
|
|
||||||
if err := os.RemoveAll(q.QliksenseHome); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
|
||||||
q, gitUrl, gitRef, profileEntered := testCase.setup(t)
|
|
||||||
configDirectory, isTemporary, profile, err := q.getConfigDirectory(gitUrl, gitRef, profileEntered)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok, reason, err := testCase.verify(q, configDirectory, isTemporary, profile); err != nil {
|
|
||||||
t.Fatalf("unexpected verification error: %v\n", err)
|
|
||||||
} else if !ok {
|
|
||||||
t.Fatalf("verification failed: %v\n", reason)
|
|
||||||
} else if err := testCase.cleanup(q, configDirectory); err != nil {
|
|
||||||
t.Fatalf("unexpected cleanup error: %v\n", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
package qliksense
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/mitchellh/go-homedir"
|
|
||||||
|
|
||||||
"github.com/qlik-oss/k-apis/pkg/cr"
|
|
||||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
|
||||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
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 {
|
|
||||||
|
|
||||||
//get the current context cr
|
|
||||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
|
||||||
qcr, err := qConfig.GetCurrentCR()
|
|
||||||
if err != nil {
|
|
||||||
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
|
|
||||||
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
return q.applyCR(dcr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 := q.configEjson(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
userHomeDir, err := homedir.Dir()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf(`error fetching user's home directory: %v\n`, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println("Manifests root: " + qcr.Spec.GetManifestsRoot())
|
|
||||||
qcr.SetNamespace(qapi.GetKubectlNamespace())
|
|
||||||
// generate patches
|
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("cannot generate manifests for "+profilePath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = qapi.KubectlApply(string(mByte), qcr.GetNamespace()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Qliksense) ConfigViewCR() error {
|
|
||||||
//get the current context cr
|
|
||||||
r, err := q.getCurrentCRString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println(r)
|
|
||||||
oth, err := q.getCurrentCrDependentResourceAsString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println(r + "\n" + oth)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Qliksense) getCurrentCRString() (string, error) {
|
|
||||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
|
||||||
return q.getCRString(qConfig.Spec.CurrentContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Qliksense) getCRString(contextName string) (string, error) {
|
|
||||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
|
||||||
qcr, err := qConfig.GetCR(contextName)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("cannot get the context cr", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
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
|
|
||||||
|
|
||||||
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.GetName(), QliksenseSecretsDir, svcName+".yaml")
|
|
||||||
|
|
||||||
if api.FileExists(secretFilePath) {
|
|
||||||
secretFile, err := ioutil.ReadFile(secretFilePath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
crString.WriteString("\n---\n")
|
|
||||||
crString.Write(secretFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
crString.WriteString("\n---\n")
|
|
||||||
return crString.String(), nil
|
|
||||||
}
|
|
||||||
@@ -1,577 +0,0 @@
|
|||||||
package qliksense
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rsa"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/qlik-oss/k-apis/pkg/config"
|
|
||||||
|
|
||||||
b64 "encoding/base64"
|
|
||||||
ansi "github.com/mattn/go-colorable"
|
|
||||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
|
||||||
"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
|
|
||||||
QliksenseConfigFile = "config.yaml"
|
|
||||||
QliksenseContextsDir = "contexts"
|
|
||||||
DefaultQliksenseContext = "qlik-default"
|
|
||||||
MaxContextNameLength = 17
|
|
||||||
QliksenseSecretsDir = "secrets"
|
|
||||||
|
|
||||||
imageRegistryConfigKey = "imageRegistry"
|
|
||||||
pullSecretName = "artifactory-docker-secret"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SetSecrets - set-secrets <key>=<value> commands
|
|
||||||
func (q *Qliksense) SetSecrets(args []string, isSecretSet bool) error {
|
|
||||||
qConfig := api.NewQConfig(q.QliksenseHome)
|
|
||||||
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metadata name in qliksense CR is the name of the current context
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// write modified content into context-yaml
|
|
||||||
api.WriteToFile(&qliksenseCR, qliksenseContextsFile)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
if e2 != nil {
|
|
||||||
return e2
|
|
||||||
}
|
|
||||||
base64EncodedSecret := b64.StdEncoding.EncodeToString(cipherText)
|
|
||||||
secretName := ""
|
|
||||||
if isSecretSet {
|
|
||||||
secretFolder := qliksenseCR.GetK8sSecretsFolder(q.QliksenseHome)
|
|
||||||
secretFileName := filepath.Join(secretFolder, ra.SvcName+".yaml")
|
|
||||||
|
|
||||||
secretName = fmt.Sprintf("%s-%s-%s", qliksenseCR.GetName(), ra.SvcName, "senseinstaller")
|
|
||||||
api.LogDebugMessage("Constructed secret name: %s", secretName)
|
|
||||||
|
|
||||||
k8sSecret := v1.Secret{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
APIVersion: "v1",
|
|
||||||
Kind: "Secret",
|
|
||||||
},
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: secretName,
|
|
||||||
},
|
|
||||||
Type: v1.SecretTypeOpaque,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !api.DirExists(secretFolder) {
|
|
||||||
if err := os.MkdirAll(secretFolder, os.ModePerm); err != nil {
|
|
||||||
err = fmt.Errorf("Not able to create %s dir: %v", secretFolder, err)
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if read from file errors out, we can ignore it here
|
|
||||||
_ = api.ReadFromFile(&k8sSecret, secretFileName)
|
|
||||||
if k8sSecret.Data == nil {
|
|
||||||
k8sSecret.Data = map[string][]byte{}
|
|
||||||
}
|
|
||||||
k8sSecret.Data[ra.Key] = []byte(base64EncodedSecret)
|
|
||||||
|
|
||||||
// Write secret to file
|
|
||||||
k8sSecretBytes, err := api.K8sSecretToYaml(k8sSecret)
|
|
||||||
if err != nil {
|
|
||||||
api.LogDebugMessage("Error while converting K8s secret to yaml")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = ioutil.WriteFile(secretFileName, k8sSecretBytes, os.ModePerm); err != nil {
|
|
||||||
api.LogDebugMessage("Error while writing K8s secret to file")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
api.LogDebugMessage("Created a Kubernetes secret")
|
|
||||||
|
|
||||||
// Prepare args to update CR in the next step
|
|
||||||
base64EncodedSecret = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// write into CR the keyref of the secret
|
|
||||||
qliksenseCR.Spec.AddToSecrets(ra.SvcName, ra.Key, base64EncodedSecret, secretName)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetConfigs - set-configs <key>=<value> commands
|
|
||||||
func (q *Qliksense) SetConfigs(args []string) error {
|
|
||||||
// retieve current context from config.yaml
|
|
||||||
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resultArgs, err := api.ProcessConfigArgs(args)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, ra := range resultArgs {
|
|
||||||
qliksenseCR.Spec.AddToConfigs(ra.SvcName, ra.Key, ra.Value)
|
|
||||||
}
|
|
||||||
// write modified content into context.yaml
|
|
||||||
api.WriteToFile(&qliksenseCR, qliksenseContextsFile)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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 nil, "", err
|
|
||||||
}
|
|
||||||
currentContext := qliksenseConfig.Spec.CurrentContext
|
|
||||||
api.LogDebugMessage("Current-context from config.yaml: %s", currentContext)
|
|
||||||
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 nil, "", err
|
|
||||||
}
|
|
||||||
// read the context.yaml file
|
|
||||||
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 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 nil, "", err
|
|
||||||
}
|
|
||||||
if err := api.ReadFromFile(qliksenseCR, qliksenseContextsFile); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
api.LogDebugMessage("Read context file: %s, Read QliksenseCR: %v", qliksenseContextsFile, qliksenseCR)
|
|
||||||
|
|
||||||
return qliksenseCR, qliksenseContextsFile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// modify appropriate fields
|
|
||||||
if len(args) == 0 {
|
|
||||||
err := fmt.Errorf("No args were provided. Please provide args to configure the current context")
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, arg := range args {
|
|
||||||
argsString := strings.Split(arg, "=")
|
|
||||||
switch argsString[0] {
|
|
||||||
case "profile":
|
|
||||||
qliksenseCR.Spec.Profile = argsString[1]
|
|
||||||
api.LogDebugMessage("Current profile after modification: %s ", qliksenseCR.Spec.Profile)
|
|
||||||
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":
|
|
||||||
qliksenseCR.Spec.StorageClassName = argsString[1]
|
|
||||||
api.LogDebugMessage("Current StorageClassName after modification: %s ", qliksenseCR.Spec.StorageClassName)
|
|
||||||
case "manifestsRoot":
|
|
||||||
qliksenseCR.Spec.ManifestsRoot = argsString[1]
|
|
||||||
case "rotateKeys":
|
|
||||||
rotateKeys, err := validateInput(argsString[1])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
qliksenseCR.Spec.RotateKeys = rotateKeys
|
|
||||||
api.LogDebugMessage("Current rotateKeys after modification: %s ", qliksenseCR.Spec.RotateKeys)
|
|
||||||
case "gitops.enabled":
|
|
||||||
if qliksenseCR.Spec.GitOps == nil {
|
|
||||||
qliksenseCR.Spec.GitOps = &config.GitOps{}
|
|
||||||
}
|
|
||||||
if strings.EqualFold(argsString[1], "false") {
|
|
||||||
qliksenseCR.Spec.GitOps.Enabled = false
|
|
||||||
} else if strings.EqualFold(argsString[1], "true") {
|
|
||||||
qliksenseCR.Spec.GitOps.Enabled = true
|
|
||||||
} else {
|
|
||||||
err := fmt.Errorf("Please use a boolean value")
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
api.LogDebugMessage("Current gitOps enabled status : %s ", qliksenseCR.Spec.GitOps.Enabled)
|
|
||||||
case "gitops.schedule":
|
|
||||||
if qliksenseCR.Spec.GitOps == nil {
|
|
||||||
qliksenseCR.Spec.GitOps = &config.GitOps{}
|
|
||||||
}
|
|
||||||
qliksenseCR.Spec.GitOps.Schedule = argsString[1]
|
|
||||||
api.LogDebugMessage("Current gitOps schedule is : %s ", qliksenseCR.Spec.GitOps.Schedule)
|
|
||||||
case "gitops.watchbranch":
|
|
||||||
if qliksenseCR.Spec.GitOps == nil {
|
|
||||||
qliksenseCR.Spec.GitOps = &config.GitOps{}
|
|
||||||
}
|
|
||||||
qliksenseCR.Spec.GitOps.WatchBranch = argsString[1]
|
|
||||||
api.LogDebugMessage("Current gitOps watchbranch is : %s ", qliksenseCR.Spec.GitOps.WatchBranch)
|
|
||||||
case "gitops.image":
|
|
||||||
if qliksenseCR.Spec.GitOps == nil {
|
|
||||||
qliksenseCR.Spec.GitOps = &config.GitOps{}
|
|
||||||
}
|
|
||||||
qliksenseCR.Spec.GitOps.Image = argsString[1]
|
|
||||||
api.LogDebugMessage("Current gitOps watchbranch is : %s ", qliksenseCR.Spec.GitOps.Image)
|
|
||||||
default:
|
|
||||||
err := fmt.Errorf("Please enter one of: profile, storageClassName,rotateKeys, manifestRoot or git.repository arguments to configure the current context")
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// write modified content into context.yaml
|
|
||||||
api.WriteToFile(&qliksenseCR, qliksenseContextsFile)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetContextConfig - set the context for qliksense kubernetes resources to live in
|
|
||||||
func (q *Qliksense) SetContextConfig(args []string) error {
|
|
||||||
if len(args) == 1 {
|
|
||||||
err := q.SetUpQliksenseContext(args[0], false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err := fmt.Errorf("Please provide a name to configure the context with")
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Qliksense) ListContextConfigs() error {
|
|
||||||
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
|
|
||||||
var qliksenseConfig api.QliksenseConfig
|
|
||||||
if err := api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
out := ansi.NewColorableStdout()
|
|
||||||
w := tabwriter.NewWriter(out, 5, 8, 0, '\t', 0)
|
|
||||||
fmt.Fprintln(w, chalk.Underline.TextStyle("Context Name"), "\t", chalk.Underline.TextStyle("CR File Location"))
|
|
||||||
w.Flush()
|
|
||||||
if len(qliksenseConfig.Spec.Contexts) > 0 {
|
|
||||||
for _, cont := range qliksenseConfig.Spec.Contexts {
|
|
||||||
fmt.Fprintln(w, cont.Name, "\t", cont.CrFile, "\t")
|
|
||||||
}
|
|
||||||
w.Flush()
|
|
||||||
fmt.Fprintln(out, "")
|
|
||||||
fmt.Fprintln(out, chalk.Bold.TextStyle("Current Context : "), qliksenseConfig.Spec.CurrentContext)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintln(out, "No Contexts Available")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetUpQliksenseContext - to setup qliksense context
|
|
||||||
func (q *Qliksense) SetUpQliksenseContext(contextName string, isDefaultContext bool) error {
|
|
||||||
if contextName == "" {
|
|
||||||
err := fmt.Errorf("Please enter a non-empty context-name")
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// check the length of the context name entered by the user, it should not exceed 17 chars
|
|
||||||
if len(contextName) > MaxContextNameLength {
|
|
||||||
err := fmt.Errorf("Please enter a context-name with utmost 17 characters")
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
|
|
||||||
var qliksenseConfig api.QliksenseConfig
|
|
||||||
configFileTrack := false
|
|
||||||
|
|
||||||
if !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 := &api.QliksenseCR{}
|
|
||||||
qliksenseCR.AddCommonConfig(contextName)
|
|
||||||
api.WriteToFile(&qliksenseCR, qliksenseContextFile)
|
|
||||||
api.LogDebugMessage("Added Context: %s", contextName)
|
|
||||||
}
|
|
||||||
// else {
|
|
||||||
// if err := api.ReadFromFile(&qliksenseCR, qliksenseContextFile); 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 !ctxTrack {
|
|
||||||
qliksenseConfig.Spec.Contexts = append(qliksenseConfig.Spec.Contexts, api.Context{
|
|
||||||
Name: contextName,
|
|
||||||
CrFile: qliksenseContextFile,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
qliksenseConfig.Spec.CurrentContext = contextName
|
|
||||||
if !configFileTrack {
|
|
||||||
api.WriteToFile(&qliksenseConfig, qliksenseConfigFile)
|
|
||||||
}
|
|
||||||
// set the encrypted default mongo
|
|
||||||
q.SetSecrets([]string{`qliksense.mongoDbUri="mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"`}, false)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateInput(input string) (string, error) {
|
|
||||||
var err error
|
|
||||||
validInputs := []string{"yes", "no", "None"}
|
|
||||||
isValid := false
|
|
||||||
for _, elem := range validInputs {
|
|
||||||
if input == elem {
|
|
||||||
isValid = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !isValid {
|
|
||||||
err = fmt.Errorf("Please enter one of: yes, no or None")
|
|
||||||
log.Println(err)
|
|
||||||
|
|
||||||
}
|
|
||||||
return input, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrepareK8sSecret decodes and decrypts the secret value in the secret.yaml file and returns a B64encoded string
|
|
||||||
func (q *Qliksense) PrepareK8sSecret(targetFile string) (string, error) {
|
|
||||||
// check if targetFile exists
|
|
||||||
if !api.FileExists(targetFile) {
|
|
||||||
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 {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
// retrieve value from data section
|
|
||||||
k8sSecret1, err := api.K8sSecretFromYaml(k8sSecret)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
dataMap := k8sSecret1.Data
|
|
||||||
var resultMap = make(map[string][]byte)
|
|
||||||
for k, v := range dataMap {
|
|
||||||
ba, err := b64.StdEncoding.DecodeString(string(v))
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Not able to decode message: %v", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
decryptedString, err := api.Decrypt(ba, rsaPrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Not able to decrypt message: %v", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
resultMap[k] = decryptedString
|
|
||||||
}
|
|
||||||
|
|
||||||
// putting the above map back into the k8sSecret struct
|
|
||||||
k8sSecret1.Data = resultMap
|
|
||||||
k8sSecretBytes, err := api.K8sSecretToYaml(k8sSecret1)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(k8sSecretBytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readTargetfile(targetFile string) ([]byte, error) {
|
|
||||||
k8sSecret, err := ioutil.ReadFile(targetFile)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Unable to read the targetFile")
|
|
||||||
log.Println(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return k8sSecret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Qliksense) SetImageRegistry(registry, pushUsername, pushPassword, pullUsername, pullPassword string) error {
|
|
||||||
qConfig := api.NewQConfig(q.QliksenseHome)
|
|
||||||
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if pushUsername != "" {
|
|
||||||
if err := qConfig.SetPushDockerConfigJsonSecret(&api.DockerConfigJsonSecret{
|
|
||||||
Uri: registry,
|
|
||||||
Username: pushUsername,
|
|
||||||
Password: pushPassword,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
} else if err := qConfig.SetPullDockerConfigJsonSecret(&api.DockerConfigJsonSecret{
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
@@ -1,936 +0,0 @@
|
|||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
if err := os.Mkdir(testDir, 0777); err != nil {
|
|
||||||
log.Printf("\nError occurred: %v", err)
|
|
||||||
}
|
|
||||||
config :=
|
|
||||||
`
|
|
||||||
apiVersion: config.qlik.com/v1
|
|
||||||
kind: QliksenseConfig
|
|
||||||
metadata:
|
|
||||||
name: qliksenseConfig
|
|
||||||
spec:
|
|
||||||
contexts:
|
|
||||||
- name: qlik-default
|
|
||||||
crFile: /root/.qliksense/contexts/qlik-default.yaml
|
|
||||||
currentContext: qlik-default
|
|
||||||
`
|
|
||||||
configFile := filepath.Join(testDir, "config.yaml")
|
|
||||||
// tests/config.yaml exists
|
|
||||||
ioutil.WriteFile(configFile, []byte(config), 0777)
|
|
||||||
|
|
||||||
contextYaml :=
|
|
||||||
`
|
|
||||||
apiVersion: qlik.com/v1
|
|
||||||
kind: Qliksense
|
|
||||||
metadata:
|
|
||||||
name: qlik-default
|
|
||||||
spec:
|
|
||||||
profile: docker-desktop
|
|
||||||
rotateKeys: "yes"
|
|
||||||
releaseName: qlik-default
|
|
||||||
`
|
|
||||||
qlikDefaultContext := "qlik-default"
|
|
||||||
// create contexts/qlik-default/ under tests/
|
|
||||||
contexts := "contexts"
|
|
||||||
contextsDir := filepath.Join(testDir, contexts, qlikDefaultContext)
|
|
||||||
if err := os.MkdirAll(contextsDir, 0777); err != nil {
|
|
||||||
err = fmt.Errorf("Not able to create directories")
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
contextFile := filepath.Join(contextsDir, qlikDefaultContext+".yaml")
|
|
||||||
ioutil.WriteFile(contextFile, []byte(contextYaml), 0777)
|
|
||||||
tearDown := func() {
|
|
||||||
os.RemoveAll(testDir)
|
|
||||||
}
|
|
||||||
return tearDown
|
|
||||||
}
|
|
||||||
|
|
||||||
func 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()
|
|
||||||
defer tearDown()
|
|
||||||
|
|
||||||
q := &Qliksense{
|
|
||||||
QliksenseHome: testDir,
|
|
||||||
}
|
|
||||||
_, _, err := retrieveCurrentContextInfo(q)
|
|
||||||
if err != nil {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetUpQliksenseContext(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
qlikSenseHome string
|
|
||||||
contextName string
|
|
||||||
isDefaultContext bool
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "valid contextname",
|
|
||||||
args: args{
|
|
||||||
qlikSenseHome: testDir,
|
|
||||||
contextName: "testContext1",
|
|
||||||
isDefaultContext: false,
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid contextname",
|
|
||||||
args: args{
|
|
||||||
qlikSenseHome: testDir,
|
|
||||||
contextName: "testContext_abcdefgh",
|
|
||||||
isDefaultContext: false,
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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 := New(tt.args.qlikSenseHome)
|
|
||||||
if err := q.SetUpQliksenseContext(tt.args.contextName, tt.args.isDefaultContext); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("SetUpQliksenseContext() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetUpQliksenseDefaultContext(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
qlikSenseHome string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "valid case",
|
|
||||||
args: args{
|
|
||||||
qlikSenseHome: testDir,
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tearDown := setup()
|
|
||||||
defer tearDown()
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
q := New(tt.args.qlikSenseHome)
|
|
||||||
if err := q.SetUpQliksenseDefaultContext(); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("SetUpQliksenseDefaultContext() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetOtherConfigs(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
q *Qliksense
|
|
||||||
args []string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "valid case",
|
|
||||||
args: args{
|
|
||||||
q: &Qliksense{
|
|
||||||
QliksenseHome: testDir,
|
|
||||||
},
|
|
||||||
args: []string{"profile=minikube", "rotateKeys=yes", "storageClassName=efs"},
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid configs",
|
|
||||||
args: args{
|
|
||||||
q: &Qliksense{
|
|
||||||
QliksenseHome: testDir,
|
|
||||||
},
|
|
||||||
args: []string{"someconfig=somevalue"},
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty configs",
|
|
||||||
args: args{
|
|
||||||
q: &Qliksense{
|
|
||||||
QliksenseHome: testDir,
|
|
||||||
},
|
|
||||||
args: []string{},
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tearDown := setup()
|
|
||||||
defer tearDown()
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if err := tt.args.q.SetOtherConfigs(tt.args.args); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("SetOtherConfigs() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetConfigs(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
q *Qliksense
|
|
||||||
args []string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "valid case",
|
|
||||||
args: args{
|
|
||||||
q: &Qliksense{
|
|
||||||
QliksenseHome: testDir,
|
|
||||||
},
|
|
||||||
args: []string{"qliksense.acceptEULA=\"yes\"", "qliksense.mongoDbUri=\"mongo://mongo:3307\""},
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tearDown := setup()
|
|
||||||
defer tearDown()
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if err := tt.args.q.SetConfigs(tt.args.args); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("SetConfigs() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetImageRegistry(t *testing.T) {
|
|
||||||
getQlikSense := func(tmpQlikSenseHome string) (*Qliksense, error) {
|
|
||||||
if err := ioutil.WriteFile(path.Join(tmpQlikSenseHome, "config.yaml"), []byte(fmt.Sprintf(`
|
|
||||||
apiVersion: config.qlik.com/v1
|
|
||||||
kind: QliksenseConfig
|
|
||||||
metadata:
|
|
||||||
name: QliksenseConfigMetadata
|
|
||||||
spec:
|
|
||||||
contexts:
|
|
||||||
- name: qlik-default
|
|
||||||
crFile: %s/contexts/qlik-default/qlik-default.yaml
|
|
||||||
currentContext: qlik-default
|
|
||||||
`, tmpQlikSenseHome)), os.ModePerm); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultContextDir := path.Join(tmpQlikSenseHome, "contexts", "qlik-default")
|
|
||||||
if err := os.MkdirAll(defaultContextDir, os.ModePerm); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
version := "foo"
|
|
||||||
manifestsRootDir := fmt.Sprintf("%s/repo/%s", defaultContextDir, version)
|
|
||||||
if err := ioutil.WriteFile(path.Join(defaultContextDir, "qlik-default.yaml"), []byte(fmt.Sprintf(`
|
|
||||||
apiVersion: qlik.com/v1
|
|
||||||
kind: Qliksense
|
|
||||||
metadata:
|
|
||||||
name: qlik-default
|
|
||||||
labels:
|
|
||||||
version: %s
|
|
||||||
spec:
|
|
||||||
profile: docker-desktop
|
|
||||||
manifestsRoot: %s
|
|
||||||
namespace: some-namespace
|
|
||||||
`, version, manifestsRootDir)), os.ModePerm); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Qliksense{
|
|
||||||
QliksenseHome: tmpQlikSenseHome,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
registry string
|
|
||||||
pushUsername string
|
|
||||||
pushPassword string
|
|
||||||
pullUsername string
|
|
||||||
pullPassword string
|
|
||||||
expectSecretsExist bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "no auth",
|
|
||||||
registry: "foobar",
|
|
||||||
pushUsername: "",
|
|
||||||
pushPassword: "",
|
|
||||||
pullUsername: "",
|
|
||||||
pullPassword: "",
|
|
||||||
expectSecretsExist: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "auth",
|
|
||||||
registry: "foobar",
|
|
||||||
pushUsername: "foo-push",
|
|
||||||
pushPassword: "bar-push",
|
|
||||||
pullUsername: "foo-pull",
|
|
||||||
pullPassword: "bar-pull",
|
|
||||||
expectSecretsExist: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
|
||||||
tmpQlikSenseHome, err := ioutil.TempDir("", "")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpQlikSenseHome)
|
|
||||||
|
|
||||||
q, err := getQlikSense(tmpQlikSenseHome)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := q.SetImageRegistry(testCase.registry, testCase.pushUsername, testCase.pushPassword,
|
|
||||||
testCase.pullUsername, testCase.pullPassword); err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
qConfig := api.NewQConfig(q.QliksenseHome)
|
|
||||||
if testCase.expectSecretsExist {
|
|
||||||
if pushSecret, err := qConfig.GetPushDockerConfigJsonSecret(); err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
} else if pushSecret.Uri != testCase.registry ||
|
|
||||||
pushSecret.Username != testCase.pushUsername || pushSecret.Password != testCase.pushPassword {
|
|
||||||
t.Fatalf("unexpected push secret content: %v", pushSecret)
|
|
||||||
}
|
|
||||||
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.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.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: /root/.qliksense/contexts/qlik-default.yaml
|
|
||||||
- name: qlik1
|
|
||||||
crFile: /root/.qliksense/contexts/qlik1.yaml
|
|
||||||
- name: qlik2
|
|
||||||
crFile: /root/.qliksense/contexts/qlik2.yaml
|
|
||||||
currentContext: qlik1
|
|
||||||
`
|
|
||||||
configFile := filepath.Join(testDir, "config.yaml")
|
|
||||||
// tests/config.yaml exists
|
|
||||||
ioutil.WriteFile(configFile, []byte(config), 0777)
|
|
||||||
contextYaml :=
|
|
||||||
`
|
|
||||||
apiVersion: qlik.com/v1
|
|
||||||
kind: Qliksense
|
|
||||||
metadata:
|
|
||||||
name: qlik-default
|
|
||||||
spec:
|
|
||||||
profile: docker-desktop
|
|
||||||
rotateKeys: "yes"
|
|
||||||
releaseName: qlik-default
|
|
||||||
`
|
|
||||||
qlikDefaultContext := "qlik-default"
|
|
||||||
// create contexts/qlik-default/ under tests/
|
|
||||||
contexts := "contexts"
|
|
||||||
contextsDir1 := filepath.Join(testDir, contexts, qlikDefaultContext)
|
|
||||||
if err := os.MkdirAll(contextsDir1, 0777); err != nil {
|
|
||||||
err = fmt.Errorf("Not able to create directories")
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
contextYaml1 :=
|
|
||||||
`
|
|
||||||
apiVersion: qlik.com/v1
|
|
||||||
kind: Qliksense
|
|
||||||
metadata:
|
|
||||||
name: qlik1
|
|
||||||
spec:
|
|
||||||
profile: docker-desktop
|
|
||||||
rotateKeys: "yes"
|
|
||||||
releaseName: qlik1`
|
|
||||||
|
|
||||||
contextYaml2 :=
|
|
||||||
`
|
|
||||||
apiVersion: qlik.com/v1
|
|
||||||
kind: Qliksense
|
|
||||||
metadata:
|
|
||||||
name: qlik2
|
|
||||||
spec:
|
|
||||||
profile: docker-desktop
|
|
||||||
rotateKeys: "yes"
|
|
||||||
releaseName: qlik2`
|
|
||||||
|
|
||||||
contextsDir := filepath.Join(testDir, contexts, "qlik1")
|
|
||||||
if err := os.MkdirAll(contextsDir, 0777); err != nil {
|
|
||||||
err = fmt.Errorf("Not able to create directories")
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
contextsDir2 := filepath.Join(testDir, contexts, "qlik2")
|
|
||||||
if err := os.MkdirAll(contextsDir2, 0777); err != nil {
|
|
||||||
err = fmt.Errorf("Not able to create directories")
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
contextFile := filepath.Join(contextsDir, "qlik1.yaml")
|
|
||||||
ioutil.WriteFile(contextFile, []byte(contextYaml1), 0777)
|
|
||||||
|
|
||||||
contextFile2 := filepath.Join(contextsDir2, "qlik2.yaml")
|
|
||||||
ioutil.WriteFile(contextFile2, []byte(contextYaml2), 0777)
|
|
||||||
|
|
||||||
contextFile1 := filepath.Join(contextsDir1, "qlik-default.yaml")
|
|
||||||
ioutil.WriteFile(contextFile1, []byte(contextYaml), 0777)
|
|
||||||
|
|
||||||
tearDown := func() {
|
|
||||||
os.RemoveAll(testDir)
|
|
||||||
}
|
|
||||||
return tearDown
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeleteContexts(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
qlikSenseHome string
|
|
||||||
contextName string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "valid context",
|
|
||||||
args: args{
|
|
||||||
qlikSenseHome: testDir,
|
|
||||||
contextName: "qlik2",
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "default context",
|
|
||||||
args: args{
|
|
||||||
qlikSenseHome: testDir,
|
|
||||||
contextName: "qlik-default",
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "non-existent context",
|
|
||||||
args: args{
|
|
||||||
qlikSenseHome: testDir,
|
|
||||||
contextName: "qlik3",
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "current context",
|
|
||||||
args: args{
|
|
||||||
qlikSenseHome: testDir,
|
|
||||||
contextName: "qlik1",
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tearDown := setupDeleteContext()
|
|
||||||
defer tearDown()
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
q := New(tt.args.qlikSenseHome)
|
|
||||||
var arg []string
|
|
||||||
arg = append(arg, tt.args.contextName)
|
|
||||||
if err := q.DeleteContextConfig(arg); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("DeleteContext() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
package qliksense
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CrdCommandOptions struct {
|
|
||||||
All bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Qliksense) ViewCrds(opts *CrdCommandOptions) error {
|
|
||||||
//io.WriteString(os.Stdout, q.GetCRDString())
|
|
||||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
|
||||||
qcr, err := qConfig.GetCurrentCR()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("cannot get the current-context cr", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if engineCRD, err := getQliksenseInitCrd(qcr); err != nil {
|
|
||||||
return err
|
|
||||||
} else if opts.All {
|
|
||||||
fmt.Printf("%s\n%s", q.GetOperatorCRDString(), engineCRD)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("%s", engineCRD)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Qliksense) InstallCrds(opts *CrdCommandOptions) error {
|
|
||||||
// install qliksense-init crd
|
|
||||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
|
||||||
qcr, err := qConfig.GetCurrentCR()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("cannot get the current-context cr", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if engineCRD, err := getQliksenseInitCrd(qcr); err != nil {
|
|
||||||
return err
|
|
||||||
} else if err = qapi.KubectlApply(engineCRD, ""); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if opts.All { // install opeartor crd
|
|
||||||
if err := qapi.KubectlApply(q.GetOperatorCRDString(), ""); err != nil {
|
|
||||||
fmt.Println("cannot do kubectl apply on opeartor CRD", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getQliksenseInitCrd(qcr *qapi.QliksenseCR) (string, error) {
|
|
||||||
var repoPath string
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if qcr.Spec.GetManifestsRoot() != "" {
|
|
||||||
repoPath = qcr.Spec.GetManifestsRoot()
|
|
||||||
} else {
|
|
||||||
if repoPath, err = downloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master"); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
qInitMsPath := filepath.Join(repoPath, Q_INIT_CRD_PATH)
|
|
||||||
qInitByte, err := executeKustomizeBuild(qInitMsPath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("cannot generate crds for qliksense-init", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(qInitByte), nil
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,297 +2,255 @@ package qliksense
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
cliflags "github.com/docker/cli/cli/flags"
|
||||||
|
"github.com/docker/distribution/reference"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
|
"github.com/docker/docker/pkg/term"
|
||||||
|
"github.com/docker/docker/registry"
|
||||||
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containers/image/v5/copy"
|
|
||||||
"github.com/containers/image/v5/signature"
|
|
||||||
"github.com/containers/image/v5/transports/alltransports"
|
|
||||||
imageTypes "github.com/containers/image/v5/types"
|
|
||||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type imageNameParts struct {
|
// Images ...
|
||||||
name string
|
type Images struct {
|
||||||
tag string
|
Images []string `yaml:"images"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
imagesDirName = "images"
|
|
||||||
imageIndexDirName = "index"
|
|
||||||
imageSharedBlobsDirName = "blobs"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PullImages ...
|
// PullImages ...
|
||||||
func (q *Qliksense) PullImagesForCurrentCR() error {
|
func (p *Qliksense) PullImages(args []string) error {
|
||||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
var (
|
||||||
qcr, err := qConfig.GetCurrentCR()
|
image string
|
||||||
if err != nil {
|
err error
|
||||||
return err
|
yamlVersion string
|
||||||
}
|
valid bool
|
||||||
version := qcr.GetLabelFromCr("version")
|
images Images
|
||||||
profile := qcr.Spec.Profile
|
)
|
||||||
repoDir := qcr.Spec.ManifestsRoot
|
println("getting images list...")
|
||||||
|
if yamlVersion, err = p.CallPorter(append([]string{"invoke", "--action", "about"}, args...),
|
||||||
imagesDir, err := setupImagesDir(q.QliksenseHome)
|
func(x string) (out *string) {
|
||||||
if err != nil {
|
if strings.HasPrefix(x, "qlikSenseVersion") {
|
||||||
|
valid = true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(x, "execution") {
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
if valid {
|
||||||
|
return &x
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
versionOut, stored, err := q.readOrGenerateVersionOutput(imagesDir, version, repoDir, profile)
|
if err = yaml.Unmarshal([]byte(yamlVersion), &images); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
for _, image = range images.Images {
|
||||||
images := versionOut.Images
|
if _, err = p.PullImage(image); err != nil {
|
||||||
if err := q.appendOperatorImages(&images); err != nil {
|
fmt.Print(err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, image := range images {
|
|
||||||
if err := pullImage(image, imagesDir); err != nil {
|
|
||||||
fmt.Printf("%v\n", err)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
fmt.Print("---\n")
|
println("---")
|
||||||
}
|
}
|
||||||
|
|
||||||
if version != "" && !stored {
|
|
||||||
if err := q.writeVersionOutput(versionOut, imagesDir, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Qliksense) appendOperatorImages(images *[]string) error {
|
// PullImage ...
|
||||||
if operatorImages, err := getImageList([]byte(q.GetOperatorControllerString())); err != nil {
|
func (p *Qliksense) PullImage(imageName string) (map[string]string, error) {
|
||||||
|
var (
|
||||||
|
cli *command.DockerCli
|
||||||
|
dockerOutput io.Writer
|
||||||
|
response io.ReadCloser
|
||||||
|
pullOptions types.ImagePullOptions
|
||||||
|
ctx context.Context
|
||||||
|
ref reference.Named
|
||||||
|
repoInfo *registry.RepositoryInfo
|
||||||
|
authConfig types.AuthConfig
|
||||||
|
encodedAuth string
|
||||||
|
termFd uintptr
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
// TODO: Create a real cli config context
|
||||||
|
ctx = context.Background()
|
||||||
|
if cli, err = command.NewDockerCli(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = cli.Initialize(cliflags.NewClientOptions()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ref, err = reference.ParseNormalizedNamed(imageName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if repoInfo, err = registry.ParseRepositoryInfo(ref); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
authConfig = command.ResolveAuthConfig(ctx, cli, repoInfo.Index)
|
||||||
|
if encodedAuth, err = command.EncodeAuthToBase64(authConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pullOptions = types.ImagePullOptions{
|
||||||
|
RegistryAuth: encodedAuth,
|
||||||
|
}
|
||||||
|
|
||||||
|
if response, err = cli.Client().ImagePull(ctx, imageName, pullOptions); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer response.Close()
|
||||||
|
|
||||||
|
dockerOutput = ioutil.Discard
|
||||||
|
// if b.IsVerbose() {
|
||||||
|
// dockerOutput = b.Out
|
||||||
|
// }
|
||||||
|
dockerOutput = os.Stdout
|
||||||
|
termFd, _ = term.GetFdInfo(dockerOutput)
|
||||||
|
// Setting this to false here because Moby os.Exit(1) all over the place and this fails on WSL (only)
|
||||||
|
// when Term is true.
|
||||||
|
isTerm := false
|
||||||
|
if err = jsonmessage.DisplayJSONMessagesStream(response, dockerOutput, termFd, isTerm, nil); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inspectData, _, err := cli.Client().ImageInspectWithRaw(ctx, imageName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return inspectData.ContainerConfig.Labels, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PullImage ...
|
||||||
|
func (p *Qliksense) TagAndPushImages(registry string) error {
|
||||||
|
var (
|
||||||
|
image string
|
||||||
|
err error
|
||||||
|
yamlVersion string
|
||||||
|
valid bool
|
||||||
|
images Images
|
||||||
|
)
|
||||||
|
|
||||||
|
if yamlVersion, err = p.CallPorter([]string{"invoke", "--action", "about"},
|
||||||
|
func(x string) (out *string) {
|
||||||
|
if strings.HasPrefix(x, "qlikSenseVersion") {
|
||||||
|
valid = true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(x, "execution") {
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
if valid {
|
||||||
|
return &x
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
}
|
||||||
*images = append(*images, operatorImages...)
|
|
||||||
|
if err = yaml.Unmarshal([]byte(yamlVersion), &images); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, image = range images.Images {
|
||||||
|
if err = p.TagAndPush(image, registry); err != nil {
|
||||||
|
fmt.Print(err)
|
||||||
|
}
|
||||||
|
println("---")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PullImage ...
|
||||||
|
func (p *Qliksense) TagAndPush(image string, registryName string) error {
|
||||||
|
var (
|
||||||
|
cli *command.DockerCli
|
||||||
|
dockerOutput io.Writer
|
||||||
|
response io.ReadCloser
|
||||||
|
pushOptions types.ImagePushOptions
|
||||||
|
ctx context.Context
|
||||||
|
newName string
|
||||||
|
segments []string
|
||||||
|
imageList []types.ImageSummary
|
||||||
|
imageListOptions types.ImageListOptions
|
||||||
|
filterArgs filters.Args
|
||||||
|
ref reference.Named
|
||||||
|
repoInfo *registry.RepositoryInfo
|
||||||
|
authConfig types.AuthConfig
|
||||||
|
encodedAuth string
|
||||||
|
termFd uintptr
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
// TODO: Create a real cli config context
|
||||||
|
ctx = context.Background()
|
||||||
|
if cli, err = command.NewDockerCli(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = cli.Initialize(cliflags.NewClientOptions()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
segments = strings.Split(image, "/")
|
||||||
|
if segments[0] == "docker.io" {
|
||||||
|
image = strings.Join(segments[1:], "/")
|
||||||
|
}
|
||||||
|
newName = registryName + "/" + segments[len(segments)-1]
|
||||||
|
|
||||||
|
filterArgs = filters.NewArgs()
|
||||||
|
filterArgs.Add("reference", image)
|
||||||
|
imageListOptions = types.ImageListOptions{
|
||||||
|
Filters: filterArgs,
|
||||||
|
}
|
||||||
|
if imageList, err = cli.Client().ImageList(ctx, imageListOptions); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if imageList == nil || len(imageList) <= 0 {
|
||||||
|
fmt.Printf("Use `qliksense pull`, to pull %v for an air gap push", newName)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func pullImage(image, imagesDir string) error {
|
if err = cli.Client().ImageTag(ctx, image, newName); err != nil {
|
||||||
srcRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%v", image))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
nameTag := getImageNameParts(image)
|
|
||||||
targetDir := filepath.Join(imagesDir, imageIndexDirName, nameTag.name, nameTag.tag)
|
|
||||||
if err := os.MkdirAll(targetDir, os.ModePerm); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
destRef, err := alltransports.ParseImageName(fmt.Sprintf("oci:%v", targetDir))
|
if ref, err = reference.ParseNormalizedNamed(image); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if repoInfo, err = registry.ParseRepositoryInfo(ref); err != nil {
|
||||||
policyContext, err := signature.NewPolicyContext(&signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer policyContext.Destroy()
|
authConfig = command.ResolveAuthConfig(ctx, cli, repoInfo.Index)
|
||||||
|
if encodedAuth, err = command.EncodeAuthToBase64(authConfig); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pushOptions = types.ImagePushOptions{
|
||||||
|
All: true,
|
||||||
|
RegistryAuth: encodedAuth,
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Printf("==> Pulling image from %v\n", srcRef.StringWithinTransport())
|
if response, err = cli.Client().ImagePush(ctx, newName, pushOptions); err != nil {
|
||||||
if _, err := copy.Image(context.Background(), policyContext, destRef, srcRef, ©.Options{
|
return err
|
||||||
ReportWriter: os.Stdout,
|
}
|
||||||
SourceCtx: &imageTypes.SystemContext{
|
defer response.Close()
|
||||||
ArchitectureChoice: "amd64",
|
|
||||||
OSChoice: "linux",
|
dockerOutput = ioutil.Discard
|
||||||
},
|
// if b.IsVerbose() {
|
||||||
DestinationCtx: &imageTypes.SystemContext{
|
// dockerOutput = b.Out
|
||||||
OCISharedBlobDirPath: filepath.Join(imagesDir, imageSharedBlobsDirName),
|
// }
|
||||||
},
|
dockerOutput = os.Stdout
|
||||||
}); err != nil {
|
termFd, _ = term.GetFdInfo(dockerOutput)
|
||||||
return err
|
// Setting this to false here because Moby os.Exit(1) all over the place and this fails on WSL (only)
|
||||||
}
|
// when Term is true.
|
||||||
return nil
|
isTerm := false
|
||||||
}
|
if err = jsonmessage.DisplayJSONMessagesStream(response, dockerOutput, termFd, isTerm, nil); err != nil {
|
||||||
|
|
||||||
// TagAndPushImages ...
|
|
||||||
func (q *Qliksense) PushImagesForCurrentCR() error {
|
|
||||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
|
||||||
qcr, err := qConfig.GetCurrentCR()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
version := qcr.GetLabelFromCr("version")
|
|
||||||
profile := qcr.Spec.Profile
|
|
||||||
repoDir := qcr.Spec.ManifestsRoot
|
|
||||||
|
|
||||||
dockerConfigJsonSecret, err := qConfig.GetPushDockerConfigJsonSecret()
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
dockerConfigJsonSecret = &qapi.DockerConfigJsonSecret{
|
|
||||||
Uri: qcr.GetImageRegistry(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
imagesDir, err := setupImagesDir(q.QliksenseHome)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
versionOut, stored, err := q.readOrGenerateVersionOutput(imagesDir, version, repoDir, profile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
fmt.Print("---\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
if version != "" && !stored {
|
|
||||||
if err := q.writeVersionOutput(versionOut, imagesDir, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func pushImage(image, imagesDir string, dockerConfigJsonSecret *qapi.DockerConfigJsonSecret) error {
|
|
||||||
imageNameParts := getImageNameParts(image)
|
|
||||||
srcDir := filepath.Join(imagesDir, imageIndexDirName, imageNameParts.name, imageNameParts.tag)
|
|
||||||
if exists, err := directoryExists(srcDir); err != nil {
|
|
||||||
return err
|
|
||||||
} else if !exists {
|
|
||||||
if err := pullImage(image, imagesDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
srcRef, err := alltransports.ParseImageName(fmt.Sprintf("oci:%v", srcDir))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
newImage := fmt.Sprintf("%v/%v:%v", dockerConfigJsonSecret.Uri, imageNameParts.name, imageNameParts.tag)
|
|
||||||
destRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%v", newImage))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
policyContext, err := signature.NewPolicyContext(&signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer policyContext.Destroy()
|
|
||||||
|
|
||||||
destinationCtx := &imageTypes.SystemContext{
|
|
||||||
DockerInsecureSkipTLSVerify: imageTypes.OptionalBoolTrue,
|
|
||||||
}
|
|
||||||
if dockerConfigJsonSecret.Username != "" {
|
|
||||||
destinationCtx.DockerAuthConfig = &imageTypes.DockerAuthConfig{
|
|
||||||
Username: dockerConfigJsonSecret.Username,
|
|
||||||
Password: dockerConfigJsonSecret.Password,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Printf("==> Pushing image to: %v\n", destRef.StringWithinTransport())
|
|
||||||
if _, err = copy.Image(context.Background(), policyContext, destRef, srcRef, ©.Options{
|
|
||||||
ReportWriter: os.Stdout,
|
|
||||||
SourceCtx: &imageTypes.SystemContext{
|
|
||||||
OCISharedBlobDirPath: filepath.Join(imagesDir, imageSharedBlobsDirName),
|
|
||||||
},
|
|
||||||
DestinationCtx: destinationCtx,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func directoryExists(path string) (exists bool, err error) {
|
|
||||||
if info, err := os.Stat(path); err != nil && os.IsNotExist(err) {
|
|
||||||
exists = false
|
|
||||||
err = nil
|
|
||||||
} else if err != nil && !os.IsNotExist(err) {
|
|
||||||
exists = false
|
|
||||||
} else if err == nil && info.IsDir() {
|
|
||||||
exists = true
|
|
||||||
} else if err == nil && !info.IsDir() {
|
|
||||||
exists = false
|
|
||||||
err = fmt.Errorf("path: %v is occupied by a file instead of a directory", path)
|
|
||||||
}
|
|
||||||
return exists, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func getImageNameParts(image string) imageNameParts {
|
|
||||||
segments := strings.Split(image, "/")
|
|
||||||
nameTag := strings.Split(segments[len(segments)-1], ":")
|
|
||||||
if len(nameTag) < 2 {
|
|
||||||
nameTag = append(nameTag, "latest")
|
|
||||||
}
|
|
||||||
return imageNameParts{
|
|
||||||
name: nameTag[0],
|
|
||||||
tag: nameTag[1],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupImagesDir(qliksenseHome string) (string, error) {
|
|
||||||
imagesDir := filepath.Join(qliksenseHome, imagesDirName)
|
|
||||||
|
|
||||||
imageIndexDir := filepath.Join(imagesDir, imageIndexDirName)
|
|
||||||
if err := os.MkdirAll(imageIndexDir, os.ModePerm); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
sharedBlobsDir := filepath.Join(imagesDir, imageSharedBlobsDirName)
|
|
||||||
if err := os.MkdirAll(sharedBlobsDir, os.ModePerm); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return imagesDir, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Qliksense) readOrGenerateVersionOutput(imagesDir, version, repoDir, profile string) (versionOut *VersionOutput, stored bool, err error) {
|
|
||||||
if version != "" {
|
|
||||||
versionOut, err = q.readVersionOutput(imagesDir, version)
|
|
||||||
if versionOut != nil {
|
|
||||||
stored = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if versionOut == nil {
|
|
||||||
if versionOut, err = q.AboutDir(repoDir, profile); err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return versionOut, stored, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Qliksense) readVersionOutput(imagesDir, version string) (*VersionOutput, error) {
|
|
||||||
var versionOut VersionOutput
|
|
||||||
versionFile := filepath.Join(imagesDir, version)
|
|
||||||
if versionOutBytes, err := ioutil.ReadFile(versionFile); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if err = yaml.Unmarshal(versionOutBytes, &versionOut); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &versionOut, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Qliksense) writeVersionOutput(versionOut *VersionOutput, imagesDir, version string) error {
|
|
||||||
versionFile := filepath.Join(imagesDir, version)
|
|
||||||
if versionOutBytes, err := yaml.Marshal(versionOut); err != nil {
|
|
||||||
return err
|
|
||||||
} else if err = ioutil.WriteFile(versionFile, versionOutBytes, os.ModePerm); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,556 +0,0 @@
|
|||||||
package qliksense
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/big"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"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"
|
|
||||||
imageTypes "github.com/containers/image/v5/types"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_locateDockerRegistryBinary(t *testing.T) {
|
|
||||||
binary, err := locateDockerRegistryBinary()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
cmd := exec.Command(binary, "--version")
|
|
||||||
out, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
t.Logf("output: %v\n", string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_getSelfSignedCertAndKey(t *testing.T) {
|
|
||||||
host := "andriy.registry.com"
|
|
||||||
validity := time.Hour * 24 * 365
|
|
||||||
selfSignedCert, key, err := getSelfSignedCertAndKey(host, validity)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
fmt.Print(string(selfSignedCert))
|
|
||||||
fmt.Print(string(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
type clientAuthType byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
clientAuthNotProvided clientAuthType = iota
|
|
||||||
clientAuthProvided
|
|
||||||
clientAuthProvidedButIncorrect
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_Pull_Push_ImagesForCurrentCR(t *testing.T) {
|
|
||||||
var testCases = []struct {
|
|
||||||
name string
|
|
||||||
registryAuth bool
|
|
||||||
clientAuth clientAuthType
|
|
||||||
expectPushSuccess bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "registry does not require auth and we do not provide auth",
|
|
||||||
registryAuth: false,
|
|
||||||
clientAuth: clientAuthNotProvided,
|
|
||||||
expectPushSuccess: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "registry does not require auth but we provide auth",
|
|
||||||
registryAuth: false,
|
|
||||||
clientAuth: clientAuthProvided,
|
|
||||||
expectPushSuccess: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "registry requires auth but we do not provide auth",
|
|
||||||
registryAuth: true,
|
|
||||||
clientAuth: clientAuthNotProvided,
|
|
||||||
expectPushSuccess: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "registry requires auth but we provide wrong auth",
|
|
||||||
registryAuth: true,
|
|
||||||
clientAuth: clientAuthProvidedButIncorrect,
|
|
||||||
expectPushSuccess: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "registry requires auth and we provide auth",
|
|
||||||
registryAuth: true,
|
|
||||||
clientAuth: clientAuthProvided,
|
|
||||||
expectPushSuccess: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
|
||||||
registryURI := "127.0.0.1:5555"
|
|
||||||
registry, err := setupRegistryV2At(registryURI, testCase.registryAuth)
|
|
||||||
if registry != nil {
|
|
||||||
defer func() {
|
|
||||||
registry.Close()
|
|
||||||
//fmt.Printf("registry stdout:\n%v\n", registry.stdOutBuffer.String())
|
|
||||||
//fmt.Printf("registry stderr:\n%v\n", registry.stdErrBuffer.String())
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error setting up local registry: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpQlikSenseHome, err := ioutil.TempDir("", "tmp-qlik-sense-home-")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error creating tmp dir: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpQlikSenseHome)
|
|
||||||
|
|
||||||
if err := setupQlikSenseHome(t, tmpQlikSenseHome, registry, testCase.clientAuth); err != nil {
|
|
||||||
t.Fatalf("unexpected error setting up qliksense home: %v", err)
|
|
||||||
}
|
|
||||||
q := &Qliksense{
|
|
||||||
QliksenseHome: tmpQlikSenseHome,
|
|
||||||
CrdBox: packr.New("crds", "./crds"),
|
|
||||||
}
|
|
||||||
var versionOut VersionOutput
|
|
||||||
|
|
||||||
if err := q.PullImagesForCurrentCR(); err != nil {
|
|
||||||
t.Fatalf("unexpected pull error: %v", err)
|
|
||||||
} else if versionOutBytes, err := ioutil.ReadFile(path.Join(tmpQlikSenseHome, "images", "foo")); err != nil {
|
|
||||||
t.Fatalf("unexpected error reading version file: %v", err)
|
|
||||||
} 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`)
|
|
||||||
} 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 {
|
|
||||||
t.Fatal("expected images/blobs/sha256 directory to be non-empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
if testCase.expectPushSuccess {
|
|
||||||
if err := q.PushImagesForCurrentCR(); err != nil {
|
|
||||||
t.Fatalf("unexpected push error: %v", err)
|
|
||||||
} else if tmpImagesDir, err := ioutil.TempDir("", "tmp-images-"); err != nil {
|
|
||||||
t.Fatalf("unexpected error creating tmp dir: %v", err)
|
|
||||||
} else if err := testPullImage(fmt.Sprintf("%s/alpine:latest", registryURI), tmpImagesDir, registry); err != nil {
|
|
||||||
t.Fatalf("unexpected error pulling alpine:latest from the local registry: %v", err)
|
|
||||||
} else if infos, err := ioutil.ReadDir(path.Join(tmpImagesDir, "index", "alpine", "latest")); err != nil || len(infos) == 0 {
|
|
||||||
t.Fatal("expected index/alpine/latest directory to be non-empty")
|
|
||||||
} else if blobInfos, err := ioutil.ReadDir(path.Join(tmpImagesDir, "blobs", "sha256")); err != nil || len(blobInfos) == 0 {
|
|
||||||
t.Fatal("expected blobs/sha256 directory to be non-empty")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := q.PushImagesForCurrentCR(); err == nil {
|
|
||||||
t.Fatal("unexpected push success")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupQlikSenseHome(t *testing.T, tmpQlikSenseHome string, registry *testRegistryV2, clientAuth clientAuthType) error {
|
|
||||||
if err := ioutil.WriteFile(path.Join(tmpQlikSenseHome, "config.yaml"), []byte(fmt.Sprintf(`
|
|
||||||
apiVersion: config.qlik.com/v1
|
|
||||||
kind: QliksenseConfig
|
|
||||||
metadata:
|
|
||||||
name: QliksenseConfigMetadata
|
|
||||||
spec:
|
|
||||||
contexts:
|
|
||||||
- name: qlik-default
|
|
||||||
crFile: %s/contexts/qlik-default/qlik-default.yaml
|
|
||||||
currentContext: qlik-default
|
|
||||||
`, tmpQlikSenseHome)), os.ModePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultContextDir := path.Join(tmpQlikSenseHome, "contexts", "qlik-default")
|
|
||||||
if err := os.MkdirAll(defaultContextDir, os.ModePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
version := "foo"
|
|
||||||
manifestsRootDir := fmt.Sprintf("%s/repo/%s", defaultContextDir, version)
|
|
||||||
if err := ioutil.WriteFile(path.Join(defaultContextDir, "qlik-default.yaml"), []byte(fmt.Sprintf(`
|
|
||||||
apiVersion: qlik.com/v1
|
|
||||||
kind: Qliksense
|
|
||||||
metadata:
|
|
||||||
name: qlik-default
|
|
||||||
labels:
|
|
||||||
version: %s
|
|
||||||
spec:
|
|
||||||
profile: docker-desktop
|
|
||||||
configs:
|
|
||||||
qliksense:
|
|
||||||
- name: imageRegistry
|
|
||||||
value: %s
|
|
||||||
manifestsRoot: %s
|
|
||||||
rotateKeys: "yes"
|
|
||||||
releaseName: qlik-default
|
|
||||||
`, version, registry.url, manifestsRootDir)), os.ModePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if clientAuth == clientAuthProvided || clientAuth == clientAuthProvidedButIncorrect {
|
|
||||||
if registry.username == "" || clientAuth == clientAuthProvidedButIncorrect {
|
|
||||||
registry.username = "bad"
|
|
||||||
}
|
|
||||||
if registry.password == "" || clientAuth == clientAuthProvidedButIncorrect {
|
|
||||||
registry.password = "worse"
|
|
||||||
}
|
|
||||||
qConfig := api.NewQConfig(tmpQlikSenseHome)
|
|
||||||
if err := qConfig.SetPushDockerConfigJsonSecret(&api.DockerConfigJsonSecret{
|
|
||||||
Uri: registry.url,
|
|
||||||
Username: registry.username,
|
|
||||||
Password: registry.password,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
profileDir := path.Join(manifestsRootDir, "manifests", "docker-desktop")
|
|
||||||
if err := os.MkdirAll(profileDir, os.ModePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile(path.Join(profileDir, "kustomization.yaml"), []byte(`
|
|
||||||
resources:
|
|
||||||
- deployment.yaml
|
|
||||||
`), os.ModePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile(path.Join(profileDir, "deployment.yaml"), []byte(`
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: the-deployment
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: the-container
|
|
||||||
image: alpine:latest
|
|
||||||
`), os.ModePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
transformersDir := path.Join(manifestsRootDir, "transformers")
|
|
||||||
if err := os.MkdirAll(transformersDir, os.ModePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile(path.Join(transformersDir, "qseokversion.yaml"), []byte(`
|
|
||||||
apiVersion: qlik.com/v1
|
|
||||||
kind: SelectivePatch
|
|
||||||
metadata:
|
|
||||||
name: qseokversion
|
|
||||||
enabled: true
|
|
||||||
patches:
|
|
||||||
- target:
|
|
||||||
kind: HelmChart
|
|
||||||
labelSelector: name!=qliksense-init
|
|
||||||
patch: |-
|
|
||||||
chartName: qliksense
|
|
||||||
chartVersion: 1.21.23
|
|
||||||
`), os.ModePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type testRegistryV2 struct {
|
|
||||||
cmd *exec.Cmd
|
|
||||||
url string
|
|
||||||
dir string
|
|
||||||
username string
|
|
||||||
password string
|
|
||||||
email string
|
|
||||||
stdOutBuffer *bytes.Buffer
|
|
||||||
stdErrBuffer *bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func locateDockerRegistryBinary() (string, error) {
|
|
||||||
if exePath, err := exec.LookPath("docker-registry"); err != nil {
|
|
||||||
if cwd, err := os.Getwd(); err != nil {
|
|
||||||
return "", err
|
|
||||||
} else {
|
|
||||||
return path.Join(cwd, "docker-registry"), nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return exePath, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupRegistryV2At(url string, auth bool) (*testRegistryV2, error) {
|
|
||||||
reg, err := newTestRegistryV2At(url, auth)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for registry to be ready to serve requests.
|
|
||||||
for i := 0; i != 50; i++ {
|
|
||||||
if err := reg.Ping("http"); err == nil {
|
|
||||||
fmt.Print("registry http ping succeeded\n")
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
fmt.Printf("registry http ping error: %v\n", err)
|
|
||||||
}
|
|
||||||
if err := reg.Ping("https"); err == nil {
|
|
||||||
fmt.Print("registry https ping succeeded\n")
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
fmt.Printf("registry https ping error: %v\n", err)
|
|
||||||
}
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return reg, errors.New("timeout waiting for test registry to become available")
|
|
||||||
}
|
|
||||||
return reg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestRegistryV2At(url string, auth bool) (*testRegistryV2, error) {
|
|
||||||
tmp, err := ioutil.TempDir("", "registry-test-")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
template := `version: 0.1
|
|
||||||
loglevel: info
|
|
||||||
storage:
|
|
||||||
filesystem:
|
|
||||||
rootdirectory: %s
|
|
||||||
delete:
|
|
||||||
enabled: true
|
|
||||||
http:
|
|
||||||
addr: %s
|
|
||||||
%s`
|
|
||||||
var (
|
|
||||||
htpasswd string
|
|
||||||
username string
|
|
||||||
password string
|
|
||||||
email string
|
|
||||||
)
|
|
||||||
var env []string
|
|
||||||
if auth {
|
|
||||||
if certificate, key, err := getSelfSignedCertAndKey("localhost", time.Hour*24); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
certPath := filepath.Join(tmp, "domain.crt")
|
|
||||||
if err := ioutil.WriteFile(certPath, certificate, os.FileMode(0644)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
keyPath := filepath.Join(tmp, "domain.key")
|
|
||||||
if err := ioutil.WriteFile(keyPath, key, os.FileMode(0644)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
env = append(env, fmt.Sprintf("REGISTRY_HTTP_TLS_CERTIFICATE=%v", certPath))
|
|
||||||
env = append(env, fmt.Sprintf("REGISTRY_HTTP_TLS_KEY=%v", keyPath))
|
|
||||||
}
|
|
||||||
|
|
||||||
htpasswdPath := filepath.Join(tmp, "htpasswd")
|
|
||||||
userpasswd := "testuser:$2y$05$sBsSqk0OpSD1uTZkHXc4FeJ0Z70wLQdAX/82UiHuQOKbNbBrzs63m"
|
|
||||||
username = "testuser"
|
|
||||||
password = "testpassword"
|
|
||||||
email = "test@test.org"
|
|
||||||
if err := ioutil.WriteFile(htpasswdPath, []byte(userpasswd), os.FileMode(0644)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
htpasswd = fmt.Sprintf(`auth:
|
|
||||||
htpasswd:
|
|
||||||
realm: basic-realm
|
|
||||||
path: %s
|
|
||||||
`, htpasswdPath)
|
|
||||||
}
|
|
||||||
confPath := filepath.Join(tmp, "config.yaml")
|
|
||||||
config, err := os.Create(confPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if _, err := fmt.Fprintf(config, template, tmp, url, htpasswd); err != nil {
|
|
||||||
os.RemoveAll(tmp)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dockerRegistryBinaryPath, err := locateDockerRegistryBinary()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cmd := exec.Command(dockerRegistryBinaryPath, "serve", confPath)
|
|
||||||
cmd.Env = env
|
|
||||||
stdOutBuf, stdErrBuf, err := consumeAndLogOutputs(fmt.Sprintf("registry-%s", url), cmd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
os.RemoveAll(tmp)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &testRegistryV2{
|
|
||||||
cmd: cmd,
|
|
||||||
url: url,
|
|
||||||
dir: tmp,
|
|
||||||
username: username,
|
|
||||||
password: password,
|
|
||||||
email: email,
|
|
||||||
stdOutBuffer: stdOutBuf,
|
|
||||||
stdErrBuffer: stdErrBuf,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testRegistryV2) Ping(protocol string) error {
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
||||||
}
|
|
||||||
client := &http.Client{Transport: tr}
|
|
||||||
|
|
||||||
resp, err := client.Get(fmt.Sprintf("%v://%s/v2/", protocol, t.url))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusUnauthorized {
|
|
||||||
return fmt.Errorf("registry ping replied with an unexpected status code %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testRegistryV2) Close() {
|
|
||||||
t.cmd.Process.Kill()
|
|
||||||
os.RemoveAll(t.dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func consumeAndLogOutputStream(id string, f io.ReadCloser) *bytes.Buffer {
|
|
||||||
buff := &bytes.Buffer{}
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
f.Close()
|
|
||||||
fmt.Fprintf(buff, "[%s]: Closed\n", id)
|
|
||||||
}()
|
|
||||||
buf := make([]byte, 1024)
|
|
||||||
for {
|
|
||||||
fmt.Fprintf(buff, "[%s]: waiting\n", id)
|
|
||||||
n, err := f.Read(buf)
|
|
||||||
fmt.Fprintf(buff, "[%s]: got %d,%#v: %s\n", id, n, err, strings.TrimSuffix(string(buf[:n]), "\n"))
|
|
||||||
if n <= 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return buff
|
|
||||||
}
|
|
||||||
|
|
||||||
// consumeAndLogOutputs causes all output to stdout and stderr from an *exec.Cmd to be logged to c
|
|
||||||
func consumeAndLogOutputs(id string, cmd *exec.Cmd) (*bytes.Buffer, *bytes.Buffer, error) {
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
stderr, err := cmd.StderrPipe()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return consumeAndLogOutputStream(id+" stdout", stdout), consumeAndLogOutputStream(id+" stderr", stderr), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
|
||||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("ailed to generate serial number: %s", err)
|
|
||||||
}
|
|
||||||
template := x509.Certificate{
|
|
||||||
SerialNumber: serialNumber,
|
|
||||||
Subject: pkix.Name{
|
|
||||||
Organization: []string{"self-signed"},
|
|
||||||
},
|
|
||||||
NotBefore: time.Now(),
|
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("failed to create certificate: %s", err)
|
|
||||||
}
|
|
||||||
certificate = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
|
||||||
|
|
||||||
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("unable to marshal private key: %v", err)
|
|
||||||
}
|
|
||||||
key = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
|
|
||||||
|
|
||||||
return certificate, key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func testPullImage(image, imagesDir string, registry *testRegistryV2) error {
|
|
||||||
srcRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%v", image))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
nameTag := getImageNameParts(image)
|
|
||||||
targetDir := filepath.Join(imagesDir, imageIndexDirName, nameTag.name, nameTag.tag)
|
|
||||||
if err := os.MkdirAll(targetDir, os.ModePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
destRef, err := alltransports.ParseImageName(fmt.Sprintf("oci:%v", targetDir))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
policyContext, err := signature.NewPolicyContext(&signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer policyContext.Destroy()
|
|
||||||
|
|
||||||
fmt.Printf("==> Test is pulling image from %v\n", srcRef.StringWithinTransport())
|
|
||||||
sourceCtx := &imageTypes.SystemContext{
|
|
||||||
ArchitectureChoice: "amd64",
|
|
||||||
OSChoice: "linux",
|
|
||||||
DockerInsecureSkipTLSVerify: imageTypes.OptionalBoolTrue,
|
|
||||||
}
|
|
||||||
if registry.username != "" {
|
|
||||||
sourceCtx.DockerAuthConfig = &imageTypes.DockerAuthConfig{
|
|
||||||
Username: registry.username,
|
|
||||||
Password: registry.password,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := copy.Image(context.Background(), policyContext, destRef, srcRef, ©.Options{
|
|
||||||
ReportWriter: os.Stdout,
|
|
||||||
SourceCtx: sourceCtx,
|
|
||||||
DestinationCtx: &imageTypes.SystemContext{
|
|
||||||
OCISharedBlobDirPath: filepath.Join(imagesDir, imageSharedBlobsDirName),
|
|
||||||
},
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package qliksense
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
kapis_git "github.com/qlik-oss/k-apis/pkg/git"
|
|
||||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
QLIK_GIT_REPO = "https://github.com/qlik-oss/qliksense-k8s"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (q *Qliksense) FetchQK8s(version string) error {
|
|
||||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
|
||||||
return fetchAndUpdateCR(qConfig, version)
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchAndUpdateCR(qConfig *qapi.QliksenseConfig, version string) error {
|
|
||||||
qcr, err := qConfig.GetCurrentCR()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("cannot get the current-context cr", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if qConfig.IsRepoExistForCurrent(version) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
destDir := qConfig.BuildRepoPath(version)
|
|
||||||
fmt.Printf("fetching version [%s] from %s\n", version, QLIK_GIT_REPO)
|
|
||||||
|
|
||||||
if repo, err := kapis_git.CloneRepository(destDir, QLIK_GIT_REPO, nil); err != nil {
|
|
||||||
return err
|
|
||||||
} else if err = kapis_git.Checkout(repo, version, fmt.Sprintf("%v-by-operator-%v", version, uuid.New().String()), nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
qcr.Spec.ManifestsRoot = qConfig.BuildCurrentManifestsRoot(version)
|
|
||||||
qcr.AddLabelToCr("version", version)
|
|
||||||
return qConfig.WriteCurrentContextCR(qcr)
|
|
||||||
}
|
|
||||||
@@ -1,212 +0,0 @@
|
|||||||
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
|
|
||||||
StorageClass string
|
|
||||||
MongoDbUri string
|
|
||||||
RotateKeys string
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
// step3: config apply | kubectl apply -f # generates patches (if required) in configuration directory, applies manifest
|
|
||||||
// step4: config view | kubectl apply -f # generates Custom Resource manifest (CR)
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
fmt.Println("cannot get the current-context cr", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.AcceptEULA != "" {
|
|
||||||
qcr.Spec.AddToConfigs("qliksense", "acceptEULA", opts.AcceptEULA)
|
|
||||||
}
|
|
||||||
if opts.MongoDbUri != "" {
|
|
||||||
qcr.Spec.AddToSecrets("qliksense", "mongoDbUri", opts.MongoDbUri, "")
|
|
||||||
}
|
|
||||||
if opts.StorageClass != "" {
|
|
||||||
qcr.Spec.StorageClassName = opts.StorageClass
|
|
||||||
}
|
|
||||||
if opts.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")
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// 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 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
qcr, err = qConfig.GetCurrentCR()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("cannot get the current-context cr", err)
|
|
||||||
return err
|
|
||||||
} else if qcr.Spec.GetManifestsRoot() == "" {
|
|
||||||
return errors.New("cannot get the manifest root. Use qliksense fetch <version> or qliksense set manifestsRoot")
|
|
||||||
}
|
|
||||||
|
|
||||||
// install generated manifests into cluster
|
|
||||||
fmt.Println("Installing generated manifests into cluster")
|
|
||||||
|
|
||||||
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
|
|
||||||
return err
|
|
||||||
} else if err := q.applyConfigToK8s(dcr); err != nil {
|
|
||||||
fmt.Println("cannot do kubectl apply on manifests")
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
return q.applyCR(dcr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 := cr.GetString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package qliksense
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
|
||||||
"sigs.k8s.io/kustomize/api/krusty"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func 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)
|
|
||||||
}()
|
|
||||||
|
|
||||||
options := &krusty.Options{
|
|
||||||
DoLegacyResourceSort: false,
|
|
||||||
LoadRestrictions: types.LoadRestrictionsNone,
|
|
||||||
DoPrune: false,
|
|
||||||
PluginConfig: konfig.DisabledPluginConfig(),
|
|
||||||
}
|
|
||||||
k := krusty.MakeKustomizer(fSys, options)
|
|
||||||
resMap, err := k.Run(directory)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return resMap.AsYaml()
|
|
||||||
}
|
|
||||||
|
|
||||||
func executeKustomizeBuildWithStdoutProgress(path string) (kuzManifest []byte, err error) {
|
|
||||||
result, err := api.ExecuteTaskWithBlinkingStdoutFeedback(func() (interface{}, error) {
|
|
||||||
return executeKustomizeBuild(path)
|
|
||||||
}, "...")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return result.([]byte), nil
|
|
||||||
}
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
package qliksense
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
|
|
||||||
"github.com/Shopify/ejson"
|
|
||||||
"github.com/qlik-oss/k-apis/pkg/config"
|
|
||||||
"github.com/qlik-oss/k-apis/pkg/qust"
|
|
||||||
|
|
||||||
kapis_git "github.com/qlik-oss/k-apis/pkg/git"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_executeKustomizeBuild(t *testing.T) {
|
|
||||||
tmpDir, err := ioutil.TempDir("", "")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v\n", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpDir)
|
|
||||||
|
|
||||||
kustomizationYamlFilePath := path.Join(tmpDir, "kustomization.yaml")
|
|
||||||
kustomizationYaml := `
|
|
||||||
generatorOptions:
|
|
||||||
disableNameSuffixHash: true
|
|
||||||
configMapGenerator:
|
|
||||||
- name: foo-config
|
|
||||||
literals:
|
|
||||||
- foo=bar
|
|
||||||
`
|
|
||||||
err = ioutil.WriteFile(kustomizationYamlFilePath, []byte(kustomizationYaml), os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error writing kustomization file to path: %v error: %v\n", kustomizationYamlFilePath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := executeKustomizeBuild(tmpDir)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected kustomize error: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedK8sYaml := `apiVersion: v1
|
|
||||||
data:
|
|
||||||
foo: bar
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: foo-config
|
|
||||||
`
|
|
||||||
if string(result) != expectedK8sYaml {
|
|
||||||
t.Fatalf("expected k8s yaml: [%v] but got: [%v]\n", expectedK8sYaml, string(result))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
|
|
||||||
tmpDir, err := ioutil.TempDir("", "")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v\n", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpDir)
|
|
||||||
|
|
||||||
configPath := path.Join(tmpDir, "config")
|
|
||||||
if repo, err := kapis_git.CloneRepository(configPath, defaultConfigRepoGitUrl, nil); err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v\n", err)
|
|
||||||
} else if err := kapis_git.Checkout(repo, "v1.21.23-edge", "", nil); err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cr := &config.CRSpec{
|
|
||||||
ManifestsRoot: configPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Setenv("EJSON_KEYDIR", tmpDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error setting EJSON_KEYDIR environment variable: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Unsetenv("EJSON_KEY"); err != nil {
|
|
||||||
t.Fatalf("unexpected error unsetting EJSON_KEY: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
generateKeys(cr, "won't-use")
|
|
||||||
|
|
||||||
yamlResources, err := executeKustomizeBuild(path.Join(configPath, "manifests", "base", "resources", "users"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected kustomize error: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder := yaml.NewDecoder(bytes.NewReader(yamlResources))
|
|
||||||
var resource map[string]interface{}
|
|
||||||
keyIdBase64 := ""
|
|
||||||
for {
|
|
||||||
err := decoder.Decode(&resource)
|
|
||||||
if err != nil {
|
|
||||||
if err != io.EOF {
|
|
||||||
t.Fatalf("unexpected yaml decode error: %v\n", err)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if resource["kind"].(string) == "Secret" && strings.Contains(resource["metadata"].(map[string]interface{})["name"].(string), "users-secrets-") {
|
|
||||||
keyIdBase64 = resource["data"].(map[string]interface{})["tokenAuthPrivateKeyId"].(string)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
untransformedKeyId := `(( (ds "data").kid ))`
|
|
||||||
if keyIdBase64 == "" {
|
|
||||||
t.Fatalf("expected keyIdBase64 for users secret to be non empty:\n")
|
|
||||||
} else if keyId, err := base64.StdEncoding.DecodeString(keyIdBase64); err != nil {
|
|
||||||
t.Fatalf("unexpected base64 decode error: %v\n", err)
|
|
||||||
} else if string(keyId) == untransformedKeyId {
|
|
||||||
t.Fatalf("unexpected users keyId: %v\n", untransformedKeyId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateKeys(cr *config.CRSpec, defaultKeyDir string) {
|
|
||||||
log.Println("rotating all keys")
|
|
||||||
keyDir := getEjsonKeyDir(defaultKeyDir)
|
|
||||||
if ejsonPublicKey, ejsonPrivateKey, err := ejson.GenerateKeypair(); err != nil {
|
|
||||||
log.Printf("error generating an ejson key pair: %v\n", err)
|
|
||||||
} else if err := qust.GenerateKeys(cr, ejsonPublicKey); err != nil {
|
|
||||||
log.Printf("error generating application keys: %v\n", err)
|
|
||||||
} else if err := os.MkdirAll(keyDir, os.ModePerm); err != nil {
|
|
||||||
log.Printf("error makeing sure private key storage directory: %v exists, error: %v\n", keyDir, err)
|
|
||||||
} else if err := ioutil.WriteFile(path.Join(keyDir, ejsonPublicKey), []byte(ejsonPrivateKey), os.ModePerm); err != nil {
|
|
||||||
log.Printf("error storing ejson private key: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getEjsonKeyDir(defaultKeyDir string) string {
|
|
||||||
ejsonKeyDir := os.Getenv("EJSON_KEYDIR")
|
|
||||||
if ejsonKeyDir == "" {
|
|
||||||
ejsonKeyDir = defaultKeyDir
|
|
||||||
}
|
|
||||||
return ejsonKeyDir
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package qliksense
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (q *Qliksense) ViewOperator() error {
|
|
||||||
io.WriteString(os.Stdout, q.GetOperatorCRDString())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Qliksense) ViewOperatorController() error {
|
|
||||||
io.WriteString(os.Stdout, q.GetOperatorControllerString())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// this will return crd,deployment,role, rolebinding,serviceaccount for operator
|
|
||||||
func (q *Qliksense) GetOperatorCRDString() string {
|
|
||||||
result := ""
|
|
||||||
for _, v := range q.getFileList("crd") {
|
|
||||||
result = q.getYamlFromPackrFile(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Qliksense) GetOperatorControllerString() string {
|
|
||||||
result := ""
|
|
||||||
for _, v := range q.getFileList("crd-deploy") {
|
|
||||||
result = result + q.getYamlFromPackrFile(v)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Qliksense) getYamlFromPackrFile(packrFile string) string {
|
|
||||||
s, err := q.CrdBox.FindString(packrFile)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Cannot read file %s", packrFile)
|
|
||||||
}
|
|
||||||
return fmt.Sprintln("#soruce: " + packrFile + "\n\n" + s + "\n---")
|
|
||||||
}
|
|
||||||
func (q *Qliksense) getFileList(resourceType string) []string {
|
|
||||||
var resList []string
|
|
||||||
for _, v := range q.CrdBox.List() {
|
|
||||||
if strings.Contains(v, filepath.Join(resourceType, "")) {
|
|
||||||
resList = append(resList, []string{v}...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resList
|
|
||||||
}
|
|
||||||
59
pkg/qliksense/porter.go
Normal file
59
pkg/qliksense/porter.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package qliksense
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessLine ...
|
||||||
|
type ProcessLine func(string) *string
|
||||||
|
|
||||||
|
// CallPorter ...
|
||||||
|
func (p *Qliksense) CallPorter(args []string, processor ProcessLine) (string, error) {
|
||||||
|
var (
|
||||||
|
outText string
|
||||||
|
cmd *exec.Cmd
|
||||||
|
err error
|
||||||
|
output io.ReadCloser
|
||||||
|
scanner *bufio.Scanner
|
||||||
|
done chan struct{}
|
||||||
|
)
|
||||||
|
cmd = exec.Command(p.PorterExe, args[:]...)
|
||||||
|
if output, err = cmd.StdoutPipe(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
done = make(chan struct{})
|
||||||
|
scanner = bufio.NewScanner(output)
|
||||||
|
go func() {
|
||||||
|
for scanner.Scan() {
|
||||||
|
var text string
|
||||||
|
var newText *string
|
||||||
|
text = scanner.Text()
|
||||||
|
if processor != nil {
|
||||||
|
newText = processor(text)
|
||||||
|
if newText != nil {
|
||||||
|
outText = outText + fmt.Sprintln(*newText)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
outText = outText + fmt.Sprintln(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done <- struct{}{}
|
||||||
|
}()
|
||||||
|
if err = cmd.Start(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
<-done
|
||||||
|
if err = cmd.Wait(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err = scanner.Err(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return outText, nil
|
||||||
|
}
|
||||||
@@ -1,22 +1,15 @@
|
|||||||
//go:generate packr2
|
|
||||||
package qliksense
|
package qliksense
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gobuffalo/packr/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Qliksense is the logic behind the qliksense client
|
// Qliksense is the logic behind the qliksense client
|
||||||
type Qliksense struct {
|
type Qliksense struct {
|
||||||
|
PorterExe string
|
||||||
QliksenseHome string
|
QliksenseHome string
|
||||||
CrdBox *packr.Box ``
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New qliksense client, initialized with useful defaults.
|
// New qliksense client, initialized with useful defaults.
|
||||||
func New(qliksenseHome string) *Qliksense {
|
func New(porterExe, qliksenseHome string) *Qliksense {
|
||||||
qliksenseClient := &Qliksense{
|
return &Qliksense{
|
||||||
|
PorterExe: porterExe,
|
||||||
QliksenseHome: qliksenseHome,
|
QliksenseHome: qliksenseHome,
|
||||||
CrdBox: packr.New("crds", "./crds"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return qliksenseClient
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package qliksense
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (q *Qliksense) UninstallQK8s(contextName string) error {
|
|
||||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
|
||||||
if contextName == "" {
|
|
||||||
contextName = qConfig.Spec.CurrentContext
|
|
||||||
} else if !qConfig.IsContextExist(contextName) {
|
|
||||||
return errors.New("context name [ " + contextName + " ] not found")
|
|
||||||
}
|
|
||||||
str, err := q.getCRString(contextName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return qapi.KubectlDelete(str, "")
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package qliksense
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (q *Qliksense) UpgradeQK8s(keepPatchFiles bool) error {
|
|
||||||
|
|
||||||
// step1: get CR
|
|
||||||
// step2: run kustomize
|
|
||||||
// step3: run kubectl apply
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
fmt.Println("cannot get the current-context cr", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
qcr.Spec.RotateKeys = "no"
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Install operator CR into cluster")
|
|
||||||
r, err := qcr.GetString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := qapi.KubectlApply(r, ""); err != nil {
|
|
||||||
fmt.Println("cannot do kubectl apply on operator CR")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
39
test/Dockerfile.tmpl
Normal file
39
test/Dockerfile.tmpl
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
FROM quay.io/deis/lightweight-docker-go:v0.2.0
|
||||||
|
FROM debian:stretch as base
|
||||||
|
# This is a template Dockerfile for the bundle's invocation image
|
||||||
|
# You can customize it to use different base images, install tools and copy configuration files.
|
||||||
|
#
|
||||||
|
# Porter will use it as a template and append lines to it for the mixins
|
||||||
|
# and to set the CMD appropriately for the CNAB specification.
|
||||||
|
#
|
||||||
|
# Add the following line to porter.yaml to instruct Porter to use this template
|
||||||
|
# dockerfile: Dockerfile.tmpl
|
||||||
|
|
||||||
|
# You can control where the mixin's Dockerfile lines are inserted into this file by moving "# PORTER_MIXINS" line
|
||||||
|
# another location in this file. If you remove that line, the mixins generated content is appended to this file.
|
||||||
|
# PORTER_MIXINS
|
||||||
|
|
||||||
|
COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
|
ARG BUNDLE_DIR
|
||||||
|
ARG HELM_VERSION=v2.16.0
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y curl && \
|
||||||
|
curl -o helm.tgz https://get.helm.sh/helm-$HELM_VERSION-linux-amd64.tar.gz && \
|
||||||
|
tar -xzf helm.tgz && \
|
||||||
|
mv linux-amd64/helm /usr/local/bin && \
|
||||||
|
rm helm.tgz
|
||||||
|
RUN helm init --client-only
|
||||||
|
|
||||||
|
# CI job will update this version and make a PR
|
||||||
|
ARG QSEOK_VERSION=1.12.89
|
||||||
|
# This is likely not needed
|
||||||
|
ARG QLIKSENSE_INIT_VERSION=1.1.0
|
||||||
|
|
||||||
|
RUN mkdir -p /tmp/.chartcache
|
||||||
|
RUN helm repo add qlik-edge https://qlik.bintray.com/edge
|
||||||
|
RUN helm fetch qlik-edge/qliksense-init --version $QLIKSENSE_INIT_VERSION --untar -d /tmp/.chartcache/
|
||||||
|
# This is now done by qliksense mixin
|
||||||
|
#RUN helm fetch qlik-edge/qliksense --version $QSEOK_VERSION --untar -d /tmp/.chartcache/
|
||||||
|
|
||||||
|
# Use the BUNDLE_DIR build argument to copy files into the bundle
|
||||||
|
COPY . $BUNDLE_DIR
|
||||||
42
test/porter.yaml
Normal file
42
test/porter.yaml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: test-version
|
||||||
|
version: 0.1.0
|
||||||
|
description: "An example Porter bundle demonstrating exec mixin outputs"
|
||||||
|
invocationImage: qlik/test-version:0.1.0
|
||||||
|
tag: qlik/test-version-bundle:0.1.0
|
||||||
|
|
||||||
|
dockerfile: Dockerfile.tmpl
|
||||||
|
|
||||||
|
mixins:
|
||||||
|
- exec
|
||||||
|
- qliksense
|
||||||
|
|
||||||
|
customActions:
|
||||||
|
about:
|
||||||
|
description: "About QLiksense"
|
||||||
|
stateless: true
|
||||||
|
modifies: true
|
||||||
|
|
||||||
|
install:
|
||||||
|
- exec:
|
||||||
|
description: "Get the qliksense version"
|
||||||
|
command: bash
|
||||||
|
arguments:
|
||||||
|
- -c
|
||||||
|
- "echo 1.2.13"
|
||||||
|
outputs:
|
||||||
|
- name: version
|
||||||
|
regex: '.*'
|
||||||
|
uninstall:
|
||||||
|
- exec:
|
||||||
|
description: "Get the qliksense version"
|
||||||
|
command: bash
|
||||||
|
arguments:
|
||||||
|
- -c
|
||||||
|
- "echo 1.2.13"
|
||||||
|
outputs:
|
||||||
|
- name: version
|
||||||
|
regex: '.*'
|
||||||
|
about:
|
||||||
|
- qliksense:
|
||||||
|
description: "Use bundled version by default"
|
||||||
|
version: "bundled"
|
||||||
Reference in New Issue
Block a user