Compare commits

...

64 Commits

Author SHA1 Message Date
Ilir Bekteshi
df19cadcb6 Merge pull request #207 from qlik-oss/ibiqlik/upx
Move build from CircleCi to GitHub Actions; use UPX to compress binaries #88
2020-03-17 12:51:46 +01:00
Ilir Bekteshi
d9cbbf54cc Merge pull request #211 from qlik-oss/ibiqlik/tidydocs
Tidying up docs/readme
2020-03-17 10:39:16 +01:00
Foysal Iqbal
69aca05a86 fix doubble print (#217)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-16 16:08:07 -04:00
Ilir Bekteshi
e4d69f059a Archive uncompressed binaries, remove lzma in upx 2020-03-16 17:58:11 +01:00
Andriy Bulynko
0b2fdae015 Scoping ejson keys and their rotations to the current context/CR (#214) 2020-03-13 16:28:06 -04:00
Ilir Bekteshi
cfc8fbb1f1 Tidying up docs/readme 2020-03-13 14:58:34 +01:00
Ilir Bekteshi
d38852398e Merge pull request #212 from qlik-oss/ibiqlik/archiveignore
Ignore files/dirs on export
2020-03-12 14:06:33 +01:00
Ilir Bekteshi
e85636822d Ignore files/dirs on export 2020-03-12 14:03:20 +01:00
Mo Kassem
b9a80f588d Merge pull request #204 from qlik-oss/initmkdocs
Init mkdocs and gh workflow for publishing docs
2020-03-11 16:44:43 -04:00
Ilir Bekteshi
b9074d9f3c Change branch to master 2020-03-11 21:33:49 +01:00
Ilir Bekteshi
f3a3e97618 Init mkdocs and gh workflow for publishing docs
Initialize mkdocs for serving documentation on GitHub pages
On push to ms-3 branch a workflow publishes the documentation to gh-pages which gets served by GitHub
The content is based on README.md
2020-03-11 21:32:34 +01:00
Sanat Nayar
5c56013a70 Merge pull request #208 from qlik-oss/context_delete
context-delete Unit Tests
2020-03-11 14:58:19 -04:00
Sanat Nayar
134dbd44ed fixed error handling 2020-03-11 14:54:30 -04:00
Sanat Nayar
9898d3b9ec added tests 2020-03-11 11:27:46 -04:00
Ilir Bekteshi
bdcadebeca Split workflow for PR and Release
Release workflow builds all variants and compresses them using UPX. All files under bin/ are uploaded to the release.
2020-03-11 11:46:37 +01:00
Ilir Bekteshi
626a2ebe68 Fetch git tags 2020-03-11 11:46:37 +01:00
Ilir Bekteshi
1f64641ab1 Use https instead of ssh for cloning git repo (ssh key issues) 2020-03-11 11:46:37 +01:00
Ilir Bekteshi
b764fd179d Add GOPATH workaround 2020-03-11 11:46:37 +01:00
Ilir Bekteshi
e8d1899a41 Use UPX to compress; mv build from circle to GH
The build process is moving to GitHub Actions from CircleCi
The tar archiving is removed as it does not serve a purpose when UPX compression is at the same file size
2020-03-11 11:46:37 +01:00
Sanat Nayar
32fa0a6570 added tests 2020-03-10 14:42:34 -04:00
Sanat Nayar
0bf1f3ca3a added tests 2020-03-10 14:22:39 -04:00
Sanat Nayar
8f56872842 Merge branch 'ms-3' into context_delete 2020-03-10 10:31:08 -04:00
Andriy Bulynko
defdb899b7 Locking pack2 dependency to v2.7.1 (#203) 2020-03-10 10:06:46 -04:00
Sanat Nayar
c7478fb8c1 added tests 2020-03-09 14:59:21 -04:00
Sanat Nayar
34df4b3a5c added tests 2020-03-09 14:54:48 -04:00
Sanat Nayar
c7bac06533 Merge branch 'ms-3' into context_delete 2020-03-09 14:52:03 -04:00
Sanat Nayar
89d5e261ab added tests 2020-03-09 14:50:33 -04:00
Sanat Nayar
6cd70cb643 added tests 2020-03-09 14:48:16 -04:00
Andriy Bulynko
941bb76444 No need to fetch anything before executing "qliksense crds" commands (#195) 2020-03-09 11:32:28 -04:00
Foysal Iqbal
513daa54f4 Text from encrypted (#196)
* fix secret naming
* Fixed a bug with setting git.repo
* fixed code to get decrypted CR and corresponding test
2020-03-08 20:08:30 -04:00
Andriy Bulynko
46b40d6011 Optionally keep temporary patches in the config repo after each install/upgrade and use an explicit command to remove these patches later (#193) 2020-03-06 15:08:02 -05:00
Sanat Nayar
7893329ab7 Merge pull request #152 from qlik-oss/context_delete
qliksense delete-context
2020-03-06 13:07:29 -05:00
Sanat Nayar
a127127317 reverted tests 2020-03-06 11:50:26 -05:00
Sanat Nayar
d8f1ab4f30 deleted secrets 2020-03-06 11:46:45 -05:00
Ashwathi Shiva
37bf4eae2b Merge branch 'context_delete' of github.com:qlik-oss/sense-installer into context_delete 2020-03-05 15:29:41 -05:00
Ashwathi Shiva
376f6ae838 Merge branch 'ms-3' into context_delete 2020-03-05 15:15:55 -05:00
Foysal Iqbal
659db113d7 Fix cr (#191) 2020-03-05 14:26:04 -05:00
Foysal Iqbal
19e8eda3a3 Fix install withsecret (#190) 2020-03-05 14:17:57 -05:00
Sanat Nayar
12e511ab04 added tests 2020-03-05 14:17:07 -05:00
Andriy Bulynko
3fec90e50b Pull/push to private image registry include the operator image (#187) 2020-03-05 14:12:27 -05:00
Sanat Nayar
36c32d4ca6 added tests 2020-03-05 13:58:55 -05:00
Sanat Nayar
21d7e63588 added tests 2020-03-05 13:57:16 -05:00
Sanat Nayar
7397fb3b34 added tests 2020-03-05 13:35:29 -05:00
Sanat Nayar
8608a69406 added tests 2020-03-05 13:32:26 -05:00
Sanat Nayar
e530a6a79e added unit tests 2020-03-05 10:27:25 -05:00
Foysal Iqbal
096ba5062b fix secret naming (#189)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-05 09:57:03 -05:00
Ashwathi Shiva
2719da19a5 fixed base64 decode logic in one spot, updated tests (#188)
* fix install with secret
* fixed code and tests
2020-03-04 18:54:16 -05:00
Foysal Iqbal
0d3ba901ef make accept eula mandatory for a context (#186)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-04 16:54:32 -05:00
Sanat Nayar
9630453a24 added unit tests 2020-03-04 15:55:01 -05:00
Sanat Nayar
a6d81fa8a5 added unit tests 2020-03-04 15:47:20 -05:00
Ashwathi Shiva
758496cac7 Will not display error message twice (#185)
* fixed a bug that shows error twice
* updated Readme and command help
2020-03-04 12:36:39 -05:00
Ashwathi Shiva
7fadbb8392 Command qliksense config fixed, unit tests added (#184)
qliksense config working and added unit tests
2020-03-04 10:51:21 -05:00
Andriy Bulynko
1c8e4df00a Applying private registry pull secret to the cluster (#181) 2020-03-03 17:51:06 -05:00
Foysal Iqbal
27226568fb Fix namespace (#182) 2020-03-03 15:10:42 -05:00
Sanat Nayar
36008ab0dc Merge branch 'ms-3' into context_delete 2020-02-28 09:36:37 -05:00
Sanat Nayar
9758746361 Merge branch 'ms-3' into context_delete 2020-02-27 16:12:04 -05:00
Sanat Nayar
1bbf82a15a prevented default deletion 2020-02-27 15:50:07 -05:00
Sanat Nayar
c65fad8f5c updated docs 2020-02-27 14:37:01 -05:00
Sanat Nayar
b29c1ec193 updated docs 2020-02-27 14:31:50 -05:00
Sanat Nayar
287ff62507 updated docs 2020-02-27 14:31:00 -05:00
Sanat Nayar
74d6863acf delete context change 2020-02-25 15:42:53 -05:00
Sanat Nayar
d261be6c13 delete context change 2020-02-25 11:41:41 -05:00
Sanat Nayar
a3a6c47375 delete context change 2020-02-24 16:41:35 -05:00
Sanat Nayar
b413e1bca9 init 2020-02-20 14:39:52 -05:00
51 changed files with 1960 additions and 559 deletions

View File

@@ -1,43 +0,0 @@
# Golang CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-go/ for more details
version: 2
jobs:
build:
docker:
- image: circleci/golang:stretch
working_directory: /go/src/github.com/qlik-oss/sense-installer
steps:
- checkout
- run: make test
- run: make build
build_release:
docker:
- image: circleci/golang:stretch
working_directory: /go/src/github.com/qlik-oss/sense-installer
steps:
- checkout
- run: make test
- run: make xbuild-all
- run:
name: "Publish Release on GitHub"
command: |
go get github.com/tcnksm/ghr
ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${CIRCLE_TAG} /go/src/github.com/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/bin/${CIRCLE_TAG}/
workflows:
version: 2
commit:
jobs:
- build:
filters:
tags:
ignore: /^v.*/
build_release:
jobs:
- build_release:
filters:
tags:
only: /^v.*/
branches:
ignore: /.*/

5
.gitattributes vendored Normal file
View File

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

27
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Build Sense installer
on: [pull_request]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.13
uses: actions/setup-go@v1
with:
go-version: 1.13
- uses: actions/checkout@v2
- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
- name: Set GOPATH
# temporary fix
# see https://github.com/actions/setup-go/issues/14
run: |
echo "##[set-env name=GOPATH;]$(dirname $GITHUB_WORKSPACE)"
echo "##[add-path]$(dirname $GITHUB_WORKSPACE)/bin"
shell: bash
- run: make test
- run: make build

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

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

37
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Release Sense installer binaries
on:
push:
tags:
- 'v*.*.*'
jobs:
release:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.13
uses: actions/setup-go@v1
with:
go-version: 1.13
- uses: actions/checkout@v2
- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* # Needed in makefile for versioning
- name: Set GOPATH
# temporary fix
# see https://github.com/actions/setup-go/issues/14
run: |
echo "##[set-env name=GOPATH;]$(dirname $GITHUB_WORKSPACE)"
echo "##[add-path]$(dirname $GITHUB_WORKSPACE)/bin"
shell: bash
- run: make test
- run: make xbuild-all
- name: Release
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: bin/**/*

1
.gitignore vendored
View File

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

23
MKDOCS.md Normal file
View File

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

View File

@@ -44,40 +44,47 @@ build: clean generate
$(MAKE) clean
.PHONY: test
test:
test: clean generate
ifeq ($(shell ${WHICH} docker-registry 2>${DEVNUL}),)
$(eval TMP := $(shell mktemp -d))
git clone https://github.com/docker/distribution.git $(TMP)/docker-distribution
cd $(TMP)/docker-distribution; git checkout -b v2.7.1; make
cp $(TMP)/docker-distribution/bin/registry pkg/qliksense/docker-registry
-rm -rf $(TMP)/docker-distribution
endif
go test -short -count=1 -tags "$(BUILDTAGS)" -v ./...
$(MAKE) clean
xbuild-all: clean generate
$(foreach OS, $(SUPPORTED_PLATFORMS), \
$(foreach ARCH, $(SUPPORTED_ARCHES), \
$(MAKE) $(MAKE_OPTS) CLIENT_PLATFORM=$(OS) CLIENT_ARCH=$(ARCH) MIXIN=$(MIXIN) xbuild; \
))
$(foreach ARCH, $(SUPPORTED_ARCHES), \
$(MAKE) $(MAKE_OPTS) CLIENT_PLATFORM=$(OS) CLIENT_ARCH=$(ARCH) MIXIN=$(MIXIN) xbuild; \
))
$(MAKE) clean
xbuild: $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
$(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT):
mkdir -p $(dir $@)
GOOS=$(CLIENT_PLATFORM) GOARCH=$(CLIENT_ARCH) $(XBUILD) -o $@ ./cmd/$(MIXIN)
tar -czvf $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).tar.gz -C $(BINDIR)/$(VERSION)/ $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
#tar -C $(BINDIR)/$(VERSION)/ -cvf $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).tar.gz $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
ifeq ($(CLIENT_PLATFORM),windows)
zip $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).zip $(BINDIR)/$(VERSION)/ $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
else
tar -czvf $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).tar.gz -C $(BINDIR)/$(VERSION)/ $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
endif
upx $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
generate: get-crds packr2
go generate ./...
HAS_PACKR2 := $(shell packr2)
packr2:
ifndef HAS_PACKR2
go get -u github.com/gobuffalo/packr/v2/packr2
ifeq ($(shell ${WHICH} packr2 2>${DEVNUL}),)
go get -u github.com/gobuffalo/packr/v2/packr2@v2.7.1
endif
clean: clean-packr
-rm -rf /tmp/operator
-rm -fr pkg/qliksense/crds
clean-packr: packr2
@@ -85,10 +92,11 @@ clean-packr: packr2
get-crds:
$(eval TMP := $(shell mktemp -d))
git clone git@github.com:qlik-oss/qliksense-operator.git -b ms-3 $(TMP)/operator
git clone https://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

143
README.md
View File

@@ -1,150 +1,25 @@
# (WIP) Qlik Sense installation and operations CLI
- [Qlik Sense installation and operations CLI](#qlik-sense-installation-and-operations-cli)
- [About](#about)
- [Future Direction](#future-direction)
- [Getting Started](#getting-started)
- [Requirements](#requirements)
- [Download](#download)
- [TL;DR](#TL;DR)
- [How qliksense CLI works](#how-qliksense-cli-works)
- [Witout Git Repo](#Without-git-repo)
- [With Git Repo](#With-a-git-repo)
- [Air Gapped](#air-gaped)
## Documentation
To learn more about Qlik Sense installer go to https://qlik-oss.github.io/sense-installer/
## About
The Qlik Sense installer CLI (qliksense) provides an imperitive interface to many of the configurations that need to be applied against the declaritive structure described in [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s). This cli faciliates to do
The Qlik Sense installer CLI (qliksense) provides an imperative interface to many of the configurations that need to be applied against the declarative structure described in [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s). This cli facilitates:
- installation of QSEoK
- installation of qliksense operator to manage QSEoK
- air gapped installation of QSEoK
This is a technology preview that uses qlik modified [kustomize](https://github.com/qlik-oss/kustomize) to kubernetes manifests of the versions of the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository.
This is a technology preview that uses Qlik modified [kustomize](https://github.com/qlik-oss/kustomize) to kubernetes manifests of the versions of the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository.
For each version of a qliksense sense edge build there should be a corresponding release in [qliksense-k8s] repository under [releases](https://github.com/qlik-oss/qliksense-k8s/releases)
For each version of a qliksense edge build there should be a corresponding release in [qliksense-k8s] repository under [releases](https://github.com/qlik-oss/qliksense-k8s/releases)
### Future Direction
- More operations:
- Expanded preflight checks
- Expand preflight checks
- backup/restore operations
- fully support airgap installation of QSEoK
- restore unwanted deletion of kubernetes resoureces
## Getting Started
### Requirements
- `kubectl` need to be installed and configured properly so that `kubectl` can connect to the kubernetes cluser. The `qliksense` CLI uses `kubectl` under the hood to perform operations on cluster
- (Docker Desktop setup tested for these instructions)
### Download
- Download the appropriate executable for your platform from the [releases page](https://github.com/qlik-oss/sense-installer/releases) and rename it to `qliksense`. All the examplease down below uses `qliksense`.
### TL;DR
- To download the version `v0.0.2` from qliksense-k8s [releases](https://github.com/qlik-oss/qliksense-k8s/releases).
```shell
$qliksense fetch v0.0.2
```
- To install CRDs for QSEoK and qliksense operator into the kubernetes cluster.
```shell
$qliksense crds install --all
```
- To install QSEoK into a namespace in the kubernetes cluster where `kubectl` is pointing to.
```shell
$qliksense install --acceptEULA="yes"
```
## How qliksense cli works
At the initialization `qliksense` cli create few files in the director `~/.qliksene` and it contains following files
```console
.qliksense
├── config.yaml
├── contexts
│   └── qlik-default
│   └── qlik-default.yaml
└── ejson
└── keys
```
`qlik-default.yaml` is a default CR has been created with some default values like this
```yaml
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: qlik-default
spec:
profile: docker-desktop
secrets:
qliksense:
- name: mongoDbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
rotateKeys: "yes"
releaseName: qlik-default
```
The `qliksense` cli creates a default qliksense context (different from kubectl context) named `qlik-default` which will be the prefix for all kubernetes resources created by the cli under this context latter on. New context and configuration can be created by the cli.
```console
$ qliksense config
do operations on/around CR
Usage:
qliksense config [command]
Available Commands:
apply generate the patchs and apply manifests to k8s
list-contexts retrieves the contexts and lists them
set configure a key value pair into the current context
set-configs set configurations into the qliksense context
set-context Sets the context in which the Kubernetes cluster and resources live in
set-secrets set secrets configurations into the qliksense context
view view the qliksense operator CR
Flags:
-h, --help help for config
Use "qliksense config [command] --help" for more information about a command.
```
`qliksense` cli works in two modes
- with a git repo fork/clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s)
- without git repo
### Without git repo
In this mode `qliksense` CLI download the specified version from [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and put it into folder `~/.qliksense/contexts/<context-name>/qlik-k8s`.
The qliksense cli create a CR for the qliksense operator and all the config operations are peformed to edit the CR. So when `qliksense install` or `qliksense config apply` both generate patches in local file system (i.e `~/.qliksense/contexts/<context-name>/qlik-k8s`) and install those manifests into the cluster and create a custom resoruce (CR) for the `qliksene operator` then the operator make association to the isntalled resoruces so that when `qliksenes uninstall` is performed the operator can delete all those kubernetes resources related to QSEoK for the current context.
### With a git repo
User has to create fork or clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and push it to their own git server. When user perform `qliksense install` or `qliksene config apply` the qliksense operator do these tasks
- downloads the corresponding version of manifests from the user's git repo.
- generate kustomize patches
- install kubernetes resoruces
- push those generated patches into a new branch in the provided git repo. so that user user can merge those patches into their master branch.
- spinup a cornjob to monitor master branch. If user modifies anything in the master branch those changes will be applied into the cluster. This is a light weight `git-ops` model
This is how repo info is provided into the CR
```console
qliksense config set git.repository="https://github.com/my-org/qliksense-k8s"
qliksense config set git.accessToken=blablalaala
```
## Air gaped
- restore unwanted deletion of kubernetes resources

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

0
docs/air_gap.md Normal file
View File

77
docs/concepts.md Normal file
View File

@@ -0,0 +1,77 @@
# How qliksense cli works
At the initialization, `qliksense` cli creates few files in the director `~/.qliksene` and it contains following files:
```console
.qliksense
├── config.yaml
├── contexts
│   └── qlik-default
│   └── qlik-default.yaml
└── ejson
└── keys
```
`qlik-default.yaml` is a default CR created with some default values like:
```yaml
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: qlik-default
spec:
profile: docker-desktop
secrets:
qliksense:
- name: mongoDbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
rotateKeys: "yes"
releaseName: qlik-default
```
The `qliksense` cli creates a default qliksense context (different from kubectl context) named `qlik-default` which will be the prefix for all kubernetes resources created by the cli under this context later on.
New context and configuration can be created by the cli, get available commands using:
```console
qliksense config -h
```
---
`qliksense` cli works in two modes
- With a git repo fork/clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s)
- Without git repo
## Without git repo
In this mode `qliksense` CLI downloads the specified version from [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and places it in `~/.qliksense/contexts/<context-name>/qlik-k8s` folder.
The qliksense cli creates a CR for the QlikSense operator and all config operations are peformed to edit the CR.
`qliksense install` or `qliksense config apply` will generate patches in local file system (i.e `~/.qliksense/contexts/<context-name>/qlik-k8s`) and
- Install those manifests into the cluster
- Create a custom resoruce (CR) for the `qliksene operator`.
The operator makes the association to the installed resoruces so that when `qliksense uninstall` is performed the operator can delete all kubernetes resources related to QSEoK for the current context.
## With a git repo
Create a fork or clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and push it to your git repo/server
To add your repo into CR, perform the following:
```bash
qliksense config set git.repository="https://github.com/my-org/qliksense-k8s"
qliksense config set git.accessToken="<mySecretToken>"
```
When you perform `qliksense install` or `qliksene config apply`, qliksense operator performs these tasks:
- Download corresponding version of manifests from the your git repo
- Generate kustomize patches
- Install kubernetes resources
- Push generated patches into a new branch in the provided git repo. _Gives you ability to merge patches into your master branch_
- Create a CronJob to monitor master branch. Any changes pushed to master branch will be applied into the cluster. _This is a light weight `git-ops` model_

53
docs/getting_started.md Normal file
View File

@@ -0,0 +1,53 @@
# Getting started
## Requirements
- Kubernetes cluster (Docker Desktop with enabled Kubernetes)
- `kubectl` installed, configured and able to communicate with kubernetes cluster. _`qliksense` CLI uses `kubectl` under the hood to perform operations on cluster_
## Installing Sense installer
Download the executable for your platform from [releases page](https://github.com/qlik-oss/sense-installer/releases) and rename it to `qliksense`
??? tldr "Linux"
``` bash
curl -Lo qliksense https://github.com/qlik-oss/sense-installer/releases/download/v0.7.0/qliksense-linux-amd64
chmod +x qliksense
sudo mv qliksense /usr/local/bin
```
??? tldr "MacOS"
``` bash
curl -Lo qliksense https://github.com/qlik-oss/sense-installer/releases/download/v0.7.0/qliksense-darwin-amd64
chmod +x qliksense
sudo mv qliksense /usr/local/bin
```
??? tldr "Windows"
Download Windows executable and add it in your `PATH` as `qliksense.exe`
[https://github.com/qlik-oss/sense-installer/releases/download/v0.7.0/qliksense-windows-amd64.exe](https://github.com/qlik-oss/sense-installer/releases/download/v0.7.0/qliksense-windows-amd64.exe)
## Quick start
- To download the version `v0.0.2` from qliksense-k8s [releases](https://github.com/qlik-oss/qliksense-k8s/releases).
```shell
qliksense fetch v0.0.2
```
- To install CRDs for QSEoK and qliksense operator into the kubernetes cluster.
```shell
qliksense crds install --all
```
- To install QSEoK into a namespace in the kubernetes cluster where `kubectl` is pointing to.
```shell
qliksense install --acceptEULA="yes"
```

22
docs/index.md Normal file
View File

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

11
go.mod
View File

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

31
go.sum
View File

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

22
mkdocs.yml Normal file
View File

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

View File

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

View File

@@ -1,7 +1,9 @@
package api
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"testing"
@@ -45,12 +47,17 @@ metadata:
spec:
profile: docker-desktop
manifestsRoot: /Users/mqb/.qliksense/contexts/contx1/qlik-k8s/v0.0.1/manifests
namespace: myqliksense
storageClassName: efs
configs:
qliksense:
- name: acceptEULA
value: "yes"
secrets:
qliksense:
- name: mongoDbUri
# this is rsa encrypted value, the pub and pri keys are in setuPublicAndPrivateKey() method
# actual value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
value: n/pDi7Z/A3i16cAHFFwMp19/egNKc8WZxm6MKHLT/B1DMv3U6pDXWyXT5fYYDV1wDTO3Vk43yECST1UgZYmMpgUOwgSfGgqTVi2VqS0JQsnwI+Twwhnvha8RJANX8b/XIoSFVWaOgy7+RP35ZkvOqHdCfC2aT8JMIHgBQqqCbsNgimCuRSxi0klR000ic/Tp5PYSz5mD+WLrkPw2FbS0OVBsQ/hIp5GZrmVpvEOZdbT63Sz+n/G4Br6GTv2LkZcU7JBuKQm2wfB+mRjJmJnNrPawLfn2UZ89Rz0BLwIy+6b24/RoIUgoNowfGkJreGiwItGK8fjCcx11oavK/yAo6pYZXCcru46pmHbxxle1OlkdTKkG6EVtJuKjSZXtVmBHZYRFzsR7HnAiXnL7QzSEcS7ieZlQvTmNLfpidJhK199oSbyKREqXGl2S8DzPKM9RLccVbQTy6X8qWimP3MYCnO4K0KoQnNQAgfuV8ZxnvdDecByLDPIpmFMGy0Xm9pUZWxmSoDBq+p5WBI2HdCX2gCYVv5yxS2iBqO5SMKo8iOglHtPI9NIMvloERdN1vZtxSRkY5uDEfrU9ysYwfayEXxvXmdWv0HxlotcgUinP02j7k+OfIapTmY/jGfvF4euyCGRKuJ9JlSD9pIiRdAcekjL6hCxXLJLdajCV4sL/YDo=
`
ctx1Dir := filepath.Join(homeDir, "contexts", "contx1")
crFile := filepath.Join(ctx1Dir, "contx1.yaml")
@@ -83,3 +90,136 @@ func TestGetCR(t *testing.T) {
}
td()
}
func TestGetDecryptedCr(t *testing.T) {
td, dir := setup()
qc := NewQConfig(dir)
if qc.Spec.CurrentContext != "contx1" {
t.Fail()
}
// create CR
createCRFile(dir)
crFile := filepath.Join(dir, "contexts", "contx1", "contx1.yaml")
qct, e := qc.SetCrLocation("contx1", crFile)
if e != nil {
t.Fail()
t.Log(e)
}
qcr, err := qct.GetCurrentCR()
if err != nil {
t.Fail()
t.Log(err)
}
setuPublicAndPrivateKey(dir)
newCr, err := qct.GetDecryptedCr(qcr)
if err != nil {
t.Fail()
t.Log(err)
}
decryptedValue := newCr.Spec.GetFromSecrets("qliksense", "mongoDbUri")
orignalValue := qcr.Spec.GetFromSecrets("qliksense", "mongoDbUri")
if decryptedValue != "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false" {
t.Fail()
b, _ := K8sToYaml(newCr)
t.Log(b)
}
if decryptedValue == orignalValue {
t.Fail()
}
td()
}
func setuPublicAndPrivateKey(homeDir string) ([]byte, []byte, error) {
privKeyBytes := []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIJJwIBAAKCAgEAwUCimKCidbF3UxEHPy8K+hvhklRB9JYhj5sJy0if4lTVibkK
1MrYCykOnmC40pPU9GLY1b8HxAg9tvyRn0YHUxOra6vVQaVcOVJhTM8D18d+lSr3
Lp1yiX+UGT4nzWI9+R1CCbwXrqeQVoZs6QZKynEXMkFI9/wNMOwPOvQFOSTuoEoC
O+zyTyUWEkNbUq825ELUQdIsjgmlWUOONudxsAr7ESRXW9QTHVh6uWmr3VRKZHby
1JdU3I/wjdlGg5M2dDuXy5nQO9w/nYLjJXiw+zzOetZ/+t7/VOkOpNTeJQhwTM1W
F7Y2VLetbi9FHgyzHatrduh07+XEiTbgDf3GIx2bp2p6oh0G3N2zpiLcK/aZj8ro
uWWydfFfsU3MZ4FfJDP8I6b9awxjmKYqIr6hiPQCJaLBED8mwK+I5evIbnKv6E6u
K+BApWA/R7ElragoFYbqQ1VpvntVMtJt9Dy5ZrI+IQARdXD3bb34oh0IPBhClnvv
MUc1cWxDoXEX6oJ4I+LzxE87Zkwnan9qOwengolMVKFwPx1o37qrbmrXID21kKt7
FL6xN4HxHLkItr1fKzdyWDFRHgASTAWfx5BIwvPuUW0vZHkvO80VyV2L63whVhPn
PASmFkbviomrBttYfpr2aGQqF/qR1Nlxe834MFxk1pS9LMa/WnzvFr0gWakCAwEA
AQKCAgARSp9B2N2wejibDiL/3E23I1eDqFZedDB8kPrHXbAwqDaTJCN79spt9TaB
pVXkQaYEV/Pe7EDdoX8kKGU/QxzUqiXkdHOYdBtUZbKfFMbbP9ZrsnR7j0r4UpoF
yDH3hprU93E5PcNAtW2M0GpeT1nR01yn+n908PCdOAIE3GC7RDq1zOl2QzVLL55R
9ATv2Q2oTvJ/ETc7XlGVMx4+e2cIwXLFjeLjLI6pSYlxnarrGuetJZeEviWxto9n
odFVZI6yx8JFTXX8ZTCr/1IjwDDVyhMPmrHI2Lsv9cqBpSpbVe32cUkKxhsGaYjz
GvesQKamOPhco2ATNxPm0yopFlPsGKMfVl0BK0J6BqFh1BvU/SYJmXfnFuUNO3vV
4u2Saa0q1iddxV0rXDwIqUfn+S6rwzK0G7y8bH2yvpB2VwiG3TFPnULep4wsefNq
Fj92kqFBjacGpQLEEslUY0CMgeZ2+NuBQSUTscP3wBRsottMR6YXJtINdvfHBx+e
EcN71z8D00w3mYqIQ7qb4Ml6HOqknunn58g43L9sACMUMTlEBXa9pUnScNYgWBAz
W2q2mH37cIydM2JRZPpA8B4yTHt5ugJmChwyNFM7941arjKrebH+6AzLkofGedOP
zg+vZQuPEXWs+3MBBnkWoyJW3Y0fbQdjsuQTtnd+7iyoxoBroQKCAQEA4dIiFlIS
MDfRhQQWSiDvaw9aneDEJ3uo63ZRH5tm/IynLgtjYgEm/ZxlBCQgqRKLYELBxhu8
SaF0uPK8pmpFJt0mIwSlsdeVhuE2obQeKUCczaqrKeaHS3PdWLjTlwph81BGRkHy
qfqtNylyyMxrdEbnR51EtsWgFq6anTUAui1Q09JMuMNZRMOzDs1F4gExgD22rc0V
c9YQ+jHJRxBGtNKMpMEqc8cvaxBidbItrN9SMTSWog7uYPBuEuaJ6K9vpgyJMOzJ
SYcQEFGqgIqIDCg+ABE4d/4YROMKZ1DV/bJCind9brUHSx6XALsF0nC5c1Q9TnUL
qI2khOwts4KYKwKCAQEA2xRC6Az97Vkdzu7BjLJ1FKmx4S2nEEgVS12ds82U+5Xf
BHKAJnjqlqmmpzzJG+d77IYktz0+mey1QCNkqlm2fhuKs8LZMnpZRf0l8VcoBsUP
/xKz7wfiE7RRFZtLJhPp4hhe43GzX5/JFMWMnC6UykwQbj4t1E/GNM/Suqwvg12M
wktAJ6nqLgfhjQSO4xWo+nPzcbX+fNtrPCZVrBhYXihhcwRRNImWUCGJ6J4LMdPY
Y9Z59qhOvE9cReH/Xw1av46omyiSyAqlgPyZ/kzA2IJSqYCjiQR/2+RD/g13jpcJ
jatXLVZ8MJSL5OTS40G/HHTNNpNHbKKh0GOyxBA3ewKCAQBAn8UXhCcmW2L/YPsL
/b7mcX9qPP+FmRLvR23R0MQ5M/tH5wRq8I969n3GIJykJeVzB8eybQ+GNslTgEvS
iAkAJTubu+G7MkndTqg2wHf9MDtvdA8Fr646Po8yq7oJuHPtkKR7yLWsRUu6xIbP
xgheP0hCq1QVxhqZQyCGKrvpi7xc0gsYuPbcAfFFJCOCmPrUi1SzCkTAYJt9LjA+
wP6rErIjGBCRD4iXaBn1OqdtmH9KC5WsDP/VCBlIGWeQCly2NVIxiSHVg+xp7yUP
IhXq/L05gbQaSsIhPKQmivCiaJg4The8TdwneDqYf+0bmxzHT203/bD3bImPbJNr
ksz/AoIBAEwu4Y1cZzkQUmNRd5D7xecnk6ngfEYXKwCIT3zlMrfCSEl9n77BMaKu
4Dsr0iuX9eosQ7xM2eYhAG6LYEg05lc4MKWOToVVMpI6E+W3Dz47bPKgiF3I+f8s
Jz5CQIG/TwfGvciOE3hfUkec4ua09BzdEqGjkcBQ9XYMBxXPJr6h2379OBQS7FKR
fwfQ2/dv4tElXTTfut2kV8gU9Jnh5Wjo1epvR+XjKpg28YQo4W+0YX1magcyRB8L
4eSTUIC3XiVa8Jr0IwbZXPBb5xkdi7o+p4w2JahSHjxTRqmj+T1mnHXdbXVgq9Mg
9Pzl7cgFZvX4UBx4XtASRf73jITNtt0CggEADH9K+O7FrIOSQly0sMvsRCMtejp3
o+MDh1Q+vEg2kEgNXjS4ZFVljUpM2kg1OdUz7feS4dLXUJiIQ8ZWtZPedcq7wjHd
02he5+s06l0jPifN3tX1ADfXGpXg5R2fbkrIzakkPP5/RO/aDxIUo7qhklNsVTXO
VlGGfWLdk0ekA4upKm02Q1+YOlbIcAicEYYY8K7IffUwnohzKwL9yfuGi1VKTXpE
4fzdegsHI03FSqR7V+LvtBpIupQ7RO4kuBmCEyI4E9FVknchg4te4gO3qwd9y0rJ
Gu7HNIOrwOHzviI7J6Nd/l9MmeKqklHSgJvko/f5TmiXuQQ8xDZf84rcjQ==
-----END RSA PRIVATE KEY-----
`)
publicKeyBytes := []byte(`-----BEGIN RSA PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwUCimKCidbF3UxEHPy8K
+hvhklRB9JYhj5sJy0if4lTVibkK1MrYCykOnmC40pPU9GLY1b8HxAg9tvyRn0YH
UxOra6vVQaVcOVJhTM8D18d+lSr3Lp1yiX+UGT4nzWI9+R1CCbwXrqeQVoZs6QZK
ynEXMkFI9/wNMOwPOvQFOSTuoEoCO+zyTyUWEkNbUq825ELUQdIsjgmlWUOONudx
sAr7ESRXW9QTHVh6uWmr3VRKZHby1JdU3I/wjdlGg5M2dDuXy5nQO9w/nYLjJXiw
+zzOetZ/+t7/VOkOpNTeJQhwTM1WF7Y2VLetbi9FHgyzHatrduh07+XEiTbgDf3G
Ix2bp2p6oh0G3N2zpiLcK/aZj8rouWWydfFfsU3MZ4FfJDP8I6b9awxjmKYqIr6h
iPQCJaLBED8mwK+I5evIbnKv6E6uK+BApWA/R7ElragoFYbqQ1VpvntVMtJt9Dy5
ZrI+IQARdXD3bb34oh0IPBhClnvvMUc1cWxDoXEX6oJ4I+LzxE87Zkwnan9qOwen
golMVKFwPx1o37qrbmrXID21kKt7FL6xN4HxHLkItr1fKzdyWDFRHgASTAWfx5BI
wvPuUW0vZHkvO80VyV2L63whVhPnPASmFkbviomrBttYfpr2aGQqF/qR1Nlxe834
MFxk1pS9LMa/WnzvFr0gWakCAwEAAQ==
-----END RSA PUBLIC KEY-----
`)
secretKeyPairDir := filepath.Join(homeDir, "secrets", "contexts", "contx1", "secrets")
if err := os.MkdirAll(secretKeyPairDir, 0777); err != nil {
err = fmt.Errorf("Not able to create directories")
log.Fatal(err)
}
os.Setenv("QLIKSENSE_KEY_LOCATION", secretKeyPairDir)
privKeyFile := filepath.Join(secretKeyPairDir, "qliksensePriv")
// construct and write priv key file into secretsDir location
err := ioutil.WriteFile(privKeyFile, privKeyBytes, 0777)
if err != nil {
log.Printf("Error while creating file: %v", err)
return nil, nil, err
}
pubKeyFile := filepath.Join(secretKeyPairDir, "qliksensePub")
// construct and write pub key file into secretsDir location
err = ioutil.WriteFile(pubKeyFile, publicKeyBytes, 0777)
if err != nil {
log.Printf("Error while creating file: %v", err)
return nil, nil, err
}
return publicKeyBytes, privKeyBytes, nil
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,21 +1,155 @@
package qliksense
import (
"encoding/base64"
b64 "encoding/base64"
"fmt"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/qlik-oss/k-apis/pkg/config"
"github.com/qlik-oss/sense-installer/pkg/api"
"gopkg.in/yaml.v2"
)
var (
testDir = "./tests"
const (
testDir = "./tests"
qlikDefaultContext = "qlik-default"
secrets = "secrets"
contexts = "contexts"
)
var targetFileStringTemplate = `
apiVersion: v1
data:
mongoDbUri: %s
kind: Secret
metadata:
name: testctx-qliksense-senseinstaller
type: Opaque
`
var encText = "SFpVZ2t5SGsrN2lLQjlTYm9rbFUxSDFRcmVYdUxhTW9MUHlQOGtGditxMEcwZTlIZDl1dVRrV0tEYm5qUURSWFp3dStuNklueGk3anI2c1djSVdsbWlKTHdWQUJwdUg0a1NXd3llMUlMa2oxK3FRSFlMM2dQUExvN1pBYkVDeDROMUVvam12M0t0NmQwbkdhSXlWWEpmWWJUVVFDM1Y4L0ZTVXBVN0JUb0l4OVZWdmlPam5HTHk4RlF2a3RUaHJxWTUvZEh2N3pVUmhiOTc2Q2YwbEovZ3I2L2NwRk9RMUFXVXdodVhrTG9lYjVzNFdtTEZzNldqT3k0bWlKM1J6VllLaWVUSFJ2SE85eDB6dUthanRwSGEzWEZkaE5QNnpySVJJNTRFalUyblVYYUNlYXVnWnZEOUxjdWluOFhFcjExbkFINURCUDAycXhoZk5BejVoMlV2eFNWVmR0aW1QTDBhMVBJTUxGQTgyWUkrQkFOQkhkSUNnZGU5SkxIRFBoTzR6c0llaE1LRmhVQkNoOUhQa3kyRnhTeDJ3YWp3M1UycEsvcFJVZUxDazRUbkhmL25LN3h5ekdpV3dSUFFFZHdsWE5JbUhjVlVPV3gvNWh4WlJCUTZtb3pGYk1HbXR1Mkh5Z3RVV2gzNFYzd1BhS01TNFRsa0hyODFjRjVCWVpxenBFK1pKWnVyLy8zbzJsU0tFMjMxTG1pcGk1K0FqbXZvUVcyWHBocjFNVWJQY1pXUkJFRkkyQXBCM0FhQXFPa0k1MkRqNG43Mko5bCtaMzdydTk1aHk5K1lzY0FxMjZVbExYRlc0S3RUUkRLSjlMNnVmdlIrUUNudER3em5UTFRHUnEwZU5COWt6S0Q4MFlUdXozeHNXK3cxdjlHbDJaMnBZMTZWTCtEV1k9"
var decText = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
func setupTargetFileAndPrivateKey() ([]byte, []byte, string, error) {
targetFileString := fmt.Sprintf(targetFileStringTemplate, encText)
privKeyBytes := []byte(`-----BEGIN RSA PRIVATE KEY-----
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
-----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 {
@@ -30,7 +164,7 @@ metadata:
spec:
contexts:
- name: qlik-default
crLocation: /root/.qliksense/contexts/qlik-default.yaml
crFile: /root/.qliksense/contexts/qlik-default.yaml
currentContext: qlik-default
`
configFile := filepath.Join(testDir, "config.yaml")
@@ -65,6 +199,22 @@ spec:
return tearDown
}
func readCRFile() (*api.QliksenseCR, error) {
qlikDefaultContext := "qlik-default"
qliksenseCR := &api.QliksenseCR{}
contextFileContents, err := ioutil.ReadFile(filepath.Join(testDir, contexts, qlikDefaultContext, qlikDefaultContext+".yaml"))
if err != nil {
log.Println(err)
err = fmt.Errorf("Not able to read current context info")
return nil, err
}
if err := yaml.Unmarshal(contextFileContents, qliksenseCR); err != nil {
err = fmt.Errorf("An error occurred during unmarshalling: %v", err)
return nil, err
}
return qliksenseCR, nil
}
func Test_retrieveCurrentContextInfo(t *testing.T) {
tearDown := setup()
@@ -108,17 +258,22 @@ func TestSetUpQliksenseContext(t *testing.T) {
},
wantErr: true,
},
{
name: "empty contextname",
args: args{
qlikSenseHome: testDir,
contextName: "",
isDefaultContext: false,
},
wantErr: true,
},
}
tearDown := setup()
defer tearDown()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
q, err := New(tt.args.qlikSenseHome)
if err != nil {
t.Errorf("unable to create a qliksense instance")
return
}
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)
}
@@ -147,11 +302,7 @@ func TestSetUpQliksenseDefaultContext(t *testing.T) {
defer tearDown()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
q, err := New(tt.args.qlikSenseHome)
if err != nil {
t.Errorf("unable to create a qliksense instance")
return
}
q := New(tt.args.qlikSenseHome)
if err := q.SetUpQliksenseDefaultContext(); (err != nil) != tt.wantErr {
t.Errorf("SetUpQliksenseDefaultContext() error = %v, wantErr %v", err, tt.wantErr)
}
@@ -175,10 +326,30 @@ func TestSetOtherConfigs(t *testing.T) {
q: &Qliksense{
QliksenseHome: testDir,
},
args: []string{"profile=minikube", "namespace=qliksense", "storageClassName=efs"},
args: []string{"profile=minikube", "rotateKeys=yes", "storageClassName=efs"},
},
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()
@@ -207,7 +378,7 @@ func TestSetConfigs(t *testing.T) {
q: &Qliksense{
QliksenseHome: testDir,
},
args: []string{"qliksense[name=acceptEULA]=\"yes\"", "qliksense[name=mongoDbUri]=\"mongo://mongo:3307\""},
args: []string{"qliksense.acceptEULA=\"yes\"", "qliksense.mongoDbUri=\"mongo://mongo:3307\""},
},
wantErr: false,
},
@@ -318,20 +489,448 @@ spec:
pushSecret.Username != testCase.pushUsername || pushSecret.Password != testCase.pushPassword {
t.Fatalf("unexpected push secret content: %v", pushSecret)
}
if pullSecret, err := qConfig.GetDockerConfigJsonSecret("image-registry-pull-secret.yaml"); err != nil {
if pullSecret, err := qConfig.GetPullDockerConfigJsonSecret(); err != nil {
t.Fatalf("unexpected error: %v", err)
} else if pullSecret.Uri != testCase.registry ||
pullSecret.Name != "artifactory-docker-secret" || pullSecret.Namespace != "some-namespace" ||
pullSecret.Name != "artifactory-docker-secret" ||
pullSecret.Username != testCase.pullUsername || pullSecret.Password != testCase.pullPassword {
t.Fatalf("unexpected pull secret content: %v", pullSecret)
}
} else {
if _, err := qConfig.GetPushDockerConfigJsonSecret(); err == nil {
t.Fatal("unexpected image-registry-push-secret.yaml")
} else if _, err := qConfig.GetDockerConfigJsonSecret("image-registry-pull-secret.yaml"); err == nil {
} else if _, err := qConfig.GetPullDockerConfigJsonSecret(); err == nil {
t.Fatal("unexpected image-registry-pull-secret.yaml")
}
}
})
}
}
func Test_PrepareK8sSecret(t *testing.T) {
type fields struct {
QliksenseHome string
}
tests := []struct {
name string
fields fields
want string
wantErr bool
setup func() (string, func())
}{
{
name: "valid case",
fields: fields{
QliksenseHome: testDir,
},
want: fmt.Sprintf(targetFileStringTemplate, base64.StdEncoding.EncodeToString([]byte(decText))),
wantErr: false,
setup: func() (string, func()) {
tearDown := setup()
_, _, targetFile, _ := setupTargetFileAndPrivateKey()
return targetFile, tearDown
},
},
{
name: "private key not supplied should result in decryption error",
fields: fields{
QliksenseHome: testDir,
},
want: "",
wantErr: true,
setup: func() (string, func()) {
tearDown := setup()
_, _, targetFile, _ := setupTargetFileAndPrivateKey()
removePrivateKey()
return targetFile, tearDown
},
},
{
name: "target file not supplied",
fields: fields{
QliksenseHome: testDir,
},
want: "",
wantErr: true,
setup: func() (string, func()) {
tearDown := setup()
_, _, _, _ = setupTargetFileAndPrivateKey()
removePrivateKey()
return "", tearDown
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
targetFile, tearDown := tt.setup()
q := &Qliksense{
QliksenseHome: tt.fields.QliksenseHome,
}
got, err := q.PrepareK8sSecret(targetFile)
if (err != nil) != tt.wantErr {
t.Errorf("Qliksense.PrepareK8sSecret() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(strings.TrimSpace(got), strings.TrimSpace(tt.want)) {
t.Errorf("Qliksense.PrepareK8sSecret() = %v, want %v", got, tt.want)
}
tearDown()
})
}
}
func Test_ListContextConfigs(t *testing.T) {
type fields struct {
QliksenseHome string
}
tests := []struct {
name string
fields fields
wantErr bool
setup func() (string, func())
}{
{
name: "valid case",
fields: fields{
QliksenseHome: testDir,
},
wantErr: false,
setup: func() (string, func()) {
tearDown := setup()
return "", tearDown
},
},
{
name: "config yaml does not exist",
fields: fields{
QliksenseHome: testDir,
},
wantErr: true,
setup: func() (string, func()) {
return "", func() {}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, tearDown := tt.setup()
q := &Qliksense{
QliksenseHome: tt.fields.QliksenseHome,
}
if err := q.ListContextConfigs(); (err != nil) != tt.wantErr {
t.Errorf("ListContextConfigs() error = %v, wantErr %v", err, tt.wantErr)
}
tearDown()
})
}
}
func Test_SetSecrets(t *testing.T) {
type fields struct {
QliksenseHome string
}
type args struct {
args []string
isSecretSet bool
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "valid secret secrets=false",
fields: fields{
QliksenseHome: testDir,
},
args: args{
args: []string{"qliksense.mongoDbUri=\"mongodb://qlik-default-mongodb:27017/qliksense?ssl=false\""},
isSecretSet: false,
},
wantErr: false,
},
{
name: "test1 valid secret secrets=true",
fields: fields{
QliksenseHome: testDir,
},
args: args{
args: []string{"qliksense.mongoDbUri=\"mongo://mongo:3307\""},
isSecretSet: true,
},
wantErr: false,
},
{
name: "test2 valid secret secrets=true",
fields: fields{
QliksenseHome: testDir,
},
args: args{
args: []string{"qliksense.mongoDbUri=\"mongodb://qlik-default-mongodb:27017/qliksense?ssl=false\""},
isSecretSet: true,
},
wantErr: false,
},
{
name: "invalid secret secrets=false",
fields: fields{
QliksenseHome: testDir,
},
args: args{
args: []string{},
isSecretSet: false,
},
wantErr: true,
},
}
tearDown := setup()
_, privateKeyBytes, _, err := setupTargetFileAndPrivateKey()
if err != nil {
t.FailNow()
}
defer tearDown()
privKey, err := api.DecodeToPrivateKey(privateKeyBytes)
if err != nil {
t.FailNow()
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
q := &Qliksense{
QliksenseHome: tt.fields.QliksenseHome,
}
if err := q.SetSecrets(tt.args.args, tt.args.isSecretSet); (err != nil) != tt.wantErr {
t.Errorf("SetSecrets() error = %v, wantErr %v", err, tt.wantErr)
t.FailNow()
}
if tt.wantErr || len(tt.args.args) == 0 {
return
}
// VERIFICATION PART BELOW
// extract the value for testing
testValueArr := strings.SplitN(tt.args.args[0], "=", 2)
testValue := strings.ReplaceAll(testValueArr[1], "\"", "")
qliksenseCR, err := readCRFile()
if err != nil {
err = fmt.Errorf("Not able to read from context file: %v", err)
log.Println(err)
t.FailNow()
}
for svcName := range qliksenseCR.Spec.Secrets { // we are sure we only have one service
for _, v := range qliksenseCR.Spec.Secrets {
for _, item := range v { // we are sure we only have one entry
valToBeEncrypted, err := getValueToBeDecodedForSetSecrets(item, qliksenseCR, svcName)
if err != nil {
err := fmt.Errorf("Error occurred while decoding: %v", err)
log.Printf("decode error: %v", err)
t.FailNow()
}
decodedValue, err := b64.StdEncoding.DecodeString(valToBeEncrypted)
if err != nil {
err := fmt.Errorf("Error occurred while decoding: %v", err)
log.Printf("decode error: %v", err)
t.FailNow()
}
decryptedVal, err := api.Decrypt(decodedValue, privKey)
if err != nil {
err := fmt.Errorf("Error occurred while testing decryption: %v", err)
log.Printf("No Data in Secret: %v", err)
t.FailNow()
}
if string(decryptedVal) != testValue {
t.FailNow()
}
}
}
}
})
}
}
func getValueToBeDecodedForSetSecrets(item config.NameValue, qliksenseCR *api.QliksenseCR, svcName string) (string, error) {
if item.ValueFrom != nil && item.ValueFrom.SecretKeyRef != nil {
// secret=true
secretFilePath := filepath.Join(testDir, contexts, qliksenseCR.GetName(), QliksenseSecretsDir, svcName+".yaml")
if api.FileExists(secretFilePath) {
secretFileContents, err := ioutil.ReadFile(secretFilePath)
if err != nil {
err = fmt.Errorf("An error occurred during unmarshalling: %v", err)
return "", err
}
k8sSecret, err := api.K8sSecretFromYaml(secretFileContents)
if err != nil {
err = fmt.Errorf("An error occurred during unmarshalling: %v", err)
return "", err
}
if k8sSecret.Data == nil {
err = fmt.Errorf("No Data in Secret: %v", err)
return "", err
}
return string(k8sSecret.Data[item.ValueFrom.SecretKeyRef.Key]), nil
}
}
// secret=false
if item.Value != "" {
return item.Value, nil
}
err := fmt.Errorf("Both Value and ValueFrom are empty")
return "", err
}
func setupDeleteContext() func() {
if err := os.Mkdir(testDir, 0777); err != nil {
log.Printf("\nError occurred: %v", err)
}
config :=
`
apiVersion: config.qlik.com/v1
kind: QliksenseConfig
metadata:
name: qliksenseConfig
spec:
contexts:
- name: qlik-default
crFile: /root/.qliksense/contexts/qlik-default.yaml
- name: qlik1
crFile: /root/.qliksense/contexts/qlik1.yaml
- name: qlik2
crFile: /root/.qliksense/contexts/qlik2.yaml
currentContext: qlik1
`
configFile := filepath.Join(testDir, "config.yaml")
// tests/config.yaml exists
ioutil.WriteFile(configFile, []byte(config), 0777)
contextYaml :=
`
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: qlik-default
spec:
profile: docker-desktop
rotateKeys: "yes"
releaseName: qlik-default
`
qlikDefaultContext := "qlik-default"
// create contexts/qlik-default/ under tests/
contexts := "contexts"
contextsDir1 := filepath.Join(testDir, contexts, qlikDefaultContext)
if err := os.MkdirAll(contextsDir1, 0777); err != nil {
err = fmt.Errorf("Not able to create directories")
log.Fatal(err)
}
contextYaml1 :=
`
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: qlik1
spec:
profile: docker-desktop
rotateKeys: "yes"
releaseName: qlik1`
contextYaml2 :=
`
apiVersion: qlik.com/v1
kind: Qliksense
metadata:
name: qlik2
spec:
profile: docker-desktop
rotateKeys: "yes"
releaseName: qlik2`
contextsDir := filepath.Join(testDir, contexts, "qlik1")
if err := os.MkdirAll(contextsDir, 0777); err != nil {
err = fmt.Errorf("Not able to create directories")
log.Fatal(err)
}
contextsDir2 := filepath.Join(testDir, contexts, "qlik2")
if err := os.MkdirAll(contextsDir2, 0777); err != nil {
err = fmt.Errorf("Not able to create directories")
log.Fatal(err)
}
contextFile := filepath.Join(contextsDir, "qlik1.yaml")
ioutil.WriteFile(contextFile, []byte(contextYaml1), 0777)
contextFile2 := filepath.Join(contextsDir2, "qlik2.yaml")
ioutil.WriteFile(contextFile2, []byte(contextYaml2), 0777)
contextFile1 := filepath.Join(contextsDir1, "qlik-default.yaml")
ioutil.WriteFile(contextFile1, []byte(contextYaml), 0777)
tearDown := func() {
os.RemoveAll(testDir)
}
return tearDown
}
func TestDeleteContexts(t *testing.T) {
type args struct {
qlikSenseHome string
contextName string
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "valid context",
args: args{
qlikSenseHome: testDir,
contextName: "qlik2",
},
wantErr: false,
},
{
name: "default context",
args: args{
qlikSenseHome: testDir,
contextName: "qlik-default",
},
wantErr: true,
},
{
name: "non-existent context",
args: args{
qlikSenseHome: testDir,
contextName: "qlik3",
},
wantErr: true,
},
{
name: "current context",
args: args{
qlikSenseHome: testDir,
contextName: "qlik1",
},
wantErr: true,
},
}
tearDown := setupDeleteContext()
defer tearDown()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
q := New(tt.args.qlikSenseHome)
var arg []string
arg = append(arg, tt.args.contextName)
if err := q.DeleteContextConfig(arg); (err != nil) != tt.wantErr {
t.Errorf("DeleteContext() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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