Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcbf22bcd5 |
43
.circleci/config.yml
Normal file
43
.circleci/config.yml
Normal file
@@ -0,0 +1,43 @@
|
||||
# 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
5
.gitattributes
vendored
@@ -1,5 +0,0 @@
|
||||
# Ignore all files and folders that start with .; .circleci, .github, .git, etc.
|
||||
# Warning! This will ignore files in subfolders as well.
|
||||
# If you needs files starting with . then change condition below to be specific
|
||||
# for each file and folder that needs to be ignored
|
||||
.* export-ignore
|
||||
27
.github/workflows/build.yml
vendored
27
.github/workflows/build.yml
vendored
@@ -1,27 +0,0 @@
|
||||
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 xbuild-all
|
||||
21
.github/workflows/mkdocs.yml
vendored
21
.github/workflows/mkdocs.yml
vendored
@@ -1,21 +0,0 @@
|
||||
name: Publish docs via GitHub Pages
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'docs/**'
|
||||
- 'mkdocs.yml'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Deploy docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout master
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Deploy docs
|
||||
uses: mhausenblas/mkdocs-deploy-gh-pages@1.11
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
37
.github/workflows/release.yml
vendored
37
.github/workflows/release.yml
vendored
@@ -1,37 +0,0 @@
|
||||
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/**/*
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -4,8 +4,3 @@ cmd/qliksense/__debug_bin
|
||||
pkg/qliksense/crds
|
||||
pkg/qliksense/packrd
|
||||
pkg/qliksense/qliksense-packr.go
|
||||
pkg/qliksense/docker-registry
|
||||
/pkg/qliksense/tests
|
||||
.DS_Store
|
||||
|
||||
.idea/
|
||||
191
LICENSE
191
LICENSE
@@ -1,191 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2019 QlikTech International AB
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
23
MKDOCS.md
23
MKDOCS.md
@@ -1,23 +0,0 @@
|
||||
# Qlik Sense installer documentation
|
||||
|
||||
## Local development of documentation
|
||||
|
||||
Documentation is built using [mkdocs](https://www.mkdocs.org/) and uses [Material for MKDocs theme](https://squidfunk.github.io/mkdocs-material/)
|
||||
|
||||
Requirements: Python and PIP or Docker
|
||||
|
||||
```console
|
||||
pip install mkdocs
|
||||
pip install mkdocs-material
|
||||
```
|
||||
|
||||
View live changes locally at http://localhost:8000
|
||||
```console
|
||||
mkdocs serve
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
```console
|
||||
docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material
|
||||
```
|
||||
63
Makefile
63
Makefile
@@ -24,13 +24,8 @@ SUPPORTED_ARCHES = amd64
|
||||
|
||||
MIXIN = qliksense
|
||||
|
||||
DEVNUL := /dev/null
|
||||
WHICH := which
|
||||
|
||||
ifeq ($(CLIENT_PLATFORM),windows)
|
||||
FILE_EXT=.exe
|
||||
DEVNUL := NUL
|
||||
WHICH := where
|
||||
else ifeq ($(RUNTIME_PLATFORM),windows)
|
||||
FILE_EXT=.exe
|
||||
else
|
||||
@@ -43,72 +38,46 @@ build: clean generate
|
||||
go build -ldflags '$(LDFLAGS)' -tags "$(BUILDTAGS)" -o $(BINDIR)/$(MIXIN)$(FILE_EXT) ./cmd/$(MIXIN)
|
||||
$(MAKE) clean
|
||||
|
||||
.PHONY: test-setup
|
||||
test-setup: clean generate
|
||||
ifeq ($(shell ${WHICH} docker-registry 2>${DEVNUL}),)
|
||||
$(eval TMP-docker-distribution := $(shell mktemp -d))
|
||||
git clone https://github.com/docker/distribution.git $(TMP-docker-distribution)/docker-distribution
|
||||
cd $(TMP-docker-distribution)/docker-distribution; git checkout -b v2.7.1; make
|
||||
cp $(TMP-docker-distribution)/docker-distribution/bin/registry pkg/qliksense/docker-registry
|
||||
-rm -rf $(TMP-docker-distribution)
|
||||
endif
|
||||
|
||||
.PHONY: test-short
|
||||
test-short: test-setup
|
||||
go test -short -count=1 -tags "$(BUILDTAGS)" -v ./...
|
||||
$(MAKE) clean
|
||||
|
||||
.PHONY: test
|
||||
test: test-setup
|
||||
go test -count=1 -tags "$(BUILDTAGS)" -v ./...
|
||||
$(MAKE) clean
|
||||
test:
|
||||
go test -short -count=1 -tags "$(BUILDTAGS)" -v ./...
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
#tar -C $(BINDIR)/$(VERSION)/ -cvf $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).tar.gz $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
||||
|
||||
|
||||
generate: get-crds packr2
|
||||
go generate ./...
|
||||
|
||||
HAS_PACKR2 := $(shell packr2)
|
||||
packr2:
|
||||
ifeq ($(shell ${WHICH} packr2 2>${DEVNUL}),)
|
||||
go get -u github.com/gobuffalo/packr/v2/packr2@v2.7.1
|
||||
ifndef HAS_PACKR2
|
||||
go get -u github.com/gobuffalo/packr/v2/packr2
|
||||
endif
|
||||
|
||||
clean: clean-packr
|
||||
-rm -rf /tmp/operator
|
||||
-rm -fr pkg/qliksense/crds
|
||||
|
||||
clean-packr: packr2
|
||||
cd pkg/qliksense && packr2 clean
|
||||
|
||||
get-crds:
|
||||
ifeq ($(QLIKSENSE_OPERATOR_DIR),)
|
||||
$(eval TMP-operator := $(shell mktemp -d))
|
||||
git clone https://github.com/qlik-oss/qliksense-operator.git -b master $(TMP-operator)/operator
|
||||
$(MAKE) QLIKSENSE_OPERATOR_DIR=$(TMP-operator)/operator get-crds
|
||||
-rm -rf $(TMP-operator)
|
||||
else
|
||||
$(eval TMP := $(shell mktemp -d))
|
||||
git clone git@github.com:qlik-oss/qliksense-operator.git -b ms-3 $(TMP)/operator
|
||||
mkdir -p pkg/qliksense/crds/cr
|
||||
mkdir -p pkg/qliksense/crds/crd
|
||||
mkdir -p pkg/qliksense/crds/crd-deploy
|
||||
cp $(QLIKSENSE_OPERATOR_DIR)/deploy/*.yaml pkg/qliksense/crds/crd-deploy
|
||||
cp $(QLIKSENSE_OPERATOR_DIR)/deploy/crds/*_crd.yaml pkg/qliksense/crds/crd
|
||||
cp $(QLIKSENSE_OPERATOR_DIR)/deploy/crds/*_cr.yaml pkg/qliksense/crds/cr
|
||||
endif
|
||||
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
|
||||
|
||||
169
README.md
169
README.md
@@ -1,25 +1,166 @@
|
||||
# (WIP) Qlik Sense installation and operations CLI
|
||||
|
||||
## Documentation
|
||||
|
||||
To learn more about Qlik Sense installer go to https://qlik-oss.github.io/sense-installer/
|
||||
# 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)
|
||||
- [Porter CLI](#porter-cli)
|
||||
- [Generate Credentials from published bundle](#generate-credentials-from-published-bundle)
|
||||
- [Qlik Sense version and image list](#qliksense-version-and-image-list)
|
||||
- [Optional: Pulling images in manifest locally, "air gap"](#optional-pulling-images-in-manifest-locally-%22air-gap%22)
|
||||
- [Running Preflight checks](#running-preflight-checks)
|
||||
- [Installation](#installation)
|
||||
- [Supported Parameters during install](#supported-parameters-during-install)
|
||||
- [How To Add Identity Provider Config](#how-to-add-identity-provider-config)
|
||||
- [Packaging a Custom bundle](#packaging-a-custom-bundle)
|
||||
|
||||
## About
|
||||
|
||||
The Qlik Sense installer CLI (qliksense) provides an imperative interface to many of the configurations that need to be applied against the declarative structure described in [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s). This cli facilitates:
|
||||
The Qlik Sense installer CLI (sense-installer) provides an imperitive interface to many of the configurations that need to be applied against the declaritive structure described in [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s).
|
||||
|
||||
- installation of QSEoK
|
||||
- installation of qliksense operator to manage QSEoK
|
||||
- air gapped installation of QSEoK
|
||||
This is a technology preview that uses [porter](https://porter.sh) to execute "actions" (operations) and bundle versions of the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository.
|
||||
|
||||
This is a technology preview that uses Qlik modified [kustomize](https://github.com/qlik-oss/kustomize) to kubernetes manifests of the versions of the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository.
|
||||
These bundles are posted to [docker hub](https://hub.docker.com/) at the following location: [qliksense-cnab-bundle](https://hub.docker.com/r/qlik/qliksense-cnab-bundle/tags).
|
||||
|
||||
For each version of a qliksense edge build there should be a corresponding release in [qliksense-k8s] repository under [releases](https://github.com/qlik-oss/qliksense-k8s/releases)
|
||||
For each version of a qliksense sense edge build there should be a corresponding release current posted on docker hub. ex. `qlik/qliksense-cnab-bundle:v1.21.23-edge` for `v1.21.23` edge release of qliksense. The latest version posted will also be labelled as `latest`
|
||||
|
||||
### Future Direction
|
||||
|
||||
- Porter is currently used as a core technology for the CLI. In the future Porter will be moved "up the stack" to allow the CLI to perform the current and expanded operations independently and encapsulate core functionality currently provided by Porter and other dependent tooling.
|
||||
- More operations:
|
||||
- Expand preflight checks
|
||||
- Expanded preflight checks
|
||||
- backup/restore operations
|
||||
- fully support airgap installation of QSEoK
|
||||
- restore unwanted deletion of kubernetes resources
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Requirements
|
||||
|
||||
- Docker Client connected to a docker engine into which images can built, pulled and pushed.
|
||||
- (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).
|
||||
- To allow the CLI to download and initialize dependencies (including porter and it's associated mixins), simply execute `qliksense` with no arguments
|
||||
- `qliksense`
|
||||
|
||||
#### Porter CLI
|
||||
- *Optional*: If wanting to use porter CLI directly, two environment variables will need to be set so as not to conflict with an existing porter installation:
|
||||
- _Bash_
|
||||
```shell
|
||||
bash# export PORTER_HOME="$HOME\.qliksense"
|
||||
bash# export PATH="$HOME\.qliksense;$PATH"
|
||||
```
|
||||
|
||||
- _PowerShell_
|
||||
```shell
|
||||
PS> $Env:PORTER_HOME="$Env:USERPROFILE\.qliksense"
|
||||
PS> $Env:PATH="$Env:USERPROFILE\.qliksense;$Env:PATH"
|
||||
```
|
||||
|
||||
### Generate Credentials from published bundle
|
||||
|
||||
- Ensure connectivity to the target cluster create a kubeconfig credential for a target bundle.
|
||||
- generating a file as follows, replace `<credential_name>` with a name of your choosing.
|
||||
- _Bash_
|
||||
```shell
|
||||
bash# CREDENTIAL_NAME=<credential_name>
|
||||
bash# cat <<EOF > $HOME/.qliksense/credentials/$CREDENTIAL_NAME.yaml
|
||||
name: $CREDENTIAL_NAME
|
||||
credentials:
|
||||
- name: kubeconfig
|
||||
source:
|
||||
path: $HOME/.kube/config
|
||||
EOF
|
||||
```
|
||||
- _PowerShell_
|
||||
```shell
|
||||
PS> $CREDENTIAL_NAME="<credential_name>"
|
||||
PS> Add-Content -Value @"
|
||||
name: $CREDENTIAL_NAME
|
||||
credentials:
|
||||
- name: kubeconfig
|
||||
source:
|
||||
path: $Env:USERPROFILE\.kube\config
|
||||
"@ -Path $Env:USERPROFILE\.qliksense\credentials\$CREDENTIAL_NAME".yaml"
|
||||
```
|
||||
- credentials can also be created using the [porter](https://porter.sh) CLI *(the correct environmental variable need to have been set up as shown in [Porter CLI](#porter-cli) above)*
|
||||
- `porter cred generate <credential_name> --tag qlik/qliksense-cnab-bundle:v1.21.23-edge`, replace `<credential_name>` with a name of your choosing.
|
||||
- Select `file path` and specify full path to a kube config file ex. _Bash_: `/home/user/.kube/config` or _PowerShell_ `C:\Users\user\.kube\config`
|
||||
|
||||
### Qlik Sense version and image list
|
||||
|
||||
It is possible verify the version of the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository bundled into the `qlik/qliksense-cnab-bundle` image and retreive the list of images included in that release. (This operation can take a minute or so)<https://github.com/qlik-oss/kustomize/issues/13> as the entire manifests needs to be rendered:
|
||||
|
||||
- `qliksense about --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
|
||||
|
||||
### Optional: Pulling images in manifest locally, "air gap"
|
||||
|
||||
If the `dockerRegistry` parameter is specified as the private docker registry to be used by the kubernetes cluster hosting qliksense, it is possible to pull images to the local docker engine for an eventual push during a `qliksense install` or `qliksense upgrade`
|
||||
|
||||
- `qliksense pull --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
|
||||
|
||||
### Running Preflight checks
|
||||
|
||||
You can run preflight checks to ensure that the cluster is in a healthy state before installing Qliksense.
|
||||
|
||||
- `qliksense preflight -c <credential_name> --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
|
||||
|
||||
The above command runs the checks in the default namespace. If you want to specify the namespace to run preflight checks on:
|
||||
|
||||
- `qliksense preflight --param namespace=<value> -c <credential_name> --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
|
||||
|
||||
### Installation
|
||||
|
||||
- Install the bundle : `qliksense install --param acceptEULA=yes -c <credential_name> --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
|
||||
|
||||
#### Supported Parameters during install
|
||||
|
||||
| Name | Descriptions | Default |
|
||||
| ------------- |:-------------:| -----:|
|
||||
| profile | select a profile i.e docker-desktop, aws-eks, gke | docker-desktop |
|
||||
| acceptEULA | yes | has to be yes |
|
||||
| namespace | any kubernetes namespace | default |
|
||||
| dockerRegistry | A private docker image regitry for pods | not specified (public) |
|
||||
| rotateKeys | regenerate application PKI keys on upgrade (yes/no) | no |
|
||||
| mongoDbUri | the mongodb URI to use | URI of development mongodb |
|
||||
| scName | storage class name | none |
|
||||
|
||||
#### How To Add Identity Provider Config
|
||||
|
||||
Since idp configs are usually multiline configs it is not conventional to pass to porter during install as a `param`. Rather put the configs in a file and refer to that file during `porter install` command. For example to add `keycloak` IDP create file named `idpconfigs.txt` and put
|
||||
|
||||
```shell
|
||||
idpConfigs=[{"discoveryUrl":"http://keycloak-insecure:8089/keycloak/realms/master22/.well-known/openid-configuration","clientId":"edge-auth","clientSecret":"e15b5075-9399-4b20-a95e-023022aa4aed","realm":"master","hostname":"elastic.example","claimsMapping":{"sub":["sub","client_id"],"name":["name","given_name","family_name","preferred_username"]}}]
|
||||
|
||||
```
|
||||
|
||||
Then pass that file during install command like this
|
||||
|
||||
```shell
|
||||
qliksense install --param acceptEULA=yes -c <credential_name> --param-file idpconfigs.txt --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
|
||||
```
|
||||
|
||||
## Packaging a Custom bundle
|
||||
|
||||
If files need to be added to the [qliksense-k8s repository](https://github.com/qlik-oss/qliksense-k8s) in order to perform advanced configuration outside the scope of the what the operator provides, a custom bundle needs to be built.
|
||||
|
||||
Packaging of Qlik Sense on Kubernetes is done through a [Porter](https://porter.sh/) definition in the [Qlik Sense on Kubernetes configuration repository](https://github.com/qlik-oss/qliksense-k8s/blob/master/porter.yaml), the resulting bundle published on DockerHub as a [Cloud Natvie Application Bundle](https://cnab.io/) called [qliksense-cnab-bundle](https://hub.docker.com/r/qlik/qliksense-cnab-bundle)
|
||||
|
||||
To start, clone [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and modify the repo as desired, once finished make sure to be in the `qliksense-k8s` directory from which the porter bundle can be built:
|
||||
|
||||
```shell
|
||||
git clone git@github.com:qlik-oss/qliksense-k8s.git
|
||||
|
||||
cd qliksense-k8s
|
||||
|
||||
qliksense build
|
||||
```
|
||||
|
||||
Once built, all of the `porter` command that were used with `--tag` can be now be used without this flag provided that porter is executed with the `qliksense-k8s` directory. `porter` will automatically use the qliksense-k8s (and the porter.yaml) in the current directory.
|
||||
|
||||
## List of Commands
|
||||
|
||||
- [qliksense about](action_about.md)
|
||||
|
||||
41
action_about.md
Normal file
41
action_about.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# qliksense about
|
||||
|
||||
About action will display inside information regarding [qliksense-k8](https://github.com/qlik-oss/qliksense-k8s) release.
|
||||
|
||||
it will support following flags
|
||||
|
||||
- `qliksense about 1.0.0` display default profile for tag `1.0.0`.
|
||||
- `qliksense about 1.0.0 --profile=docker-desktop`
|
||||
- `qliksense about`
|
||||
- assuming current directory has `manifests/docker-desktop`
|
||||
- or get version information from pull of `qliksense-k8s` `master`
|
||||
|
||||
using other supported commands user might have built the CR into the location `~/.qliksense/myqliksense.yaml`
|
||||
|
||||
```yaml
|
||||
apiVersion: qlik.com/v1
|
||||
kind: QlikSense
|
||||
metadata:
|
||||
name: myqliksense
|
||||
spec:
|
||||
profile: docker-desktop
|
||||
manifestsRoot: /Usr/ddd/my-k8-repo/manifests
|
||||
namespace: myqliksense
|
||||
storageClassName: efs
|
||||
configs:
|
||||
qliksense:
|
||||
- name: acceptEULA
|
||||
value: "yes"
|
||||
secrets:
|
||||
qliksense:
|
||||
- name: mongoDbUri
|
||||
value: "mongo://mongo:3307"
|
||||
- name: messagingPassword
|
||||
valueFromKey: messagingPassword
|
||||
```
|
||||
|
||||
In that case the command would be
|
||||
|
||||
- `qliksense about`
|
||||
- display from `/Usr/ddd/my-k8-repo/manifests/docker-desktop` location
|
||||
- pull from `master` if directory invalid/empty
|
||||
31
action_config.md
Normal file
31
action_config.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# qliksense config
|
||||
|
||||
Config action will perform operations on configurations and contexts regarding the [qliksense-k8](https://github.com/qlik-oss/qliksense-k8s) release.
|
||||
|
||||
it will support following commands:
|
||||
|
||||
- `qliksense config apply` - generate the patchs and apply manifests to k8s
|
||||
- `qliksense config list-contexts` - retrieves the contexts and lists them
|
||||
- `qliksense config set` - configure a key value pair into the current context
|
||||
- `qliksense config set-configs` - set configurations into the qliksense context
|
||||
- `qliksense config set-context` - sets the context in which the Kubernetes cluster and resources live in
|
||||
- `qliksense config set-secrets` - set secrets configurations into the qliksense context
|
||||
- `qliksense config view` - view the qliksense operator CR
|
||||
|
||||
the global file that abstracts all the contexts is `config.yaml`, located at: `~/.qliksense/config.yaml`:
|
||||
```yaml
|
||||
apiVersion: config.qlik.com/v1
|
||||
kind: QliksenseConfig
|
||||
metadata:
|
||||
name: QliksenseConfigMetadata
|
||||
spec:
|
||||
contexts:
|
||||
- name: qlik-default
|
||||
crFile: /Users/fff/.qliksense/contexts/qlik-default/qlik-default.yaml
|
||||
- name: myqliksense
|
||||
crFile: /Users/fff/.qliksense/contexts/myqliksense/myqliksense.yaml
|
||||
- name: hello
|
||||
crFile: /Users/fff/.qliksense/contexts/hello/hello.yaml
|
||||
currentContext: hello
|
||||
```
|
||||
|
||||
@@ -19,7 +19,7 @@ func about(q *qliksense.Qliksense) *cobra.Command {
|
||||
|
||||
c := &cobra.Command{
|
||||
Use: "about ref",
|
||||
Short: "Displays information pertaining to Qliksense on Kubernetes",
|
||||
Short: "About Qlik Sense",
|
||||
Long: "Gives the version of QLik Sense on Kubernetes and versions of images.",
|
||||
Example: `
|
||||
qliksense about 1.0.0
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func applyCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
opts := &qliksense.InstallCommandOptions{}
|
||||
filePath := ""
|
||||
keepPatchFiles := false
|
||||
c := &cobra.Command{
|
||||
Use: "apply",
|
||||
Short: "install qliksense based on provided cr file",
|
||||
Long: `install qliksense based on provided cr file`,
|
||||
Example: `qliksense apply -f file_name or cat cr_file | qliksense apply -f -`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runLoadOrApplyCommandE(cmd, func(reader io.Reader) error {
|
||||
return q.ApplyCRFromReader(reader, opts, keepPatchFiles, true)
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
f := c.Flags()
|
||||
f.StringVarP(&filePath, "file", "f", "", "Install from a CR file")
|
||||
c.MarkFlagRequired("file")
|
||||
f.StringVarP(&opts.StorageClass, "storageClass", "s", "", "Storage class for qliksense")
|
||||
f.StringVarP(&opts.MongoDbUri, "mongoDbUri", "m", "", "mongoDbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
|
||||
f.StringVarP(&opts.RotateKeys, "rotateKeys", "r", "", "Rotate JWT keys for qliksense (yes:rotate keys/ no:use exising keys from cluster/ None: use default EJSON_KEY from env")
|
||||
f.BoolVar(&keepPatchFiles, keepPatchFilesFlagName, keepPatchFiles, keepPatchFilesFlagUsage)
|
||||
|
||||
eulaPreRunHooks.addValidator(c.Name(), loadOrApplyCommandEulaPreRunHook)
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -1,26 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func configCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
var configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "do operations on/around CR",
|
||||
Long: `do operations on/around CR`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.ConfigViewCR()
|
||||
},
|
||||
}
|
||||
return configCmd
|
||||
var configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "do operations on/around CR",
|
||||
Long: `do operations on/around CR`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("Use like: config view or config apply")
|
||||
},
|
||||
}
|
||||
|
||||
func configApplyCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "apply",
|
||||
Short: "generate the patches and apply manifests to k8s",
|
||||
Short: "generate the patchs and apply manifests to k8s",
|
||||
Long: `generate patches based on CR and apply manifests to k8s`,
|
||||
Example: `qliksense config apply`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -42,20 +40,3 @@ func configViewCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func configEditCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "edit [context-name]",
|
||||
Short: "Edit the context cr",
|
||||
Long: `edit the context cr. if no context name provided default context will be edited
|
||||
It will open the vim editor unless KUBE_EDITOR is defined`,
|
||||
Example: `qliksense config edit [context-name]`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 1 {
|
||||
return q.EditCR(args[0])
|
||||
}
|
||||
return q.EditCR("")
|
||||
},
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -17,12 +11,9 @@ 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>
|
||||
- The above configuration will be displayed in the CR
|
||||
`,
|
||||
Use: "set-context",
|
||||
Short: "Sets the context in which the Kubernetes cluster and resources live in",
|
||||
Example: `qliksense config set-context <context_name>`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.SetContextConfig(args)
|
||||
},
|
||||
@@ -52,12 +43,9 @@ 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>
|
||||
- The above configuration will be displayed in the CR
|
||||
`,
|
||||
Use: "set",
|
||||
Short: "configure a key value pair into the current context",
|
||||
Example: `qliksense config set <key>=<value>`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.SetOtherConfigs(args)
|
||||
},
|
||||
@@ -69,30 +57,15 @@ func setConfigsCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
var (
|
||||
cmd *cobra.Command
|
||||
)
|
||||
base64Encoded := false
|
||||
cmd = &cobra.Command{
|
||||
Use: "set-configs",
|
||||
Short: "set configurations into the qliksense context as key-value pairs",
|
||||
Example: `
|
||||
qliksense config set-configs <service_name>.<attribute>="<value>"
|
||||
- The above configuration will be displayed in the CR
|
||||
qliksense config set-configs <service_name>.<attribute>="<value" --base64
|
||||
- if the value is base64 encoded
|
||||
echo "something" | base64 | qliksense config set-configs <service_name>.<attribute> --base64
|
||||
- value is coming from input pipe as base64 encoded
|
||||
echo "something" | qliksense config set-configs <service_name>.<attribute>
|
||||
- value is coming from input pipe
|
||||
|
||||
`,
|
||||
cmd = &cobra.Command{
|
||||
Use: "set-configs",
|
||||
Short: "set configurations into the qliksense context",
|
||||
Example: `qliksense config set-configs <key>=<value>`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if isInputFromPipe() && len(args) == 1 {
|
||||
return q.SetConfigFromReader(args[0], os.Stdin, base64Encoded)
|
||||
}
|
||||
return q.SetConfigs(args, base64Encoded)
|
||||
return q.SetConfigs(args)
|
||||
},
|
||||
}
|
||||
f := cmd.Flags()
|
||||
f.BoolVarP(&base64Encoded, "base64", "", false, "if the arguments value is base64 encoded")
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -101,119 +74,16 @@ func setSecretsCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
cmd *cobra.Command
|
||||
secret bool
|
||||
)
|
||||
base64Encoded := false
|
||||
cmd = &cobra.Command{
|
||||
Use: "set-secrets",
|
||||
Short: "set secrets configurations into the qliksense context as key-value pairs",
|
||||
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
|
||||
qliksense config set-secrets <service_name>.<attribute>="<value>" --base64
|
||||
- the <value> is base64 encoded
|
||||
echo "something" | base64 | qliksense config set-secrets <service_name>.<attribute> --base64
|
||||
- value coming from input pipe as base64 encoded
|
||||
echo "something" | qliksense config set-secrets <service_name>.<attribute>
|
||||
- value coming from input pipe`,
|
||||
cmd = &cobra.Command{
|
||||
Use: "set-secrets",
|
||||
Short: "set secrets configurations into the qliksense context",
|
||||
Example: `qliksense config set-secrets <key>=<value> --secret=true`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if isInputFromPipe() && len(args) == 1 {
|
||||
return q.SetSecretsFromReader(args[0], os.Stdin, secret, base64Encoded)
|
||||
}
|
||||
return q.SetSecrets(args, secret, base64Encoded)
|
||||
return q.SetSecrets(args, secret)
|
||||
},
|
||||
}
|
||||
f := cmd.Flags()
|
||||
f.BoolVar(&secret, "secret", false, "Whether secrets should be encrypted as a Kubernetes Secret resource")
|
||||
f.BoolVarP(&base64Encoded, "base64", "", false, "if the arguments value is base64 encoded")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func deleteContextConfigCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
var (
|
||||
cmd *cobra.Command
|
||||
)
|
||||
|
||||
cmd = &cobra.Command{
|
||||
Use: "delete-context",
|
||||
Short: "deletes a specific context locally (not in-cluster)",
|
||||
Example: `qliksense config delete-contexts <context_name>`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.DeleteContextConfig(args)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func setImageRegistryCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
var (
|
||||
cmd *cobra.Command
|
||||
pushUsername string
|
||||
pushPassword string
|
||||
pullUsername string
|
||||
pullPassword string
|
||||
username string
|
||||
password string
|
||||
)
|
||||
|
||||
cmd = &cobra.Command{
|
||||
Use: "set-image-registry",
|
||||
Short: "set private image registry",
|
||||
Example: `
|
||||
qliksense config set-image-registry https://your.private.registry.example.com:5000 --push-username foo1 --push-password bar1 --pull-username foo2 --pull-password bar2
|
||||
qliksense config set-image-registry https://your.private.registry.example.com:5000 --username foo --password bar
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return errors.New("private docker image registry FQDN is required")
|
||||
}
|
||||
registry := args[0]
|
||||
|
||||
if username != "" {
|
||||
pullUsername = username
|
||||
pushUsername = username
|
||||
}
|
||||
if password != "" {
|
||||
pullPassword = password
|
||||
pushPassword = password
|
||||
}
|
||||
if (pullUsername != "" && pushUsername == "") || (pullUsername == "" && pushUsername != "") {
|
||||
return errors.New("if you specify pull credentials, you must specify push credentials as well and vise versa")
|
||||
}
|
||||
if (pullUsername == "" && pullPassword != "") || (pushUsername == "" && pushPassword != "") {
|
||||
return errors.New("if you specify passwords, you must specify usernames as well")
|
||||
}
|
||||
return q.SetImageRegistry(registry, pushUsername, pushPassword, pullUsername, pullPassword)
|
||||
},
|
||||
}
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&pushUsername, "push-username", "", "Username used for pushing images")
|
||||
f.StringVar(&pushPassword, "push-password", "", "Password used for pushing images")
|
||||
f.StringVar(&pullUsername, "pull-username", "", "Username used for pulling images")
|
||||
f.StringVar(&pullPassword, "pull-password", "", "Password used for pulling images")
|
||||
f.StringVar(&username, "username", "", "Username used for both pushing and pulling images")
|
||||
f.StringVar(&password, "password", "", "Password used for both pushing and pulling images")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func cleanConfigRepoPatchesCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "clean-config-repo-patches",
|
||||
Short: "Clean config repo patch files",
|
||||
Example: "qliksense config clean-config-repo-patches",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
|
||||
return fmt.Errorf("error removing temporary changes to the config: %v\n", err)
|
||||
}
|
||||
fmt.Println("done")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var crdsCmd = &cobra.Command{
|
||||
Use: "crds",
|
||||
Short: "crds for qliksense and operators",
|
||||
Long: `crds for qliksense and operators`,
|
||||
}
|
||||
|
||||
func crdsViewCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
opts := &qliksense.CrdCommandOptions{}
|
||||
c := &cobra.Command{
|
||||
Use: "view",
|
||||
Short: "View CRDs for qliksense application. use view --all to see opearator crd as well ",
|
||||
Long: `View CRDs for qliksense application. use view --all to see opearator crd as well`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.ViewCrds(opts)
|
||||
},
|
||||
}
|
||||
f := c.Flags()
|
||||
f.BoolVarP(&opts.All, "all", "", false, "Include All CRDs")
|
||||
return c
|
||||
}
|
||||
|
||||
func crdsInstallCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
opts := &qliksense.CrdCommandOptions{}
|
||||
c := &cobra.Command{
|
||||
Use: "install",
|
||||
Short: "Install CRDs fro Qliksense application. Use install --all to include operator crd",
|
||||
Long: `Install CRDs fro Qliksense application. Use install --all to include operator crd`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.InstallCrds(opts)
|
||||
},
|
||||
}
|
||||
f := c.Flags()
|
||||
f.BoolVarP(&opts.All, "all", "", false, "Include All CRDs")
|
||||
return c
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-tty"
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type eulaPreRunHooksT struct {
|
||||
validators map[string]func(cmd *cobra.Command, q *qliksense.Qliksense) (bool, error)
|
||||
postValidationArtifacts map[string]interface{}
|
||||
}
|
||||
|
||||
func (e *eulaPreRunHooksT) addValidator(command string, validator func(cmd *cobra.Command, q *qliksense.Qliksense) (bool, error)) {
|
||||
e.validators[command] = validator
|
||||
}
|
||||
|
||||
func (e *eulaPreRunHooksT) getValidator(command string) func(cmd *cobra.Command, q *qliksense.Qliksense) (bool, error) {
|
||||
if validator, ok := e.validators[command]; ok {
|
||||
return validator
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *eulaPreRunHooksT) addPostValidationArtifact(artifactName string, artifact interface{}) {
|
||||
e.postValidationArtifacts[artifactName] = artifact
|
||||
}
|
||||
|
||||
func (e *eulaPreRunHooksT) getPostValidationArtifact(artifactName string) interface{} {
|
||||
if artifact, ok := e.postValidationArtifacts[artifactName]; ok {
|
||||
return artifact
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var eulaEnforced = os.Getenv("QLIKSENSE_EULA_ENFORCE") == "true"
|
||||
var eulaText = "Please read the end user license agreement at: https://www.qlik.com/us/legal/license-terms"
|
||||
var eulaPrompt = "Do you accept our EULA? (y/n): "
|
||||
var eulaErrorInstruction = `You must enter "y" to continue`
|
||||
var eulaPreRunHooks = eulaPreRunHooksT{
|
||||
validators: make(map[string]func(cmd *cobra.Command, q *qliksense.Qliksense) (bool, error)),
|
||||
postValidationArtifacts: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
func commandAlwaysRequiresEulaAcceptance(commandName string) bool {
|
||||
return commandName == "install" || commandName == "upgrade" || commandName == "apply"
|
||||
}
|
||||
|
||||
func globalEulaPreRun(cmd *cobra.Command, q *qliksense.Qliksense) {
|
||||
if isEulaEnforced(cmd.Name()) {
|
||||
if strings.TrimSpace(strings.ToLower(cmd.Flag("acceptEULA").Value.String())) != "yes" {
|
||||
if eulaPreRunHook := eulaPreRunHooks.getValidator(cmd.Name()); eulaPreRunHook != nil {
|
||||
if eulaAccepted, err := eulaPreRunHook(cmd, q); err != nil {
|
||||
panic(err)
|
||||
} else if !eulaAccepted {
|
||||
doEnforceEula()
|
||||
}
|
||||
} else if qConfig, err := qapi.NewQConfigE(q.QliksenseHome); err != nil {
|
||||
doEnforceEula()
|
||||
} else if qcr, err := qConfig.GetCurrentCR(); err != nil || !qcr.IsEULA() {
|
||||
doEnforceEula()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func globalEulaPostRun(cmd *cobra.Command, q *qliksense.Qliksense) {
|
||||
if isEulaEnforced(cmd.Name()) {
|
||||
if err := q.SetEulaAccepted(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isEulaEnforced(commandName string) bool {
|
||||
return eulaEnforced || commandAlwaysRequiresEulaAcceptance(commandName)
|
||||
}
|
||||
|
||||
func doEnforceEula() {
|
||||
fmt.Println(eulaText)
|
||||
fmt.Print(eulaPrompt)
|
||||
answer := readRuneFromTty()
|
||||
if strings.ToLower(answer) != "y" {
|
||||
fmt.Println(eulaErrorInstruction)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func readRuneFromTty() string {
|
||||
t, err := tty.Open()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer t.Close()
|
||||
answer, err := t.ReadString()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return answer
|
||||
}
|
||||
@@ -1,30 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func fetchCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
opts := &qliksense.FetchCommandOptions{}
|
||||
c := &cobra.Command{
|
||||
Use: "fetch",
|
||||
Short: "fetch a release from qliksense-k8s repo, if version not supplied, will use from context",
|
||||
Long: `fetch a release from qliksense-k8s repo, if version not supplied, will use from context`,
|
||||
Example: `qliksense fetch [version]`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 1 {
|
||||
opts.Version = args[0]
|
||||
Short: "fetch a release from qliksense-k8s repo",
|
||||
Long: `fetch a release from qliksense-k8s repo`,
|
||||
Example: `qliksense fetch <version>`,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return errors.New("requires a version (i.e. v1.0.0)")
|
||||
}
|
||||
return q.FetchK8sWithOpts(opts)
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.FetchQK8s(args[0])
|
||||
},
|
||||
}
|
||||
|
||||
f := c.Flags()
|
||||
f.StringVarP(&opts.GitUrl, "url", "", "", "git url from where configuration will be pulled")
|
||||
f.StringVarP(&opts.AccessToken, "accessToken", "", "", "access token for git url")
|
||||
f.StringVarP(&opts.SecretName, "secretName", "", "", "kubernetes secret name where a key name accessToken exist")
|
||||
f.BoolVarP(&opts.Overwrite, "overwrite", "", false, "Ovewrite previously fetched veersion as well as local chagnes")
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const defaultVersionsLimit = 10
|
||||
|
||||
func getInstallableVersionsCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
opts := &qliksense.LsRemoteCmdOptions{
|
||||
IncludeBranches: false,
|
||||
Limit: defaultVersionsLimit,
|
||||
}
|
||||
c := &cobra.Command{
|
||||
Use: "get-versions",
|
||||
Short: "list remote/installable versions",
|
||||
Long: `list remote/installable versions`,
|
||||
Example: `qliksense get-versions`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.GetInstallableVersions(opts)
|
||||
},
|
||||
}
|
||||
|
||||
f := c.Flags()
|
||||
f.BoolVarP(&opts.IncludeBranches, "include-branches", "", opts.IncludeBranches, "Include branches")
|
||||
f.IntVarP(&opts.Limit, "limit", "", opts.Limit, "Maximum versions to list (starting with the highest)")
|
||||
return c
|
||||
}
|
||||
@@ -1,32 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func installCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
opts := &qliksense.InstallCommandOptions{}
|
||||
keepPatchFiles := false
|
||||
c := &cobra.Command{
|
||||
Use: "install",
|
||||
Short: "install a qliksense release",
|
||||
Long: `install a qliksense release`,
|
||||
Example: `qliksense install <version> #if no version provides, expect manifestsRoot is set somewhere in the file system`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
version := ""
|
||||
if len(args) != 0 {
|
||||
version = args[0]
|
||||
Long: `install a qliksesne release`,
|
||||
Example: `qliksense install <version>`,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return errors.New("requires a version (i.e. v1.0.0)")
|
||||
}
|
||||
return q.InstallQK8s(version, opts, keepPatchFiles)
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.InstallQK8s(args[0], opts)
|
||||
},
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func loadCrFile(q *qliksense.Qliksense) *cobra.Command {
|
||||
filePath := ""
|
||||
overwriteExistingContext := false
|
||||
c := &cobra.Command{
|
||||
Use: "load",
|
||||
Short: "load a CR a file and create necessary structure for future use",
|
||||
Long: `load a CR a file and create necessary structure for future use`,
|
||||
Example: `qliksense load -f file_name or cat cr_file | qliksense load -f -`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runLoadOrApplyCommandE(cmd, func(reader io.Reader) error {
|
||||
return q.LoadCr(reader, overwriteExistingContext)
|
||||
})
|
||||
},
|
||||
}
|
||||
f := c.Flags()
|
||||
f.StringVarP(&filePath, "file", "f", "", "File to load CR from")
|
||||
c.MarkFlagRequired("file")
|
||||
f.BoolVarP(&overwriteExistingContext, "overwrite", "o", overwriteExistingContext, "Overwrite any existing contexts with the same name")
|
||||
|
||||
eulaPreRunHooks.addValidator(c.Name(), loadOrApplyCommandEulaPreRunHook)
|
||||
return c
|
||||
}
|
||||
|
||||
func getCrFileFromFlag(cmd *cobra.Command, flagName string) (*os.File, error) {
|
||||
filePath := cmd.Flag(flagName).Value.String()
|
||||
if filePath == "-" {
|
||||
if !isInputFromPipe() {
|
||||
return nil, errors.New("No input pipe present")
|
||||
}
|
||||
return os.Stdin, nil
|
||||
}
|
||||
file, e := os.Open(filePath)
|
||||
if e != nil {
|
||||
return nil, errors.Wrapf(e,
|
||||
"unable to read the file %s", filePath)
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func isInputFromPipe() bool {
|
||||
fileInfo, _ := os.Stdin.Stat()
|
||||
return fileInfo.Mode()&os.ModeCharDevice == 0
|
||||
}
|
||||
|
||||
func loadOrApplyCommandEulaPreRunHook(cmd *cobra.Command, q *qliksense.Qliksense) (bool, error) {
|
||||
file, err := getCrFileFromFlag(cmd, "file")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if crBytes, err := ioutil.ReadAll(file); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
eulaPreRunHooks.addPostValidationArtifact("CR", crBytes)
|
||||
return q.IsEulaAcceptedInCrFile(bytes.NewBuffer(crBytes))
|
||||
}
|
||||
}
|
||||
|
||||
func runLoadOrApplyCommandE(cmd *cobra.Command, callBack func(io.Reader) error) error {
|
||||
if crBytes := eulaPreRunHooks.getPostValidationArtifact("CR"); crBytes != nil {
|
||||
return callBack(bytes.NewBuffer(crBytes.([]byte)))
|
||||
} else {
|
||||
file, err := getCrFileFromFlag(cmd, "file")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
return callBack(file)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -9,41 +10,18 @@ var operatorCmd = &cobra.Command{
|
||||
Use: "operator",
|
||||
Short: "Configuration for operator",
|
||||
Long: `Configuration for operator`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("User like: operator view")
|
||||
},
|
||||
}
|
||||
|
||||
/*
|
||||
func operatorViewCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "view",
|
||||
Short: "View CRD for operator",
|
||||
Long: `View CRD for operator`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.ViewOperator()
|
||||
},
|
||||
}
|
||||
return c
|
||||
}
|
||||
*/
|
||||
|
||||
func operatorCrdCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "crd",
|
||||
Short: "View CRD for operator",
|
||||
Long: `View CRD for operator`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.ViewOperator()
|
||||
},
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func operatorControllerCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "controller",
|
||||
Short: "View manifests for operator controller",
|
||||
Long: `View manifests for operator controller`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.ViewOperatorController()
|
||||
Short: "View Configuration for operator",
|
||||
Long: `View Configuration for operator`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
q.ViewOperatorCrd()
|
||||
},
|
||||
}
|
||||
return c
|
||||
|
||||
@@ -1,350 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/qlik-oss/sense-installer/pkg/preflight"
|
||||
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func preflightCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
var preflightCmd = &cobra.Command{
|
||||
Use: "preflight",
|
||||
Short: "perform preflight checks on the cluster",
|
||||
Long: `perform preflight checks on the cluster`,
|
||||
Example: `qliksense preflight <preflight_check_to_run>`,
|
||||
}
|
||||
return preflightCmd
|
||||
}
|
||||
|
||||
func pfDnsCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
var preflightDnsCmd = &cobra.Command{
|
||||
Use: "dns",
|
||||
Short: "perform preflight dns check",
|
||||
Long: `perform preflight dns check to check DNS connectivity status in the cluster`,
|
||||
Example: `qliksense preflight dns`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qp := &preflight.QliksensePreflight{Q: q}
|
||||
|
||||
// Preflight DNS check
|
||||
fmt.Printf("Preflight DNS check\n")
|
||||
fmt.Println("---------------------")
|
||||
namespace, kubeConfigContents, err := preflight.InitPreflight()
|
||||
if err != nil {
|
||||
fmt.Printf("Preflight DNS check FAILED\n")
|
||||
log.Fatal(err)
|
||||
}
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
if err = qp.CheckDns(namespace, kubeConfigContents); err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Print("Preflight DNS check FAILED\n")
|
||||
log.Fatal()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return preflightDnsCmd
|
||||
}
|
||||
|
||||
func pfK8sVersionCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
var preflightCheckK8sVersionCmd = &cobra.Command{
|
||||
Use: "kube-version",
|
||||
Short: "check kubernetes version",
|
||||
Long: `check minimum valid kubernetes version on the cluster`,
|
||||
Example: `qliksense preflight kube-version`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qp := &preflight.QliksensePreflight{Q: q}
|
||||
|
||||
// Preflight Kubernetes minimum version check
|
||||
fmt.Printf("Preflight kubernetes minimum version check\n")
|
||||
fmt.Println("------------------------------------------")
|
||||
namespace, kubeConfigContents, err := preflight.InitPreflight()
|
||||
if err != nil {
|
||||
fmt.Printf("Preflight kubernetes minimum version check FAILED\n")
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err = qp.CheckK8sVersion(namespace, kubeConfigContents); err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Printf("Preflight kubernetes minimum version check FAILED\n")
|
||||
log.Fatal()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return preflightCheckK8sVersionCmd
|
||||
}
|
||||
|
||||
func pfAllChecksCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
var mongodbUrl string
|
||||
var preflightAllChecksCmd = &cobra.Command{
|
||||
Use: "all",
|
||||
Short: "perform all checks",
|
||||
Long: `perform all preflight checks on the target cluster`,
|
||||
Example: `qliksense preflight all`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qp := &preflight.QliksensePreflight{Q: q}
|
||||
|
||||
// Preflight run all checks
|
||||
fmt.Printf("Running all preflight checks\n")
|
||||
namespace, kubeConfigContents, err := preflight.InitPreflight()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Printf("Running preflight check suite has FAILED...\n")
|
||||
log.Fatal()
|
||||
}
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
qp.RunAllPreflightChecks(namespace, kubeConfigContents, mongodbUrl)
|
||||
return nil
|
||||
|
||||
},
|
||||
}
|
||||
f := preflightAllChecksCmd.Flags()
|
||||
f.StringVarP(&mongodbUrl, "mongodb-url", "", "", "mongodbUrl to try connecting to")
|
||||
return preflightAllChecksCmd
|
||||
}
|
||||
|
||||
func pfDeploymentCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
var pfDeploymentCheckCmd = &cobra.Command{
|
||||
Use: "deployment",
|
||||
Short: "perform preflight deploymwnt check",
|
||||
Long: `perform preflight deployment check to ensure that we can create deployments in the cluster`,
|
||||
Example: `qliksense preflight deployment`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qp := &preflight.QliksensePreflight{Q: q}
|
||||
|
||||
// Preflight deployments check
|
||||
fmt.Printf("Preflight deployment check\n")
|
||||
fmt.Println("--------------------------")
|
||||
namespace, kubeConfigContents, err := preflight.InitPreflight()
|
||||
if err != nil {
|
||||
fmt.Printf("Preflight deployment check FAILED\n")
|
||||
log.Fatal(err)
|
||||
}
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
if err = qp.CheckDeployment(namespace, kubeConfigContents); err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Print("Preflight deploy check FAILED\n")
|
||||
log.Fatal()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return pfDeploymentCheckCmd
|
||||
}
|
||||
|
||||
func pfServiceCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
var pfServiceCheckCmd = &cobra.Command{
|
||||
Use: "service",
|
||||
Short: "perform preflight service check",
|
||||
Long: `perform preflight service check to ensure that we are able to create services in the cluster`,
|
||||
Example: `qliksense preflight service`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qp := &preflight.QliksensePreflight{Q: q}
|
||||
|
||||
// Preflight service check
|
||||
fmt.Printf("Preflight service check\n")
|
||||
fmt.Println("-----------------------")
|
||||
namespace, kubeConfigContents, err := preflight.InitPreflight()
|
||||
if err != nil {
|
||||
fmt.Printf("Preflight service check FAILED\n")
|
||||
log.Fatal(err)
|
||||
}
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
if err = qp.CheckService(namespace, kubeConfigContents); err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Print("Preflight service check FAILED\n")
|
||||
log.Fatal()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return pfServiceCheckCmd
|
||||
}
|
||||
|
||||
func pfPodCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
var pfPodCheckCmd = &cobra.Command{
|
||||
Use: "pod",
|
||||
Short: "perform preflight pod check",
|
||||
Long: `perform preflight pod check to ensure we can create pods in the cluster`,
|
||||
Example: `qliksense preflight pod`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qp := &preflight.QliksensePreflight{Q: q}
|
||||
|
||||
// Preflight pod check
|
||||
fmt.Printf("Preflight pod check\n")
|
||||
fmt.Println("--------------------")
|
||||
namespace, kubeConfigContents, err := preflight.InitPreflight()
|
||||
if err != nil {
|
||||
fmt.Printf("Preflight pod check FAILED\n")
|
||||
log.Fatal(err)
|
||||
}
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
if err = qp.CheckPod(namespace, kubeConfigContents); err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Print("Preflight pod check FAILED\n")
|
||||
log.Fatal()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return pfPodCheckCmd
|
||||
}
|
||||
|
||||
func pfCreateRoleCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
var preflightRoleCmd = &cobra.Command{
|
||||
Use: "role",
|
||||
Short: "preflight create role check",
|
||||
Long: `perform preflight role check to ensure we are able to create a role in the cluster`,
|
||||
Example: `qliksense preflight createRole`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qp := &preflight.QliksensePreflight{Q: q}
|
||||
|
||||
// Preflight role check
|
||||
fmt.Printf("Preflight role check\n")
|
||||
fmt.Println("---------------------------")
|
||||
namespace, _, err := preflight.InitPreflight()
|
||||
if err != nil {
|
||||
fmt.Printf("Preflight role check FAILED\n")
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err = qp.CheckCreateRole(namespace); err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Print("Preflight role FAILED\n")
|
||||
log.Fatal()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return preflightRoleCmd
|
||||
}
|
||||
|
||||
func pfCreateRoleBindingCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
var preflightRoleBindingCmd = &cobra.Command{
|
||||
Use: "rolebinding",
|
||||
Short: "preflight create rolebinding check",
|
||||
Long: `perform preflight rolebinding check to ensure we are able to create a rolebinding in the cluster`,
|
||||
Example: `qliksense preflight rolebinding`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qp := &preflight.QliksensePreflight{Q: q}
|
||||
|
||||
// Preflight createRoleBinding check
|
||||
fmt.Printf("Preflight rolebinding check\n")
|
||||
fmt.Println("---------------------------")
|
||||
namespace, _, err := preflight.InitPreflight()
|
||||
if err != nil {
|
||||
fmt.Printf("Preflight rolebinding check FAILED\n")
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err = qp.CheckCreateRoleBinding(namespace); err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Print("Preflight rolebinding check FAILED\n")
|
||||
log.Fatal()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return preflightRoleBindingCmd
|
||||
}
|
||||
|
||||
func pfCreateServiceAccountCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
var preflightServiceAccountCmd = &cobra.Command{
|
||||
Use: "serviceaccount",
|
||||
Short: "preflight create ServiceAccount check",
|
||||
Long: `perform preflight serviceaccount check to ensure we are able to create a service account in the cluster`,
|
||||
Example: `qliksense preflight serviceaccount`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qp := &preflight.QliksensePreflight{Q: q}
|
||||
|
||||
// Preflight createServiceAccount check
|
||||
fmt.Printf("Preflight ServiceAccount check\n")
|
||||
fmt.Println("-------------------------------------")
|
||||
namespace, _, err := preflight.InitPreflight()
|
||||
if err != nil {
|
||||
fmt.Printf("Preflight serviceaccount check FAILED\n")
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err = qp.CheckCreateServiceAccount(namespace); err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Print("Preflight serviceaccount check FAILED\n")
|
||||
log.Fatal()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return preflightServiceAccountCmd
|
||||
}
|
||||
|
||||
func pfCreateAuthCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
var preflightCreateAuthCmd = &cobra.Command{
|
||||
Use: "authcheck",
|
||||
Short: "preflight authcheck",
|
||||
Long: `perform preflight authcheck that combines the role, rolebinding and serviceaccount checks`,
|
||||
Example: `qliksense preflight authcheck`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qp := &preflight.QliksensePreflight{Q: q}
|
||||
|
||||
// Preflight authcheck
|
||||
fmt.Printf("Preflight authcheck\n")
|
||||
fmt.Println("------------------------")
|
||||
namespace, kubeConfigContents, err := preflight.InitPreflight()
|
||||
if err != nil {
|
||||
fmt.Printf("Preflight authcheck FAILED\n")
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err = qp.CheckCreateRB(namespace, kubeConfigContents); err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Print("Preflight authcheck FAILED\n")
|
||||
log.Fatal()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return preflightCreateAuthCmd
|
||||
}
|
||||
|
||||
func pfMongoCheckCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
var mongodbUrl string
|
||||
var preflightMongoCmd = &cobra.Command{
|
||||
Use: "mongo",
|
||||
Short: "preflight mongo OR preflight mongo --url=<url>",
|
||||
Long: `perform preflight mongo check to ensure we are able to connect to a mongodb instance in the cluster`,
|
||||
Example: `qliksense preflight mongo OR preflight mongo --url=<url>`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qp := &preflight.QliksensePreflight{Q: q}
|
||||
|
||||
// Preflight mongo check
|
||||
fmt.Printf("Preflight mongo check\n")
|
||||
fmt.Println("-------------------------------------")
|
||||
namespace, kubeConfigContents, err := preflight.InitPreflight()
|
||||
if err != nil {
|
||||
fmt.Printf("Preflight mongo check FAILED\n")
|
||||
log.Fatal(err)
|
||||
}
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
if err = qp.CheckMongo(kubeConfigContents, namespace, mongodbUrl); err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Print("Preflight mongo check FAILED\n")
|
||||
log.Fatal()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
f := preflightMongoCmd.Flags()
|
||||
f.StringVarP(&mongodbUrl, "url", "", "", "mongodbUrl to try connecting to")
|
||||
return preflightMongoCmd
|
||||
}
|
||||
27
cmd/qliksense/pull.go
Normal file
27
cmd/qliksense/pull.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func pullQliksenseImages(q *qliksense.Qliksense) *cobra.Command {
|
||||
opts := &aboutCommandOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "pull",
|
||||
Short: "Pull docke images for offline install",
|
||||
Example: `qliksense pull`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if gitRef, err := getAboutCommandGitRef(args); err != nil {
|
||||
return err
|
||||
} else if err = q.PullImages(gitRef, opts.Profile, false); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&opts.Profile, "profile", "", "Configuration profile")
|
||||
return cmd
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func pullQliksenseImages(q *qliksense.Qliksense) *cobra.Command {
|
||||
opts := &aboutCommandOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "pull",
|
||||
Short: "Pull docker images for offline install",
|
||||
Example: `qliksense pull`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
version, err := getAboutCommandGitRef(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return q.PullImages(version, opts.Profile)
|
||||
},
|
||||
}
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&opts.Profile, "profile", "", "Configuration profile")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func pushQliksenseImages(q *qliksense.Qliksense) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "push",
|
||||
Short: "Push docker images for offline install",
|
||||
Example: `qliksense push`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
if qcr, err := qConfig.GetCurrentCR(); err != nil {
|
||||
return err
|
||||
} else if registry := qcr.Spec.GetImageRegistry(); registry == "" {
|
||||
return errors.New("no image registry in config")
|
||||
} else {
|
||||
return q.PushImagesForCurrentCR()
|
||||
}
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
@@ -4,29 +4,27 @@ 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"
|
||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/ttacon/chalk"
|
||||
)
|
||||
|
||||
// To run this project in debug mode, run:
|
||||
// To run this project in ddebug mode, run:
|
||||
// export QLIKSENSE_DEBUG=true
|
||||
// qliksense <command>
|
||||
|
||||
const (
|
||||
qlikSenseHomeVar = "QLIKSENSE_HOME"
|
||||
qlikSenseDirVar = ".qliksense"
|
||||
keepPatchFilesFlagName = "keep-config-repo-patches"
|
||||
keepPatchFilesFlagUsage = "Keep config repo patch files (for debugging)"
|
||||
qlikSenseHomeVar = "QLIKSENSE_HOME"
|
||||
qlikSenseDirVar = ".qliksense"
|
||||
)
|
||||
|
||||
func initAndExecute() error {
|
||||
@@ -34,18 +32,21 @@ func initAndExecute() error {
|
||||
qlikSenseHome string
|
||||
err error
|
||||
)
|
||||
|
||||
qlikSenseHome, err = setUpPaths()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// create dirs and appropriate files for setting up contexts
|
||||
api.LogDebugMessage("QliksenseHomeDir: %s", qlikSenseHome)
|
||||
|
||||
qliksenseClient := qliksense.New(qlikSenseHome)
|
||||
cmd := rootCmd(qliksenseClient)
|
||||
if err := cmd.Execute(); err != nil {
|
||||
//levenstein checks (auto-suggestions)
|
||||
levenstein(cmd)
|
||||
qliksenseClient, err := qliksense.New(qlikSenseHome)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
qliksenseClient.SetUpQliksenseDefaultContext()
|
||||
if err := rootCmd(qliksenseClient).Execute(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -78,76 +79,40 @@ func setUpPaths() (string, error) {
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the version number of qliksense cli",
|
||||
Long: "Print the version number of qliksense cli",
|
||||
Long: `All software has versions. This is Hugo's`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("%s (%s, %s)\n", pkg.Version, pkg.Commit, pkg.CommitDate)
|
||||
},
|
||||
}
|
||||
|
||||
func commandUsesContext(commandName string) bool {
|
||||
return commandName != "" && commandName != "qliksense" && commandName != "help" && commandName != "version"
|
||||
}
|
||||
func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||
var (
|
||||
cmd *cobra.Command
|
||||
)
|
||||
|
||||
func getRootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
cmd = &cobra.Command{
|
||||
Use: "qliksense",
|
||||
Short: "Qliksense cli tool",
|
||||
Long: `qliksense cli tool provides functionality to perform operations on qliksense-k8s, qliksense operator, and kubernetes cluster`,
|
||||
Args: cobra.ArbitraryArgs,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
if commandUsesContext(cmd.Name()) {
|
||||
globalEulaPreRun(cmd, p)
|
||||
if err := p.SetUpQliksenseDefaultContext(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pf := api.NewPreflightConfig(p.QliksenseHome)
|
||||
if err := pf.Initialize(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
globalEulaPostRun(cmd, p)
|
||||
}
|
||||
},
|
||||
PersistentPostRun: func(cmd *cobra.Command, args []string) {
|
||||
if commandUsesContext(cmd.Name()) {
|
||||
globalEulaPostRun(cmd, p)
|
||||
}
|
||||
},
|
||||
Long: `qliksense cli tool provides a wrapper around the porter api as well as
|
||||
provides addition functionality`,
|
||||
Args: cobra.ArbitraryArgs,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
origHelpFunc := cmd.HelpFunc()
|
||||
cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
|
||||
if !commandUsesContext(cmd.Name()) {
|
||||
cmd.Flags().MarkHidden("acceptEULA")
|
||||
}
|
||||
origHelpFunc(cmd, args)
|
||||
})
|
||||
accept := ""
|
||||
cmd.PersistentFlags().StringVarP(&accept, "acceptEULA", "a", "", "Accept EULA for qliksense")
|
||||
|
||||
cmd.Flags().SetInterspersed(false)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
viper.SetEnvPrefix("QLIKSENSE")
|
||||
viper.AutomaticEnv()
|
||||
}
|
||||
|
||||
func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||
cmd := getRootCmd(p)
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
cmd.AddCommand(getInstallableVersionsCmd(p))
|
||||
// For qliksense overrides/commands
|
||||
|
||||
cmd.AddCommand(pullQliksenseImages(p))
|
||||
cmd.AddCommand(pushQliksenseImages(p))
|
||||
cmd.AddCommand(about(p))
|
||||
// add version command
|
||||
cmd.AddCommand(versionCmd)
|
||||
|
||||
// add operator command
|
||||
cmd.AddCommand(operatorCmd)
|
||||
//operatorCmd.AddCommand(operatorViewCmd(p))
|
||||
operatorCmd.AddCommand(operatorCrdCmd(p))
|
||||
operatorCmd.AddCommand(operatorControllerCmd(p))
|
||||
|
||||
operatorCmd.AddCommand(operatorViewCmd(p))
|
||||
//add fetch command
|
||||
cmd.AddCommand(fetchCmd(p))
|
||||
|
||||
@@ -155,7 +120,6 @@ 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))
|
||||
@@ -180,45 +144,42 @@ 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))
|
||||
|
||||
// open editor for config
|
||||
configCmd.AddCommand(configEditCmd(p))
|
||||
// add uninstall command
|
||||
cmd.AddCommand(uninstallCmd(p))
|
||||
|
||||
// add crds
|
||||
cmd.AddCommand(crdsCmd)
|
||||
crdsCmd.AddCommand(crdsViewCmd(p))
|
||||
crdsCmd.AddCommand(crdsInstallCmd(p))
|
||||
|
||||
// add preflight command
|
||||
preflightCmd := preflightCmd(p)
|
||||
preflightCmd.AddCommand(pfDnsCheckCmd(p))
|
||||
preflightCmd.AddCommand(pfK8sVersionCheckCmd(p))
|
||||
preflightCmd.AddCommand(pfAllChecksCmd(p))
|
||||
preflightCmd.AddCommand(pfMongoCheckCmd(p))
|
||||
preflightCmd.AddCommand(pfDeploymentCheckCmd(p))
|
||||
preflightCmd.AddCommand(pfServiceCheckCmd(p))
|
||||
preflightCmd.AddCommand(pfPodCheckCmd(p))
|
||||
preflightCmd.AddCommand(pfCreateRoleCheckCmd(p))
|
||||
preflightCmd.AddCommand(pfCreateRoleBindingCheckCmd(p))
|
||||
preflightCmd.AddCommand(pfCreateServiceAccountCheckCmd(p))
|
||||
preflightCmd.AddCommand(pfCreateAuthCheckCmd(p))
|
||||
|
||||
cmd.AddCommand(preflightCmd)
|
||||
cmd.AddCommand(loadCrFile(p))
|
||||
cmd.AddCommand((applyCmd(p)))
|
||||
return cmd
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
viper.SetEnvPrefix("QLIKSENSE")
|
||||
viper.AutomaticEnv()
|
||||
}
|
||||
|
||||
func downloadFile(url string, filepath string) error {
|
||||
var (
|
||||
out *os.File
|
||||
err error
|
||||
resp *http.Response
|
||||
)
|
||||
// Create the file
|
||||
if out, err = os.Create(filepath); err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// Get the data
|
||||
if resp, err = http.Get(url); err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Write the body to file
|
||||
if _, err = io.Copy(out, resp.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copy(src, dst string) (int64, error) {
|
||||
var (
|
||||
source, destination *os.File
|
||||
@@ -246,22 +207,3 @@ func copy(src, dst string) (int64, error) {
|
||||
nBytes, err = io.Copy(destination, source)
|
||||
return nBytes, err
|
||||
}
|
||||
|
||||
func levenstein(cmd *cobra.Command) {
|
||||
cmd.SuggestionsMinimumDistance = 2
|
||||
if len(os.Args) > 1 {
|
||||
args := os.Args[1]
|
||||
suggest := cmd.SuggestionsFor(args)
|
||||
if len(suggest) > 0 {
|
||||
arg := []string{}
|
||||
for _, cm := range os.Args {
|
||||
arg = append(arg, cm)
|
||||
}
|
||||
if !strings.EqualFold(arg[1], suggest[0]) {
|
||||
arg[1] = suggest[0]
|
||||
out := ansi.NewColorableStdout()
|
||||
fmt.Fprintln(out, chalk.Green.Color("Did you mean: "), chalk.Bold.TextStyle(strings.Join(arg, " ")), "?")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -8,7 +9,7 @@ import (
|
||||
func uninstallCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "uninstall",
|
||||
Short: "Uninstall the deployed qliksense.",
|
||||
Short: "Uninstall the deployed qliksense with release name [ " + qapi.NewQConfig(q.QliksenseHome).Spec.CurrentContext + " ]",
|
||||
Long: `Uninstall the deployed qliksense. By default uninstall the current context`,
|
||||
Example: `qliksense uninstall <context-name>`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -6,18 +6,15 @@ import (
|
||||
)
|
||||
|
||||
func upgradeCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||
keepPatchFiles := false
|
||||
c := &cobra.Command{
|
||||
Use: "upgrade",
|
||||
Short: "upgrade qliksense release",
|
||||
Long: `upgrade qliksense release`,
|
||||
Long: `upgrade qliksesne release`,
|
||||
Example: `qliksense upgrade <version>`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return q.UpgradeQK8s(keepPatchFiles)
|
||||
return q.UpgradeQK8s()
|
||||
},
|
||||
}
|
||||
|
||||
f := c.Flags()
|
||||
f.BoolVar(&keepPatchFiles, keepPatchFilesFlagName, keepPatchFiles, keepPatchFilesFlagUsage)
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
# qliksense command reference
|
||||
|
||||
## qliksense apply
|
||||
|
||||
`qliksense apply` command takes input a cr file or input from pipe
|
||||
|
||||
- `qliksense apply -f cr-file.yaml`
|
||||
- `cat cr-file.yaml | qliksense apply -f -`
|
||||
|
||||
the content of `cr-file.yaml` should be something similar
|
||||
|
||||
```yaml
|
||||
apiVersion: qlik.com/v1
|
||||
kind: Qliksense
|
||||
metadata:
|
||||
name: qlik-test
|
||||
labels:
|
||||
version: v0.0.2
|
||||
spec:
|
||||
configs:
|
||||
qliksense:
|
||||
- name: acceptEULA
|
||||
value: "yes"
|
||||
secrets:
|
||||
qliksense:
|
||||
- name: mongoDbUri
|
||||
value: mongodb://qlik-test-mongodb:27017/qliksense?ssl=false
|
||||
profile: docker-desktop
|
||||
rotateKeys: "yes"
|
||||
```
|
||||
|
||||
This will do everything `qliksense load` does and install the qliksense into the cluster.
|
||||
|
||||
## qliksense load
|
||||
|
||||
`qliksense load` command takes input a cr file or input from pipe.
|
||||
|
||||
- `qliksense load -f cr-file.yaml`
|
||||
- `cat cr-file.yaml | qliksense load -f -`
|
||||
|
||||
This will load the CR into `${QLIKSENSE_HOME}` folder, create context structure and set the current context to that CR.
|
||||
This will also encrypt the secrets from CR while writing the CR into the disk.
|
||||
|
||||
## qliksense about
|
||||
|
||||
About action will display inside information regarding [qliksense-k8](https://github.com/qlik-oss/qliksense-k8s) release.
|
||||
|
||||
it will support following flags
|
||||
|
||||
- `qliksense about 1.0.0` display default profile for tag `1.0.0`.
|
||||
- `qliksense about 1.0.0 --profile=docker-desktop`
|
||||
- `qliksense about`
|
||||
- assuming current directory has `manifests/docker-desktop`
|
||||
- or get version information from pull of `qliksense-k8s` `master`
|
||||
|
||||
using other supported commands user might have built the CR into the location `~/.qliksense/myqliksense.yaml`
|
||||
|
||||
```yaml
|
||||
apiVersion: qlik.com/v1
|
||||
kind: QlikSense
|
||||
metadata:
|
||||
name: myqliksense
|
||||
spec:
|
||||
profile: docker-desktop
|
||||
manifestsRoot: /Usr/ddd/my-k8-repo/manifests
|
||||
namespace: myqliksense
|
||||
storageClassName: efs
|
||||
configs:
|
||||
qliksense:
|
||||
- name: acceptEULA
|
||||
value: "yes"
|
||||
secrets:
|
||||
qliksense:
|
||||
- name: mongoDbUri
|
||||
value: "mongo://mongo:3307"
|
||||
- name: messagingPassword
|
||||
valueFromKey: messagingPassword
|
||||
```
|
||||
|
||||
In that case the command would be
|
||||
|
||||
- `qliksense about`
|
||||
- display from `/Usr/ddd/my-k8-repo/manifests/docker-desktop` location
|
||||
- pull from `master` if directory invalid/empty
|
||||
|
||||
|
||||
## qliksense config
|
||||
|
||||
Config action will perform operations on configurations and contexts regarding the [qliksense-k8](https://github.com/qlik-oss/qliksense-k8s) release.
|
||||
|
||||
it will support following commands:
|
||||
|
||||
- `qliksense config apply` - generate the patchs and apply manifests to k8s
|
||||
- `qliksense config list-contexts` - retrieves the contexts and lists them
|
||||
- `qliksense config set` - configure a key value pair into the current context
|
||||
- `qliksense config set-configs` - set configurations into the qliksense context as key-value pairs
|
||||
- `qliksense config set-context` - sets the context in which the Kubernetes cluster and resources live in
|
||||
- `qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=false` - set secrets configurations into the qliksense context as key-value pairs and show encrypted value as part of CR
|
||||
- `qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=true` - set secrets configurations into the qliksense context as key-value pairs and show a key reference to the created Kubernetes secret resource as part of the CR
|
||||
- `qliksense config view` - view the qliksense operator CR
|
||||
- `qliksense config delete-context` - deletes a specific context locally (not in-cluster). Deletes context in spec of `config.yaml` and locally deletes entire folder of specified context (does not delete in-cluster secrets)
|
||||
|
||||
|
||||
the global file that abstracts all the contexts is `config.yaml`, located at: `~/.qliksense/config.yaml`:
|
||||
```yaml
|
||||
apiVersion: config.qlik.com/v1
|
||||
kind: QliksenseConfig
|
||||
metadata:
|
||||
name: QliksenseConfigMetadata
|
||||
spec:
|
||||
contexts:
|
||||
- name: qlik-default
|
||||
crFile: /Users/fff/.qliksense/contexts/qlik-default/qlik-default.yaml
|
||||
- name: myqliksense
|
||||
crFile: /Users/fff/.qliksense/contexts/myqliksense/myqliksense.yaml
|
||||
- name: hello
|
||||
crFile: /Users/fff/.qliksense/contexts/hello/hello.yaml
|
||||
currentContext: hello
|
||||
```
|
||||
@@ -1,97 +0,0 @@
|
||||
# How qliksense cli works
|
||||
|
||||
At the initialization, `qliksense` cli creates few files in the director `~/.qliksene` and it contains following files:
|
||||
|
||||
```console
|
||||
.qliksense
|
||||
├── config.yaml
|
||||
├── contexts
|
||||
│ └── qlik-default
|
||||
│ └── qlik-default.yaml
|
||||
└── ejson
|
||||
└── keys
|
||||
```
|
||||
|
||||
`qlik-default.yaml` is a default CR created with some default values like:
|
||||
|
||||
```yaml
|
||||
apiVersion: qlik.com/v1
|
||||
kind: Qliksense
|
||||
metadata:
|
||||
name: qlik-default
|
||||
spec:
|
||||
profile: docker-desktop
|
||||
secrets:
|
||||
qliksense:
|
||||
- name: mongoDbUri
|
||||
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
|
||||
rotateKeys: "yes"
|
||||
releaseName: qlik-default
|
||||
```
|
||||
|
||||
The `qliksense` cli creates a default qliksense context (different from kubectl context) named `qlik-default` which will be the prefix for all kubernetes resources created by the cli under this context later on.
|
||||
|
||||
New context and configuration can be created by the cli, get available commands using:
|
||||
|
||||
```console
|
||||
qliksense config -h
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
`qliksense` cli works in two modes
|
||||
|
||||
- With a git repo fork/clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s)
|
||||
- Without git repo
|
||||
|
||||
## Without git repo
|
||||
|
||||
In this mode `qliksense` CLI downloads the specified version from [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and places it in `~/.qliksense/contexts/<context-name>/qlik-k8s` folder.
|
||||
|
||||
The qliksense cli creates a CR for the QlikSense operator and all config operations are peformed to edit the CR.
|
||||
|
||||
`qliksense install` or `qliksense config apply` will generate patches in local file system (i.e `~/.qliksense/contexts/<context-name>/qlik-k8s`) and
|
||||
|
||||
- Install those manifests into the cluster
|
||||
- Create a custom resoruce (CR) for the `qliksene operator`.
|
||||
|
||||
The operator makes the association to the installed resoruces so that when `qliksense uninstall` is performed the operator can delete all kubernetes resources related to QSEoK for the current context.
|
||||
|
||||
## With a git repo
|
||||
|
||||
Create a fork or clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and push it to your git repo/server
|
||||
|
||||
To add your repo into CR, perform the following:
|
||||
|
||||
```bash
|
||||
qliksense config set git.repository="https://github.com/my-org/qliksense-k8s"
|
||||
qliksense config set git.accessToken="<mySecretToken>"
|
||||
```
|
||||
|
||||
When you perform `qliksense install` or `qliksene config apply`, qliksense operator performs these tasks:
|
||||
|
||||
- Download corresponding version of manifests from the your git repo
|
||||
- Generate kustomize patches
|
||||
- Install kubernetes resources
|
||||
- Push generated patches into a new branch in the provided git repo. _Gives you ability to merge patches into your master branch_
|
||||
- Create a CronJob to monitor master branch. Any changes pushed to master branch will be applied into the cluster. _This is a light weight `git-ops` model_
|
||||
|
||||
## GitOps
|
||||
|
||||
To enable gitops, the following section should be in the CR
|
||||
|
||||
```yaml
|
||||
....
|
||||
spec:
|
||||
git:
|
||||
repository: https://github.com/<OWNER>/<REPO>
|
||||
accessToken: "<git-token>"
|
||||
userName: "<git-username>"
|
||||
gitOps:
|
||||
enabled: "yes"
|
||||
schedule: "*/5 * * * *"
|
||||
watchBranch: <myBranch>
|
||||
image: qlik-docker-oss.bintray.io/qliksense-repo-watcher
|
||||
....
|
||||
```
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
# 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"
|
||||
```
|
||||
@@ -1,22 +0,0 @@
|
||||
# Overview
|
||||
|
||||
The Qlik Sense installer CLI (`qliksense`) provides an imperative interface to many of the configurations that need to be applied against the declarative structure described in [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s). This cli facilitates:
|
||||
|
||||
- Installation of QSEoK
|
||||
- Installation of qliksense operator to manage QSEoK
|
||||
- Air gapped installation of QSEoK
|
||||
|
||||
!!! 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
|
||||
@@ -1,253 +0,0 @@
|
||||
##Preflight checks
|
||||
Preflight checks provide pre-installation cluster conformance testing and validation before we install qliksense on the cluster. We gather a suite of conformance tests that can be easily written and run on the target cluster to verify that cluster-specific requirements are met.
|
||||
The suite consists of a set of `collectors` which run the specifications of every test and `analyzers` which analyze the results of every test run by the collector.
|
||||
We support the following tests at the moment as part of preflight checks, and the range of the suite will be expanded in future.
|
||||
|
||||
Run the following command to view help about the commands supported by preflight at any moment:
|
||||
```console
|
||||
$ qliksense preflight
|
||||
perform preflight checks on the cluster
|
||||
|
||||
Usage:
|
||||
qliksense preflight [command]
|
||||
|
||||
Examples:
|
||||
qliksense preflight <preflight_check_to_run>
|
||||
|
||||
Available Commands:
|
||||
all perform all checks
|
||||
dns perform preflight dns check
|
||||
k8s-version check k8s version
|
||||
|
||||
Flags:
|
||||
-h, --help help for preflight
|
||||
```
|
||||
|
||||
### DNS check
|
||||
Run the following command to perform preflight DNS check. We setup a kubernetes deployment and try to reach it as part of establishing DNS connectivity in this check.
|
||||
The expected output should be similar to the one shown below.
|
||||
```console
|
||||
$ qliksense preflight dns
|
||||
|
||||
Preflight DNS check
|
||||
---------------------
|
||||
Created deployment "dep-dns-preflight-check"
|
||||
Created service "svc-dns-pf-check"
|
||||
Created pod: pf-pod-1
|
||||
Fetching pod: pf-pod-1
|
||||
Fetching pod: pf-pod-1
|
||||
Exec-ing into the container...
|
||||
Preflight DNS check: PASSED
|
||||
Completed preflight DNS check
|
||||
Cleaning up resources...
|
||||
Deleted pod: pf-pod-1
|
||||
Deleted service: svc-dns-pf-check
|
||||
Deleted deployment: dep-dns-preflight-check
|
||||
|
||||
```
|
||||
|
||||
### Kubernetes version check
|
||||
We check the version of the target kubernetes cluster and ensure that it falls in the valid range of kubernetes versions that are supported by qliksense.
|
||||
The command to run this check and the expected similar output are as shown below:
|
||||
```console
|
||||
$ qliksense preflight k8s-version
|
||||
|
||||
Preflight kubernetes minimum version check
|
||||
------------------------------------------
|
||||
Kubernetes API Server version: v1.15.5
|
||||
Current K8s Version: 1.15.5
|
||||
Current 1.15.5 is greater than minimum required version:1.11.0, hence good to go
|
||||
Preflight minimum kubernetes version check: PASSED
|
||||
Completed Preflight kubernetes minimum version check
|
||||
|
||||
```
|
||||
|
||||
### Service check
|
||||
We use the commmand below to test if we are able to create a service in the cluster.
|
||||
```console
|
||||
$ qliksense preflight service
|
||||
|
||||
Preflight service check
|
||||
-----------------------
|
||||
|
||||
Preflight service check:
|
||||
Created service "svc-pf-check"
|
||||
Preflight service creation check: PASSED
|
||||
Cleaning up resources...
|
||||
Deleted service: svc-pf-check
|
||||
Completed preflight service check
|
||||
```
|
||||
|
||||
### Deployment check
|
||||
We use the commmand below to test if we are able to create a deployment in the cluster. After the test exexutes, we wait until the created deployment terminates before we exit the command.
|
||||
```console
|
||||
$ qliksense preflight deployment
|
||||
|
||||
Preflight deployment check
|
||||
-----------------------
|
||||
Preflight deployment check:
|
||||
Created deployment "deployment-preflight-check"
|
||||
Preflight Deployment check: PASSED
|
||||
Cleaning up resources...
|
||||
Deleted deployment: deployment-preflight-check
|
||||
Completed preflight deployment check
|
||||
```
|
||||
|
||||
### Pod check
|
||||
We use the commmand below to test if we are able to create a pod in the cluster.
|
||||
```console
|
||||
$ qliksense preflight pod
|
||||
|
||||
Preflight pod check
|
||||
--------------------
|
||||
|
||||
Preflight pod check:
|
||||
Created pod: pod-pf-check
|
||||
Preflight pod creation check: PASSED
|
||||
Cleaning up resources...
|
||||
Deleted pod: pod-pf-check
|
||||
Completed preflight pod check
|
||||
```
|
||||
|
||||
### Create-Role check
|
||||
We use the command below to test if we are able to create a role in the cluster
|
||||
```shell
|
||||
$ qliksense preflight create-role
|
||||
Preflight create-role check
|
||||
---------------------------
|
||||
Preflight create-role check:
|
||||
Created role: role-preflight-check
|
||||
Preflight create-role check: PASSED
|
||||
Cleaning up resources...
|
||||
Deleted role: role-preflight-check
|
||||
|
||||
Completed preflight create-role check
|
||||
```
|
||||
|
||||
### Create-RoleBinding check
|
||||
We use the command below to test if we are able to create a role binding in the cluster
|
||||
```shell
|
||||
$ qliksense preflight createRoleBinding
|
||||
|
||||
Preflight create roleBinding check
|
||||
---------------------------
|
||||
Preflight createRoleBinding check:
|
||||
Created RoleBinding: role-binding-preflight-check
|
||||
Preflight createRoleBinding check: PASSED
|
||||
Cleaning up resources...
|
||||
Deleting RoleBinding: role-binding-preflight-check
|
||||
Deleted RoleBinding: role-binding-preflight-check
|
||||
|
||||
Completed preflight createRoleBinding check
|
||||
```
|
||||
|
||||
### Create-ServiceAccount check
|
||||
We use the command below to test if we are able to create a service account in the cluster
|
||||
```shell
|
||||
$ qliksense preflight createServiceAccount
|
||||
|
||||
Preflight create ServiceAccount check
|
||||
-------------------------------------
|
||||
Preflight createServiceAccount check:
|
||||
Created Service Account: preflight-check-test-serviceaccount
|
||||
Preflight createServiceAccount check: PASSED
|
||||
Cleaning up resources...
|
||||
Deleting ServiceAccount: preflight-check-test-serviceaccount
|
||||
Deleted ServiceAccount: preflight-check-test-serviceaccount
|
||||
|
||||
Completed preflight createServiceAccount check
|
||||
```
|
||||
|
||||
### CreateRB check
|
||||
We use the command below to combine creation of role, role binding, and service account tests
|
||||
```shell
|
||||
$ qliksense preflight createRB
|
||||
|
||||
Preflight createRB check
|
||||
-------------------------------------
|
||||
Preflight create-role check:
|
||||
Created role: role-preflight-check
|
||||
Preflight create-role check: PASSED
|
||||
Cleaning up resources...
|
||||
Deleted role: role-preflight-check
|
||||
|
||||
Completed preflight create-role check
|
||||
|
||||
Preflight create RoleBinding check:
|
||||
Created RoleBinding: role-binding-preflight-check
|
||||
Preflight create RoleBinding check: PASSED
|
||||
Cleaning up resources...
|
||||
Deleted RoleBinding: role-binding-preflight-check
|
||||
|
||||
Completed preflight create RoleBinding check
|
||||
|
||||
Preflight createServiceAccount check:
|
||||
Created Service Account: preflight-check-test-serviceaccount
|
||||
Preflight createServiceAccount check: PASSED
|
||||
Cleaning up resources...
|
||||
Deleted ServiceAccount: preflight-check-test-serviceaccount
|
||||
|
||||
Completed preflight createServiceAccount check
|
||||
Completed preflight CreateRB check
|
||||
```
|
||||
|
||||
### Mongodb check
|
||||
We can check if we are able to connect to an instance of mongodb on the cluster by either supplying the mongodbUri as part of the command or infer it from the current context.
|
||||
|
||||
```shell
|
||||
qliksense preflight mongo --url=<url> OR
|
||||
qliksense preflight mongo
|
||||
|
||||
Preflight mongo check
|
||||
---------------------
|
||||
Preflight mongodb check:
|
||||
Created pod: pf-mongo-pod
|
||||
stdout: MongoDB shell version v4.2.5
|
||||
connecting to: <url>/?compressors=disabled&gssapiServiceName=mongodb
|
||||
Implicit session: session { "id" : UUID("64f639d3-2c93-4894-80f6-ee14acaf56a5") }
|
||||
MongoDB server version: 4.2.5
|
||||
bye
|
||||
stderr:
|
||||
Preflight mongo check: PASSED
|
||||
Deleted pod: pf-mongo-pod
|
||||
Completed preflight mongodb check
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Running all checks
|
||||
Run the command shown below to execute all preflight checks.
|
||||
```console
|
||||
$ qliksense preflight all --mongodb-url=<url> OR
|
||||
$ qliksense preflight all
|
||||
|
||||
Running all preflight checks
|
||||
|
||||
Preflight DNS check
|
||||
-------------------
|
||||
Created deployment "dep-dns-preflight-check"
|
||||
Created service "svc-dns-pf-check"
|
||||
Created pod: pf-pod-1
|
||||
Fetching pod: pf-pod-1
|
||||
Fetching pod: pf-pod-1
|
||||
Exec-ing into the container...
|
||||
Preflight DNS check: PASSED
|
||||
Completed preflight DNS check
|
||||
Cleaning up resources...
|
||||
Deleted pod: pf-pod-1
|
||||
Deleted service: svc-dns-pf-check
|
||||
Deleted deployment: dep-dns-preflight-check
|
||||
|
||||
Preflight kubernetes minimum version check
|
||||
------------------------------------------
|
||||
Kubernetes API Server version: v1.15.5
|
||||
Current K8s Version: 1.15.5
|
||||
Current 1.15.5 is greater than minimum required version:1.11.0, hence good to go
|
||||
Preflight minimum kubernetes version check: PASSED
|
||||
Completed Preflight kubernetes minimum version check
|
||||
...
|
||||
...
|
||||
All preflight checks have PASSED
|
||||
Completed running all preflight checks
|
||||
|
||||
```
|
||||
46
go.mod
46
go.mod
@@ -10,58 +10,66 @@ replace (
|
||||
k8s.io/client-go => k8s.io/client-go v0.0.0-20191016111102-bec269661e48
|
||||
k8s.io/kubectl => k8s.io/kubectl v0.0.0-20191016120415-2ed914427d51
|
||||
|
||||
sigs.k8s.io/kustomize/api => github.com/qlik-oss/kustomize/api v0.3.3-0.20200402170547-2e8140160c36
|
||||
sigs.k8s.io/kustomize/api => github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.52.0 // indirect
|
||||
cloud.google.com/go/storage v1.5.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.0.3
|
||||
github.com/Shopify/ejson v1.2.1
|
||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
|
||||
github.com/aws/aws-sdk-go v1.28.9 // indirect
|
||||
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 // indirect
|
||||
github.com/bugsnag/bugsnag-go v1.5.3 // indirect
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
||||
github.com/cloudflare/cfssl v1.4.1 // indirect
|
||||
github.com/containerd/containerd v1.3.2 // indirect
|
||||
github.com/containerd/continuity v0.0.0-20191214063359-1097c8bae83b // indirect
|
||||
github.com/containers/image/v5 v5.1.0
|
||||
github.com/docker/cli v0.0.0-20191212191748-ebca1413117a // indirect
|
||||
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7 // indirect
|
||||
github.com/docker/cli v0.0.0-20191212191748-ebca1413117a
|
||||
github.com/docker/distribution v2.7.1+incompatible
|
||||
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7
|
||||
github.com/docker/go v1.5.1-1 // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/go-git/go-git/v5 v5.0.0
|
||||
github.com/gobuffalo/envy v1.9.0 // indirect
|
||||
github.com/gobuffalo/logger v1.0.3 // indirect
|
||||
github.com/gobuffalo/packd v1.0.0 // indirect
|
||||
github.com/gobuffalo/packr/v2 v2.7.1
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||
github.com/golang/protobuf v1.3.3 // indirect
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gorilla/mux v1.7.3 // indirect
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
|
||||
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
||||
github.com/jinzhu/gorm v1.9.11 // indirect
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.4
|
||||
github.com/mattn/go-tty v0.0.3
|
||||
github.com/mattn/go-sqlite3 v2.0.1+incompatible // indirect
|
||||
github.com/miekg/pkcs11 v1.0.3 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/otiai10/copy v1.1.1
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/qlik-oss/k-apis v0.1.1
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/qlik-oss/k-apis v0.0.8
|
||||
github.com/rogpeppe/go-internal v1.5.2 // indirect
|
||||
github.com/spf13/cobra v0.0.6
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/spf13/viper v1.6.1
|
||||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31
|
||||
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 // indirect
|
||||
github.com/theupdateframework/notary v0.6.1 // indirect
|
||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 // indirect
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a // indirect
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
|
||||
golang.org/x/tools v0.0.0-20200312194400-c312e98713c2 // indirect
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
|
||||
golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c // indirect
|
||||
golang.org/x/tools v0.0.0-20200219054238-753a1d49df85 // indirect
|
||||
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b // indirect
|
||||
google.golang.org/grpc v1.27.0 // indirect
|
||||
gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect
|
||||
gopkg.in/fatih/pool.v2 v2.0.0 // indirect
|
||||
gopkg.in/gorethink/gorethink.v3 v3.0.5 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
gopkg.in/yaml.v3 v3.0.0-20190924164351-c8b7dadae555
|
||||
k8s.io/api v0.17.0
|
||||
k8s.io/apimachinery v0.17.0
|
||||
k8s.io/client-go v11.0.0+incompatible
|
||||
sigs.k8s.io/kustomize/api v0.3.2
|
||||
sigs.k8s.io/yaml v1.1.0
|
||||
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 // indirect
|
||||
)
|
||||
|
||||
exclude github.com/Azure/go-autorest v12.0.0+incompatible
|
||||
|
||||
167
go.sum
167
go.sum
@@ -1,6 +1,9 @@
|
||||
bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
|
||||
bitbucket.org/liamstask/goose v0.0.0-20150115234039-8488cc47d90c/go.mod h1:hSVuE3qU7grINVSwrmzHfpg9k87ALBk+XaualNyUzI4=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
|
||||
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.39.0 h1:UgQP9na6OTfp4dsAiz/eFpFA1C6tPdH5wiRdi19tuMw=
|
||||
cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts=
|
||||
@@ -36,7 +39,6 @@ github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuE
|
||||
github.com/Azure/azure-pipeline-go v0.2.1 h1:OLBdZJ3yvOn2MezlWvbrBMTEUQC72zAftRZOMdj5HYo=
|
||||
github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
|
||||
github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v30.1.0+incompatible h1:HyYPft8wXpxMd0kfLtXo6etWcO+XuPbLkcgx9g2cqxU=
|
||||
github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-service-bus-go v0.9.1/go.mod h1:yzBx6/BUGfjfeqbRZny9AQIbIe3AcV9WZbAdpkoXOa0=
|
||||
github.com/Azure/azure-storage-blob-go v0.8.0 h1:53qhf0Oxa0nOjgbDeeYPUeyiNmafAFEY95rZLK0Tj6o=
|
||||
@@ -54,7 +56,6 @@ github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjW
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY=
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k=
|
||||
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
|
||||
@@ -63,6 +64,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/DataDog/zstd v1.4.0/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
||||
github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20190605020000-c4ba1fdf4d36/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo=
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e h1:eb0Pzkt15Bm7f2FFYv7sjY7NPFi3cPkS3tv1CcrFBWA=
|
||||
@@ -95,9 +98,14 @@ github.com/Shopify/ejson v1.2.1 h1:Dx0Ipn0mUgrZlzIa5oIUrH0rdSmBOyod/UJmQQK1KHo=
|
||||
github.com/Shopify/ejson v1.2.1/go.mod h1:J8cw5GOA0l/aMOPp+uDfwNYVbeqIaBhzRkv1+76UCvk=
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM=
|
||||
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
|
||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
|
||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@@ -108,6 +116,7 @@ github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6 h1:bZ28Hqta7TFA
|
||||
github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
@@ -131,9 +140,9 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
|
||||
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
|
||||
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
|
||||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
||||
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
@@ -150,8 +159,11 @@ github.com/bugsnag/bugsnag-go v1.5.3 h1:yeRUT3mUE13jL1tGwvoQsKdVbAsQx9AJ+fqahKve
|
||||
github.com/bugsnag/bugsnag-go v1.5.3/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
|
||||
github.com/bugsnag/panicwrap v1.2.0 h1:OzrKrRvXis8qEvOkfcxNcYbOd2O7xXS2nnKMEMABFQA=
|
||||
github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
|
||||
@@ -163,6 +175,11 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY=
|
||||
github.com/cloudflare/cfssl v1.4.1 h1:vScfU2DrIUI9VPHBVeeAQ0q5A+9yshO1Gz+3QoUQiKw=
|
||||
github.com/cloudflare/cfssl v1.4.1/go.mod h1:KManx/OJPb5QY+y0+o/898AMcM128sF0bURvoVUSjTo=
|
||||
github.com/cloudflare/go-metrics v0.0.0-20151117154305-6a9aea36fb41/go.mod h1:eaZPlJWD+G9wseg1BuRXlHnjntPMrywMsyxf+LTOdP4=
|
||||
github.com/cloudflare/redoctober v0.0.0-20171127175943-746a508df14c/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo=
|
||||
github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f h1:tSNMc+rJDfmYntojat8lljbt1mgKNpTxUZJsSzJ9Y1s=
|
||||
github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
|
||||
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
||||
@@ -205,11 +222,10 @@ github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfc
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
|
||||
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
|
||||
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -217,6 +233,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=
|
||||
github.com/deislabs/oras v0.7.0 h1:RnDoFd3tQYODMiUqxgQ8JxlrlWL0/VMKIKRD01MmNYk=
|
||||
github.com/deislabs/oras v0.7.0/go.mod h1:sqMKPG3tMyIX9xwXUBRLhZ24o+uT4y6jgBD2RzUTKDM=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
|
||||
github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
@@ -232,6 +250,8 @@ github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc
|
||||
github.com/docker/docker-credential-helpers v0.6.1/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
||||
github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ=
|
||||
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
||||
github.com/docker/go v1.5.1-1 h1:hr4w35acWBPhGBXlzPoHpmZ/ygPjnmFVxGxxGnMyP7k=
|
||||
github.com/docker/go v1.5.1-1/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
|
||||
github.com/docker/go-connections v0.0.0-20180212134524-7beb39f0b969/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
@@ -256,6 +276,9 @@ github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 h1:qk/FSDDxo05w
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad h1:Qk76DOWdOp+GlyDKBAG3Klr9cn7N+LcYc82AZ2S7+cA=
|
||||
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad/go.mod h1:mPKfmRa823oBIgl2r20LeMSpTAteW5j7FLkc0vjmzyQ=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e h1:p1yVGRW3nmb85p1Sh1ZJSDm4A4iKLS5QNbvUHMgGu/M=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
@@ -266,6 +289,8 @@ github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM=
|
||||
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
|
||||
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
@@ -290,6 +315,7 @@ github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa h1:RDBNVkRviHZtvD
|
||||
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
|
||||
github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
|
||||
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v0.0.0-20161207003320-04f313413ffd/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
@@ -300,12 +326,6 @@ github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aev
|
||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
|
||||
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
|
||||
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
|
||||
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
|
||||
github.com/go-git/go-git/v5 v5.0.0 h1:k5RWPm4iJwYtfWoxIJy4wJX9ON7ihPeZZYC1fLYDnpg=
|
||||
github.com/go-git/go-git/v5 v5.0.0/go.mod h1:oYD8y9kWsGINPFJoLdaScGCN6dlKg23blmClfZwtUVA=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
@@ -359,6 +379,7 @@ github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tF
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
|
||||
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
|
||||
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
@@ -400,6 +421,7 @@ github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v0.0.0-20170815085658-fcdc5011193f/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
|
||||
@@ -432,6 +454,7 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=
|
||||
@@ -455,6 +478,8 @@ github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
|
||||
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@@ -508,6 +533,7 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51
|
||||
github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA=
|
||||
github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/mux v0.0.0-20170217192616-94e7d24fd285/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
@@ -528,6 +554,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpg
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||
github.com/hairyhenderson/gomplate/v3 v3.6.0 h1:EryWG7cCxvZ2awoZ957B3AMAd20Zy0uRXeZ7TXXMIp0=
|
||||
github.com/hairyhenderson/gomplate/v3 v3.6.0/go.mod h1:RbEC6Y14nNTHCtNWpBAkwqDP4ICFUrAH0S8PUFa0qT4=
|
||||
github.com/hairyhenderson/toml v0.3.1-0.20191004034452-2a4f3b6160f2 h1:Dc4YWWuY02jqhCnErAH++juCTwEPLstAOOVhyPXeE7Q=
|
||||
@@ -570,8 +598,6 @@ github.com/hashicorp/go-retryablehttp v0.6.3/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-rootcerts v1.0.1 h1:DMo4fmknnz0E0evoNYnV48RjWndOsmd6OW+09R3cEP8=
|
||||
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
|
||||
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
|
||||
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
|
||||
@@ -579,7 +605,6 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
|
||||
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
@@ -624,9 +649,17 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
|
||||
github.com/jinzhu/gorm v1.9.11 h1:gaHGvE+UnWGlbWG4Y3FUwY1EcZ5n6S9WtqBA/uySMLE=
|
||||
github.com/jinzhu/gorm v1.9.11/go.mod h1:bu/pK8szGZ2puuErfU0RwyeNdsf3e6nCX/noXaVxkfw=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
|
||||
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo=
|
||||
github.com/jmoiron/sqlx v0.0.0-20180124204410-05cef0741ade/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
|
||||
github.com/johannesboyne/gofakes3 v0.0.0-20191029185751-e238f04965fe h1:9kkgzfTjcHQqS6wGlEhJBJmAMI75lKyHX69w/ii+5So=
|
||||
github.com/johannesboyne/gofakes3 v0.0.0-20191029185751-e238f04965fe/go.mod h1:cPDudDcSR9fls3ZmrXgt0GU2QpQGQRJc4JBNtKyNr1s=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
@@ -650,6 +683,8 @@ github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
|
||||
github.com/kisom/goutils v1.1.0/go.mod h1:+UBTfd78habUYWFbNWTJNG+jNG/i/lGURakr4A/yNRw=
|
||||
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.9.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
@@ -674,7 +709,8 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28/go.mod h1:T/T7jsxVqf9k/zYOqbgNAsANsjxTd1Yq3htjDhQ1H0c=
|
||||
github.com/lib/pq v0.0.0-20180201184707-88edab080323/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
@@ -709,13 +745,13 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME
|
||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k=
|
||||
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-shellwords v1.0.5/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI=
|
||||
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI=
|
||||
github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw=
|
||||
github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
@@ -724,6 +760,8 @@ github.com/mholt/archiver/v3 v3.3.0/go.mod h1:YnQtqsp+94Rwd0D/rk5cnLrxusUBUXg+08
|
||||
github.com/miekg/dns v0.0.0-20181005163659-0d29b283ac0f/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/pkcs11 v1.0.3 h1:iMwmD7I5225wv84WxIG/bmxz9AXjWvTWIbM/TYHvWtw=
|
||||
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mistifyio/go-zfs v2.1.1+incompatible h1:gAMO1HM9xBRONLHHYnu5iFsOJUiJdNZo6oqSENd4eW8=
|
||||
github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
@@ -762,13 +800,14 @@ github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
||||
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
|
||||
github.com/mtrmac/gpgme v0.0.0-20170102180018-b2432428689c h1:xa+eQWKuJ9MbB9FBL/eoNvDFvveAkz2LQoz8PzX7Q/4=
|
||||
github.com/mtrmac/gpgme v0.0.0-20170102180018-b2432428689c/go.mod h1:GhAqVMEWnTcW2dxoD/SO3n2enrgWl3y6Dnx4m59GvcA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
|
||||
github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs=
|
||||
github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
@@ -776,13 +815,16 @@ github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
@@ -799,16 +841,9 @@ github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.m
|
||||
github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
|
||||
github.com/opencontainers/selinux v1.3.0 h1:xsI95WzPZu5exzA6JzkLSfdr/DilzOhCJOqGe5TgR0g=
|
||||
github.com/opencontainers/selinux v1.3.0/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913 h1:TnbXhKzrTOyuvWrjI8W6pcoI9XPbLHFXCdN2dtUw7Rw=
|
||||
github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913/go.mod h1:J6OG6YJVEWopen4avK3VNQSnALmmjvniMmni/YFYAwc=
|
||||
github.com/otiai10/copy v1.1.1 h1:PH7IFlRQ6Fv9vYmuXbDRLdgTHoP1w483kPNUP2bskpo=
|
||||
github.com/otiai10/copy v1.1.1/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||
github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI=
|
||||
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
|
||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||
github.com/otiai10/mint v1.3.1 h1:BCmzIS3n71sGfHB5NMNDB3lHYPz8fWSkCAErHed//qc=
|
||||
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
@@ -839,6 +874,7 @@ github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9 h1:kyf9snWXHvQc+yxE
|
||||
github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
|
||||
@@ -846,6 +882,7 @@ github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQ
|
||||
github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI=
|
||||
github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
|
||||
@@ -861,6 +898,7 @@ github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLy
|
||||
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190129233650-316cf8ccfec5/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
@@ -869,20 +907,15 @@ 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.39 h1:fIGCC7f9kU7319VTSJKr3fLoA9E4MjusRFmOjX3ypis=
|
||||
github.com/qlik-oss/k-apis v0.0.39/go.mod h1:yoYGgPJ/H0t9H3NSq64dWfyQY6QWi2L9c+hCJoVO03U=
|
||||
github.com/qlik-oss/k-apis v0.1.0 h1:uMl1316SNYy5Hm6jy1U7wiCMkut0tKqdP8mBpSuXXp8=
|
||||
github.com/qlik-oss/k-apis v0.1.0/go.mod h1:yoYGgPJ/H0t9H3NSq64dWfyQY6QWi2L9c+hCJoVO03U=
|
||||
github.com/qlik-oss/k-apis v0.1.1 h1:aZ4eTMB3mSn03Kuj7+RI0eFLkjK9+0qxADBioRb3qVA=
|
||||
github.com/qlik-oss/k-apis v0.1.1/go.mod h1:yoYGgPJ/H0t9H3NSq64dWfyQY6QWi2L9c+hCJoVO03U=
|
||||
github.com/qlik-oss/kustomize/api v0.3.3-0.20200402170547-2e8140160c36 h1:BuT+cnXPQ6mcOWTDS1S8GXy65LAEMdPuNQCC36rMq28=
|
||||
github.com/qlik-oss/kustomize/api v0.3.3-0.20200402170547-2e8140160c36/go.mod h1:tSQaDZ4Jt9KwYvD7LlMUPi5nkiGOno3PAKl5/XqEfxs=
|
||||
github.com/qlik-oss/k-apis v0.0.8 h1:8ZZefgFT+rI4PuE/sp3Wpc+d5LtZBKZuDb2G/YelTOs=
|
||||
github.com/qlik-oss/k-apis v0.0.8/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE=
|
||||
github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9 h1:iqeqTS4zjp6rPEaxmFB7pemA2CMjOEN5dYSXZaQ82uw=
|
||||
github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9/go.mod h1:OCt7FTrRVHj4kmR2xLJJUIqu00BTr6GeF09hSmM17Kw=
|
||||
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
|
||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
|
||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
@@ -919,6 +952,7 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
|
||||
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
@@ -944,8 +978,6 @@ github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKv
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v0.0.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs=
|
||||
github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
@@ -960,8 +992,6 @@ github.com/spf13/viper v1.6.1 h1:VPZzIkznI1YhVMRi6vNFLHSwhnhReBfgTxIPccpfdZk=
|
||||
github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
|
||||
github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
|
||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||
github.com/src-d/go-git v4.7.0+incompatible h1:IYSSnbAHeKmsfbQFi9ozbid+KNh0bKjlorMfQehQbcE=
|
||||
github.com/src-d/go-git v4.7.0+incompatible/go.mod h1:1bQciz+hn0jzPQNsYj0hDFZHLJBdV7gXE2mWhC7EkFk=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
@@ -977,11 +1007,11 @@ github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 h1:b6uOv7YOFK0
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/tchap/go-patricia v2.3.0+incompatible h1:GkY4dP3cEfEASBPPkWd+AmjYxhmDkqO9/zg7R0lSQRs=
|
||||
github.com/tchap/go-patricia v2.3.0+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
|
||||
github.com/theupdateframework/notary v0.6.1 h1:7wshjstgS9x9F5LuB1L5mBI2xNMObWqjz+cjWoom6l0=
|
||||
github.com/theupdateframework/notary v0.6.1/go.mod h1:MOfgIfmox8s7/7fduvB2xyPPMJCrjRLRizA8OFwpnKY=
|
||||
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31 h1:OXcKh35JaYsGMRzpvFkLv/MEyPuL49CThT1pZ8aSml4=
|
||||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
@@ -989,7 +1019,6 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
|
||||
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
|
||||
@@ -999,12 +1028,16 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
|
||||
github.com/uudashr/gocognit v0.0.0-20190926065955-1655d0de0517/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/vbatts/tar-split v0.11.1 h1:0Odu65rhcZ3JZaPHxl7tCI3V/C/Q9Zf82UFravl02dE=
|
||||
github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g=
|
||||
github.com/vbauerster/mpb/v4 v4.11.1 h1:ZOYQSVHgmeanXsbyC44aDg76tBGCS/54Rk8VkL8dJGA=
|
||||
github.com/vbauerster/mpb/v4 v4.11.1/go.mod h1:vMLa1J/ZKC83G2lB/52XpqT+ZZtFG4aZOdKhmpRL1uM=
|
||||
github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
|
||||
github.com/weppos/publicsuffix-go v0.5.0 h1:rutRtjBJViU/YjcI5d80t4JAVvDltS6bciJg2K1HrLU=
|
||||
github.com/weppos/publicsuffix-go v0.5.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
|
||||
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
|
||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
@@ -1023,9 +1056,9 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofm
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 h1:j2hhcujLRHAg872RWAV5yaUrEjHEObwDv3aImCaNLek=
|
||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yujunz/go-getter v1.4.1-lite h1:FhvNc94AXMZkfqUwfMKhnQEC9phkphSGdPTL7tIdhOM=
|
||||
github.com/yujunz/go-getter v1.4.1-lite/go.mod h1:sbmqxXjyLunH1PkF3n7zSlnVeMvmYUuIl9ZVs/7NyCc=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 h1:p7OofyZ509h8DmPLh8Hn+EIIZm/xYhdZHJ9GnXHdr6U=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
||||
github.com/yvasiyarov/gorelic v0.0.6 h1:qMJQYPNdtJ7UNYHjX38KXZtltKTqimMuoQjNnSVIuJg=
|
||||
@@ -1034,11 +1067,19 @@ github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1
|
||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
|
||||
github.com/zealic/xignore v0.3.3 h1:EpLXUgZY/JEzFkTc+Y/VYypzXtNz+MSOMVCGW5Q4CKQ=
|
||||
github.com/zealic/xignore v0.3.3/go.mod h1:lhS8V7fuSOtJOKsvKI7WfsZE276/7AYEqokv3UiqEAU=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
|
||||
github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
|
||||
github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e h1:mvOa4+/DXStR4ZXOks/UsjeFdn5O5JpLUtzqk9U8xXw=
|
||||
github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e/go.mod h1:w7kd3qXHh8FNaczNjslXqvFQiv5mMWRXlL9klTUAHc8=
|
||||
github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb h1:vxqkjztXSaPVDc8FQCdHTaejm2x747f6yPbnu1h2xkg=
|
||||
github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb/go.mod h1:29UiAJNsiVdvTBFCJW8e3q6dcDbOoPkhMgttOSCIMMY=
|
||||
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
@@ -1061,6 +1102,7 @@ golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@@ -1077,9 +1119,8 @@ golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
|
||||
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA=
|
||||
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 h1:Sy5bstxEqwwbYs6n0/pBuxKENqOeZUgD45Gp3Q3pqLg=
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6/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=
|
||||
@@ -1114,8 +1155,6 @@ 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=
|
||||
@@ -1128,6 +1167,7 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190310074541-c10a0554eabf/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@@ -1156,10 +1196,6 @@ 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/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/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=
|
||||
@@ -1237,14 +1273,13 @@ 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-20200312194400-c312e98713c2 h1:6TB4+MaZlkcSsJDu+BS5yxSEuZIYhjWz+jhbSLEZylI=
|
||||
golang.org/x/tools v0.0.0-20200312194400-c312e98713c2/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200219054238-753a1d49df85 h1:XNHaQ2CZDl/SjEZlUXGh7+OQvfLuFgmk3oNWkCFfERE=
|
||||
golang.org/x/tools v0.0.0-20200219054238-753a1d49df85/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -1254,6 +1289,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
@@ -1303,6 +1340,7 @@ google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b h1:c8OBoXP3kTbDWWB
|
||||
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
|
||||
@@ -1323,11 +1361,16 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/dancannon/gorethink.v3 v3.0.5 h1:/g7PWP7zUS6vSNmHSDbjCHQh1Rqn8Jy6zSMQxAsBSMQ=
|
||||
gopkg.in/dancannon/gorethink.v3 v3.0.5/go.mod h1:GXsi1e3N2OcKhcP6nsYABTiUejbWMFO4GY5a4pEaeEc=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fatih/pool.v2 v2.0.0 h1:xIFeWtxifuQJGk/IEPKsTduEKcKvPmhoiVDGpC40nKg=
|
||||
gopkg.in/fatih/pool.v2 v2.0.0/go.mod h1:8xVGeu1/2jr2wm5V9SPuMht2H5AEmf5aFMGSQixtjTY=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||
gopkg.in/gorethink/gorethink.v3 v3.0.5 h1:e2Uc/Xe+hpcVQFsj6MuHlYog3r0JYpnTzwDj/y2O4MU=
|
||||
gopkg.in/gorethink/gorethink.v3 v3.0.5/go.mod h1:+3yIIHJUGMBK+wyPH+iN5TP+88ikFDfZdqTlK3Y9q8I=
|
||||
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
@@ -1426,3 +1469,5 @@ sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:I
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 h1:O69FD9pJA4WUZlEwYatBEEkRWKQ5cKodWpdKTrCS/iQ=
|
||||
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=
|
||||
|
||||
24
mkdocs.yml
24
mkdocs.yml
@@ -1,24 +0,0 @@
|
||||
site_name: Qlik Sense installer
|
||||
repo_url: 'https://github.com/qlik-oss/sense-installer'
|
||||
strict: true
|
||||
theme:
|
||||
name: "material"
|
||||
palette:
|
||||
primary: 'green'
|
||||
accent: 'indigo'
|
||||
markdown_extensions:
|
||||
- toc:
|
||||
permalink: true
|
||||
- admonition
|
||||
- codehilite
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.superfences
|
||||
- pymdownx.details
|
||||
nav:
|
||||
- Overview: index.md
|
||||
- getting_started.md
|
||||
- command_reference.md
|
||||
- concepts.md
|
||||
- preflight_checks.md
|
||||
- air_gap.md
|
||||
- Releases ⧉: https://github.com/qlik-oss/sense-installer/releases
|
||||
BIN
pkg/.DS_Store
vendored
BIN
pkg/.DS_Store
vendored
Binary file not shown.
485
pkg/api/apis.go
485
pkg/api/apis.go
@@ -6,59 +6,37 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/qlik-oss/k-apis/pkg/config"
|
||||
|
||||
b64 "encoding/base64"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
const (
|
||||
pushSecretFileName = "image-registry-push-secret.yaml"
|
||||
pullSecretFileName = "image-registry-pull-secret.yaml"
|
||||
qliksenseContextsDirName = "contexts"
|
||||
qliksenseSecretsDirName = "secrets"
|
||||
qliksenseEjsonDirName = "ejson"
|
||||
QLIK_GIT_REPO = "https://github.com/qlik-oss/qliksense-k8s"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// NewQConfig create QliksenseConfig object from file ~/.qliksense/config.yaml
|
||||
func NewQConfig(qsHome string) *QliksenseConfig {
|
||||
qc, err := NewQConfigE(qsHome)
|
||||
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)
|
||||
if err != nil {
|
||||
fmt.Println("yaml unmarshalling error ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return qc
|
||||
}
|
||||
|
||||
func NewQConfigE(qsHome string) (*QliksenseConfig, error) {
|
||||
configFile := filepath.Join(qsHome, "config.yaml")
|
||||
qc := &QliksenseConfig{}
|
||||
|
||||
err := ReadFromFile(qc, configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qc.QliksenseHomePath = qsHome
|
||||
return qc, nil
|
||||
}
|
||||
func NewQConfigEmpty(qsHome string) *QliksenseConfig {
|
||||
return &QliksenseConfig{
|
||||
QliksenseHomePath: qsHome,
|
||||
}
|
||||
return qc
|
||||
}
|
||||
|
||||
// GetCR create a QliksenseCR object for a particular context
|
||||
// from file ~/.qliksense/contexts/<contx-name>/<contx-name>.yaml
|
||||
func (qc *QliksenseConfig) GetCR(contextName string) (*QliksenseCR, error) {
|
||||
crFilePath := qc.GetCRFilePath(contextName)
|
||||
crFilePath := qc.getCRFilePath(contextName)
|
||||
if crFilePath == "" {
|
||||
return nil, errors.New("context name " + contextName + " not found")
|
||||
}
|
||||
return qc.GetAndTransformCrObject(crFilePath)
|
||||
return getCRObject(crFilePath)
|
||||
}
|
||||
|
||||
// GetCurrentCR create a QliksenseCR object for current context
|
||||
@@ -67,14 +45,14 @@ func (qc *QliksenseConfig) GetCurrentCR() (*QliksenseCR, error) {
|
||||
}
|
||||
|
||||
// SetCrLocation sets the CR location for a context. Helpful during test
|
||||
func (qc *QliksenseConfig) SetCrLocation(contextName, filePath string) (*QliksenseConfig, error) {
|
||||
func (qc *QliksenseConfig) SetCrLocation(contextName, filepath string) (*QliksenseConfig, error) {
|
||||
tempQc := &QliksenseConfig{}
|
||||
copier.Copy(tempQc, qc)
|
||||
found := false
|
||||
tempQc.Spec.Contexts = []Context{}
|
||||
for _, c := range qc.Spec.Contexts {
|
||||
if c.Name == contextName {
|
||||
c.CrFile = filePath
|
||||
c.CrFile = filepath
|
||||
found = true
|
||||
}
|
||||
tempQc.Spec.Contexts = append(tempQc.Spec.Contexts, []Context{c}...)
|
||||
@@ -85,36 +63,14 @@ func (qc *QliksenseConfig) SetCrLocation(contextName, filePath string) (*Qliksen
|
||||
return nil, errors.New("cannot find the context")
|
||||
}
|
||||
|
||||
// GetCRObject create a qliksense CR object from file
|
||||
func GetCRObject(crfile string) (*QliksenseCR, error) {
|
||||
cr := &QliksenseCR{}
|
||||
err := ReadFromFile(cr, crfile)
|
||||
func getCRObject(crfile string) (*QliksenseCR, error) {
|
||||
data, err := ioutil.ReadFile(crfile)
|
||||
if err != nil {
|
||||
fmt.Println("cannot unmarshal cr ", err)
|
||||
fmt.Println("Cannot read config file from: "+crfile, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cr, nil
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) GetAndTransformCrObject(crfile string) (*QliksenseCR, error) {
|
||||
cr, err := GetCRObject(crfile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cr.Spec.ManifestsRoot != "" && !filepath.IsAbs(cr.Spec.ManifestsRoot) {
|
||||
cr.Spec.ManifestsRoot = filepath.Join(qc.QliksenseHomePath, cr.Spec.ManifestsRoot)
|
||||
}
|
||||
return cr, nil
|
||||
}
|
||||
|
||||
//CreateCRObjectFromString create a QliksenseCR from string content
|
||||
func CreateCRObjectFromString(crContent string) (*QliksenseCR, error) {
|
||||
if crContent == "" {
|
||||
return nil, errors.New("empty string cannot qliksensecr")
|
||||
}
|
||||
cr := &QliksenseCR{}
|
||||
err := ReadFromStream(cr, strings.NewReader(crContent))
|
||||
err = yaml.Unmarshal(data, cr)
|
||||
if err != nil {
|
||||
fmt.Println("cannot unmarshal cr ", err)
|
||||
return nil, err
|
||||
@@ -122,87 +78,16 @@ func CreateCRObjectFromString(crContent string) (*QliksenseCR, error) {
|
||||
return cr, nil
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) GetCRFilePath(contextName string) string {
|
||||
func (qc *QliksenseConfig) getCRFilePath(contextName string) string {
|
||||
crFilePath := ""
|
||||
for _, ctx := range qc.Spec.Contexts {
|
||||
if ctx.Name == contextName {
|
||||
crFilePath = filepath.Join(qc.QliksenseHomePath, ctx.CrFile)
|
||||
crFilePath = ctx.CrFile
|
||||
break
|
||||
}
|
||||
}
|
||||
return crFilePath
|
||||
}
|
||||
|
||||
func (cr *QliksenseCR) IsRepoExist() bool {
|
||||
if cr.Spec.ManifestsRoot == "" {
|
||||
return false
|
||||
}
|
||||
if _, err := os.Lstat(cr.Spec.ManifestsRoot); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (cr *QliksenseCR) GetFetchUrl() string {
|
||||
if cr.Spec.FetchSource == nil || cr.Spec.FetchSource.Repository == "" {
|
||||
return QLIK_GIT_REPO
|
||||
}
|
||||
return cr.Spec.FetchSource.Repository
|
||||
}
|
||||
|
||||
func (cr *QliksenseCR) GetFetchAccessToken(encryptionKey string) string {
|
||||
if cr.Spec.FetchSource == nil {
|
||||
return ""
|
||||
}
|
||||
if tok, err := cr.Spec.FetchSource.GetAccessToken(); err != nil {
|
||||
fmt.Println(err)
|
||||
return ""
|
||||
} else {
|
||||
by, _ := b64.StdEncoding.DecodeString(tok)
|
||||
res, err := DecryptData(by, encryptionKey)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return ""
|
||||
}
|
||||
return string(res)
|
||||
}
|
||||
}
|
||||
|
||||
func (cr *QliksenseCR) SetFetchUrl(url string) {
|
||||
if cr.Spec.FetchSource == nil {
|
||||
cr.Spec.FetchSource = &config.Repo{}
|
||||
}
|
||||
cr.Spec.FetchSource.Repository = url
|
||||
}
|
||||
|
||||
func (cr *QliksenseCR) SetFetchAccessToken(token, encryptionKey string) error {
|
||||
if cr.Spec.FetchSource == nil {
|
||||
cr.Spec.FetchSource = &config.Repo{}
|
||||
}
|
||||
res, err := EncryptData([]byte(token), encryptionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cr.Spec.FetchSource.AccessToken = b64.StdEncoding.EncodeToString(res)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cr *QliksenseCR) SetFetchAccessSecretName(sec string) {
|
||||
if cr.Spec.FetchSource == nil {
|
||||
cr.Spec.FetchSource = &config.Repo{}
|
||||
}
|
||||
cr.Spec.FetchSource.SecretName = sec
|
||||
}
|
||||
|
||||
//DeleteRepo delete the manifest repo and unset manifestsRoot
|
||||
func (cr *QliksenseCR) DeleteRepo() error {
|
||||
if err := os.RemoveAll(cr.Spec.ManifestsRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
cr.Spec.ManifestsRoot = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) IsRepoExist(contextName, version string) bool {
|
||||
if _, err := os.Lstat(qc.BuildRepoPathForContext(contextName, version)); err != nil {
|
||||
return false
|
||||
@@ -217,81 +102,34 @@ func (qc *QliksenseConfig) IsRepoExistForCurrent(version string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) DeleteRepoForCurrent(version string) error {
|
||||
path := qc.BuildRepoPath(version)
|
||||
return os.RemoveAll(path)
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) BuildRepoPath(version string) string {
|
||||
return qc.BuildRepoPathForContext(qc.Spec.CurrentContext, version)
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) BuildRepoPathForContext(contextName, version string) string {
|
||||
return filepath.Join(qc.GetContextPath(contextName), "qlik-k8s", version)
|
||||
return filepath.Join(qc.QliksenseHomePath, "contexts", contextName, "qlik-k8s", version)
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) BuildCurrentManifestsRoot(version string) string {
|
||||
return qc.BuildRepoPath(version)
|
||||
return filepath.Join(qc.BuildRepoPath(version), "manifests")
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) WriteCR(cr *QliksenseCR) error {
|
||||
crf := qc.GetCRFilePath(cr.GetName())
|
||||
func (qc *QliksenseConfig) WriteCR(cr *QliksenseCR, contextName string) error {
|
||||
crf := qc.getCRFilePath(contextName)
|
||||
if crf == "" {
|
||||
return errors.New("context name " + cr.GetName() + " not found")
|
||||
return errors.New("context name " + contextName + " not found")
|
||||
}
|
||||
|
||||
return qc.TransformAndWriteCr(cr, crf)
|
||||
}
|
||||
|
||||
//CreateOrWriteCrAndContext create necessary folder structure, update config.yaml and context yaml files
|
||||
func (qc *QliksenseConfig) CreateOrWriteCrAndContext(cr *QliksenseCR) error {
|
||||
if qc.QliksenseHomePath == "" {
|
||||
return errors.New("qliksense home is not set")
|
||||
}
|
||||
crf := qc.GetCRFilePath(cr.GetName())
|
||||
if crf == "" {
|
||||
// create direcotry structure for context
|
||||
cDir := filepath.Join(qc.QliksenseHomePath, "contexts", cr.GetName())
|
||||
if err := os.MkdirAll(cDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
crf = filepath.Join(cDir, cr.GetName()+".yaml")
|
||||
ctx := Context{
|
||||
Name: cr.GetName(),
|
||||
CrFile: "contexts/" + cr.GetName() + "/" + cr.GetName() + ".yaml", //filepath.Join("contexts", cr.GetName(), cr.GetName()+".yaml"),
|
||||
}
|
||||
qc.AddToContexts(ctx)
|
||||
|
||||
if err := qc.Write(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return qc.TransformAndWriteCr(cr, crf)
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) TransformAndWriteCr(cr *QliksenseCR, file string) error {
|
||||
if strings.HasPrefix(cr.Spec.ManifestsRoot, qc.QliksenseHomePath) {
|
||||
cr.Spec.ManifestsRoot = strings.Replace(cr.Spec.ManifestsRoot, qc.QliksenseHomePath+"/", "", 1)
|
||||
cr.Spec.ManifestsRoot = strings.Replace(cr.Spec.ManifestsRoot, qc.QliksenseHomePath+"\\", "", 1)
|
||||
cr.Spec.ManifestsRoot = strings.Replace(cr.Spec.ManifestsRoot, "\\", "/", -1)
|
||||
}
|
||||
if err := WriteToFile(cr, file); err != nil {
|
||||
by, err := yaml.Marshal(cr)
|
||||
if err != nil {
|
||||
fmt.Println("cannot marshal cr ", err)
|
||||
return err
|
||||
}
|
||||
if cr.Spec.ManifestsRoot != "" {
|
||||
cr.Spec.ManifestsRoot = filepath.Join(qc.QliksenseHomePath, cr.Spec.ManifestsRoot)
|
||||
}
|
||||
ioutil.WriteFile(crf, by, 0644)
|
||||
return nil
|
||||
}
|
||||
func (qc *QliksenseConfig) AddToContexts(ctx Context) error {
|
||||
//TODO: additional duplicate check may be added latter
|
||||
qc.Spec.Contexts = append(qc.Spec.Contexts, ctx)
|
||||
|
||||
return nil
|
||||
}
|
||||
func (qc *QliksenseConfig) WriteCurrentContextCR(cr *QliksenseCR) error {
|
||||
return qc.WriteCR(cr)
|
||||
return qc.WriteCR(cr, qc.Spec.CurrentContext)
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) IsContextExist(ctxName string) bool {
|
||||
@@ -303,270 +141,19 @@ func (qc *QliksenseConfig) IsContextExist(ctxName string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) GetCurrentContextDir() (string, error) {
|
||||
if qcr, err := qc.GetCurrentCR(); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return filepath.Join(qc.QliksenseHomePath, qliksenseContextsDirName, qcr.GetObjectMeta().GetName()), nil
|
||||
func (cr *QliksenseCR) AddLabelToCr(key, value string) error {
|
||||
if cr.Metadata.Labels == nil {
|
||||
cr.Metadata.Labels = make(map[string]string)
|
||||
}
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) GetCurrentContextSecretsDir() (string, error) {
|
||||
if currentContextDir, err := qc.GetCurrentContextDir(); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return filepath.Join(currentContextDir, qliksenseSecretsDirName), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) setDockerConfigJsonSecret(filename string, dockerConfigJsonSecret *DockerConfigJsonSecret) error {
|
||||
if secretsDir, err := qc.GetCurrentContextSecretsDir(); err != nil {
|
||||
return err
|
||||
} else if encryptionKey, err := qc.GetEncryptionKeyForCurrent(); err != nil {
|
||||
return err
|
||||
} else if dockerConfigJsonSecretYaml, err := dockerConfigJsonSecret.ToYaml(encryptionKey); err != nil {
|
||||
return err
|
||||
} else if err := os.MkdirAll(secretsDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return ioutil.WriteFile(filepath.Join(secretsDir, filename), dockerConfigJsonSecretYaml, os.ModePerm)
|
||||
}
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) SetPushDockerConfigJsonSecret(dockerConfigJsonSecret *DockerConfigJsonSecret) error {
|
||||
return qc.setDockerConfigJsonSecret(pushSecretFileName, dockerConfigJsonSecret)
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) SetPullDockerConfigJsonSecret(dockerConfigJsonSecret *DockerConfigJsonSecret) error {
|
||||
return qc.setDockerConfigJsonSecret(pullSecretFileName, dockerConfigJsonSecret)
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) GetPushDockerConfigJsonSecret() (*DockerConfigJsonSecret, error) {
|
||||
return qc.getDockerConfigJsonSecret(pushSecretFileName)
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) GetPullDockerConfigJsonSecret() (*DockerConfigJsonSecret, error) {
|
||||
return qc.getDockerConfigJsonSecret(pullSecretFileName)
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) DeletePushDockerConfigJsonSecret() error {
|
||||
return qc.deleteDockerConfigJsonSecret(pushSecretFileName)
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) DeletePullDockerConfigJsonSecret() error {
|
||||
return qc.deleteDockerConfigJsonSecret(pullSecretFileName)
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) deleteDockerConfigJsonSecret(name string) error {
|
||||
if secretsDir, err := qc.GetCurrentContextSecretsDir(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return os.Remove(filepath.Join(secretsDir, name))
|
||||
}
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) getDockerConfigJsonSecret(name string) (*DockerConfigJsonSecret, error) {
|
||||
dockerConfigJsonSecret := &DockerConfigJsonSecret{}
|
||||
if secretsDir, err := qc.GetCurrentContextSecretsDir(); err != nil {
|
||||
return nil, err
|
||||
} else if dockerConfigJsonSecretYaml, err := ioutil.ReadFile(filepath.Join(secretsDir, name)); err != nil {
|
||||
return nil, err
|
||||
} else if encryptionKey, err := qc.GetEncryptionKeyForCurrent(); err != nil {
|
||||
return nil, err
|
||||
} else if err := dockerConfigJsonSecret.FromYaml(dockerConfigJsonSecretYaml, encryptionKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dockerConfigJsonSecret, nil
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) getCurrentContextEncryptionKeyPairLocation() (string, error) {
|
||||
|
||||
if qcr, err := qc.GetCurrentCR(); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return qc.getContextEncryptionKeyLocation(qcr.GetName())
|
||||
}
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) getContextEncryptionKeyLocation(contextName string) (string, error) {
|
||||
// Check env var: QLIKSENSE_KEY_LOCATION to determine location to store keypair
|
||||
var secretKeyPairLocation string
|
||||
if os.Getenv("QLIKSENSE_KEY_LOCATION") != "" {
|
||||
LogDebugMessage("Env variable: QLIKSENSE_KEY_LOCATION= %s", os.Getenv("QLIKSENSE_KEY_LOCATION"))
|
||||
secretKeyPairLocation = os.Getenv("QLIKSENSE_KEY_LOCATION")
|
||||
} else {
|
||||
// QLIKSENSE_KEY_LOCATION has not been set, hence storing key pair in default location:
|
||||
// /.qliksense/secrets/contexts/<current-context>/secrets/
|
||||
secretKeyPairLocation = filepath.Join(qc.QliksenseHomePath, qliksenseSecretsDirName, qliksenseContextsDirName, contextName, qliksenseSecretsDirName)
|
||||
}
|
||||
|
||||
return secretKeyPairLocation, os.MkdirAll(secretKeyPairLocation, os.ModePerm)
|
||||
}
|
||||
|
||||
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) GetEncryptionKeyForCurrent() (string, error) {
|
||||
if qcr, err := qc.GetCurrentCR(); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return qc.GetEncryptionKeyFor(qcr.GetName())
|
||||
}
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) GetEncryptionKeyFor(contextName string) (string, error) {
|
||||
secretKeyLocation, err := qc.getContextEncryptionKeyLocation(contextName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
key, err := LoadSecretKey(secretKeyLocation)
|
||||
if key != "" {
|
||||
return key, nil
|
||||
}
|
||||
fmt.Println("Generating new encryption key for the context: " + contextName)
|
||||
return GenerateAndStoreSecretKey(secretKeyLocation)
|
||||
}
|
||||
|
||||
func (cr *QliksenseCR) AddLabelToCr(key, value string) {
|
||||
m := cr.GetObjectMeta().GetLabels()
|
||||
if m == nil {
|
||||
m = make(map[string]string)
|
||||
}
|
||||
m[key] = value
|
||||
cr.GetObjectMeta().SetLabels(m)
|
||||
}
|
||||
|
||||
func (cr *QliksenseCR) GetLabelFromCr(key string) string {
|
||||
return cr.GetObjectMeta().GetLabels()[key]
|
||||
cr.Metadata.Labels[key] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cr *QliksenseCR) GetString() (string, error) {
|
||||
out, err := K8sToYaml(cr)
|
||||
out, err := yaml.Marshal(cr)
|
||||
if err != nil {
|
||||
fmt.Println("cannot unmarshal cr ", err)
|
||||
return "", err
|
||||
}
|
||||
return string(out), nil
|
||||
}
|
||||
|
||||
func (cr *QliksenseCR) GetK8sSecretsFolder(qlikSenseHomeDir string) string {
|
||||
return filepath.Join(qlikSenseHomeDir, qliksenseContextsDirName, cr.GetName(), qliksenseSecretsDirName)
|
||||
}
|
||||
|
||||
func (cr *QliksenseCR) IsEULA() bool {
|
||||
for k, nvs := range cr.Spec.Configs {
|
||||
if k == "qliksense" {
|
||||
for _, nv := range nvs {
|
||||
if nv.Name == "acceptEULA" {
|
||||
return nv.Value == "yes"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (cr *QliksenseCR) SetEULA(value string) {
|
||||
cr.Spec.AddToConfigs("qliksense", "acceptEULA", value)
|
||||
}
|
||||
|
||||
// GetCustomCrdsPath get crds path if exist in the profile dir
|
||||
func (cr *QliksenseCR) GetCustomCrdsPath() string {
|
||||
if cr.Spec.ManifestsRoot == "" || cr.Spec.Profile == "" {
|
||||
return ""
|
||||
}
|
||||
crdsPath := filepath.Join(cr.Spec.GetManifestsRoot(), "manifests", cr.Spec.Profile, "crds")
|
||||
if _, err := os.Lstat(crdsPath); err != nil {
|
||||
return ""
|
||||
}
|
||||
return crdsPath
|
||||
}
|
||||
|
||||
// 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)
|
||||
encryptionKey, err := qc.GetEncryptionKeyFor(cr.GetName())
|
||||
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 := DecryptData(b, encryptionKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newNvs = append(newNvs, config.NameValue{
|
||||
Name: nv.Name,
|
||||
Value: string(db),
|
||||
})
|
||||
}
|
||||
}
|
||||
finalSecrets[k] = newNvs
|
||||
}
|
||||
newCr.Spec.Secrets = finalSecrets
|
||||
|
||||
if newCr.Spec.FetchSource != nil && newCr.Spec.FetchSource.AccessToken != "" {
|
||||
decData := cr.GetFetchAccessToken(encryptionKey)
|
||||
newCr.Spec.FetchSource.AccessToken = decData
|
||||
}
|
||||
return newCr, nil
|
||||
}
|
||||
|
||||
//Validate validate CR
|
||||
func (cr *QliksenseCR) Validate() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
//CreateContextDirs create context dir structure ~/.qliksense/contexts/contextName
|
||||
func (qc *QliksenseConfig) CreateContextDirs(contextName string) error {
|
||||
return os.MkdirAll(qc.GetContextPath(contextName), os.ModePerm)
|
||||
}
|
||||
|
||||
func (qc *QliksenseConfig) GetContextPath(contextName string) string {
|
||||
return filepath.Join(qc.QliksenseHomePath, qliksenseContextsDirName, contextName)
|
||||
}
|
||||
|
||||
//BuildCrFileAbsolutePath build absolute path for a cr ie. ~/.qliksense/contexts/qlik-defautl/qlik-default.yaml
|
||||
func (qc *QliksenseConfig) BuildCrFileAbsolutePath(contextName string) string {
|
||||
return filepath.Join(qc.GetContextPath(contextName), contextName+".yaml")
|
||||
}
|
||||
|
||||
//BuildCrFilePath build cr file path i.e. contexts/qlik-default/qlik-default.yaml
|
||||
func (qc *QliksenseConfig) BuildCrFilePath(contextName string) string {
|
||||
return filepath.Join(qc.GetContextPath(contextName), contextName+".yaml")
|
||||
}
|
||||
|
||||
//AddToContexts add the context into qc.Spec.Contexts
|
||||
func (qc *QliksenseConfig) AddToContextsRaw(crName, crFile string) {
|
||||
qc.Spec.Contexts = append(qc.Spec.Contexts, []Context{
|
||||
{CrFile: crFile,
|
||||
Name: crName},
|
||||
}...)
|
||||
}
|
||||
|
||||
//SetCurrentContextName set the qc.Spec.CurrentContext
|
||||
func (qc *QliksenseConfig) SetCurrentContextName(name string) {
|
||||
qc.Spec.CurrentContext = name
|
||||
}
|
||||
|
||||
//Write write QliksenseConfig into config.yaml
|
||||
func (qc *QliksenseConfig) Write() error {
|
||||
return WriteToFile(qc, filepath.Join(qc.QliksenseHomePath, "config.yaml"))
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
b64 "encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -48,6 +45,7 @@ metadata:
|
||||
spec:
|
||||
profile: docker-desktop
|
||||
manifestsRoot: /Users/mqb/.qliksense/contexts/contx1/qlik-k8s/v0.0.1/manifests
|
||||
namespace: myqliksense
|
||||
storageClassName: efs
|
||||
configs:
|
||||
qliksense:
|
||||
@@ -69,7 +67,7 @@ func TestGetCR(t *testing.T) {
|
||||
// create CR
|
||||
createCRFile(dir)
|
||||
|
||||
crFile := filepath.Join("contexts", "contx1", "contx1.yaml")
|
||||
crFile := filepath.Join(dir, "contexts", "contx1", "contx1.yaml")
|
||||
qct, e := qc.SetCrLocation("contx1", crFile)
|
||||
if e != nil {
|
||||
t.Fail()
|
||||
@@ -85,88 +83,3 @@ func TestGetCR(t *testing.T) {
|
||||
}
|
||||
td()
|
||||
}
|
||||
|
||||
func TestGetDecryptedCr(t *testing.T) {
|
||||
td, dir := setup()
|
||||
qc := NewQConfig(dir)
|
||||
if qc.Spec.CurrentContext != "contx1" {
|
||||
t.Fail()
|
||||
}
|
||||
// create CR
|
||||
createCRFile(dir)
|
||||
|
||||
crFile := filepath.Join("contexts", "contx1", "contx1.yaml")
|
||||
qct, e := qc.SetCrLocation("contx1", crFile)
|
||||
if e != nil {
|
||||
t.Fail()
|
||||
t.Log(e)
|
||||
}
|
||||
|
||||
qcr, err := qct.GetCurrentCR()
|
||||
|
||||
key, _ := setupGenerateKey(dir)
|
||||
ecn, _ := EncryptData([]byte("mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"), key)
|
||||
b := b64.StdEncoding.EncodeToString(ecn)
|
||||
qcr.Spec.AddToSecrets("qliksense", "mongoDbUri", b, "")
|
||||
|
||||
qcr.SetFetchAccessToken("mytoken", key)
|
||||
|
||||
newCr, err := qct.GetDecryptedCr(qcr)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
t.Log(err)
|
||||
}
|
||||
|
||||
decryptedValue := newCr.Spec.GetFromSecrets("qliksense", "mongoDbUri")
|
||||
orignalValue := qcr.Spec.GetFromSecrets("qliksense", "mongoDbUri")
|
||||
if decryptedValue != "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false" {
|
||||
t.Fail()
|
||||
b, _ := K8sToYaml(newCr)
|
||||
t.Log(b)
|
||||
}
|
||||
if decryptedValue == orignalValue {
|
||||
t.Fail()
|
||||
}
|
||||
if newCr.Spec.FetchSource.AccessToken != "mytoken" {
|
||||
t.Fail()
|
||||
}
|
||||
td()
|
||||
}
|
||||
func setupGenerateKey(homeDir string) (string, error) {
|
||||
secretKeyPairDir := filepath.Join(homeDir, "secrets", "contexts", "contx1", "secrets")
|
||||
if err := os.MkdirAll(secretKeyPairDir, 0777); err != nil {
|
||||
err = fmt.Errorf("Not able to create directories")
|
||||
log.Fatal(err)
|
||||
}
|
||||
os.Setenv("QLIKSENSE_KEY_LOCATION", secretKeyPairDir)
|
||||
|
||||
key, _ := LoadSecretKey(secretKeyPairDir)
|
||||
|
||||
if key == "" {
|
||||
return GenerateAndStoreSecretKey(secretKeyPairDir)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func Test_set_and_get_fetch_access_token(t *testing.T) {
|
||||
td, homeDir := setup()
|
||||
defer td()
|
||||
createCRFile(homeDir)
|
||||
crFile := filepath.Join("contexts", "contx1", "contx1.yaml")
|
||||
qConfig := NewQConfig(homeDir)
|
||||
newQ, _ := qConfig.SetCrLocation("contx1", crFile)
|
||||
newQ.Write()
|
||||
qConfig = NewQConfig(homeDir)
|
||||
qcr, _ := qConfig.GetCurrentCR()
|
||||
key, _ := qConfig.GetEncryptionKeyFor(qcr.GetName())
|
||||
if err := qcr.SetFetchAccessToken("mytokenbeforeencryption", key); err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
tok := qcr.GetFetchAccessToken(key)
|
||||
if tok != "mytokenbeforeencryption" {
|
||||
t.Log("Expected: mytokenbeforeencryption, got: " + tok)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,56 +1,48 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/qlik-oss/k-apis/pkg/config"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
machine_yaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
QliksenseConfigApiVersion = "v1"
|
||||
QliksenseConfigApiGroup = "config.qlik.com"
|
||||
QliksenseConfigKind = "QliksenseConfig"
|
||||
|
||||
QliksenseApiVersion = "v1"
|
||||
QliksenseKind = "Qliksense"
|
||||
QliksenseGroup = "qlik.com"
|
||||
QliksenseDefaultProfile = "docker-desktop"
|
||||
DefaultRotateKeys = "yes"
|
||||
QliksenseMetadataName = "QliksenseConfigMetadata"
|
||||
DefaultMongoDbUri = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
|
||||
DefaultMongoDbUriKey = "mongoDbUri"
|
||||
QliksenseConfigApiVersion = "config.qlik.com/v1"
|
||||
QliksenseConfigKind = "QliksenseConfig"
|
||||
QliksenseContextApiVersion = "qlik.com/v1"
|
||||
QliksenseContextKind = "Qliksense"
|
||||
QliksenseDefaultProfile = "docker-desktop"
|
||||
DefaultRotateKeys = "yes"
|
||||
QliksenseMetadataName = "QliksenseConfigMetadata"
|
||||
)
|
||||
|
||||
// AddCommonConfig adds common configs into CRs
|
||||
func (qliksenseCR *QliksenseCR) AddCommonConfig(contextName string) {
|
||||
qliksenseCR.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Group: QliksenseGroup,
|
||||
Kind: QliksenseKind,
|
||||
Version: QliksenseApiVersion,
|
||||
})
|
||||
qliksenseCR.SetName(contextName)
|
||||
qliksenseCR.Spec = &config.CRSpec{
|
||||
Profile: QliksenseDefaultProfile,
|
||||
RotateKeys: DefaultRotateKeys,
|
||||
qliksenseCR.ApiVersion = QliksenseContextApiVersion
|
||||
qliksenseCR.Kind = QliksenseContextKind
|
||||
if qliksenseCR.Metadata == nil {
|
||||
qliksenseCR.Metadata = &Metadata{}
|
||||
}
|
||||
qliksenseCR.Spec.AddToSecrets("qliksense", DefaultMongoDbUriKey, DefaultMongoDbUri, "")
|
||||
if qliksenseCR.Metadata.Name == "" {
|
||||
qliksenseCR.Metadata.Name = contextName
|
||||
}
|
||||
qliksenseCR.Spec = &config.CRSpec{}
|
||||
qliksenseCR.Spec.Profile = QliksenseDefaultProfile
|
||||
qliksenseCR.Spec.ReleaseName = contextName
|
||||
qliksenseCR.Spec.RotateKeys = DefaultRotateKeys
|
||||
}
|
||||
|
||||
// AddBaseQliksenseConfigs adds configs into config.yaml
|
||||
func (qliksenseConfig *QliksenseConfig) AddBaseQliksenseConfigs(defaultQliksenseContext string) {
|
||||
qliksenseConfig.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Group: QliksenseConfigApiGroup,
|
||||
Kind: QliksenseConfigKind,
|
||||
Version: QliksenseConfigApiVersion,
|
||||
})
|
||||
qliksenseConfig.SetName(QliksenseMetadataName)
|
||||
qliksenseConfig.ApiVersion = QliksenseConfigApiVersion
|
||||
qliksenseConfig.Kind = QliksenseConfigKind
|
||||
if qliksenseConfig.Metadata == nil {
|
||||
qliksenseConfig.Metadata = &Metadata{}
|
||||
}
|
||||
qliksenseConfig.Metadata.Name = QliksenseMetadataName
|
||||
if defaultQliksenseContext != "" {
|
||||
if qliksenseConfig.Spec == nil {
|
||||
qliksenseConfig.Spec = &ContextSpec{}
|
||||
@@ -59,37 +51,19 @@ func (qliksenseConfig *QliksenseConfig) AddBaseQliksenseConfigs(defaultQliksense
|
||||
}
|
||||
}
|
||||
|
||||
func (qliksenseConfig *QliksenseConfig) SwitchCurrentCRToVersionAndProfile(version, profile string) error {
|
||||
if qcr, err := qliksenseConfig.GetCurrentCR(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
versionManifestRoot := qliksenseConfig.BuildCurrentManifestsRoot(version)
|
||||
if (qcr.Spec.ManifestsRoot != versionManifestRoot) || (profile != "" && qcr.Spec.Profile != profile) || (qcr.GetLabelFromCr("version") != version) {
|
||||
qcr.Spec.ManifestsRoot = versionManifestRoot
|
||||
if profile != "" {
|
||||
qcr.Spec.Profile = profile
|
||||
}
|
||||
qcr.AddLabelToCr("version", version)
|
||||
if err := qliksenseConfig.WriteCurrentContextCR(qcr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteToFile (content, targetFile) writes content into specified file
|
||||
func WriteToFile(content interface{}, targetFile string) error {
|
||||
if content == nil || targetFile == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
x, err := K8sToYaml(content)
|
||||
x, err := yaml.Marshal(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 {
|
||||
@@ -105,22 +79,16 @@ func ReadFromFile(content interface{}, sourceFile string) error {
|
||||
if content == nil || sourceFile == "" {
|
||||
return nil
|
||||
}
|
||||
file, e := os.Open(sourceFile)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
return ReadFromStream(content, file)
|
||||
}
|
||||
|
||||
// ReadFromStream reads from input stream and creat yaml struct of type content
|
||||
func ReadFromStream(content interface{}, reader io.Reader) error {
|
||||
contents, err := ioutil.ReadAll(reader)
|
||||
contents, err := ioutil.ReadFile(sourceFile)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("There was an error reading from reader: %v", err)
|
||||
err = fmt.Errorf("There was an error reading from file: %s, %v", sourceFile, err)
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
// reading k8s style object
|
||||
// https://stackoverflow.com/questions/44306554/how-to-deserialize-kubernetes-yaml-file
|
||||
dec := machine_yaml.NewYAMLOrJSONDecoder(bytes.NewReader(contents), 10000)
|
||||
return dec.Decode(content)
|
||||
if err := yaml.Unmarshal(contents, content); err != nil {
|
||||
err = fmt.Errorf("An error occurred during unmarshalling: %v", err)
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/qlik-oss/k-apis/pkg/config"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -13,26 +12,6 @@ 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
|
||||
@@ -48,7 +27,20 @@ func TestAddCommonConfig(t *testing.T) {
|
||||
qliksenseCR: &QliksenseCR{},
|
||||
contextName: "myqliksense",
|
||||
},
|
||||
want: q,
|
||||
want: &QliksenseCR{
|
||||
CommonConfig: CommonConfig{
|
||||
ApiVersion: QliksenseContextApiVersion,
|
||||
Kind: QliksenseContextKind,
|
||||
Metadata: &Metadata{
|
||||
Name: "myqliksense",
|
||||
},
|
||||
},
|
||||
Spec: &config.CRSpec{
|
||||
Profile: QliksenseDefaultProfile,
|
||||
ReleaseName: "myqliksense",
|
||||
RotateKeys: DefaultRotateKeys,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -62,18 +54,6 @@ 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
|
||||
@@ -89,7 +69,18 @@ func TestAddBaseQliksenseConfigs(t *testing.T) {
|
||||
qliksenseConfig: &QliksenseConfig{},
|
||||
defaultQliksenseContext: "qlik-default",
|
||||
},
|
||||
want: qc,
|
||||
want: &QliksenseConfig{
|
||||
CommonConfig: CommonConfig{
|
||||
ApiVersion: QliksenseConfigApiVersion,
|
||||
Kind: QliksenseConfigKind,
|
||||
Metadata: &Metadata{
|
||||
Name: QliksenseMetadataName,
|
||||
},
|
||||
},
|
||||
Spec: &ContextSpec{
|
||||
CurrentContext: "qlik-default",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package api
|
||||
|
||||
import "github.com/otiai10/copy"
|
||||
|
||||
//copy source directory to destination
|
||||
func CopyDirectory(source string, dest string) error {
|
||||
return copy.Copy(source, dest)
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
kapis_git "github.com/qlik-oss/k-apis/pkg/git"
|
||||
"sigs.k8s.io/kustomize/api/filesys"
|
||||
"sigs.k8s.io/kustomize/api/konfig"
|
||||
"sigs.k8s.io/kustomize/api/krusty"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
func TestCopyDirectory(t *testing.T) {
|
||||
src, _ := ioutil.TempDir("", "")
|
||||
f1, _ := ioutil.TempFile(src, "")
|
||||
ioutil.TempFile(src, "")
|
||||
|
||||
dest, _ := ioutil.TempDir("", "")
|
||||
CopyDirectory(src, dest)
|
||||
if _, err := os.Lstat(filepath.Join(dest, filepath.Base(f1.Name()))); err != nil {
|
||||
t.Log(err)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyDirectory_withGit_withKuz(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping in short test mode")
|
||||
}
|
||||
|
||||
tmpDir1, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir1)
|
||||
|
||||
tmpDir2, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir2)
|
||||
|
||||
repoPath1 := path.Join(tmpDir1, "repo")
|
||||
repo1, err := kapis_git.CloneRepository(repoPath1, "https://github.com/qlik-oss/qliksense-k8s", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := CopyDirectory(repoPath1, tmpDir2); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
repoPath2 := tmpDir2
|
||||
repo2, err := kapis_git.OpenRepository(repoPath2)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := kapis_git.Checkout(repo2, "v0.0.2", "", nil); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
repo2Manifest, err := kuz(path.Join(repoPath2, "manifests", "docker-desktop"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := kapis_git.Checkout(repo1, "v0.0.2", "", nil); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
repo1Manifest, err := kuz(path.Join(repoPath1, "manifests", "docker-desktop"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if string(repo2Manifest) != string(repo1Manifest) {
|
||||
t.Logf("manifest generated on the original config:\n%v", string(repo1Manifest))
|
||||
t.Logf("manifest generated on the copied config:\n%v", string(repo2Manifest))
|
||||
t.Fatal("expected manifests to be equal, but they were not")
|
||||
}
|
||||
}
|
||||
|
||||
func kuz(directory string) ([]byte, error) {
|
||||
options := &krusty.Options{
|
||||
DoLegacyResourceSort: false,
|
||||
LoadRestrictions: types.LoadRestrictionsNone,
|
||||
DoPrune: false,
|
||||
PluginConfig: konfig.DisabledPluginConfig(),
|
||||
}
|
||||
k := krusty.MakeKustomizer(filesys.MakeFsOnDisk(), options)
|
||||
resMap, err := k.Run(directory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resMap.AsYaml()
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type k8sDockerConfigJsonMapType struct {
|
||||
Auths map[string]k8sDockerConfigJsonType `json:"auths"`
|
||||
}
|
||||
|
||||
type k8sDockerConfigJsonType struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Auth string `json:"auth"`
|
||||
}
|
||||
|
||||
func (kdcjt *k8sDockerConfigJsonType) GenerateAuth() {
|
||||
kdcjt.Auth = base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", kdcjt.Username, kdcjt.Password)))
|
||||
}
|
||||
|
||||
type DockerConfigJsonSecret struct {
|
||||
Name string
|
||||
Uri string
|
||||
Username string
|
||||
Password string
|
||||
Email string
|
||||
}
|
||||
|
||||
func (d *DockerConfigJsonSecret) ToYaml(encryptionKey string) ([]byte, error) {
|
||||
k8sDockerConfigJson := k8sDockerConfigJsonType{
|
||||
Username: d.Username,
|
||||
Password: d.Password,
|
||||
Email: d.Email,
|
||||
}
|
||||
k8sDockerConfigJson.GenerateAuth()
|
||||
k8sDockerConfigJsonMap := k8sDockerConfigJsonMapType{
|
||||
Auths: map[string]k8sDockerConfigJsonType{
|
||||
d.Uri: k8sDockerConfigJson,
|
||||
},
|
||||
}
|
||||
k8sDockerConfigJsonMapBytes, err := json.Marshal(k8sDockerConfigJsonMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var k8sDockerConfigJsonMapMaybeEncryptedBytes []byte
|
||||
if encryptionKey != "" {
|
||||
if k8sDockerConfigJsonMapMaybeEncryptedBytes, err = EncryptData(k8sDockerConfigJsonMapBytes, encryptionKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
k8sDockerConfigJsonMapMaybeEncryptedBytes = k8sDockerConfigJsonMapBytes
|
||||
}
|
||||
|
||||
k8sSecret := v1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: d.Name,
|
||||
},
|
||||
Type: v1.SecretTypeDockerConfigJson,
|
||||
Data: map[string][]byte{
|
||||
".dockerconfigjson": k8sDockerConfigJsonMapMaybeEncryptedBytes,
|
||||
},
|
||||
}
|
||||
|
||||
return K8sSecretToYaml(k8sSecret)
|
||||
}
|
||||
|
||||
func (d *DockerConfigJsonSecret) FromYaml(secretBytes []byte, decryptionKey string) error {
|
||||
k8sDockerConfigJsonMap := k8sDockerConfigJsonMapType{}
|
||||
if k8sSecret, err := K8sSecretFromYaml(secretBytes); err != nil {
|
||||
return err
|
||||
} else if k8sSecret.TypeMeta.Kind != "Secret" {
|
||||
return errors.New("not a Secret kind")
|
||||
} else if k8sSecret.Type != v1.SecretTypeDockerConfigJson {
|
||||
return errors.New("not a kubernetes.io/dockerconfigjson type")
|
||||
} else if k8sDockerConfigJsonMapEncryptedBytes, ok := k8sSecret.Data[".dockerconfigjson"]; !ok {
|
||||
return errors.New("secret data is missing a value for the .dockerconfigjson key")
|
||||
} else if k8sDockerConfigJsonMapBytes, err := DecryptData(k8sDockerConfigJsonMapEncryptedBytes, decryptionKey); err != nil {
|
||||
return errors.New("secret data is missing a value for the .dockerconfigjson key")
|
||||
} else if err := json.Unmarshal(k8sDockerConfigJsonMapBytes, &k8sDockerConfigJsonMap); err != nil {
|
||||
return err
|
||||
} else {
|
||||
d.Name = k8sSecret.ObjectMeta.Name
|
||||
for registry, k8sDockerConfigJson := range k8sDockerConfigJsonMap.Auths {
|
||||
d.Uri = registry
|
||||
d.Username = k8sDockerConfigJson.Username
|
||||
d.Password = k8sDockerConfigJson.Password
|
||||
d.Email = k8sDockerConfigJson.Email
|
||||
break
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestDockerConfigJsonSecret(t *testing.T) {
|
||||
dockerConfigJsonSecret := DockerConfigJsonSecret{
|
||||
Name: "some-name",
|
||||
Uri: "some-uri",
|
||||
Username: "some-username",
|
||||
Password: "some-password",
|
||||
Email: "some-email",
|
||||
}
|
||||
dockerConfigJsonSecretFromYaml := DockerConfigJsonSecret{}
|
||||
validYamlMap := map[string]interface{}{}
|
||||
|
||||
encryptionKey, err := GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("error generating RSA private key: %v\n", err)
|
||||
}
|
||||
|
||||
dockerConfigJsonSecretYamlBytes, err := dockerConfigJsonSecret.ToYaml(encryptionKey)
|
||||
dockerConfigJsonMap := map[string]interface{}{}
|
||||
if err != nil {
|
||||
t.Fatalf("error converting secret to yaml: %v", err)
|
||||
} else if err := yaml.Unmarshal(dockerConfigJsonSecretYamlBytes, &validYamlMap); err != nil {
|
||||
t.Fatalf("error unmarshalling yaml string: %v, error: %v", string(dockerConfigJsonSecretYamlBytes), err)
|
||||
} else if validYamlMap["apiVersion"] != "v1" ||
|
||||
validYamlMap["kind"] != "Secret" ||
|
||||
validYamlMap["metadata"].(map[string]interface{})["name"] != dockerConfigJsonSecret.Name ||
|
||||
validYamlMap["type"] != "kubernetes.io/dockerconfigjson" {
|
||||
t.Fatalf("error verifying validity of secret yaml: %v", string(dockerConfigJsonSecretYamlBytes))
|
||||
} else if dockerConfigJsonBytesBase64, ok := validYamlMap["data"].(map[string]interface{})[".dockerconfigjson"]; !ok {
|
||||
t.Fatalf("no .dockerconfigjson data key in the secret yaml: %v", string(dockerConfigJsonSecretYamlBytes))
|
||||
} else if dockerConfigJsonEncryptedBytes, err := base64.StdEncoding.DecodeString(dockerConfigJsonBytesBase64.(string)); err != nil {
|
||||
t.Fatalf("error decoding dockerConfigJsonBytes from base64: %v", err)
|
||||
} else if dockerConfigJsonBytes, err := DecryptData(dockerConfigJsonEncryptedBytes, encryptionKey); err != nil {
|
||||
t.Fatalf("error decrypting dockerConfigJsonBytes: %v", err)
|
||||
} else if err := json.Unmarshal(dockerConfigJsonBytes, &dockerConfigJsonMap); err != nil {
|
||||
t.Fatalf("error unmarshalling dockerConfigJson from json: %v", err)
|
||||
} else if dockerConfigJson, ok := dockerConfigJsonMap["auths"].(map[string]interface{})[dockerConfigJsonSecret.Uri]; !ok {
|
||||
t.Fatalf("dockerConfigJson map does not contain data for the registry: %v", dockerConfigJsonSecret.Uri)
|
||||
} else if dockerConfigJson.(map[string]interface{})["username"] != dockerConfigJsonSecret.Username ||
|
||||
dockerConfigJson.(map[string]interface{})["password"] != dockerConfigJsonSecret.Password ||
|
||||
dockerConfigJson.(map[string]interface{})["email"] != dockerConfigJsonSecret.Email {
|
||||
t.Fatal("dockerConfigJson map does not contain expected values")
|
||||
} else {
|
||||
authBase64 := dockerConfigJson.(map[string]interface{})["auth"]
|
||||
if auth, err := base64.StdEncoding.DecodeString(authBase64.(string)); err != nil {
|
||||
t.Fatal("error base64 decoding auth value")
|
||||
} else if string(auth) != fmt.Sprintf("%s:%s", dockerConfigJsonSecret.Username, dockerConfigJsonSecret.Password) {
|
||||
t.Fatal("auth value was not what we expected")
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("dockerConfigJsonSecretYaml: \n%v\n", string(dockerConfigJsonSecretYamlBytes))
|
||||
if err := dockerConfigJsonSecretFromYaml.FromYaml(dockerConfigJsonSecretYamlBytes, encryptionKey); err != nil {
|
||||
t.Fatalf("error reading secret in from yaml: %v", err)
|
||||
} else if !reflect.DeepEqual(dockerConfigJsonSecret, dockerConfigJsonSecretFromYaml) {
|
||||
t.Fatalf("secret: %v does not equal secret: %v", dockerConfigJsonSecret, dockerConfigJsonSecretFromYaml)
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,60 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
key_file_name = "user_secret_key"
|
||||
RSA_KEY_LENGTH = 4096
|
||||
privateKeyPath = "privKey"
|
||||
publicKeyPath = "pubKey"
|
||||
|
||||
QliksensePublicKey = "qliksensePub"
|
||||
QliksensePrivateKey = "qliksensePriv"
|
||||
)
|
||||
|
||||
// GenerateAndStoreSecretKey generates and stores key
|
||||
func GenerateAndStoreSecretKey(secretsDir string) (string, error) {
|
||||
// creating contexts/qlik-default/secrets/user_secret_key
|
||||
keyFile := filepath.Join(secretsDir, key_file_name)
|
||||
key, err := GenerateKey()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := writeContentToFile([]byte(key), keyFile); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return key, nil
|
||||
// GenerateAndStoreSecretKeypair generates and stores key pairs
|
||||
func GenerateAndStoreSecretKeypair(secretsPath string) error {
|
||||
LogDebugMessage("%s exists", secretsPath)
|
||||
// creating contexts/qlik-default/secrets/qliksensePub and contexts/qlik-default/secrets/qliksensePriv files
|
||||
publicKeyFilePath := filepath.Join(secretsPath, QliksensePublicKey)
|
||||
privateKeyFilePath := filepath.Join(secretsPath, QliksensePrivateKey)
|
||||
LogDebugMessage("Generating public-private key pair.....")
|
||||
GenerateRSAEncryptionKeys(publicKeyFilePath, privateKeyFilePath)
|
||||
LogDebugMessage("Generated public-private key pairs")
|
||||
|
||||
return nil
|
||||
}
|
||||
func LoadSecretKey(secretsDir string) (string, error) {
|
||||
keyFile := filepath.Join(secretsDir, key_file_name)
|
||||
by, err := ioutil.ReadFile(keyFile)
|
||||
|
||||
// GenerateRSAEncryptionKeys is used to generate a new public-private key pair
|
||||
func GenerateRSAEncryptionKeys(publicKeyFilePath, privateKeyFilePath string) error {
|
||||
LogDebugMessage("Generating new RSA key pair")
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, RSA_KEY_LENGTH)
|
||||
if err != nil {
|
||||
return "", err
|
||||
log.Printf("error generating RSA private key: %v\n", err)
|
||||
return err
|
||||
}
|
||||
return string(by), nil
|
||||
|
||||
privateKeyPEM := EncodePrivateKey(privateKey)
|
||||
if err := writeContentToFile(privateKeyPEM, privateKeyFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
pubKeyPEM, err2 := EncodePublicKey(&privateKey.PublicKey)
|
||||
if err2 != nil {
|
||||
log.Printf("error occurred when encoding public key: %v\n", err2)
|
||||
return err2
|
||||
}
|
||||
if err := writeContentToFile(pubKeyPEM, publicKeyFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeContentToFile writes keys to a file
|
||||
@@ -46,57 +64,108 @@ func writeContentToFile(keyData []byte, fileName string) error {
|
||||
log.Printf("error writing to file (%s): %v", fileName, err)
|
||||
return err
|
||||
}
|
||||
LogDebugMessage("Key saved: %s", fileName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func GenerateKey() (string, error) {
|
||||
salt := make([]byte, 32)
|
||||
if _, err := rand.Read(salt); err != nil {
|
||||
return "", err
|
||||
// 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)
|
||||
ct, err := rsa.EncryptPKCS1v15(rand.Reader, pub, pt)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
s := fmt.Sprintf("%x", salt)
|
||||
return s, nil
|
||||
return ct, nil
|
||||
}
|
||||
|
||||
func EncryptData(plaintext []byte, userKey string) ([]byte, error) {
|
||||
key, _ := hex.DecodeString(userKey)
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
// Decrypt decrypts data with private key
|
||||
func Decrypt(ct []byte, priv *rsa.PrivateKey) ([]byte, error) {
|
||||
// hash := sha512.New()
|
||||
// plaintext, err := rsa.DecryptOAEP(hash, rand.Reader, priv, ciphertext, nil)
|
||||
pt, err := rsa.DecryptPKCS1v15(rand.Reader, priv, ct)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aesgcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nonce := make([]byte, aesgcm.NonceSize())
|
||||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return aesgcm.Seal(nonce, nonce, plaintext, nil), nil
|
||||
return pt, nil
|
||||
}
|
||||
|
||||
func DecryptData(ciphertext []byte, userKey string) ([]byte, error) {
|
||||
key, _ := hex.DecodeString(userKey)
|
||||
block, err := aes.NewCipher(key)
|
||||
// EncodePrivateKey private key to bytes
|
||||
func EncodePrivateKey(priv *rsa.PrivateKey) []byte {
|
||||
privBytes := pem.EncodeToMemory(
|
||||
&pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(priv),
|
||||
},
|
||||
)
|
||||
|
||||
return privBytes
|
||||
}
|
||||
|
||||
// EncodePublicKey public key to bytes
|
||||
func EncodePublicKey(pub *rsa.PublicKey) ([]byte, error) {
|
||||
pubASN1, err := x509.MarshalPKIXPublicKey(pub)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aesgcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nonceSize := aesgcm.NonceSize()
|
||||
if len(ciphertext) < nonceSize {
|
||||
return nil, errors.New("ciphertext too short")
|
||||
}
|
||||
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
||||
plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return plaintext, nil
|
||||
pubBytes := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "RSA PUBLIC KEY",
|
||||
Bytes: pubASN1,
|
||||
})
|
||||
|
||||
return pubBytes, nil
|
||||
}
|
||||
|
||||
// DecodeToPrivateKey bytes to private key
|
||||
func DecodeToPrivateKey(priv []byte) (*rsa.PrivateKey, error) {
|
||||
block, _ := pem.Decode(priv)
|
||||
enc := x509.IsEncryptedPEMBlock(block)
|
||||
b := block.Bytes
|
||||
var err error
|
||||
if enc {
|
||||
log.Println("is encrypted pem block")
|
||||
b, err = x509.DecryptPEMBlock(block, nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
key, err := x509.ParsePKCS1PrivateKey(b)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// DecodeToPublicKey bytes to public key
|
||||
func DecodeToPublicKey(pub []byte) (*rsa.PublicKey, error) {
|
||||
block, _ := pem.Decode(pub)
|
||||
enc := x509.IsEncryptedPEMBlock(block)
|
||||
b := block.Bytes
|
||||
var err error
|
||||
if enc {
|
||||
log.Println("is encrypted pem block")
|
||||
b, err = x509.DecryptPEMBlock(block, nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
iface, err := x509.ParsePKIXPublicKey(b)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
key, ok := iface.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
err := fmt.Errorf("Unable to decode public key")
|
||||
log.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
@@ -1,29 +1,129 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_encrypt_decrypt(t *testing.T) {
|
||||
key, err := GenerateKey()
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
func Test_generateRSAEncryptionKeys(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid case",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
testData := "this is a secret value"
|
||||
enc, err := EncryptData([]byte(testData), key)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
dec, err := DecryptData(enc, key)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
if testData != string(dec) {
|
||||
t.Log("expected: " + testData)
|
||||
t.Log("actual: " + string(dec))
|
||||
t.Fail()
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := GenerateAndStoreSecretKeypair(os.TempDir()); (err != nil) != tt.wantErr {
|
||||
t.Errorf("generateRSAEncryptionKeys() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_encryption_decryption(t *testing.T) {
|
||||
privKeyBytes := []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIJJwIBAAKCAgEAwUCimKCidbF3UxEHPy8K+hvhklRB9JYhj5sJy0if4lTVibkK
|
||||
1MrYCykOnmC40pPU9GLY1b8HxAg9tvyRn0YHUxOra6vVQaVcOVJhTM8D18d+lSr3
|
||||
Lp1yiX+UGT4nzWI9+R1CCbwXrqeQVoZs6QZKynEXMkFI9/wNMOwPOvQFOSTuoEoC
|
||||
O+zyTyUWEkNbUq825ELUQdIsjgmlWUOONudxsAr7ESRXW9QTHVh6uWmr3VRKZHby
|
||||
1JdU3I/wjdlGg5M2dDuXy5nQO9w/nYLjJXiw+zzOetZ/+t7/VOkOpNTeJQhwTM1W
|
||||
F7Y2VLetbi9FHgyzHatrduh07+XEiTbgDf3GIx2bp2p6oh0G3N2zpiLcK/aZj8ro
|
||||
uWWydfFfsU3MZ4FfJDP8I6b9awxjmKYqIr6hiPQCJaLBED8mwK+I5evIbnKv6E6u
|
||||
K+BApWA/R7ElragoFYbqQ1VpvntVMtJt9Dy5ZrI+IQARdXD3bb34oh0IPBhClnvv
|
||||
MUc1cWxDoXEX6oJ4I+LzxE87Zkwnan9qOwengolMVKFwPx1o37qrbmrXID21kKt7
|
||||
FL6xN4HxHLkItr1fKzdyWDFRHgASTAWfx5BIwvPuUW0vZHkvO80VyV2L63whVhPn
|
||||
PASmFkbviomrBttYfpr2aGQqF/qR1Nlxe834MFxk1pS9LMa/WnzvFr0gWakCAwEA
|
||||
AQKCAgARSp9B2N2wejibDiL/3E23I1eDqFZedDB8kPrHXbAwqDaTJCN79spt9TaB
|
||||
pVXkQaYEV/Pe7EDdoX8kKGU/QxzUqiXkdHOYdBtUZbKfFMbbP9ZrsnR7j0r4UpoF
|
||||
yDH3hprU93E5PcNAtW2M0GpeT1nR01yn+n908PCdOAIE3GC7RDq1zOl2QzVLL55R
|
||||
9ATv2Q2oTvJ/ETc7XlGVMx4+e2cIwXLFjeLjLI6pSYlxnarrGuetJZeEviWxto9n
|
||||
odFVZI6yx8JFTXX8ZTCr/1IjwDDVyhMPmrHI2Lsv9cqBpSpbVe32cUkKxhsGaYjz
|
||||
GvesQKamOPhco2ATNxPm0yopFlPsGKMfVl0BK0J6BqFh1BvU/SYJmXfnFuUNO3vV
|
||||
4u2Saa0q1iddxV0rXDwIqUfn+S6rwzK0G7y8bH2yvpB2VwiG3TFPnULep4wsefNq
|
||||
Fj92kqFBjacGpQLEEslUY0CMgeZ2+NuBQSUTscP3wBRsottMR6YXJtINdvfHBx+e
|
||||
EcN71z8D00w3mYqIQ7qb4Ml6HOqknunn58g43L9sACMUMTlEBXa9pUnScNYgWBAz
|
||||
W2q2mH37cIydM2JRZPpA8B4yTHt5ugJmChwyNFM7941arjKrebH+6AzLkofGedOP
|
||||
zg+vZQuPEXWs+3MBBnkWoyJW3Y0fbQdjsuQTtnd+7iyoxoBroQKCAQEA4dIiFlIS
|
||||
MDfRhQQWSiDvaw9aneDEJ3uo63ZRH5tm/IynLgtjYgEm/ZxlBCQgqRKLYELBxhu8
|
||||
SaF0uPK8pmpFJt0mIwSlsdeVhuE2obQeKUCczaqrKeaHS3PdWLjTlwph81BGRkHy
|
||||
qfqtNylyyMxrdEbnR51EtsWgFq6anTUAui1Q09JMuMNZRMOzDs1F4gExgD22rc0V
|
||||
c9YQ+jHJRxBGtNKMpMEqc8cvaxBidbItrN9SMTSWog7uYPBuEuaJ6K9vpgyJMOzJ
|
||||
SYcQEFGqgIqIDCg+ABE4d/4YROMKZ1DV/bJCind9brUHSx6XALsF0nC5c1Q9TnUL
|
||||
qI2khOwts4KYKwKCAQEA2xRC6Az97Vkdzu7BjLJ1FKmx4S2nEEgVS12ds82U+5Xf
|
||||
BHKAJnjqlqmmpzzJG+d77IYktz0+mey1QCNkqlm2fhuKs8LZMnpZRf0l8VcoBsUP
|
||||
/xKz7wfiE7RRFZtLJhPp4hhe43GzX5/JFMWMnC6UykwQbj4t1E/GNM/Suqwvg12M
|
||||
wktAJ6nqLgfhjQSO4xWo+nPzcbX+fNtrPCZVrBhYXihhcwRRNImWUCGJ6J4LMdPY
|
||||
Y9Z59qhOvE9cReH/Xw1av46omyiSyAqlgPyZ/kzA2IJSqYCjiQR/2+RD/g13jpcJ
|
||||
jatXLVZ8MJSL5OTS40G/HHTNNpNHbKKh0GOyxBA3ewKCAQBAn8UXhCcmW2L/YPsL
|
||||
/b7mcX9qPP+FmRLvR23R0MQ5M/tH5wRq8I969n3GIJykJeVzB8eybQ+GNslTgEvS
|
||||
iAkAJTubu+G7MkndTqg2wHf9MDtvdA8Fr646Po8yq7oJuHPtkKR7yLWsRUu6xIbP
|
||||
xgheP0hCq1QVxhqZQyCGKrvpi7xc0gsYuPbcAfFFJCOCmPrUi1SzCkTAYJt9LjA+
|
||||
wP6rErIjGBCRD4iXaBn1OqdtmH9KC5WsDP/VCBlIGWeQCly2NVIxiSHVg+xp7yUP
|
||||
IhXq/L05gbQaSsIhPKQmivCiaJg4The8TdwneDqYf+0bmxzHT203/bD3bImPbJNr
|
||||
ksz/AoIBAEwu4Y1cZzkQUmNRd5D7xecnk6ngfEYXKwCIT3zlMrfCSEl9n77BMaKu
|
||||
4Dsr0iuX9eosQ7xM2eYhAG6LYEg05lc4MKWOToVVMpI6E+W3Dz47bPKgiF3I+f8s
|
||||
Jz5CQIG/TwfGvciOE3hfUkec4ua09BzdEqGjkcBQ9XYMBxXPJr6h2379OBQS7FKR
|
||||
fwfQ2/dv4tElXTTfut2kV8gU9Jnh5Wjo1epvR+XjKpg28YQo4W+0YX1magcyRB8L
|
||||
4eSTUIC3XiVa8Jr0IwbZXPBb5xkdi7o+p4w2JahSHjxTRqmj+T1mnHXdbXVgq9Mg
|
||||
9Pzl7cgFZvX4UBx4XtASRf73jITNtt0CggEADH9K+O7FrIOSQly0sMvsRCMtejp3
|
||||
o+MDh1Q+vEg2kEgNXjS4ZFVljUpM2kg1OdUz7feS4dLXUJiIQ8ZWtZPedcq7wjHd
|
||||
02he5+s06l0jPifN3tX1ADfXGpXg5R2fbkrIzakkPP5/RO/aDxIUo7qhklNsVTXO
|
||||
VlGGfWLdk0ekA4upKm02Q1+YOlbIcAicEYYY8K7IffUwnohzKwL9yfuGi1VKTXpE
|
||||
4fzdegsHI03FSqR7V+LvtBpIupQ7RO4kuBmCEyI4E9FVknchg4te4gO3qwd9y0rJ
|
||||
Gu7HNIOrwOHzviI7J6Nd/l9MmeKqklHSgJvko/f5TmiXuQQ8xDZf84rcjQ==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`)
|
||||
|
||||
publicKeyBytes := []byte(`-----BEGIN RSA PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwUCimKCidbF3UxEHPy8K
|
||||
+hvhklRB9JYhj5sJy0if4lTVibkK1MrYCykOnmC40pPU9GLY1b8HxAg9tvyRn0YH
|
||||
UxOra6vVQaVcOVJhTM8D18d+lSr3Lp1yiX+UGT4nzWI9+R1CCbwXrqeQVoZs6QZK
|
||||
ynEXMkFI9/wNMOwPOvQFOSTuoEoCO+zyTyUWEkNbUq825ELUQdIsjgmlWUOONudx
|
||||
sAr7ESRXW9QTHVh6uWmr3VRKZHby1JdU3I/wjdlGg5M2dDuXy5nQO9w/nYLjJXiw
|
||||
+zzOetZ/+t7/VOkOpNTeJQhwTM1WF7Y2VLetbi9FHgyzHatrduh07+XEiTbgDf3G
|
||||
Ix2bp2p6oh0G3N2zpiLcK/aZj8rouWWydfFfsU3MZ4FfJDP8I6b9awxjmKYqIr6h
|
||||
iPQCJaLBED8mwK+I5evIbnKv6E6uK+BApWA/R7ElragoFYbqQ1VpvntVMtJt9Dy5
|
||||
ZrI+IQARdXD3bb34oh0IPBhClnvvMUc1cWxDoXEX6oJ4I+LzxE87Zkwnan9qOwen
|
||||
golMVKFwPx1o37qrbmrXID21kKt7FL6xN4HxHLkItr1fKzdyWDFRHgASTAWfx5BI
|
||||
wvPuUW0vZHkvO80VyV2L63whVhPnPASmFkbviomrBttYfpr2aGQqF/qR1Nlxe834
|
||||
MFxk1pS9LMa/WnzvFr0gWakCAwEAAQ==
|
||||
-----END RSA PUBLIC KEY-----
|
||||
`)
|
||||
origStr := "Value1234"
|
||||
|
||||
pubKey, err := DecodeToPublicKey(publicKeyBytes)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
privKey, err := DecodeToPrivateKey(privKeyBytes)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
encData, err := Encrypt([]byte(origStr), pubKey)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
encDataStr := base64.StdEncoding.EncodeToString(encData)
|
||||
log.Printf("encrypted data: %s\n", encDataStr)
|
||||
dec, _ := base64.StdEncoding.DecodeString(encDataStr)
|
||||
data, err := Decrypt(dec, privKey)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
if string(data) != origStr {
|
||||
t.Error("original string and decrypted string don't match")
|
||||
t.FailNow()
|
||||
}
|
||||
log.Printf("decrypted data: %s\n", data)
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
func K8sSecretToYaml(k8sSecret v1.Secret) ([]byte, error) {
|
||||
return K8sToYaml(k8sSecret)
|
||||
}
|
||||
|
||||
func K8sSecretFromYaml(k8sSecretBytes []byte) (v1.Secret, error) {
|
||||
k8sSecret := v1.Secret{}
|
||||
if err := yaml.UnmarshalStrict(k8sSecretBytes, &k8sSecret); err != nil {
|
||||
return k8sSecret, err
|
||||
}
|
||||
return k8sSecret, nil
|
||||
}
|
||||
|
||||
func K8sToYaml(k8sObj interface{}) ([]byte, error) {
|
||||
k8sSecretYamlMap := map[string]interface{}{}
|
||||
if k8sSecretYamlBytes, err := yaml.Marshal(k8sObj); err != nil {
|
||||
return nil, err
|
||||
} else if err := yaml.Unmarshal(k8sSecretYamlBytes, &k8sSecretYamlMap); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
delete(k8sSecretYamlMap["metadata"].(map[string]interface{}), "creationTimestamp")
|
||||
return yaml.Marshal(k8sSecretYamlMap)
|
||||
}
|
||||
}
|
||||
@@ -1,66 +1,21 @@
|
||||
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)
|
||||
func KubectlApply(manifests string) error {
|
||||
return kubectlOperation(manifests, "apply")
|
||||
}
|
||||
|
||||
// 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 KubectlDelete(manifests string) error {
|
||||
return kubectlOperation(manifests, "delete")
|
||||
}
|
||||
|
||||
func GetKubectlNamespace() string {
|
||||
namespace := ""
|
||||
cmd := exec.Command("kubectl", "config", "current-context")
|
||||
var out, out2 bytes.Buffer
|
||||
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
fmt.Printf("kubectl config current-context %q\n", err)
|
||||
return namespace
|
||||
}
|
||||
if out.String() == "" {
|
||||
fmt.Println("kubectl config current-context does not return anything")
|
||||
return namespace
|
||||
}
|
||||
|
||||
cmd = exec.Command("kubectl", "config", "view", "-o", `jsonpath={.contexts[?(@.name == "`+strings.TrimSpace(out.String())+`")].context.namespace}`)
|
||||
cmd.Stdout = &out2
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
fmt.Printf("kubectl config view failed with %q\n", err)
|
||||
return namespace
|
||||
}
|
||||
namespace = out2.String()
|
||||
return namespace
|
||||
}
|
||||
|
||||
func SetKubectlNamespace(ns string) {
|
||||
cmd := exec.Command("kubectl", "config", "set-context", "--namespace="+ns, "--current")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
fmt.Printf("kubectl config set-context --namespace failed with %q\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func kubectlOperation(manifests string, oprName string, namespace string) error {
|
||||
func kubectlOperation(manifests string, oprName string) error {
|
||||
tempYaml, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
fmt.Println("cannot create file ", err)
|
||||
@@ -68,51 +23,21 @@ func kubectlOperation(manifests string, oprName string, namespace string) error
|
||||
}
|
||||
tempYaml.WriteString(manifests)
|
||||
|
||||
arguments := make([]string, 0)
|
||||
arguments = append(arguments, oprName)
|
||||
arguments = append(arguments, "-f")
|
||||
arguments = append(arguments, tempYaml.Name())
|
||||
|
||||
if oprName == "apply" {
|
||||
arguments = append(arguments, "--validate=false")
|
||||
}
|
||||
if namespace != "" {
|
||||
arguments = append(arguments, "-n")
|
||||
arguments = append(arguments, namespace)
|
||||
}
|
||||
var cmd *exec.Cmd
|
||||
if oprName == "apply" {
|
||||
cmd = exec.Command("kubectl", arguments...)
|
||||
cmd = exec.Command("kubectl", oprName, "-f", tempYaml.Name(), "--validate=false")
|
||||
} else {
|
||||
cmd = exec.Command("kubectl", arguments...)
|
||||
cmd = exec.Command("kubectl", oprName, "-f", tempYaml.Name())
|
||||
}
|
||||
|
||||
sterrBuffer := &bytes.Buffer{}
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = sterrBuffer
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("kubectl %v failed with: %v, %v, temp k8s yaml file:%v\n", oprName, err, sterrBuffer.String(), tempYaml.Name())
|
||||
fmt.Printf("kubectl apply failed with %s\n", err)
|
||||
fmt.Println("temp CRD file: " + tempYaml.Name())
|
||||
return err
|
||||
}
|
||||
os.Remove(tempYaml.Name())
|
||||
return nil
|
||||
}
|
||||
|
||||
func KubectlDirectOps(opr []string, namespace string) (string, error) {
|
||||
arguments := []string{}
|
||||
if namespace != "" {
|
||||
arguments = append(arguments, "-n", namespace)
|
||||
}
|
||||
arguments = append(arguments, opr...)
|
||||
var out bytes.Buffer
|
||||
cmd := exec.Command("kubectl", arguments...)
|
||||
LogDebugMessage("Kubectl command: %s %v\n", "kubectl", arguments)
|
||||
sterrBuffer := &bytes.Buffer{}
|
||||
cmd.Stderr = sterrBuffer
|
||||
cmd.Stdout = &out
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", fmt.Errorf("kubectl %v failed with: %v, %v\n", opr, err, sterrBuffer.String())
|
||||
}
|
||||
s := out.String()
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetKubectlNamespace(t *testing.T) {
|
||||
t.Skip()
|
||||
ns := GetKubectlNamespace()
|
||||
SetKubectlNamespace("tada")
|
||||
got := GetKubectlNamespace()
|
||||
if got != "tada" {
|
||||
t.Log(got)
|
||||
t.Fail()
|
||||
}
|
||||
SetKubectlNamespace(ns)
|
||||
}
|
||||
|
||||
func TestKubectlDirectOps(t *testing.T) {
|
||||
t.Skip()
|
||||
SetKubectlNamespace("test")
|
||||
ns := GetKubectlNamespace()
|
||||
opr := fmt.Sprintf("version")
|
||||
opr1 := strings.Fields(opr)
|
||||
_, err := KubectlDirectOps(opr1, ns)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type PreflightConfig struct {
|
||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
|
||||
Spec *PreflightSpec `json:"spec" yaml:"spec"`
|
||||
QliksenseHomePath string `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
type PreflightSpec struct {
|
||||
MinK8sVersion string `json:"minK8sVersion,omitempty" yaml:"minK8sVersion,omitempty"`
|
||||
Images map[string]string `json:"images,omitempty" yaml:"images,omitempty"`
|
||||
}
|
||||
|
||||
//NewPreflightConfigEmpty create empty PreflightConfig object
|
||||
func NewPreflightConfigEmpty(qHome string) *PreflightConfig {
|
||||
p := &PreflightConfig{
|
||||
QliksenseHomePath: qHome,
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "config.qlik.com/v1",
|
||||
Kind: "PreflightConfig",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "PreflightConfigMetadata",
|
||||
},
|
||||
Spec: &PreflightSpec{},
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
//NewPreflightConfig create empty PreflightConfig object if preflit/preflight-config.yaml not exist
|
||||
func NewPreflightConfig(qHome string) *PreflightConfig {
|
||||
p := NewPreflightConfigEmpty(qHome)
|
||||
conFile := p.GetConfigFilePath()
|
||||
if _, err := os.Lstat(conFile); err != nil {
|
||||
return p
|
||||
}
|
||||
p = &PreflightConfig{
|
||||
QliksenseHomePath: qHome,
|
||||
}
|
||||
if err := ReadFromFile(p, conFile); err != nil {
|
||||
return nil
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
//GetConfigFilePath return preflight-config.yaml file path
|
||||
func (p *PreflightConfig) GetConfigFilePath() string {
|
||||
return filepath.Join(p.QliksenseHomePath, "preflight", "preflight-config.yaml")
|
||||
}
|
||||
|
||||
//Write write PreflightConfig object into the ~/.qliksense/preflight/preflight-config.yaml file
|
||||
func (p *PreflightConfig) Write() error {
|
||||
pDir := filepath.Join(p.QliksenseHomePath, "preflight")
|
||||
if err := os.MkdirAll(pDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
return WriteToFile(p, p.GetConfigFilePath())
|
||||
}
|
||||
|
||||
func (p *PreflightConfig) AddMinK8sV(version string) {
|
||||
if p.Spec == nil {
|
||||
p.Spec = &PreflightSpec{}
|
||||
}
|
||||
p.Spec.MinK8sVersion = version
|
||||
}
|
||||
|
||||
func (p *PreflightConfig) AddImage(imageFor, imageName string) {
|
||||
if p.Spec.Images == nil {
|
||||
p.Spec.Images = make(map[string]string)
|
||||
}
|
||||
p.Spec.Images[imageFor] = imageName
|
||||
}
|
||||
|
||||
func (p *PreflightConfig) GetImageName(imageFor string, accountForImageRegistry bool) (string, error) {
|
||||
if p.Spec.Images == nil {
|
||||
return "", nil
|
||||
}
|
||||
image := p.Spec.Images[imageFor]
|
||||
if accountForImageRegistry {
|
||||
qConfig := NewQConfig(p.QliksenseHomePath)
|
||||
if currentCR, err := qConfig.GetCurrentCR(); err != nil {
|
||||
return "", err
|
||||
} else if imageRegistry := currentCR.Spec.GetImageRegistry(); imageRegistry != "" {
|
||||
imageSegments := strings.Split(image, "/")
|
||||
imageNameAndTag := imageSegments[len(imageSegments)-1]
|
||||
return path.Join(imageRegistry, imageNameAndTag), nil
|
||||
}
|
||||
}
|
||||
return image, nil
|
||||
}
|
||||
func (p *PreflightConfig) GetMinK8sVersion() string {
|
||||
return p.Spec.MinK8sVersion
|
||||
}
|
||||
func (p *PreflightConfig) IsExistOnDisk() bool {
|
||||
if _, err := os.Lstat(p.GetConfigFilePath()); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *PreflightConfig) GetImageMap() map[string]string {
|
||||
return p.Spec.Images
|
||||
}
|
||||
|
||||
func (p *PreflightConfig) Initialize() error {
|
||||
if p.IsExistOnDisk() {
|
||||
return nil
|
||||
}
|
||||
p.AddMinK8sV("1.15")
|
||||
p.AddImage("nginx", "nginx")
|
||||
p.AddImage("netcat", "subfuzion/netcat")
|
||||
p.AddImage("mongo", "mongo")
|
||||
return p.Write()
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Initalize(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
validate func(t *testing.T, tempDir string)
|
||||
}{
|
||||
{
|
||||
name: "without account for imageRegistry",
|
||||
validate: func(t *testing.T, tempDir string) {
|
||||
preflightConfig := NewPreflightConfig(tempDir)
|
||||
imageName, err := preflightConfig.GetImageName("test", false)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if imageName != "testimage" {
|
||||
t.Fatalf("expected image name: testimage, got: %v", imageName)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with account for configured imageRegistry",
|
||||
validate: func(t *testing.T, tempDir string) {
|
||||
registry := "registryFoo"
|
||||
setupQliksenseTestDefaultContext(t, tempDir, fmt.Sprintf(`
|
||||
apiVersion: qlik.com/v1
|
||||
kind: Qliksense
|
||||
metadata:
|
||||
name: qlik-default
|
||||
spec:
|
||||
configs:
|
||||
qliksense:
|
||||
- name: imageRegistry
|
||||
value: %v
|
||||
`, registry))
|
||||
preflightConfig := NewPreflightConfig(tempDir)
|
||||
imageName, err := preflightConfig.GetImageName("test", true)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
expectedImageName := fmt.Sprintf("%v/testimage", registry)
|
||||
if imageName != expectedImageName {
|
||||
t.Fatalf("expected image name: %v, got: %v", expectedImageName, imageName)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with account for un-configured imageRegistry",
|
||||
validate: func(t *testing.T, tempDir string) {
|
||||
setupQliksenseTestDefaultContext(t, tempDir, `
|
||||
apiVersion: qlik.com/v1
|
||||
kind: Qliksense
|
||||
metadata:
|
||||
name: qlik-default
|
||||
spec:
|
||||
configs:
|
||||
qliksense:
|
||||
- name: something
|
||||
value: other
|
||||
`)
|
||||
preflightConfig := NewPreflightConfig(tempDir)
|
||||
imageName, err := preflightConfig.GetImageName("test", true)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
expectedImageName := "testimage"
|
||||
if imageName != expectedImageName {
|
||||
t.Fatalf("expected image name: %v, got: %v", expectedImageName, imageName)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
tempDir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
setupPreflightConfig(t, tempDir)
|
||||
testCase.validate(t, tempDir)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setupPreflightConfig(t *testing.T, tempDir string) {
|
||||
pf := NewPreflightConfig(tempDir)
|
||||
if err := pf.Initialize(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p := &PreflightConfig{
|
||||
QliksenseHomePath: tempDir,
|
||||
}
|
||||
if err := ReadFromFile(p, pf.GetConfigFilePath()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if p.GetMinK8sVersion() != "1.15" {
|
||||
t.Fatalf("expected k8 version: 1.15, but got " + p.GetMinK8sVersion())
|
||||
}
|
||||
p.AddImage("test", "testimage")
|
||||
if err := p.Write(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func setupQliksenseTestDefaultContext(t *testing.T, tmpQlikSenseHome, CR string) {
|
||||
if err := ioutil.WriteFile(path.Join(tmpQlikSenseHome, "config.yaml"), []byte(`
|
||||
apiVersion: config.qlik.com/v1
|
||||
kind: QliksenseConfig
|
||||
metadata:
|
||||
name: QliksenseConfigMetadata
|
||||
spec:
|
||||
contexts:
|
||||
- name: qlik-default
|
||||
crFile: contexts/qlik-default/qlik-default.yaml
|
||||
currentContext: qlik-default
|
||||
`), os.ModePerm); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
defaultContextDir := path.Join(tmpQlikSenseHome, "contexts", "qlik-default")
|
||||
if err := os.MkdirAll(defaultContextDir, os.ModePerm); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(path.Join(defaultContextDir, "qlik-default.yaml"), []byte(CR), os.ModePerm); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,25 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
kapi_config "github.com/qlik-oss/k-apis/pkg/config"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
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"`
|
||||
}
|
||||
|
||||
// QliksenseConfig is exported
|
||||
type QliksenseConfig struct {
|
||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
|
||||
CommonConfig `json:",inline" yaml:",inline"`
|
||||
Spec *ContextSpec `json:"spec" yaml:"spec"`
|
||||
QliksenseHomePath string `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
/*type CommonConfig struct {
|
||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
|
||||
}
|
||||
*/
|
||||
// QliksenseCR is exported
|
||||
type QliksenseCR struct {
|
||||
kapi_config.KApiCr `json:",inline" yaml:",inline"`
|
||||
CommonConfig `json:",inline" yaml:",inline"`
|
||||
Spec *config.CRSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// ContextSpec is exported
|
||||
|
||||
236
pkg/api/utils.go
236
pkg/api/utils.go
@@ -1,26 +1,21 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"compress/gzip"
|
||||
b64 "encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func checkExists(filename string) os.FileInfo {
|
||||
func checkExists(filename string, isFile bool) os.FileInfo {
|
||||
info, err := os.Stat(filename)
|
||||
if os.IsNotExist(err) {
|
||||
if isFile {
|
||||
LogDebugMessage("File does not exist")
|
||||
} else {
|
||||
LogDebugMessage("Dir does not exist")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
LogDebugMessage("File exists")
|
||||
@@ -29,7 +24,7 @@ func checkExists(filename string) os.FileInfo {
|
||||
|
||||
// FileExists checks if a file exists
|
||||
func FileExists(filename string) bool {
|
||||
if fe := checkExists(filename); fe != nil && !fe.IsDir() {
|
||||
if fe := checkExists(filename, true); fe != nil && !fe.IsDir() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -37,7 +32,7 @@ func FileExists(filename string) bool {
|
||||
|
||||
// DirExists checks if a directory exists
|
||||
func DirExists(dirname string) bool {
|
||||
if fe := checkExists(dirname); fe != nil && fe.IsDir() {
|
||||
if fe := checkExists(dirname, false); fe != nil && fe.IsDir() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -57,222 +52,37 @@ func ReadKeys(keyFile string) ([]byte, error) {
|
||||
err = fmt.Errorf("There was an error reading from file: %s, %v", keyFile, err)
|
||||
log.Println(err)
|
||||
return nil, err
|
||||
} else {
|
||||
LogDebugMessage("Read key as byte[]: %+v", keybyteArray)
|
||||
}
|
||||
return keybyteArray, nil
|
||||
}
|
||||
|
||||
// ProcessConfigArgs processes args and returns an service, key, value slice
|
||||
func ProcessConfigArgs(args []string, base64Encoded bool) ([]*ServiceKeyValue, error) {
|
||||
func ProcessConfigArgs(args []string) ([]*ServiceKeyValue, error) {
|
||||
// prepare received args
|
||||
// split args[0] into key and value
|
||||
if len(args) == 0 {
|
||||
err := fmt.Errorf("No args were provided. Please provide args to configure the current context")
|
||||
log.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
notValidErr := fmt.Errorf("Please provide valid args for this command")
|
||||
resultSvcKV := make([]*ServiceKeyValue, len(args))
|
||||
// qliksense.mongodb=somethig
|
||||
re1 := regexp.MustCompile(`(\w{1,})\[name=(\w{1,})\]=("*[\w\-_/:0-9]+"*)`)
|
||||
for i, arg := range args {
|
||||
LogDebugMessage("Arg received: %s", arg)
|
||||
first := strings.SplitN(arg, "=", 2)
|
||||
if len(first) != 2 {
|
||||
return nil, notValidErr
|
||||
}
|
||||
second := strings.SplitN(first[0], ".", 2)
|
||||
if len(second) != 2 {
|
||||
return nil, notValidErr
|
||||
}
|
||||
resultValue := strings.Trim(first[1], "\"")
|
||||
if base64Encoded {
|
||||
if decodeByte, err := b64.StdEncoding.DecodeString(resultValue); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
resultValue = strings.Trim(string(decodeByte), "\n ")
|
||||
}
|
||||
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: second[0],
|
||||
Key: second[1],
|
||||
Value: resultValue,
|
||||
SvcName: result[1],
|
||||
Key: result[2],
|
||||
Value: result[3],
|
||||
}
|
||||
}
|
||||
return resultSvcKV, nil
|
||||
}
|
||||
|
||||
func ExecuteTaskWithBlinkingStdoutFeedback(task func() (interface{}, error), feedback string) (result interface{}, err error) {
|
||||
taskDone := make(chan bool)
|
||||
go func() {
|
||||
result, err = task()
|
||||
taskDone <- true
|
||||
}()
|
||||
progressOnTicker := time.NewTicker(500 * time.Millisecond)
|
||||
progressOffTicker := time.NewTicker(1000 * time.Millisecond)
|
||||
printProgress := func(on bool) {
|
||||
if on {
|
||||
fmt.Printf("%s\r", feedback)
|
||||
} else {
|
||||
fmt.Printf("%s\r", strings.Repeat(" ", len(feedback)))
|
||||
}
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-taskDone:
|
||||
progressOnTicker.Stop()
|
||||
progressOffTicker.Stop()
|
||||
printProgress(false)
|
||||
return result, err
|
||||
case <-progressOnTicker.C:
|
||||
printProgress(true)
|
||||
case <-progressOffTicker.C:
|
||||
printProgress(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func DownloadFile(url, baseFolder, installerName string) error {
|
||||
var (
|
||||
out *os.File
|
||||
err error
|
||||
resp *http.Response
|
||||
)
|
||||
// Create the file
|
||||
fileName := filepath.Join(baseFolder, installerName)
|
||||
LogDebugMessage("Installer Filename: %s\n", fileName)
|
||||
if out, err = os.Create(fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// Get the data
|
||||
if resp, err = http.Get(url); err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err = fmt.Errorf("unable to download the file from URL: %s, status: %s", url, resp.Status)
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the body to file
|
||||
if _, err = io.Copy(out, resp.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Chmod(fileName, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ExplodePackage(destination, fileToUntar string) error {
|
||||
LogDebugMessage("Destination: %s\n", destination)
|
||||
LogDebugMessage("fileToUntar: %s\n", fileToUntar)
|
||||
|
||||
if strings.HasSuffix(fileToUntar, "zip") {
|
||||
LogDebugMessage("This is a windows file : %s", fileToUntar)
|
||||
err := UnZipFile(destination, fileToUntar)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
} else if strings.HasSuffix(fileToUntar, "tar.gz") {
|
||||
LogDebugMessage("This is a mac/linux file: %s", fileToUntar)
|
||||
err := UntarGzFile(destination, fileToUntar)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UntarGzFile(destination, fileToUntar string) error {
|
||||
lFile, err := os.Open(fileToUntar)
|
||||
if err != nil {
|
||||
err = errors.Wrapf(err, "unable to read the local file %s", fileToUntar)
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
|
||||
gzReader, err := gzip.NewReader(lFile)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "unable to load the file into a gz reader")
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
defer gzReader.Close()
|
||||
|
||||
tarReader := tar.NewReader(gzReader)
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
return nil
|
||||
case err != nil:
|
||||
err = errors.Wrap(err, "error during untar")
|
||||
log.Fatal(err)
|
||||
return err
|
||||
case header == nil:
|
||||
continue
|
||||
}
|
||||
|
||||
fileInLoop := filepath.Join(destination, header.Name)
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
if _, err := os.Stat(fileInLoop); err != nil {
|
||||
if err := os.MkdirAll(fileInLoop, 0755); err != nil {
|
||||
err = errors.Wrapf(err, "error creating directory %s", fileInLoop)
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
case tar.TypeReg:
|
||||
fileAtLoc, err := os.OpenFile(fileInLoop, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
|
||||
if err != nil {
|
||||
err = errors.Wrapf(err, "error opening file %s", fileInLoop)
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(fileAtLoc, tarReader); err != nil {
|
||||
err = errors.Wrapf(err, "error writing file %s", fileInLoop)
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
fileAtLoc.Close()
|
||||
fileAtLoc.Chmod(os.ModePerm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func UnZipFile(destination, fileToUnzip string) error {
|
||||
zipReader, _ := zip.OpenReader(fileToUnzip)
|
||||
for _, file := range zipReader.Reader.File {
|
||||
|
||||
zippedFile, err := file.Open()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer zippedFile.Close()
|
||||
extractedFilePath := filepath.Join(
|
||||
destination,
|
||||
file.Name,
|
||||
)
|
||||
outputFile, err := os.OpenFile(
|
||||
extractedFilePath,
|
||||
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
|
||||
file.Mode(),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer outputFile.Close()
|
||||
|
||||
_, err = io.Copy(outputFile, zippedFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
LogDebugMessage("File extracted: %s, Extracted file path: %s\n", file.Name, extractedFilePath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProcessConfigArgs(t *testing.T) {
|
||||
args := []string{
|
||||
"qliksense.mongodb=mongouri://something?ffall",
|
||||
"test_under.test=value_under",
|
||||
"test-dash.dash-key=value-dash",
|
||||
"test-dot.dot-key=127.0.0.1",
|
||||
"test123.key123=value123",
|
||||
"test-equal.keyequal=newvalue=@hj",
|
||||
}
|
||||
expectedKeys := []string{"mongodb", "test", "dash-key", "dot-key", "key123", "keyequal"}
|
||||
expectedValue := []string{"mongouri://something?ffall", "value_under", "value-dash", "127.0.0.1", "value123", "newvalue=@hj"}
|
||||
exppectedSvc := []string{"qliksense", "test_under", "test-dash", "test-dot", "test123", "test-equal"}
|
||||
sv, err := ProcessConfigArgs(args, false)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
for _, v := range sv {
|
||||
if !contains(expectedKeys, v.Key) {
|
||||
t.Fail()
|
||||
t.Log("expectd key " + v.Key + " not found")
|
||||
}
|
||||
if !contains(expectedValue, v.Value) {
|
||||
t.Fail()
|
||||
t.Log("expectd Value " + v.Value + " not found")
|
||||
}
|
||||
if !contains(exppectedSvc, v.SvcName) {
|
||||
t.Fail()
|
||||
t.Log("expectd service " + v.SvcName + " not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func contains(arr []string, str string) bool {
|
||||
for _, a := range arr {
|
||||
if a == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package preflight
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (qp *QliksensePreflight) RunAllPreflightChecks(namespace string, kubeConfigContents []byte, mongodbUrl string) {
|
||||
|
||||
checkCount := 0
|
||||
totalCount := 0
|
||||
// Preflight minimum kuberenetes version check
|
||||
fmt.Printf("\nPreflight kubernetes minimum version check\n")
|
||||
fmt.Println("------------------------------------------")
|
||||
if err := qp.CheckK8sVersion(namespace, kubeConfigContents); err != nil {
|
||||
fmt.Printf("Preflight kubernetes minimum version check: FAILED\n")
|
||||
} else {
|
||||
checkCount++
|
||||
}
|
||||
totalCount++
|
||||
|
||||
// Preflight deployment check
|
||||
fmt.Printf("\nPreflight deployment check\n")
|
||||
fmt.Println("--------------------------")
|
||||
if err := qp.CheckDeployment(namespace, kubeConfigContents); err != nil {
|
||||
fmt.Printf("Preflight deployment check: FAILED\n")
|
||||
} else {
|
||||
checkCount++
|
||||
}
|
||||
totalCount++
|
||||
|
||||
// Preflight service check
|
||||
fmt.Printf("\nPreflight service check\n")
|
||||
fmt.Println("-----------------------")
|
||||
if err := qp.CheckService(namespace, kubeConfigContents); err != nil {
|
||||
fmt.Printf("Preflight service check: FAILED\n")
|
||||
} else {
|
||||
checkCount++
|
||||
}
|
||||
totalCount++
|
||||
|
||||
// Preflight pod check
|
||||
fmt.Printf("\nPreflight pod check\n")
|
||||
fmt.Println("-----------------------")
|
||||
if err := qp.CheckPod(namespace, kubeConfigContents); err != nil {
|
||||
fmt.Printf("Preflight pod check: FAILED\n")
|
||||
} else {
|
||||
checkCount++
|
||||
}
|
||||
totalCount++
|
||||
|
||||
// Preflight role check
|
||||
fmt.Printf("\nPreflight role check\n")
|
||||
fmt.Println("--------------------------")
|
||||
if err := qp.CheckCreateRole(namespace); err != nil {
|
||||
fmt.Printf("Preflight role check: FAILED\n")
|
||||
} else {
|
||||
checkCount++
|
||||
}
|
||||
totalCount++
|
||||
|
||||
// Preflight rolebinding check
|
||||
fmt.Printf("\nPreflight rolebinding check\n")
|
||||
fmt.Println("---------------------------------")
|
||||
if err := qp.CheckCreateRoleBinding(namespace); err != nil {
|
||||
fmt.Printf("Preflight rolebinding check: FAILED\n")
|
||||
} else {
|
||||
checkCount++
|
||||
}
|
||||
totalCount++
|
||||
|
||||
// Preflight serviceaccount check
|
||||
fmt.Printf("\nPreflight serviceaccount check\n")
|
||||
fmt.Println("------------------------------------")
|
||||
if err := qp.CheckCreateServiceAccount(namespace); err != nil {
|
||||
fmt.Printf("Preflight serviceaccount check: FAILED\n")
|
||||
} else {
|
||||
checkCount++
|
||||
}
|
||||
totalCount++
|
||||
|
||||
// Preflight mongo check
|
||||
fmt.Printf("\nPreflight mongo check\n")
|
||||
fmt.Println("---------------------")
|
||||
if err := qp.CheckMongo(kubeConfigContents, namespace, mongodbUrl); err != nil {
|
||||
fmt.Printf("Preflight mongo check: FAILED\n")
|
||||
} else {
|
||||
checkCount++
|
||||
}
|
||||
totalCount++
|
||||
|
||||
// Preflight DNS check
|
||||
fmt.Printf("\nPreflight DNS check\n")
|
||||
fmt.Println("-------------------")
|
||||
if err := qp.CheckDns(namespace, kubeConfigContents); err != nil {
|
||||
fmt.Printf("Preflight DNS check: FAILED\n")
|
||||
} else {
|
||||
checkCount++
|
||||
}
|
||||
totalCount++
|
||||
|
||||
if checkCount == totalCount {
|
||||
fmt.Printf("\nAll preflight checks have PASSED\n")
|
||||
} else {
|
||||
fmt.Printf("\n1 or more preflight checks have FAILED\n")
|
||||
}
|
||||
fmt.Println("Completed running all preflight checks")
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
package preflight
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
func (qp *QliksensePreflight) CheckDeployment(namespace string, kubeConfigContents []byte) error {
|
||||
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Kube config error: %v\n", err)
|
||||
fmt.Print(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Deployment check
|
||||
fmt.Printf("Preflight deployment check: \n")
|
||||
err = qp.checkPfDeployment(clientset, namespace, "deployment-preflight-check")
|
||||
if err != nil {
|
||||
fmt.Println("Preflight Deployment check: FAILED")
|
||||
return err
|
||||
}
|
||||
fmt.Println("Completed preflight deployment check")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) CheckService(namespace string, kubeConfigContents []byte) error {
|
||||
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error: unable to create a kubernetes client: %v\n", err)
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
// Service check
|
||||
fmt.Printf("\nPreflight service check: \n")
|
||||
err = checkPfService(clientset, namespace)
|
||||
if err != nil {
|
||||
fmt.Println("Preflight Service check: FAILED")
|
||||
return err
|
||||
}
|
||||
fmt.Println("Completed preflight service check")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) CheckPod(namespace string, kubeConfigContents []byte) error {
|
||||
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error: unable to create a kubernetes client: %v\n", err)
|
||||
fmt.Print(err)
|
||||
return err
|
||||
}
|
||||
// Pod check
|
||||
fmt.Printf("\nPreflight pod check: \n")
|
||||
|
||||
err = qp.checkPfPod(clientset, namespace)
|
||||
if err != nil {
|
||||
fmt.Println("Preflight Pod check: FAILED")
|
||||
return err
|
||||
}
|
||||
fmt.Println("Completed preflight pod check")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) checkPfPod(clientset *kubernetes.Clientset, namespace string) error {
|
||||
// create a pod
|
||||
podName := "pod-pf-check"
|
||||
commandToRun := []string{}
|
||||
|
||||
imageName, err := qp.GetPreflightConfigObj().GetImageName(nginx, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pod, err := createPreflightTestPod(clientset, namespace, podName, imageName, commandToRun)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error: unable to create pod %s - %v\n", podName, err)
|
||||
return err
|
||||
}
|
||||
defer deletePod(clientset, namespace, podName)
|
||||
|
||||
if err := waitForPod(clientset, namespace, pod); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("Preflight pod creation check: PASSED")
|
||||
fmt.Println("Cleaning up resources...")
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPfService(clientset *kubernetes.Clientset, namespace string) error {
|
||||
// creating service
|
||||
serviceName := "svc-pf-check"
|
||||
pfService, err := createPreflightTestService(clientset, namespace, serviceName)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error: unable to create service : %s\n", serviceName)
|
||||
return err
|
||||
}
|
||||
defer deleteService(clientset, namespace, serviceName)
|
||||
_, err = getService(clientset, namespace, pfService.GetName())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error: unable to retrieve service: %s\n", serviceName)
|
||||
return err
|
||||
}
|
||||
fmt.Println("Preflight service creation check: PASSED")
|
||||
fmt.Println("Cleaning up resources...")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) checkPfDeployment(clientset *kubernetes.Clientset, namespace, depName string) error {
|
||||
// check if we are able to create a deployment
|
||||
imageName, err := qp.GetPreflightConfigObj().GetImageName(nginx, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pfDeployment, err := createPreflightTestDeployment(clientset, namespace, depName, imageName)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error: unable to create deployment: %v\n", err)
|
||||
return err
|
||||
}
|
||||
defer deleteDeployment(clientset, namespace, depName)
|
||||
if err := waitForDeployment(clientset, namespace, pfDeployment); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Preflight Deployment check: PASSED")
|
||||
fmt.Println("Cleaning up resources...")
|
||||
return nil
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package preflight
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
nginx = "nginx"
|
||||
netcat = "netcat"
|
||||
)
|
||||
|
||||
func (qp *QliksensePreflight) CheckDns(namespace string, kubeConfigContents []byte) error {
|
||||
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error: unable to create a kubernetes client: %v\n", err)
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// creating deployment
|
||||
depName := "dep-dns-preflight-check"
|
||||
nginxImageName, err := qp.GetPreflightConfigObj().GetImageName(nginx, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dnsDeployment, err := createPreflightTestDeployment(clientset, namespace, depName, nginxImageName)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error: unable to create deployment: %v\n", err)
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
defer deleteDeployment(clientset, namespace, depName)
|
||||
|
||||
if err := waitForDeployment(clientset, namespace, dnsDeployment); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// creating service
|
||||
serviceName := "svc-dns-pf-check"
|
||||
dnsService, err := createPreflightTestService(clientset, namespace, serviceName)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error: unable to create service : %s\n", serviceName)
|
||||
return err
|
||||
}
|
||||
defer deleteService(clientset, namespace, serviceName)
|
||||
|
||||
// create a pod
|
||||
podName := "pf-pod-1"
|
||||
commandToRun := []string{"sh", "-c", "sleep 10; nc -z -v -w 1 " + dnsService.Name + " 80"}
|
||||
netcatImageName, err := qp.GetPreflightConfigObj().GetImageName(netcat, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dnsPod, err := createPreflightTestPod(clientset, namespace, podName, netcatImageName, commandToRun)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error: unable to create pod : %s\n", podName)
|
||||
return err
|
||||
}
|
||||
|
||||
defer deletePod(clientset, namespace, podName)
|
||||
|
||||
if err := waitForPod(clientset, namespace, dnsPod); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(dnsPod.Spec.Containers) == 0 {
|
||||
err := fmt.Errorf("error: there are no containers in the pod")
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
waitForPodToDie(clientset, namespace, dnsPod)
|
||||
|
||||
logStr, err := getPodLogs(clientset, dnsPod)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error: unable to execute dns check in the cluster: %v", err)
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.HasSuffix(strings.TrimSpace(logStr), "succeeded!") {
|
||||
fmt.Println("Preflight DNS check: PASSED")
|
||||
} else {
|
||||
err = fmt.Errorf("Expected response not found\n")
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("Completed preflight DNS check")
|
||||
fmt.Println("Cleaning up resources...")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package preflight
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
const (
|
||||
mongo = "mongo"
|
||||
)
|
||||
|
||||
func (qp *QliksensePreflight) CheckMongo(kubeConfigContents []byte, namespace, mongodbUrl string) error {
|
||||
fmt.Printf("Preflight mongodb check: \n")
|
||||
|
||||
if mongodbUrl == "" {
|
||||
// infer mongoDbUrl from currentCR
|
||||
fmt.Println("MongoDbUri is empty, infer from CR")
|
||||
qConfig := qapi.NewQConfig(qp.Q.QliksenseHome)
|
||||
var currentCR *qapi.QliksenseCR
|
||||
|
||||
var err error
|
||||
qConfig.SetNamespace(namespace)
|
||||
currentCR, err = qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
fmt.Printf("Unable to retrieve current CR: %v\n", err)
|
||||
return err
|
||||
}
|
||||
decryptedCR, err := qConfig.GetDecryptedCr(currentCR)
|
||||
mongodbUrl = decryptedCR.Spec.GetFromSecrets("qliksense", "mongoDbUri")
|
||||
}
|
||||
|
||||
fmt.Printf("mongodbUrl: %s\n", mongodbUrl)
|
||||
if err := qp.mongoConnCheck(kubeConfigContents, namespace, mongodbUrl); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Completed preflight mongodb check")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) mongoConnCheck(kubeConfigContents []byte, namespace, mongodbUrl string) error {
|
||||
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error: unable to create a kubernetes client: %v\n", err)
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
// create a pod
|
||||
podName := "pf-mongo-pod"
|
||||
commandToRun := []string{"sh", "-c", "sleep 10;mongo " + mongodbUrl}
|
||||
imageName, err := qp.GetPreflightConfigObj().GetImageName(mongo, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mongoPod, err := createPreflightTestPod(clientset, namespace, podName, imageName, commandToRun)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error: unable to create pod : %v\n", err)
|
||||
return err
|
||||
}
|
||||
defer deletePod(clientset, namespace, podName)
|
||||
|
||||
if err := waitForPod(clientset, namespace, mongoPod); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(mongoPod.Spec.Containers) == 0 {
|
||||
err := fmt.Errorf("error: there are no containers in the pod- %v\n", err)
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
waitForPodToDie(clientset, namespace, mongoPod)
|
||||
logStr, err := getPodLogs(clientset, mongoPod)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error: unable to execute mongo check in the cluster: %v\n", err)
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
stringToCheck := "Implicit session:"
|
||||
if strings.Contains(logStr, stringToCheck) {
|
||||
fmt.Println("Preflight mongo check: PASSED")
|
||||
} else {
|
||||
err = fmt.Errorf("Expected response not found\n")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,645 +0,0 @@
|
||||
package preflight
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/rbac/v1beta1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/client-go/util/retry"
|
||||
)
|
||||
|
||||
var gracePeriod int64 = 0
|
||||
|
||||
type QliksensePreflight struct {
|
||||
Q *qliksense.Qliksense
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) GetPreflightConfigObj() *api.PreflightConfig {
|
||||
return api.NewPreflightConfig(qp.Q.QliksenseHome)
|
||||
}
|
||||
|
||||
func InitPreflight() (string, []byte, error) {
|
||||
api.LogDebugMessage("Reading .kube/config file...")
|
||||
|
||||
homeDir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Unable to deduce home dir\n")
|
||||
return "", nil, err
|
||||
}
|
||||
api.LogDebugMessage("Kube config location: %s\n\n", filepath.Join(homeDir, ".kube", "config"))
|
||||
|
||||
kubeConfig := filepath.Join(homeDir, ".kube", "config")
|
||||
kubeConfigContents, err := ioutil.ReadFile(kubeConfig)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Unable to deduce home dir\n")
|
||||
return "", nil, err
|
||||
}
|
||||
// retrieve namespace
|
||||
namespace := api.GetKubectlNamespace()
|
||||
// if namespace comes back empty, we will run checks in the default namespace
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
api.LogDebugMessage("Namespace: %s\n", namespace)
|
||||
return namespace, kubeConfigContents, nil
|
||||
}
|
||||
|
||||
func initiateK8sOps(opr, namespace string) error {
|
||||
opr1 := strings.Fields(opr)
|
||||
_, err := api.KubectlDirectOps(opr1, namespace)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func int32Ptr(i int32) *int32 { return &i }
|
||||
|
||||
func retryOnError(mf func() error) error {
|
||||
return retry.OnError(wait.Backoff{
|
||||
Duration: 1 * time.Second,
|
||||
Factor: 1,
|
||||
Jitter: 0.1,
|
||||
Steps: 5,
|
||||
}, func(err error) bool {
|
||||
return k8serrors.IsConflict(err) || k8serrors.IsGone(err) || k8serrors.IsServerTimeout(err) ||
|
||||
k8serrors.IsServiceUnavailable(err) || k8serrors.IsTimeout(err) || k8serrors.IsTooManyRequests(err)
|
||||
}, mf)
|
||||
}
|
||||
|
||||
func getK8SClientSet(kubeconfig []byte, contextName string) (*kubernetes.Clientset, *rest.Config, error) {
|
||||
var clientConfig *rest.Config
|
||||
var err error
|
||||
if len(kubeconfig) == 0 {
|
||||
clientConfig, err = rest.InClusterConfig()
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "Unable to load in-cluster kubeconfig")
|
||||
fmt.Println(err)
|
||||
return nil, nil, err
|
||||
}
|
||||
} else {
|
||||
config, err := clientcmd.Load(kubeconfig)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "Unable to load kubeconfig")
|
||||
fmt.Println(err)
|
||||
return nil, nil, err
|
||||
}
|
||||
if contextName != "" {
|
||||
config.CurrentContext = contextName
|
||||
}
|
||||
clientConfig, err = clientcmd.NewDefaultClientConfig(*config, &clientcmd.ConfigOverrides{}).ClientConfig()
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "Unable to create client config from config")
|
||||
fmt.Println(err)
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
clientset, err := kubernetes.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "Unable to create clientset")
|
||||
fmt.Println(err)
|
||||
return nil, nil, err
|
||||
}
|
||||
return clientset, clientConfig, nil
|
||||
}
|
||||
|
||||
func createPreflightTestDeployment(clientset *kubernetes.Clientset, namespace string, depName string, imageName string) (*appsv1.Deployment, error) {
|
||||
deploymentsClient := clientset.AppsV1().Deployments(namespace)
|
||||
deployment := &appsv1.Deployment{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: depName,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Replicas: int32Ptr(1),
|
||||
Selector: &v1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"app": "preflight-check",
|
||||
},
|
||||
},
|
||||
Template: apiv1.PodTemplateSpec{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app": "preflight-check",
|
||||
"label": "preflight-check-label",
|
||||
},
|
||||
},
|
||||
Spec: apiv1.PodSpec{
|
||||
Containers: []apiv1.Container{
|
||||
{
|
||||
Name: "dep",
|
||||
Image: imageName,
|
||||
Ports: []apiv1.ContainerPort{
|
||||
{
|
||||
Name: "http",
|
||||
Protocol: apiv1.ProtocolTCP,
|
||||
ContainerPort: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create Deployment
|
||||
var result *appsv1.Deployment
|
||||
if err := retryOnError(func() (err error) {
|
||||
result, err = deploymentsClient.Create(deployment)
|
||||
return err
|
||||
}); err != nil {
|
||||
err = errors.Wrapf(err, "error: unable to create deployments in the %s namespace", namespace)
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
fmt.Printf("Created deployment %q\n", result.GetObjectMeta().GetName())
|
||||
|
||||
return deployment, nil
|
||||
}
|
||||
|
||||
func getDeployment(clientset *kubernetes.Clientset, namespace, depName string) (*appsv1.Deployment, error) {
|
||||
deploymentsClient := clientset.AppsV1().Deployments(namespace)
|
||||
var deployment *appsv1.Deployment
|
||||
if err := retryOnError(func() (err error) {
|
||||
deployment, err = deploymentsClient.Get(depName, v1.GetOptions{})
|
||||
return err
|
||||
}); err != nil {
|
||||
err = errors.Wrapf(err, "error: unable to get deployments in the %s namespace", namespace)
|
||||
api.LogDebugMessage("%v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
return deployment, nil
|
||||
}
|
||||
|
||||
func deleteDeployment(clientset *kubernetes.Clientset, namespace, name string) error {
|
||||
deploymentsClient := clientset.AppsV1().Deployments(namespace)
|
||||
// Create Deployment
|
||||
deletePolicy := v1.DeletePropagationForeground
|
||||
deleteOptions := v1.DeleteOptions{
|
||||
PropagationPolicy: &deletePolicy,
|
||||
GracePeriodSeconds: &gracePeriod,
|
||||
}
|
||||
|
||||
if err := retryOnError(func() (err error) {
|
||||
return deploymentsClient.Delete(name, &deleteOptions)
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
if err := waitForDeploymentToDelete(clientset, namespace, name); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Deleted deployment: %s\n", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func createPreflightTestService(clientset *kubernetes.Clientset, namespace string, svcName string) (*apiv1.Service, error) {
|
||||
iptr := int32Ptr(80)
|
||||
servicesClient := clientset.CoreV1().Services(namespace)
|
||||
service := &apiv1.Service{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: svcName,
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{
|
||||
"app": "preflight-check",
|
||||
},
|
||||
},
|
||||
Spec: apiv1.ServiceSpec{
|
||||
Ports: []apiv1.ServicePort{
|
||||
{Name: "port1",
|
||||
Port: *iptr,
|
||||
},
|
||||
},
|
||||
Selector: map[string]string{
|
||||
"app": "preflight-check",
|
||||
},
|
||||
ClusterIP: "",
|
||||
},
|
||||
}
|
||||
var result *apiv1.Service
|
||||
if err := retryOnError(func() (err error) {
|
||||
result, err = servicesClient.Create(service)
|
||||
return err
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
fmt.Printf("Created service %q\n", result.GetObjectMeta().GetName())
|
||||
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func getService(clientset *kubernetes.Clientset, namespace, svcName string) (*apiv1.Service, error) {
|
||||
servicesClient := clientset.CoreV1().Services(namespace)
|
||||
var svc *apiv1.Service
|
||||
if err := retryOnError(func() (err error) {
|
||||
svc, err = servicesClient.Get(svcName, v1.GetOptions{})
|
||||
return err
|
||||
}); err != nil {
|
||||
err = errors.Wrapf(err, "unable to get services in the %s namespace", namespace)
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
func deleteService(clientset *kubernetes.Clientset, namespace, name string) error {
|
||||
servicesClient := clientset.CoreV1().Services(namespace)
|
||||
// Create Deployment
|
||||
deletePolicy := v1.DeletePropagationForeground
|
||||
deleteOptions := v1.DeleteOptions{
|
||||
PropagationPolicy: &deletePolicy,
|
||||
}
|
||||
if err := retryOnError(func() (err error) {
|
||||
return servicesClient.Delete(name, &deleteOptions)
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Deleted service: %s\n", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func deletePod(clientset *kubernetes.Clientset, namespace, name string) error {
|
||||
|
||||
podsClient := clientset.CoreV1().Pods(namespace)
|
||||
deletePolicy := v1.DeletePropagationForeground
|
||||
deleteOptions := v1.DeleteOptions{
|
||||
PropagationPolicy: &deletePolicy,
|
||||
GracePeriodSeconds: &gracePeriod,
|
||||
}
|
||||
if err := retryOnError(func() (err error) {
|
||||
return podsClient.Delete(name, &deleteOptions)
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
if err := waitForPodToDelete(clientset, namespace, name); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Deleted pod: %s\n", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func createPreflightTestPod(clientset *kubernetes.Clientset, namespace string, podName string, imageName string, commandToRun []string) (*apiv1.Pod, error) {
|
||||
// build the pod definition we want to deploy
|
||||
pod := &apiv1.Pod{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: podName,
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{
|
||||
"app": "preflight",
|
||||
},
|
||||
},
|
||||
Spec: apiv1.PodSpec{
|
||||
RestartPolicy: apiv1.RestartPolicyNever,
|
||||
Containers: []apiv1.Container{
|
||||
{
|
||||
Name: "cnt",
|
||||
Image: imageName,
|
||||
ImagePullPolicy: apiv1.PullIfNotPresent,
|
||||
Command: commandToRun,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// now create the pod in kubernetes cluster using the clientset
|
||||
if err := retryOnError(func() (err error) {
|
||||
pod, err = clientset.CoreV1().Pods(namespace).Create(pod)
|
||||
return err
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
fmt.Printf("Created pod: %s\n", pod.Name)
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
func getPod(clientset *kubernetes.Clientset, namespace, podName string) (*apiv1.Pod, error) {
|
||||
api.LogDebugMessage("Fetching pod: %s\n", podName)
|
||||
var pod *apiv1.Pod
|
||||
if err := retryOnError(func() (err error) {
|
||||
pod, err = clientset.CoreV1().Pods(namespace).Get(podName, v1.GetOptions{})
|
||||
return err
|
||||
}); err != nil {
|
||||
api.LogDebugMessage("%v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
func getPodLogs(clientset *kubernetes.Clientset, pod *apiv1.Pod) (string, error) {
|
||||
podLogOpts := apiv1.PodLogOptions{}
|
||||
|
||||
api.LogDebugMessage("Retrieving logs for pod: %s namespace: %s\n", pod.GetName(), pod.Namespace)
|
||||
req := clientset.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &podLogOpts)
|
||||
podLogs, err := req.Stream()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer podLogs.Close()
|
||||
time.Sleep(15 * time.Second)
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = io.Copy(buf, podLogs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
api.LogDebugMessage("Log from pod: %s\n", buf.String())
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func waitForResource(checkFunc func() (interface{}, error), validateFunc func(interface{}) bool) error {
|
||||
timeout := time.NewTicker(2 * time.Minute)
|
||||
defer timeout.Stop()
|
||||
OUT:
|
||||
for {
|
||||
r, err := checkFunc()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-timeout.C:
|
||||
break OUT
|
||||
default:
|
||||
if validateFunc(r) {
|
||||
break OUT
|
||||
}
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForDeployment(clientset *kubernetes.Clientset, namespace string, pfDeployment *appsv1.Deployment) error {
|
||||
var err error
|
||||
depName := pfDeployment.GetName()
|
||||
checkFunc := func() (interface{}, error) {
|
||||
pfDeployment, err = getDeployment(clientset, namespace, depName)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error: unable to retrieve deployment: %s\n", depName)
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
return pfDeployment, nil
|
||||
}
|
||||
validateFunc := func(data interface{}) bool {
|
||||
d := data.(*appsv1.Deployment)
|
||||
return int(d.Status.ReadyReplicas) > 0
|
||||
}
|
||||
if err := waitForResource(checkFunc, validateFunc); err != nil {
|
||||
return err
|
||||
}
|
||||
if int(pfDeployment.Status.ReadyReplicas) == 0 {
|
||||
err = fmt.Errorf("error: deployment took longer than expected to spin up pods")
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForPod(clientset *kubernetes.Clientset, namespace string, pod *apiv1.Pod) error {
|
||||
var err error
|
||||
if len(pod.Spec.Containers) == 0 {
|
||||
err = fmt.Errorf("error: there are no containers in the pod")
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
podName := pod.Name
|
||||
checkFunc := func() (interface{}, error) {
|
||||
pod, err = getPod(clientset, namespace, podName)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error: unable to retrieve %s pod by name", podName)
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
return pod, nil
|
||||
}
|
||||
validateFunc := func(data interface{}) bool {
|
||||
po := data.(*apiv1.Pod)
|
||||
return len(po.Status.ContainerStatuses) > 0 && po.Status.ContainerStatuses[0].Ready
|
||||
}
|
||||
|
||||
if err := waitForResource(checkFunc, validateFunc); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(pod.Status.ContainerStatuses) == 0 || !pod.Status.ContainerStatuses[0].Ready {
|
||||
err = fmt.Errorf("error: container is taking much longer than expected")
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForPodToDie(clientset *kubernetes.Clientset, namespace string, pod *apiv1.Pod) error {
|
||||
podName := pod.Name
|
||||
checkFunc := func() (interface{}, error) {
|
||||
po, err := getPod(clientset, namespace, podName)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error: unable to retrieve %s pod by name", podName)
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
api.LogDebugMessage("pod status: %v\n", po.Status.Phase)
|
||||
return po, nil
|
||||
}
|
||||
validateFunc := func(r interface{}) bool {
|
||||
po := r.(*apiv1.Pod)
|
||||
return po.Status.Phase == apiv1.PodFailed || po.Status.Phase == apiv1.PodSucceeded
|
||||
}
|
||||
if err := waitForResource(checkFunc, validateFunc); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForPodToDelete(clientset *kubernetes.Clientset, namespace, podName string) error {
|
||||
checkFunc := func() (interface{}, error) {
|
||||
po, err := getPod(clientset, namespace, podName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return po, nil
|
||||
}
|
||||
validateFunc := func(po interface{}) bool {
|
||||
return false
|
||||
}
|
||||
if err := waitForResource(checkFunc, validateFunc); err != nil {
|
||||
return nil
|
||||
}
|
||||
err := fmt.Errorf("error: delete pod is taking unusually long")
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
func waitForDeploymentToDelete(clientset *kubernetes.Clientset, namespace, deploymentName string) error {
|
||||
checkFunc := func() (interface{}, error) {
|
||||
dep, err := getDeployment(clientset, namespace, deploymentName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dep, nil
|
||||
}
|
||||
validateFunc := func(po interface{}) bool {
|
||||
return false
|
||||
}
|
||||
if err := waitForResource(checkFunc, validateFunc); err != nil {
|
||||
return nil
|
||||
}
|
||||
err := fmt.Errorf("error: delete deployment is taking unusually long")
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
func createPfRole(clientset *kubernetes.Clientset, namespace, roleName string) (*v1beta1.Role, error) {
|
||||
// build the role defination we want to create
|
||||
var role *v1beta1.Role
|
||||
roleSpec := &v1beta1.Role{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: roleName,
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{
|
||||
"app": "preflight",
|
||||
},
|
||||
},
|
||||
Rules: []v1beta1.PolicyRule{},
|
||||
}
|
||||
|
||||
// now create the role in kubernetes cluster using the clientset
|
||||
if err := retryOnError(func() (err error) {
|
||||
role, err = clientset.RbacV1beta1().Roles(namespace).Create(roleSpec)
|
||||
return err
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Printf("Created role: %s\n", role.Name)
|
||||
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func deleteRole(clientset *kubernetes.Clientset, namespace string, role *v1beta1.Role) {
|
||||
rolesClient := clientset.RbacV1beta1().Roles(namespace)
|
||||
|
||||
deletePolicy := v1.DeletePropagationForeground
|
||||
deleteOptions := v1.DeleteOptions{
|
||||
PropagationPolicy: &deletePolicy,
|
||||
}
|
||||
err := rolesClient.Delete(role.GetName(), &deleteOptions)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Deleted role: %s\n\n", role.Name)
|
||||
}
|
||||
|
||||
func createPfRoleBinding(clientset *kubernetes.Clientset, namespace, roleBindingName string) (*v1beta1.RoleBinding, error) {
|
||||
var roleBinding *v1beta1.RoleBinding
|
||||
// build the rolebinding defination we want to create
|
||||
roleBindingSpec := &v1beta1.RoleBinding{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: roleBindingName,
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{
|
||||
"app": "demo",
|
||||
},
|
||||
},
|
||||
Subjects: []v1beta1.Subject{
|
||||
{
|
||||
Kind: "ServiceAccount",
|
||||
APIGroup: "",
|
||||
Name: "preflight-check-subject",
|
||||
Namespace: namespace,
|
||||
},
|
||||
},
|
||||
RoleRef: v1beta1.RoleRef{
|
||||
APIGroup: "",
|
||||
Kind: "Role",
|
||||
Name: "preflight-check-roleref",
|
||||
},
|
||||
}
|
||||
|
||||
// now create the roleBinding in kubernetes cluster using the clientset
|
||||
if err := retryOnError(func() (err error) {
|
||||
roleBinding, err = clientset.RbacV1beta1().RoleBindings(namespace).Create(roleBindingSpec)
|
||||
return err
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
fmt.Printf("Created RoleBinding: %s\n", roleBindingSpec.Name)
|
||||
return roleBinding, nil
|
||||
}
|
||||
|
||||
func deleteRoleBinding(clientset *kubernetes.Clientset, namespace string, roleBinding *v1beta1.RoleBinding) {
|
||||
roleBindingClient := clientset.RbacV1beta1().RoleBindings(namespace)
|
||||
|
||||
deletePolicy := v1.DeletePropagationForeground
|
||||
deleteOptions := v1.DeleteOptions{
|
||||
PropagationPolicy: &deletePolicy,
|
||||
}
|
||||
err := roleBindingClient.Delete(roleBinding.GetName(), &deleteOptions)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Deleted RoleBinding: %s\n\n", roleBinding.Name)
|
||||
}
|
||||
|
||||
func createPfServiceAccount(clientset *kubernetes.Clientset, namespace, serviceAccountName string) (*apiv1.ServiceAccount, error) {
|
||||
var serviceAccount *apiv1.ServiceAccount
|
||||
// build the serviceAccount defination we want to create
|
||||
serviceAccountSpec := &apiv1.ServiceAccount{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "preflight-check-test-serviceaccount",
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{
|
||||
"app": "demo",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// now create the serviceAccount in kubernetes cluster using the clientset
|
||||
if err := retryOnError(func() (err error) {
|
||||
serviceAccount, err = clientset.CoreV1().ServiceAccounts(namespace).Create(serviceAccountSpec)
|
||||
return err
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
fmt.Printf("Created Service Account: %s\n", serviceAccountSpec.Name)
|
||||
return serviceAccount, nil
|
||||
}
|
||||
|
||||
func deleteServiceAccount(clientset *kubernetes.Clientset, namespace string, serviceAccount *apiv1.ServiceAccount) {
|
||||
serviceAccountClient := clientset.CoreV1().ServiceAccounts(namespace)
|
||||
|
||||
deletePolicy := v1.DeletePropagationForeground
|
||||
deleteOptions := v1.DeleteOptions{
|
||||
PropagationPolicy: &deletePolicy,
|
||||
}
|
||||
err := serviceAccountClient.Delete(serviceAccount.GetName(), &deleteOptions)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Deleted ServiceAccount: %s\n\n", serviceAccount.Name)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package preflight
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_initiateK8sOps(t *testing.T) {
|
||||
t.Skip()
|
||||
type args struct {
|
||||
opr string
|
||||
namespace string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid case",
|
||||
args: args{
|
||||
opr: fmt.Sprintf("version"),
|
||||
namespace: "test-ns",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid case",
|
||||
args: args{
|
||||
opr: fmt.Sprintf("versions"),
|
||||
namespace: "test-ns",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := initiateK8sOps(tt.args.opr, tt.args.namespace); (err != nil) != tt.wantErr {
|
||||
t.Errorf("initiateK8sOps() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
package preflight
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
)
|
||||
|
||||
var resultYamlBytes = []byte("")
|
||||
|
||||
func (qp *QliksensePreflight) CheckCreateRole(namespace string) error {
|
||||
// create a Role
|
||||
fmt.Printf("Preflight role check: \n")
|
||||
err := qp.checkCreateEntity(namespace, "Role")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Completed preflight role check")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) CheckCreateRoleBinding(namespace string) error {
|
||||
// create a RoleBinding
|
||||
fmt.Printf("Preflight rolebinding check: \n")
|
||||
err := qp.checkCreateEntity(namespace, "RoleBinding")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Completed preflight rolebinding check")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) CheckCreateServiceAccount(namespace string) error {
|
||||
// create a service account
|
||||
fmt.Printf("Preflight serviceaccount check: \n")
|
||||
err := qp.checkCreateEntity(namespace, "ServiceAccount")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Completed preflight serviceaccount check")
|
||||
return nil
|
||||
}
|
||||
func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string) error {
|
||||
qConfig := qapi.NewQConfig(qp.Q.QliksenseHome)
|
||||
var currentCR *qapi.QliksenseCR
|
||||
mfroot := ""
|
||||
kusDir := ""
|
||||
var err error
|
||||
currentCR, err = qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
fmt.Printf("Unable to retrieve current CR: %v\n", err)
|
||||
return err
|
||||
}
|
||||
if currentCR.IsRepoExist() {
|
||||
mfroot = currentCR.Spec.GetManifestsRoot()
|
||||
} else if tempDownloadedDir, err := qliksense.DownloadFromGitRepoToTmpDir(qliksense.QLIK_GIT_REPO, "master"); err != nil {
|
||||
fmt.Printf("Unable to Download from git repo to tmp dir: %v\n", err)
|
||||
return err
|
||||
} else {
|
||||
mfroot = tempDownloadedDir
|
||||
}
|
||||
|
||||
if currentCR.Spec.Profile == "" {
|
||||
kusDir = filepath.Join(mfroot, "manifests", "docker-desktop")
|
||||
} else {
|
||||
kusDir = filepath.Join(mfroot, "manifests", currentCR.Spec.Profile)
|
||||
}
|
||||
if len(resultYamlBytes) == 0 {
|
||||
resultYamlBytes, err = qliksense.ExecuteKustomizeBuild(kusDir)
|
||||
if err != nil {
|
||||
fmt.Printf("Unable to retrieve manifests from executing kustomize: %v\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
sa := qliksense.GetYamlsFromMultiDoc(string(resultYamlBytes), entityToTest)
|
||||
if sa != "" {
|
||||
sa = strings.Replace(sa, "name: qliksense", "name: preflight", -1)
|
||||
} else {
|
||||
err := fmt.Errorf("Unable to retrieve yamls to apply on cluster")
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
namespace = "" // namespace is handled when generating the manifests
|
||||
|
||||
defer func() {
|
||||
fmt.Println("Cleaning up resources")
|
||||
api.KubectlDelete(sa, namespace)
|
||||
if err != nil {
|
||||
fmt.Println("Preflight cleanup failed!")
|
||||
}
|
||||
}()
|
||||
|
||||
err = api.KubectlApply(sa, namespace)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Failed to create entity on the cluster: %v", err)
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Preflight %s check: PASSED\n", entityToTest)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qp *QliksensePreflight) CheckCreateRB(namespace string, kubeConfigContents []byte) error {
|
||||
|
||||
// create a role
|
||||
fmt.Printf("Preflight createRole check: \n")
|
||||
err := qp.checkCreateEntity(namespace, "Role")
|
||||
if err != nil {
|
||||
fmt.Println("Preflight role check: FAILED")
|
||||
}
|
||||
fmt.Printf("Completed preflight role check\n\n")
|
||||
|
||||
// create a roleBinding
|
||||
fmt.Printf("Preflight rolebinding check: \n")
|
||||
err = qp.checkCreateEntity(namespace, "RoleBinding")
|
||||
if err != nil {
|
||||
fmt.Println("Preflight rolebinding check: FAILED")
|
||||
}
|
||||
fmt.Printf("Completed preflight rolebinding check\n\n")
|
||||
|
||||
// create a service account
|
||||
fmt.Printf("Preflight serviceaccount check: \n")
|
||||
err = qp.checkCreateEntity(namespace, "ServiceAccount")
|
||||
if err != nil {
|
||||
fmt.Println("Preflight serviceaccount check: FAILED")
|
||||
}
|
||||
fmt.Printf("Completed preflight serviceaccount check\n\n")
|
||||
|
||||
fmt.Println("Preflight RB check: PASSED")
|
||||
fmt.Println("Completed preflight CreateRB check")
|
||||
return nil
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package preflight
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
)
|
||||
|
||||
func (qp *QliksensePreflight) CheckK8sVersion(namespace string, kubeConfigContents []byte) error {
|
||||
|
||||
var currentVersion *semver.Version
|
||||
|
||||
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Unable to create clientset: %v\n", err)
|
||||
return err
|
||||
}
|
||||
var serverVersion *version.Info
|
||||
if err := retryOnError(func() (err error) {
|
||||
serverVersion, err = clientset.ServerVersion()
|
||||
return err
|
||||
}); err != nil {
|
||||
err = fmt.Errorf("Unable to get server version: %v\n", err)
|
||||
//fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Kubernetes API Server version: %s\n", serverVersion.String())
|
||||
|
||||
// Compare K8s version on the cluster with minimum supported k8s version
|
||||
currentVersion, err = semver.NewVersion(serverVersion.String())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Unable to convert server version into semver version: %v\n", err)
|
||||
//fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
//fmt.Printf("Current K8s Version: %v\n", currentVersion)
|
||||
|
||||
minK8sVersionSemver, err := semver.NewVersion(qp.GetPreflightConfigObj().GetMinK8sVersion())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Unable to convert minimum Kubernetes version into semver version:%v\n", err)
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if currentVersion.GreaterThan(minK8sVersionSemver) {
|
||||
//fmt.Printf("\n\nCurrent %s Component version: %s is less than minimum required version:%s\n", component, currentComponentVersion, componentVersionFromDependenciesYaml)
|
||||
fmt.Printf("Current %s is greater than minimum required version:%s\n", currentVersion, minK8sVersionSemver)
|
||||
fmt.Println("Preflight minimum kubernetes version check: PASSED")
|
||||
} else {
|
||||
fmt.Printf("Current %s is less than minimum required version:%s\n", currentVersion, minK8sVersionSemver)
|
||||
fmt.Println("Preflight minimum kubernetes version check: FAILED")
|
||||
}
|
||||
fmt.Printf("Completed Preflight kubernetes minimum version check\n")
|
||||
return nil
|
||||
}
|
||||
BIN
pkg/qliksense/.DS_Store
vendored
BIN
pkg/qliksense/.DS_Store
vendored
Binary file not shown.
@@ -2,6 +2,7 @@ package qliksense
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -58,33 +59,42 @@ func (nw *nullWriter) Write(p []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
const (
|
||||
defaultProfile = "docker-desktop"
|
||||
defaultConfigRepoGitUrl = "https://github.com/qlik-oss/qliksense-k8s"
|
||||
defaultProfile = "docker-desktop"
|
||||
defaultGitUrl = "https://github.com/qlik-oss/qliksense-k8s"
|
||||
)
|
||||
|
||||
func (q *Qliksense) About(gitRef, profile string) (*VersionOutput, error) {
|
||||
configDirectory, isTemporary, profile, err := q.getConfigDirectory(defaultConfigRepoGitUrl, gitRef, profile)
|
||||
configDirectory, isTemporary, profile, err := q.getConfigDirectory(defaultGitUrl, gitRef, profile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if isTemporary {
|
||||
}
|
||||
if isTemporary {
|
||||
defer os.RemoveAll(configDirectory)
|
||||
}
|
||||
|
||||
return q.AboutDir(configDirectory, profile)
|
||||
}
|
||||
|
||||
func (q *Qliksense) AboutDir(configDirectory, profile string) (*VersionOutput, error) {
|
||||
if chartVersion, err := getChartVersion(filepath.Join(configDirectory, "transformers", "qseokversion.yaml"), "qliksense"); err != nil {
|
||||
chartVersion, err := getChartVersion(filepath.Join(configDirectory, "transformers", "qseokversion.yaml"), "qliksense")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if kuzManifest, err := executeKustomizeBuildWithStdoutProgress(filepath.Join(configDirectory, "manifests", profile)); err != nil {
|
||||
return nil, err
|
||||
} else if images, err := getImageList(kuzManifest); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return &VersionOutput{
|
||||
QliksenseVersion: chartVersion,
|
||||
Images: images,
|
||||
}, nil
|
||||
}
|
||||
|
||||
kuzManifest, err := executeKustomizeBuild(filepath.Join(configDirectory, "manifests", profile))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
images, err := getImageList(kuzManifest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &VersionOutput{
|
||||
QliksenseVersion: chartVersion,
|
||||
Images: images,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) getConfigDirectory(gitUrl, gitRef, profileEntered string) (dir string, isTemporary bool, profile string, err error) {
|
||||
@@ -94,7 +104,7 @@ func (q *Qliksense) getConfigDirectory(gitUrl, gitRef, profileEntered string) (d
|
||||
}
|
||||
|
||||
if gitRef != "" {
|
||||
if dir, err = DownloadFromGitRepoToTmpDir(gitUrl, gitRef); err != nil {
|
||||
if dir, err = downloadFromGitRepoToTmpDir(gitUrl, gitRef); err != nil {
|
||||
return "", false, "", err
|
||||
} else {
|
||||
return dir, true, profile, nil
|
||||
@@ -110,7 +120,7 @@ func (q *Qliksense) getConfigDirectory(gitUrl, gitRef, profileEntered string) (d
|
||||
}
|
||||
|
||||
var profileFromCurrentContext string
|
||||
exists, dir, profileFromCurrentContext, err = q.configExistsInCurrentContext()
|
||||
exists, dir, profileFromCurrentContext, err = q.ConfigExistsInCurrentContext()
|
||||
if err != nil {
|
||||
return "", false, "", err
|
||||
} else if exists {
|
||||
@@ -120,15 +130,14 @@ func (q *Qliksense) getConfigDirectory(gitUrl, gitRef, profileEntered string) (d
|
||||
return dir, false, profile, nil
|
||||
}
|
||||
|
||||
if dir, err = DownloadFromGitRepoToTmpDir(gitUrl, "master"); err != nil {
|
||||
if dir, err = downloadFromGitRepoToTmpDir(gitUrl, "master"); err != nil {
|
||||
return "", false, "", err
|
||||
} else {
|
||||
return dir, true, profile, nil
|
||||
}
|
||||
}
|
||||
|
||||
//DownloadFromGitRepoToTmpDir download git repo to a temporary directory
|
||||
func DownloadFromGitRepoToTmpDir(gitUrl, gitRef string) (string, error) {
|
||||
func downloadFromGitRepoToTmpDir(gitUrl, gitRef string) (string, error) {
|
||||
if tmpDir, err := ioutil.TempDir("", ""); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
@@ -161,14 +170,16 @@ func configExistsInCurrentDirectory(profile string) (exists bool, currentDirecto
|
||||
return exists, currentDirectory, err
|
||||
}
|
||||
|
||||
func (q *Qliksense) configExistsInCurrentContext() (exists bool, directory string, profile string, err error) {
|
||||
func (q *Qliksense) ConfigExistsInCurrentContext() (exists bool, directory string, profile string, err error) {
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
if currentCr, err := qConfig.GetCurrentCR(); err != nil {
|
||||
return false, "", "", err
|
||||
} else if currentCr.Spec.ManifestsRoot == "" {
|
||||
return false, "", "", nil
|
||||
} else if path.Base(currentCr.Spec.ManifestsRoot) != "manifests" {
|
||||
return false, "", "", errors.New("currentCr.Spec.ManifestsRoot path should terminate with manifests/")
|
||||
} else {
|
||||
return true, currentCr.Spec.GetManifestsRoot(), currentCr.Spec.Profile, nil
|
||||
return true, path.Join(currentCr.Spec.ManifestsRoot, "../"), currentCr.Spec.Profile, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,10 +205,8 @@ func getImageList(yamlContent []byte) ([]string, error) {
|
||||
})
|
||||
}
|
||||
var sortedImageList []string
|
||||
for image, v := range imageMap {
|
||||
for image, _ := range imageMap {
|
||||
sortedImageList = append(sortedImageList, image)
|
||||
// a warning "simplify range expression" if written like this 'for image _ :=range imageMap'
|
||||
_ = v
|
||||
}
|
||||
sort.Strings(sortedImageList)
|
||||
return sortedImageList, nil
|
||||
|
||||
@@ -525,16 +525,12 @@ func Test_About_getConfigDirectory(t *testing.T) {
|
||||
if err := q.SetUpQliksenseDefaultContext(); err != nil {
|
||||
t.Fatalf("error setting up default context in the tmp dir: %v\n", err)
|
||||
return nil, "", "", ""
|
||||
} else if qConfig, err := qapi.NewQConfigE(q.QliksenseHome); err != nil {
|
||||
t.Fatalf("cannot initiallize qConfig: %v\n", err)
|
||||
} else if err := q.FetchQK8s("master"); err != nil {
|
||||
t.Fatalf("error fetching master config to the tmp dir: %v\n", err)
|
||||
return nil, "", "", ""
|
||||
} else if !qConfig.IsRepoExistForCurrent("master") {
|
||||
if err := q.FetchQK8s("master"); err != nil {
|
||||
t.Fatalf("error fetching master config to the tmp dir: %v\n", err)
|
||||
return nil, "", "", ""
|
||||
}
|
||||
} else {
|
||||
return q, "no-git-clone-for-you", "", ""
|
||||
}
|
||||
return q, "no-git-clone-for-you", "", ""
|
||||
}
|
||||
},
|
||||
verify: func(q *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
func (q *Qliksense) ApplyCRFromReader(r io.Reader, opts *InstallCommandOptions, keepPatchFiles, overwriteExistingContext bool) error {
|
||||
if err := q.LoadCr(r, overwriteExistingContext); err != nil {
|
||||
return err
|
||||
}
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
cr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if IsQliksenseInstalled(cr.GetName()) {
|
||||
// it is needed in case want to upgrade from one version to another
|
||||
if cr.Spec.ManifestsRoot == "" && cr.Spec.Git == nil {
|
||||
v := cr.GetLabelFromCr("version")
|
||||
if !qConfig.IsRepoExistForCurrent(v) {
|
||||
if err := q.FetchQK8s(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return q.UpgradeQK8s(keepPatchFiles)
|
||||
}
|
||||
return q.InstallQK8s(cr.GetLabelFromCr("version"), opts, keepPatchFiles)
|
||||
}
|
||||
|
||||
func IsQliksenseInstalled(crName string) bool {
|
||||
args := []string{
|
||||
"get",
|
||||
"qliksense",
|
||||
crName,
|
||||
"-ogo-template",
|
||||
`--template='{{ .metadata.name}}'`,
|
||||
}
|
||||
_, err := qapi.KubectlDirectOps(args, "")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1,28 +1,19 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
|
||||
"github.com/qlik-oss/k-apis/pkg/cr"
|
||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
const (
|
||||
Q_INIT_CRD_PATH = "manifests/base/crds"
|
||||
agreementTempalte = `
|
||||
Please read the agreement at https://www.qlik.com/us/legal/license-terms
|
||||
Accept the end user license agreement by providing acceptEULA=yes
|
||||
`
|
||||
Q_INIT_CRD_PATH = "manifests/base/manifests/qliksense-init"
|
||||
)
|
||||
|
||||
func (q *Qliksense) ConfigApplyQK8s() error {
|
||||
@@ -34,67 +25,51 @@ 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
|
||||
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return q.applyCR(dcr)
|
||||
}
|
||||
}
|
||||
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return q.applyConfigToK8s(dcr)
|
||||
}
|
||||
return q.applyConfigToK8s(qcr, "")
|
||||
}
|
||||
|
||||
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, cmd string) error {
|
||||
// apply qliksense-init crd first
|
||||
mroot := qcr.Spec.GetManifestsRoot()
|
||||
qInitMsPath := filepath.Join(mroot, Q_INIT_CRD_PATH)
|
||||
|
||||
func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error {
|
||||
if qcr.Spec.RotateKeys != "None" {
|
||||
if err := q.configEjson(); err != nil {
|
||||
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)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if cmd != "upgrade" {
|
||||
qInitByte, err := executeKustomizeBuild(qInitMsPath)
|
||||
if err != nil {
|
||||
fmt.Println("cannot generate crds for qliksense-init", err)
|
||||
return err
|
||||
}
|
||||
if err = qapi.KubectlApply(string(qInitByte)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
userHomeDir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
fmt.Printf(`error fetching user's home directory: %v\n`, err)
|
||||
return err
|
||||
}
|
||||
fmt.Println("Manifests root: " + qcr.Spec.GetManifestsRoot())
|
||||
qcr.SetNamespace(qapi.GetKubectlNamespace())
|
||||
|
||||
// generate patches
|
||||
cr.GeneratePatches(&qcr.KApiCr, path.Join(userHomeDir, ".kube", "config"))
|
||||
cr.GeneratePatches(qcr.Spec, path.Join(userHomeDir, ".kube", "config"))
|
||||
// apply generated manifests
|
||||
profilePath := filepath.Join(qcr.Spec.GetManifestsRoot(), qcr.Spec.GetProfileDir())
|
||||
mByte, err := ExecuteKustomizeBuild(profilePath)
|
||||
profilePath := filepath.Join(qcr.Spec.ManifestsRoot, qcr.Spec.Profile)
|
||||
mByte, err := executeKustomizeBuild(profilePath)
|
||||
if err != nil {
|
||||
fmt.Println("cannot generate manifests for "+profilePath, err)
|
||||
return err
|
||||
}
|
||||
if err = qapi.KubectlApply(string(mByte), qcr.GetNamespace()); err != nil {
|
||||
if err = qapi.KubectlApply(string(mByte)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -102,16 +77,13 @@ func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error {
|
||||
}
|
||||
|
||||
func (q *Qliksense) ConfigViewCR() error {
|
||||
|
||||
//get the current context cr
|
||||
r, err := q.getCurrentCRString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oth, err := q.getCurrentCrDependentResourceAsString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(r + "\n" + oth)
|
||||
fmt.Println(r)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -127,95 +99,5 @@ func (q *Qliksense) getCRString(contextName string) (string, error) {
|
||||
fmt.Println("cannot get the context cr", err)
|
||||
return "", err
|
||||
}
|
||||
out, err := qapi.K8sToYaml(qcr)
|
||||
if err != nil {
|
||||
fmt.Println("cannot unmarshal cr ", err)
|
||||
return "", err
|
||||
}
|
||||
return string(out), nil
|
||||
|
||||
}
|
||||
|
||||
func (q *Qliksense) getCurrentCrDependentResourceAsString() (string, error) {
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
qcr, err := qConfig.GetCR(qConfig.Spec.CurrentContext)
|
||||
if err != nil {
|
||||
fmt.Println("cannot get the context cr", err)
|
||||
return "", err
|
||||
}
|
||||
var crString strings.Builder
|
||||
|
||||
for svcName, v := range qcr.Spec.Secrets {
|
||||
hasFile := false
|
||||
for _, item := range v {
|
||||
if item.ValueFrom != nil && item.ValueFrom.SecretKeyRef != nil {
|
||||
hasFile = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasFile {
|
||||
secretFilePath := filepath.Join(q.QliksenseHome, QliksenseContextsDir, qcr.GetName(), QliksenseSecretsDir, svcName+".yaml")
|
||||
if api.FileExists(secretFilePath) {
|
||||
secretFile, err := ioutil.ReadFile(secretFilePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
crString.WriteString("\n---\n")
|
||||
crString.Write(secretFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
crString.WriteString("\n---\n")
|
||||
return crString.String(), nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) EditCR(contextName string) error {
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
if contextName == "" {
|
||||
cr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
contextName = cr.GetName()
|
||||
}
|
||||
crFilePath := qConfig.GetCRFilePath(contextName)
|
||||
tempFile, err := ioutil.TempFile("", "*.yaml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
crContent, err := ioutil.ReadFile(crFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(tempFile.Name(), crContent, os.ModePerm); err != nil {
|
||||
return nil
|
||||
}
|
||||
cmd := exec.Command(getKubeEditorTool(), tempFile.Name())
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newCr, err := qapi.GetCRObject(tempFile.Name())
|
||||
if err != nil {
|
||||
return errors.New("cannot save the cr. Someting wrong in the file format. It is not saved\n" + err.Error())
|
||||
}
|
||||
oldCr, err := qapi.GetCRObject(crFilePath)
|
||||
|
||||
if oldCr.GetName() != newCr.GetName() {
|
||||
return errors.New("cr name cannot be chagned")
|
||||
}
|
||||
if newCr.Validate() {
|
||||
return qConfig.WriteCR(newCr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getKubeEditorTool() string {
|
||||
editor := os.Getenv("KUBE_EDITOR")
|
||||
if editor == "" {
|
||||
editor = "vim"
|
||||
}
|
||||
return editor
|
||||
return qcr.GetString()
|
||||
}
|
||||
|
||||
@@ -1,166 +1,133 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/qlik-oss/k-apis/pkg/config"
|
||||
"github.com/robfig/cron/v3"
|
||||
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
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"
|
||||
|
||||
imageRegistryConfigKey = "imageRegistry"
|
||||
pullSecretName = "artifactory-docker-secret"
|
||||
qliksenseOperatorImageRepo = "qlik-docker-oss.bintray.io"
|
||||
qliksenseOperatorImageName = "qliksense-operator"
|
||||
)
|
||||
|
||||
func (q *Qliksense) SetSecretsFromReader(arg string, reader io.Reader, createSecret, base64Encoded bool) error {
|
||||
//take only name from the arguments, value should be from reader
|
||||
argName := strings.SplitN(arg, "=", 1)
|
||||
if len(argName) != 1 {
|
||||
return errors.New("can only have one argument from pipe")
|
||||
}
|
||||
valueBytes, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return q.SetSecrets([]string{argName[0] + "=" + string(valueBytes)}, createSecret, base64Encoded)
|
||||
}
|
||||
|
||||
// SetSecrets - set-secrets <key>=<value> commands
|
||||
func (q *Qliksense) SetSecrets(args []string, isSecretSet bool, base64Encoded bool) error {
|
||||
qConfig := api.NewQConfig(q.QliksenseHome)
|
||||
qliksenseCR, err := qConfig.GetCurrentCR()
|
||||
func (q *Qliksense) SetSecrets(args []string, isK8sSecret bool) error {
|
||||
api.LogDebugMessage("Args received: %v\n", args)
|
||||
api.LogDebugMessage("isK8sSecret: %v\n", isK8sSecret)
|
||||
|
||||
// retieve current context from config.yaml
|
||||
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Metadata name in qliksense CR is the name of the current context
|
||||
api.LogDebugMessage("Current context: %s", qliksenseCR.GetName())
|
||||
encryptionKey, err := qConfig.GetEncryptionKeyForCurrent()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resultArgs, err := api.ProcessConfigArgs(args, base64Encoded)
|
||||
secretKeyPairLocation := filepath.Join(q.QliksenseHome, QliksenseSecretsDir, QliksenseContextsDir, qliksenseCR.Metadata.Name, QliksenseSecretsDir)
|
||||
api.LogDebugMessage("SecretKeyLocation to store key pair: %s", secretKeyPairLocation)
|
||||
|
||||
if os.Getenv("QLIKSENSE_KEY_LOCATION") != "" {
|
||||
api.LogDebugMessage("Env variable: QLIKSENSE_KEY_LOCATION= %s", os.Getenv("QLIKSENSE_KEY_LOCATION"))
|
||||
secretKeyPairLocation = os.Getenv("QLIKSENSE_KEY_LOCATION")
|
||||
}
|
||||
// Env var: QLIKSENSE_KEY_LOCATION hasn't been set, so dropping key pair in the location:
|
||||
// /.qliksense/secrets/contexts/<current-context>/secrets/
|
||||
api.LogDebugMessage("Using default location to store keys: %s", secretKeyPairLocation)
|
||||
|
||||
publicKeyFilePath := filepath.Join(secretKeyPairLocation, api.QliksensePublicKey)
|
||||
privateKeyFilePath := filepath.Join(secretKeyPairLocation, api.QliksensePrivateKey)
|
||||
|
||||
// try to create the dir if it doesn't exist
|
||||
if !api.FileExists(publicKeyFilePath) || !api.FileExists(privateKeyFilePath) {
|
||||
api.LogDebugMessage("Qliksense secretKeyLocation dir does not exist, creating it now: %s", secretKeyPairLocation)
|
||||
if err := os.MkdirAll(secretKeyPairLocation, os.ModePerm); err != nil {
|
||||
err = fmt.Errorf("Not able to create %s dir: %v", secretKeyPairLocation, err)
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
// generating and storing key-pair
|
||||
err1 := api.GenerateAndStoreSecretKeypair(secretKeyPairLocation)
|
||||
if err1 != nil {
|
||||
err1 = fmt.Errorf("Not able to generate and store key pair for encryption")
|
||||
log.Println(err1)
|
||||
return err1
|
||||
}
|
||||
}
|
||||
|
||||
var rsaPublicKey *rsa.PublicKey
|
||||
var e1 error
|
||||
|
||||
// Read Public Key
|
||||
publicKeybytes, err2 := api.ReadKeys(publicKeyFilePath)
|
||||
if err2 != nil {
|
||||
api.LogDebugMessage("Not able to read public key")
|
||||
return err2
|
||||
}
|
||||
// api.LogDebugMessage("PublicKey: %+v", publicKeybytes)
|
||||
|
||||
// convert []byte into RSA public key object
|
||||
rsaPublicKey, e1 = api.DecodeToPublicKey(publicKeybytes)
|
||||
if e1 != nil {
|
||||
return e1
|
||||
}
|
||||
|
||||
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, encryptionKey, qliksenseCR, isSecretSet); err != nil {
|
||||
return err
|
||||
// Metadata name in qliksense CR is the name of the current context
|
||||
api.LogDebugMessage("Trying to retreive current context: %+v ----- %s", qliksenseCR.Metadata.Name, qliksenseContextsFile)
|
||||
|
||||
// encrypt value with RSA key pair
|
||||
valueBytes := []byte(ra.Value)
|
||||
cipherText, e2 := api.Encrypt(valueBytes, rsaPublicKey)
|
||||
if e2 != nil {
|
||||
return e2
|
||||
}
|
||||
api.LogDebugMessage("Returned cipher text: %s", b64.StdEncoding.EncodeToString(cipherText))
|
||||
|
||||
if isK8sSecret {
|
||||
// TODO: store the key value in k8s as secret
|
||||
api.LogDebugMessage("Need to create a Kubernetes secret")
|
||||
}
|
||||
// TODO: Extend AddToSecrets to support adding k8s key ref. (OR) create a separate method to support that
|
||||
// store the encrypted value in the file (OR)
|
||||
// TODO: k8s secret name in the file
|
||||
qliksenseCR.Spec.AddToSecrets(ra.SvcName, ra.Key, string(cipherText))
|
||||
}
|
||||
// write modified content into context-yaml
|
||||
return qConfig.WriteCR(qliksenseCR)
|
||||
}
|
||||
|
||||
func (q *Qliksense) processSecret(ra *api.ServiceKeyValue, encryptionKey string, qliksenseCR *api.QliksenseCR, isSecretSet bool) error {
|
||||
cipherText, e2 := api.EncryptData([]byte(ra.Value), encryptionKey)
|
||||
if e2 != nil {
|
||||
return e2
|
||||
}
|
||||
secretName := ""
|
||||
if isSecretSet {
|
||||
secretFolder := qliksenseCR.GetK8sSecretsFolder(q.QliksenseHome)
|
||||
secretFileName := filepath.Join(secretFolder, ra.SvcName+".yaml")
|
||||
// write modified content into context.yaml
|
||||
api.WriteToFile(&qliksenseCR, qliksenseContextsFile)
|
||||
|
||||
secretName = fmt.Sprintf("%s-%s-%s", qliksenseCR.GetName(), ra.SvcName, "senseinstaller")
|
||||
api.LogDebugMessage("Constructed secret name: %s", secretName)
|
||||
|
||||
k8sSecret := v1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
},
|
||||
Type: v1.SecretTypeOpaque,
|
||||
}
|
||||
|
||||
if !api.DirExists(secretFolder) {
|
||||
if err := os.MkdirAll(secretFolder, os.ModePerm); err != nil {
|
||||
err = fmt.Errorf("Not able to create %s dir: %v", secretFolder, err)
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
// if read from file errors out, we can ignore it here
|
||||
_ = api.ReadFromFile(&k8sSecret, secretFileName)
|
||||
if k8sSecret.Data == nil {
|
||||
k8sSecret.Data = map[string][]byte{}
|
||||
}
|
||||
// v1.Secret does enconding, so no need to encode again
|
||||
k8sSecret.Data[ra.Key] = []byte(cipherText)
|
||||
|
||||
// Write secret to file
|
||||
k8sSecretBytes, err := api.K8sSecretToYaml(k8sSecret)
|
||||
if err != nil {
|
||||
api.LogDebugMessage("Error while converting K8s secret to yaml")
|
||||
return err
|
||||
}
|
||||
if err = ioutil.WriteFile(secretFileName, k8sSecretBytes, os.ModePerm); err != nil {
|
||||
api.LogDebugMessage("Error while writing K8s secret to file")
|
||||
return err
|
||||
}
|
||||
api.LogDebugMessage("Created a Kubernetes secret")
|
||||
}
|
||||
base64EncodedSecret := b64.StdEncoding.EncodeToString([]byte(cipherText))
|
||||
// write into CR the keyref of the secret
|
||||
qliksenseCR.Spec.AddToSecrets(ra.SvcName, ra.Key, base64EncodedSecret, secretName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) SetConfigFromReader(arg string, reader io.Reader, base64Encoded bool) error {
|
||||
//take only name from the arguments, value should be from reader
|
||||
argName := strings.SplitN(arg, "=", 1)
|
||||
if len(argName) != 1 {
|
||||
return errors.New("can only have one argument from pipe")
|
||||
}
|
||||
valueBytes, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return q.SetConfigs([]string{argName[0] + "=" + string(valueBytes)}, base64Encoded)
|
||||
}
|
||||
|
||||
// SetConfigs - set-configs <key>=<value> commands
|
||||
func (q *Qliksense) SetConfigs(args []string, base64Encoded bool) error {
|
||||
func (q *Qliksense) SetConfigs(args []string) error {
|
||||
// retieve current context from config.yaml
|
||||
qConfig := api.NewQConfig(q.QliksenseHome)
|
||||
qliksenseCR, err := qConfig.GetCurrentCR()
|
||||
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resultArgs, err := api.ProcessConfigArgs(args, base64Encoded)
|
||||
resultArgs, err := api.ProcessConfigArgs(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -168,66 +135,47 @@ func (q *Qliksense) SetConfigs(args []string, base64Encoded bool) error {
|
||||
qliksenseCR.Spec.AddToConfigs(ra.SvcName, ra.Key, ra.Value)
|
||||
}
|
||||
// write modified content into context.yaml
|
||||
return qConfig.WriteCR(qliksenseCR)
|
||||
}
|
||||
api.WriteToFile(&qliksenseCR, qliksenseContextsFile)
|
||||
|
||||
func caseInsenstiveFieldByName(v reflect.Value, name string) reflect.Value {
|
||||
name = strings.ToLower(name)
|
||||
return v.FieldByNameFunc(func(n string) bool { return strings.ToLower(n) == name })
|
||||
return nil
|
||||
}
|
||||
func retrieveCurrentContextInfo(q *Qliksense) (api.QliksenseCR, string, error) {
|
||||
var qliksenseConfig api.QliksenseConfig
|
||||
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
|
||||
|
||||
func validateCR(key string, keySub string, value string, crSpec *api.QliksenseCR) (bool, *api.QliksenseCR) {
|
||||
cr := reflect.ValueOf(crSpec.Spec)
|
||||
keyValid := caseInsenstiveFieldByName(reflect.Indirect(cr), key)
|
||||
if !keyValid.IsValid() {
|
||||
//not in main spec
|
||||
fmt.Println(key, "is an invalid key")
|
||||
return false, crSpec
|
||||
} else if keySub == "" {
|
||||
if key == "rotatekeys" {
|
||||
if _, err := validateInput(value); err != nil {
|
||||
return false, crSpec
|
||||
}
|
||||
}
|
||||
api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile)
|
||||
currentContext := qliksenseConfig.Spec.CurrentContext
|
||||
api.LogDebugMessage("Current-context from config.yaml: %s", currentContext)
|
||||
if currentContext == "" {
|
||||
// current-context is empty
|
||||
err := fmt.Errorf(`Please run the "qliksense config set-context <context-name>" first before viewing the current context info`)
|
||||
log.Println(err)
|
||||
return api.QliksenseCR{}, "", err
|
||||
}
|
||||
// checks if it is git or gitops
|
||||
if keySub != "" {
|
||||
if !keyValid.IsNil() {
|
||||
if !caseInsenstiveFieldByName(reflect.Indirect(keyValid), keySub).IsValid() {
|
||||
fmt.Println(keySub, "is an invalid key")
|
||||
return false, crSpec
|
||||
} else {
|
||||
// verify gitops enabled and gitops schedule
|
||||
switch keySub {
|
||||
case "schedule":
|
||||
if _, err := cron.ParseStandard(value); err != nil {
|
||||
fmt.Println("Please enter string with standard cron scheduling syntax ")
|
||||
return false, crSpec
|
||||
}
|
||||
case "enabled":
|
||||
if !strings.EqualFold(value, "yes") && !strings.EqualFold(value, "no") {
|
||||
fmt.Println("Please use yes or no for key enabled")
|
||||
return false, crSpec
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch key {
|
||||
case "gitops":
|
||||
crSpec.Spec.GitOps = &config.GitOps{}
|
||||
case "git":
|
||||
crSpec.Spec.Git = &config.Repo{}
|
||||
}
|
||||
}
|
||||
// read the context.yaml file
|
||||
var 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 true, crSpec
|
||||
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
|
||||
}
|
||||
api.ReadFromFile(&qliksenseCR, qliksenseContextsFile)
|
||||
|
||||
api.LogDebugMessage("Read context file: %s, Read QliksenseCR: %v", qliksenseContextsFile, qliksenseCR)
|
||||
return qliksenseCR, qliksenseContextsFile, nil
|
||||
}
|
||||
|
||||
// SetOtherConfigs - set profile/storageclassname/git.repository/manifestRoot commands
|
||||
// SetOtherConfigs - set profile/namespace/storageclassname/git.repository commands
|
||||
func (q *Qliksense) SetOtherConfigs(args []string) error {
|
||||
// retieve current context from config.yaml
|
||||
qConfig := api.NewQConfig(q.QliksenseHome)
|
||||
qliksenseCR, err := qConfig.GetCurrentCR()
|
||||
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -240,137 +188,41 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
|
||||
}
|
||||
|
||||
for _, arg := range args {
|
||||
if strings.HasPrefix(arg, "fetchSource.") {
|
||||
if err := q.processSetFetchSource(arg, qliksenseCR); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if strings.HasPrefix(arg, "git.") {
|
||||
if err := q.processSetGit(arg, qliksenseCR); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if strings.HasPrefix(arg, "gitOps.") {
|
||||
if err := q.processSetGitOps(arg, qliksenseCR); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := processSetSingleArg(arg, qliksenseCR); err != nil {
|
||||
argsString := strings.Split(arg, "=")
|
||||
switch argsString[0] {
|
||||
case "profile":
|
||||
qliksenseCR.Spec.Profile = argsString[1]
|
||||
api.LogDebugMessage("Current profile after modification: %s ", qliksenseCR.Spec.Profile)
|
||||
case "namespace":
|
||||
qliksenseCR.Spec.NameSpace = argsString[1]
|
||||
api.LogDebugMessage("Current namespace after modification: %s ", qliksenseCR.Spec.NameSpace)
|
||||
case "git.repository":
|
||||
qliksenseCR.Spec.Git.Repository = argsString[1]
|
||||
api.LogDebugMessage("Current git repository after modification: %s ", qliksenseCR.Spec.Git.Repository)
|
||||
case "storageClassName":
|
||||
qliksenseCR.Spec.StorageClassName = argsString[1]
|
||||
api.LogDebugMessage("Current StorageClassName after modification: %s ", qliksenseCR.Spec.StorageClassName)
|
||||
case "rotateKeys":
|
||||
rotateKeys, err := validateInput(argsString[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
qliksenseCR.Spec.RotateKeys = rotateKeys
|
||||
api.LogDebugMessage("Current rotateKeys after modification: %s ", qliksenseCR.Spec.RotateKeys)
|
||||
default:
|
||||
log.Println("As part of the `qliksense config set` command, please enter one of: profile, namespace, storageClassName,rotateKeys or git.repository arguments")
|
||||
}
|
||||
fmt.Println(chalk.Green.Color("Successfully added to Custom Resource Spec"))
|
||||
}
|
||||
|
||||
// write modified content into context.yaml
|
||||
return qConfig.WriteCR(qliksenseCR)
|
||||
}
|
||||
api.WriteToFile(&qliksenseCR, qliksenseContextsFile)
|
||||
|
||||
func processSetSingleArg(arg string, cr *api.QliksenseCR) error {
|
||||
nv := strings.Split(arg, "=")
|
||||
switch nv[0] {
|
||||
case "manifestsRoot":
|
||||
cr.Spec.ManifestsRoot = nv[1]
|
||||
case "profile":
|
||||
cr.Spec.Profile = nv[1]
|
||||
case "storageClassName":
|
||||
cr.Spec.StorageClassName = nv[1]
|
||||
case "rotateKeys":
|
||||
valid := false
|
||||
for _, v := range []string{"yes", "no", "None"} {
|
||||
if nv[1] == v {
|
||||
valid = true
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
return errors.New("please povide rotateKeys=yes|no|None")
|
||||
}
|
||||
cr.Spec.RotateKeys = nv[1]
|
||||
default:
|
||||
return errors.New("Please enter one of: profile, storageClassName,rotateKeys, manifestRoot to configure the current context")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) processSetFetchSource(arg string, cr *api.QliksenseCR) error {
|
||||
args := strings.Split(arg, "=")
|
||||
subs := strings.Split(args[0], ".")
|
||||
if cr.Spec.FetchSource == nil {
|
||||
cr.Spec.FetchSource = &config.Repo{}
|
||||
}
|
||||
switch subs[1] {
|
||||
case "repository":
|
||||
cr.Spec.FetchSource.Repository = args[1]
|
||||
case "accessToken":
|
||||
qConfig := api.NewQConfig(q.QliksenseHome)
|
||||
key, err := qConfig.GetEncryptionKeyFor(cr.GetName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cr.SetFetchAccessToken(args[1], key)
|
||||
case "secretName":
|
||||
cr.Spec.FetchSource.SecretName = args[1]
|
||||
case "userName":
|
||||
cr.Spec.FetchSource.UserName = args[1]
|
||||
default:
|
||||
return errors.New(arg + " does not match any cr spec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) processSetGit(arg string, cr *api.QliksenseCR) error {
|
||||
args := strings.Split(arg, "=")
|
||||
subs := strings.Split(args[0], ".")
|
||||
if cr.Spec.Git == nil {
|
||||
cr.Spec.Git = &config.Repo{}
|
||||
}
|
||||
switch subs[1] {
|
||||
case "repository":
|
||||
cr.Spec.Git.Repository = args[1]
|
||||
case "accessToken":
|
||||
cr.Spec.Git.AccessToken = args[1]
|
||||
case "secretName":
|
||||
cr.Spec.Git.SecretName = args[1]
|
||||
case "userName":
|
||||
cr.Spec.Git.UserName = args[1]
|
||||
default:
|
||||
return errors.New(arg + " does not match any cr spec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) processSetGitOps(arg string, cr *api.QliksenseCR) error {
|
||||
args := strings.Split(arg, "=")
|
||||
subs := strings.Split(args[0], ".")
|
||||
if cr.Spec.Git == nil {
|
||||
cr.Spec.GitOps = &config.GitOps{}
|
||||
}
|
||||
switch subs[1] {
|
||||
case "enabled":
|
||||
if args[1] != "yes" && args[1] != "no" {
|
||||
return errors.New("Please use yes or no for key enabled")
|
||||
}
|
||||
cr.Spec.GitOps.Enabled = args[1]
|
||||
case "schedule":
|
||||
if _, err := cron.ParseStandard(args[1]); err != nil {
|
||||
return errors.New("Please enter string with standard cron scheduling syntax ")
|
||||
}
|
||||
cr.Spec.GitOps.Schedule = args[1]
|
||||
case "watchBranch":
|
||||
cr.Spec.GitOps.WatchBranch = args[1]
|
||||
case "image":
|
||||
cr.Spec.GitOps.Image = args[1]
|
||||
default:
|
||||
return errors.New(arg + " does not match any cr spec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetContextConfig - set the context for qliksense kubernetes resources to live in
|
||||
func (q *Qliksense) SetContextConfig(args []string) error {
|
||||
if len(args) == 1 {
|
||||
err := q.SetUpQliksenseContext(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.SetUpQliksenseContext(args[0], false)
|
||||
} else {
|
||||
err := fmt.Errorf("Please provide a name to configure the context with")
|
||||
log.Println(err)
|
||||
@@ -382,17 +234,14 @@ func (q *Qliksense) SetContextConfig(args []string) error {
|
||||
func (q *Qliksense) ListContextConfigs() error {
|
||||
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
|
||||
var qliksenseConfig api.QliksenseConfig
|
||||
if err := api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile); err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile)
|
||||
out := ansi.NewColorableStdout()
|
||||
w := tabwriter.NewWriter(out, 5, 8, 0, '\t', 0)
|
||||
fmt.Fprintln(w, chalk.Underline.TextStyle("Context Name"), "\t", chalk.Underline.TextStyle("CR File Location"))
|
||||
w.Flush()
|
||||
if len(qliksenseConfig.Spec.Contexts) > 0 {
|
||||
for _, cont := range qliksenseConfig.Spec.Contexts {
|
||||
fmt.Fprintln(w, cont.Name, "\t", qliksenseConfig.GetCRFilePath(cont.Name), "\t")
|
||||
fmt.Fprintln(w, cont.Name, "\t", cont.CrFile, "\t")
|
||||
}
|
||||
w.Flush()
|
||||
fmt.Fprintln(out, "")
|
||||
@@ -403,84 +252,13 @@ 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 {
|
||||
if api.FileExists(filepath.Join(q.QliksenseHome, "config.yaml")) {
|
||||
qliksenseConfig := api.NewQConfig(q.QliksenseHome)
|
||||
if qliksenseConfig.IsContextExist(DefaultQliksenseContext) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return q.SetUpQliksenseContext(DefaultQliksenseContext)
|
||||
return q.SetUpQliksenseContext(DefaultQliksenseContext, true)
|
||||
}
|
||||
|
||||
// SetUpQliksenseContext - to setup qliksense context
|
||||
func (q *Qliksense) SetUpQliksenseContext(contextName string) error {
|
||||
if contextName == "" {
|
||||
err := fmt.Errorf("Please enter a non-empty context-name")
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
func (q *Qliksense) SetUpQliksenseContext(contextName string, isDefaultContext bool) error {
|
||||
// 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")
|
||||
@@ -489,30 +267,72 @@ func (q *Qliksense) SetUpQliksenseContext(contextName string) error {
|
||||
}
|
||||
|
||||
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
|
||||
qliksenseConfig := api.NewQConfigEmpty(q.QliksenseHome)
|
||||
var qliksenseConfig api.QliksenseConfig
|
||||
configFileTrack := false
|
||||
|
||||
if !api.FileExists(qliksenseConfigFile) {
|
||||
qliksenseConfig.AddBaseQliksenseConfigs(contextName)
|
||||
} else {
|
||||
if err := api.ReadFromFile(qliksenseConfig, qliksenseConfigFile); err != nil {
|
||||
api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile)
|
||||
if isDefaultContext { // if config file exits but a default context is requested, we want to prevent writing to config file
|
||||
configFileTrack = true
|
||||
}
|
||||
}
|
||||
// creating a file in the name of the context if it does not exist/ opening it to append/modify content if it already exists
|
||||
|
||||
qliksenseContextsDir1 := filepath.Join(q.QliksenseHome, QliksenseContextsDir)
|
||||
if !api.DirExists(qliksenseContextsDir1) {
|
||||
if err := os.Mkdir(qliksenseContextsDir1, os.ModePerm); err != nil {
|
||||
err = fmt.Errorf("Not able to create %s dir: %v", qliksenseContextsDir1, err)
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
api.LogDebugMessage("%s exists", qliksenseContextsDir1)
|
||||
|
||||
if qliksenseConfig.IsContextExist(contextName) {
|
||||
qliksenseConfig.Spec.CurrentContext = contextName
|
||||
return qliksenseConfig.Write()
|
||||
// creating contexts/qlik-default/qlik-default.yaml file
|
||||
qliksenseContextFile := filepath.Join(qliksenseContextsDir1, contextName, contextName+".yaml")
|
||||
var qliksenseCR api.QliksenseCR
|
||||
|
||||
defaultContextsDir := filepath.Join(qliksenseContextsDir1, contextName)
|
||||
if !api.DirExists(defaultContextsDir) {
|
||||
if err := os.Mkdir(defaultContextsDir, os.ModePerm); err != nil {
|
||||
err = fmt.Errorf("Not able to create %s: %v", defaultContextsDir, err)
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
api.LogDebugMessage("%s exists", defaultContextsDir)
|
||||
if !api.FileExists(qliksenseContextFile) {
|
||||
qliksenseCR.AddCommonConfig(contextName)
|
||||
api.LogDebugMessage("Added Context: %s", contextName)
|
||||
} else {
|
||||
api.ReadFromFile(&qliksenseCR, qliksenseContextFile)
|
||||
}
|
||||
|
||||
api.WriteToFile(&qliksenseCR, qliksenseContextFile)
|
||||
ctxTrack := false
|
||||
if len(qliksenseConfig.Spec.Contexts) > 0 {
|
||||
for _, ctx := range qliksenseConfig.Spec.Contexts {
|
||||
if ctx.Name == contextName {
|
||||
ctx.CrFile = qliksenseContextFile
|
||||
ctxTrack = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !ctxTrack {
|
||||
qliksenseConfig.Spec.Contexts = append(qliksenseConfig.Spec.Contexts, api.Context{
|
||||
Name: contextName,
|
||||
CrFile: qliksenseContextFile,
|
||||
})
|
||||
}
|
||||
qliksenseCR := &api.QliksenseCR{}
|
||||
qliksenseCR.AddCommonConfig(contextName)
|
||||
qliksenseConfig.Spec.CurrentContext = contextName
|
||||
if err := qliksenseConfig.CreateOrWriteCrAndContext(qliksenseCR); err != nil {
|
||||
return err
|
||||
if !configFileTrack {
|
||||
api.WriteToFile(&qliksenseConfig, qliksenseConfigFile)
|
||||
}
|
||||
|
||||
// set the encrypted default mongo
|
||||
return q.SetSecrets([]string{`qliksense.mongoDbUri="mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"`}, false, false)
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateInput(input string) (string, error) {
|
||||
@@ -532,104 +352,3 @@ func validateInput(input string) (string, error) {
|
||||
}
|
||||
return input, err
|
||||
}
|
||||
|
||||
// PrepareK8sSecret targetFile contains base64 encoded value of encrypted value.
|
||||
// this method decodes and decrypts the secret value in the secret.yaml file and returns a B64encoded string
|
||||
func (q *Qliksense) PrepareK8sSecret(targetFile string) (string, error) {
|
||||
// check if targetFile exists
|
||||
if !api.FileExists(targetFile) {
|
||||
err := fmt.Errorf("Target file does not exist in the path provided")
|
||||
log.Println(err)
|
||||
return "", err
|
||||
}
|
||||
qConfig := api.NewQConfig(q.QliksenseHome)
|
||||
encryptionKey, err := qConfig.GetEncryptionKeyForCurrent()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// read the target file
|
||||
k8sSecret, err := readTargetfile(targetFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// retrieve value from data section
|
||||
k8sSecret1, err := api.K8sSecretFromYaml(k8sSecret)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dataMap := k8sSecret1.Data
|
||||
var resultMap = make(map[string][]byte)
|
||||
for k, v := range dataMap {
|
||||
//k8s secrets has already base64 decoed value
|
||||
decryptedString, err := api.DecryptData(v, encryptionKey)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Not able to decrypt message: %v", err)
|
||||
return "", err
|
||||
}
|
||||
resultMap[k] = []byte(decryptedString)
|
||||
}
|
||||
|
||||
// putting the above map back into the k8sSecret struct
|
||||
k8sSecret1.Data = resultMap
|
||||
k8sSecretBytes, err := api.K8sSecretToYaml(k8sSecret1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(k8sSecretBytes), nil
|
||||
}
|
||||
|
||||
func readTargetfile(targetFile string) ([]byte, error) {
|
||||
k8sSecret, err := ioutil.ReadFile(targetFile)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Unable to read the targetFile")
|
||||
log.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
return k8sSecret, nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) SetImageRegistry(registry, pushUsername, pushPassword, pullUsername, pullPassword string) error {
|
||||
qConfig := api.NewQConfig(q.QliksenseHome)
|
||||
qliksenseCR, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pushUsername != "" {
|
||||
if err := qConfig.SetPushDockerConfigJsonSecret(&api.DockerConfigJsonSecret{
|
||||
Uri: registry,
|
||||
Username: pushUsername,
|
||||
Password: pushPassword,
|
||||
}); err != nil {
|
||||
return err
|
||||
} else if err := qConfig.SetPullDockerConfigJsonSecret(&api.DockerConfigJsonSecret{
|
||||
Name: pullSecretName,
|
||||
Uri: registry,
|
||||
Username: pullUsername,
|
||||
Password: pullPassword,
|
||||
Email: pullUsername,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := qConfig.DeletePushDockerConfigJsonSecret(); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
} else if err := qConfig.DeletePullDockerConfigJsonSecret(); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
qliksenseCR.Spec.AddToConfigs("qliksense", imageRegistryConfigKey, registry)
|
||||
return qConfig.WriteCR(qliksenseCR)
|
||||
}
|
||||
|
||||
func (q *Qliksense) SetEulaAccepted() error {
|
||||
qConfig := api.NewQConfig(q.QliksenseHome)
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !qcr.IsEULA() {
|
||||
qcr.SetEULA("yes")
|
||||
return qConfig.WriteCurrentContextCR(qcr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,74 +1,20 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
b64 "encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/qlik-oss/k-apis/pkg/config"
|
||||
|
||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
testDir = "./tests"
|
||||
qlikDefaultContext = "qlik-default"
|
||||
secrets = "secrets"
|
||||
contexts = "contexts"
|
||||
var (
|
||||
testDir = "./tests"
|
||||
)
|
||||
|
||||
var targetFileStringTemplate = `
|
||||
apiVersion: v1
|
||||
data:
|
||||
mongoDbUri: %s
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: testctx-qliksense-senseinstaller
|
||||
type: Opaque
|
||||
`
|
||||
var decText = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
|
||||
|
||||
func setupTargetFileAndPrivateKey() (string, string, error) {
|
||||
|
||||
secretKeyLocation := filepath.Join(testDir, secrets, contexts, qlikDefaultContext, secrets)
|
||||
if err := os.MkdirAll(secretKeyLocation, 0777); err != nil {
|
||||
err = fmt.Errorf("Not able to create directories")
|
||||
log.Fatal(err)
|
||||
}
|
||||
os.Setenv("QLIKSENSE_KEY_LOCATION", secretKeyLocation)
|
||||
|
||||
//privKeyFile := filepath.Join(secretKeyLocation, "user_secret_key")
|
||||
key, err := api.LoadSecretKey(secretKeyLocation)
|
||||
if key == "" {
|
||||
key, err = api.GenerateAndStoreSecretKey(secretKeyLocation)
|
||||
}
|
||||
encData, _ := api.EncryptData([]byte(decText), key)
|
||||
encText := b64.StdEncoding.EncodeToString(encData)
|
||||
|
||||
targetFileString := fmt.Sprintf(targetFileStringTemplate, encText)
|
||||
targetFile := filepath.Join(testDir, "targetfile.yaml")
|
||||
// tests/config.yaml exists
|
||||
err = ioutil.WriteFile(targetFile, []byte(targetFileString), 0777)
|
||||
if err != nil {
|
||||
log.Printf("Error while creating file: %v", err)
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return targetFile, key, err
|
||||
}
|
||||
|
||||
func setup() func() {
|
||||
// create tests dir
|
||||
os.RemoveAll(testDir)
|
||||
if err := os.Mkdir(testDir, 0777); err != nil {
|
||||
log.Printf("\nError occurred: %v", err)
|
||||
}
|
||||
@@ -81,7 +27,7 @@ metadata:
|
||||
spec:
|
||||
contexts:
|
||||
- name: qlik-default
|
||||
crFile: contexts/qlik-default/qlik-default.yaml
|
||||
crLocation: /root/.qliksense/contexts/qlik-default.yaml
|
||||
currentContext: qlik-default
|
||||
`
|
||||
configFile := filepath.Join(testDir, "config.yaml")
|
||||
@@ -116,22 +62,6 @@ 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()
|
||||
@@ -140,8 +70,7 @@ func Test_retrieveCurrentContextInfo(t *testing.T) {
|
||||
q := &Qliksense{
|
||||
QliksenseHome: testDir,
|
||||
}
|
||||
qConfig := api.NewQConfig(q.QliksenseHome)
|
||||
_, err := qConfig.GetCurrentCR()
|
||||
_, _, err := retrieveCurrentContextInfo(q)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
@@ -176,23 +105,18 @@ 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 := New(tt.args.qlikSenseHome)
|
||||
if err := q.SetUpQliksenseContext(tt.args.contextName); (err != nil) != tt.wantErr {
|
||||
q, err := New(tt.args.qlikSenseHome)
|
||||
if err != nil {
|
||||
t.Errorf("unable to create a qliksense instance")
|
||||
return
|
||||
}
|
||||
if err := q.SetUpQliksenseContext(tt.args.contextName, tt.args.isDefaultContext); (err != nil) != tt.wantErr {
|
||||
t.Errorf("SetUpQliksenseContext() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
@@ -220,7 +144,11 @@ func TestSetUpQliksenseDefaultContext(t *testing.T) {
|
||||
defer tearDown()
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
q := New(tt.args.qlikSenseHome)
|
||||
q, err := New(tt.args.qlikSenseHome)
|
||||
if err != nil {
|
||||
t.Errorf("unable to create a qliksense instance")
|
||||
return
|
||||
}
|
||||
if err := q.SetUpQliksenseDefaultContext(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("SetUpQliksenseDefaultContext() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
@@ -244,30 +172,10 @@ func TestSetOtherConfigs(t *testing.T) {
|
||||
q: &Qliksense{
|
||||
QliksenseHome: testDir,
|
||||
},
|
||||
args: []string{"profile=minikube", "rotateKeys=yes", "storageClassName=efs", "gitOps.enabled=yes", "gitOps.schedule=30 * * * *", "git.repository=master", "git.userName=foo", "git.accessToken=1234"},
|
||||
args: []string{"profile=minikube", "namespace=qliksense", "storageClassName=efs"},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid configs",
|
||||
args: args{
|
||||
q: &Qliksense{
|
||||
QliksenseHome: testDir,
|
||||
},
|
||||
args: []string{"someconfig=somevalue, gitOps.schedule=bar", "gitOps.enabled=bar", "git.foo=bar", "rotateKeys=bar"},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty configs",
|
||||
args: args{
|
||||
q: &Qliksense{
|
||||
QliksenseHome: testDir,
|
||||
},
|
||||
args: []string{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
tearDown := setup()
|
||||
defer tearDown()
|
||||
@@ -296,7 +204,7 @@ func TestSetConfigs(t *testing.T) {
|
||||
q: &Qliksense{
|
||||
QliksenseHome: testDir,
|
||||
},
|
||||
args: []string{"qliksense.acceptEULA=\"yes\"", "qliksense.mongoDbUri=\"mongo://mongo:3307\""},
|
||||
args: []string{"qliksense[name=acceptEULA]=\"yes\"", "qliksense[name=mongoDbUri]=\"mongo://mongo:3307\""},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
@@ -305,560 +213,9 @@ func TestSetConfigs(t *testing.T) {
|
||||
defer tearDown()
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := tt.args.q.SetConfigs(tt.args.args, false); (err != nil) != tt.wantErr {
|
||||
if err := tt.args.q.SetConfigs(tt.args.args); (err != nil) != tt.wantErr {
|
||||
t.Errorf("SetConfigs() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetImageRegistry(t *testing.T) {
|
||||
getQlikSense := func(tmpQlikSenseHome string) (*Qliksense, error) {
|
||||
if err := ioutil.WriteFile(path.Join(tmpQlikSenseHome, "config.yaml"), []byte(`
|
||||
apiVersion: config.qlik.com/v1
|
||||
kind: QliksenseConfig
|
||||
metadata:
|
||||
name: QliksenseConfigMetadata
|
||||
spec:
|
||||
contexts:
|
||||
- name: qlik-default
|
||||
crFile: contexts/qlik-default/qlik-default.yaml
|
||||
currentContext: qlik-default
|
||||
`), os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defaultContextDir := path.Join(tmpQlikSenseHome, "contexts", "qlik-default")
|
||||
if err := os.MkdirAll(defaultContextDir, os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
version := "foo"
|
||||
manifestsRootDir := fmt.Sprintf("%s/repo/%s", defaultContextDir, version)
|
||||
if err := ioutil.WriteFile(path.Join(defaultContextDir, "qlik-default.yaml"), []byte(fmt.Sprintf(`
|
||||
apiVersion: qlik.com/v1
|
||||
kind: Qliksense
|
||||
metadata:
|
||||
name: qlik-default
|
||||
labels:
|
||||
version: %s
|
||||
spec:
|
||||
profile: docker-desktop
|
||||
manifestsRoot: %s
|
||||
namespace: some-namespace
|
||||
`, version, manifestsRootDir)), os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Qliksense{
|
||||
QliksenseHome: tmpQlikSenseHome,
|
||||
}, nil
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
registry string
|
||||
pushUsername string
|
||||
pushPassword string
|
||||
pullUsername string
|
||||
pullPassword string
|
||||
expectSecretsExist bool
|
||||
}{
|
||||
{
|
||||
name: "no auth",
|
||||
registry: "foobar",
|
||||
pushUsername: "",
|
||||
pushPassword: "",
|
||||
pullUsername: "",
|
||||
pullPassword: "",
|
||||
expectSecretsExist: false,
|
||||
},
|
||||
{
|
||||
name: "auth",
|
||||
registry: "foobar",
|
||||
pushUsername: "foo-push",
|
||||
pushPassword: "bar-push",
|
||||
pullUsername: "foo-pull",
|
||||
pullPassword: "bar-pull",
|
||||
expectSecretsExist: true,
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
tmpQlikSenseHome, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpQlikSenseHome)
|
||||
|
||||
q, err := getQlikSense(tmpQlikSenseHome)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := q.SetImageRegistry(testCase.registry, testCase.pushUsername, testCase.pushPassword,
|
||||
testCase.pullUsername, testCase.pullPassword); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
qConfig := api.NewQConfig(q.QliksenseHome)
|
||||
if testCase.expectSecretsExist {
|
||||
if pushSecret, err := qConfig.GetPushDockerConfigJsonSecret(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
} else if pushSecret.Uri != testCase.registry ||
|
||||
pushSecret.Username != testCase.pushUsername || pushSecret.Password != testCase.pushPassword {
|
||||
t.Fatalf("unexpected push secret content: %v", pushSecret)
|
||||
}
|
||||
if pullSecret, err := qConfig.GetPullDockerConfigJsonSecret(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
} else if pullSecret.Uri != testCase.registry ||
|
||||
pullSecret.Name != "artifactory-docker-secret" ||
|
||||
pullSecret.Username != testCase.pullUsername || pullSecret.Password != testCase.pullPassword {
|
||||
t.Fatalf("unexpected pull secret content: %v", pullSecret)
|
||||
}
|
||||
} else {
|
||||
if _, err := qConfig.GetPushDockerConfigJsonSecret(); err == nil {
|
||||
t.Fatal("unexpected image-registry-push-secret.yaml")
|
||||
} else if _, err := qConfig.GetPullDockerConfigJsonSecret(); err == nil {
|
||||
t.Fatal("unexpected image-registry-pull-secret.yaml")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func removePrivateKey() {
|
||||
err := os.Remove(filepath.Join(testDir, secrets, contexts, qlikDefaultContext, secrets, "user_secret_key"))
|
||||
if err != nil {
|
||||
log.Fatalf("Could not delete private key %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
func Test_PrepareK8sSecret(t *testing.T) {
|
||||
type fields struct {
|
||||
QliksenseHome string
|
||||
}
|
||||
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()
|
||||
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
|
||||
base64 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: "valid secret secrets=false base64 encoded",
|
||||
fields: fields{
|
||||
QliksenseHome: testDir,
|
||||
},
|
||||
args: args{
|
||||
args: []string{"qliksense.mongoDbUri=bW9uZ29kYjovL3FsaWstZGVmYXVsdC1tb25nb2RiOjI3MDE3L3FsaWtzZW5zZT9zc2w9ZmFsc2U="},
|
||||
isSecretSet: false,
|
||||
base64: true,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "test1 valid secret secrets=true",
|
||||
fields: fields{
|
||||
QliksenseHome: testDir,
|
||||
},
|
||||
args: args{
|
||||
args: []string{"qliksense.mongoDbUri=\"mongo://mongo:3307\""},
|
||||
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()
|
||||
_, encryptionKey, err := setupTargetFileAndPrivateKey()
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
defer tearDown()
|
||||
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, tt.args.base64); (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], "\"", "")
|
||||
if tt.args.base64 {
|
||||
d, _ := b64.StdEncoding.DecodeString(testValue)
|
||||
testValue = strings.Trim(string(d), "\n ")
|
||||
}
|
||||
qliksenseCR, err := readCRFile()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Not able to read from context file: %v", err)
|
||||
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()
|
||||
}
|
||||
decryptedVal, err := api.DecryptData([]byte(valToBeEncrypted), encryptionKey)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error occurred while testing decryption: %v", err)
|
||||
log.Printf("No Data in Secret: %v", err)
|
||||
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 != "" {
|
||||
b, err := b64.RawStdEncoding.DecodeString(item.Value)
|
||||
return string(b), err
|
||||
}
|
||||
err := fmt.Errorf("Both Value and ValueFrom are empty")
|
||||
return "", err
|
||||
}
|
||||
|
||||
func setupDeleteContext() func() {
|
||||
if err := os.Mkdir(testDir, 0777); err != nil {
|
||||
log.Printf("\nError occurred: %v", err)
|
||||
}
|
||||
config :=
|
||||
`
|
||||
apiVersion: config.qlik.com/v1
|
||||
kind: QliksenseConfig
|
||||
metadata:
|
||||
name: qliksenseConfig
|
||||
spec:
|
||||
contexts:
|
||||
- name: qlik-default
|
||||
crFile: contexts/qlik-default.yaml
|
||||
- name: qlik1
|
||||
crFile: contexts/qlik1.yaml
|
||||
- name: qlik2
|
||||
crFile: contexts/qlik2.yaml
|
||||
currentContext: qlik1
|
||||
`
|
||||
configFile := filepath.Join(testDir, "config.yaml")
|
||||
// tests/config.yaml exists
|
||||
ioutil.WriteFile(configFile, []byte(config), 0777)
|
||||
contextYaml :=
|
||||
`
|
||||
apiVersion: qlik.com/v1
|
||||
kind: Qliksense
|
||||
metadata:
|
||||
name: qlik-default
|
||||
spec:
|
||||
profile: docker-desktop
|
||||
rotateKeys: "yes"
|
||||
releaseName: qlik-default
|
||||
`
|
||||
qlikDefaultContext := "qlik-default"
|
||||
// create contexts/qlik-default/ under tests/
|
||||
contexts := "contexts"
|
||||
contextsDir1 := filepath.Join(testDir, contexts, qlikDefaultContext)
|
||||
if err := os.MkdirAll(contextsDir1, 0777); err != nil {
|
||||
err = fmt.Errorf("Not able to create directories")
|
||||
log.Fatal(err)
|
||||
}
|
||||
contextYaml1 :=
|
||||
`
|
||||
apiVersion: qlik.com/v1
|
||||
kind: Qliksense
|
||||
metadata:
|
||||
name: qlik1
|
||||
spec:
|
||||
profile: docker-desktop
|
||||
rotateKeys: "yes"
|
||||
releaseName: qlik1`
|
||||
|
||||
contextYaml2 :=
|
||||
`
|
||||
apiVersion: qlik.com/v1
|
||||
kind: Qliksense
|
||||
metadata:
|
||||
name: qlik2
|
||||
spec:
|
||||
profile: docker-desktop
|
||||
rotateKeys: "yes"
|
||||
releaseName: qlik2`
|
||||
|
||||
contextsDir := filepath.Join(testDir, contexts, "qlik1")
|
||||
if err := os.MkdirAll(contextsDir, 0777); err != nil {
|
||||
err = fmt.Errorf("Not able to create directories")
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
contextsDir2 := filepath.Join(testDir, contexts, "qlik2")
|
||||
if err := os.MkdirAll(contextsDir2, 0777); err != nil {
|
||||
err = fmt.Errorf("Not able to create directories")
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
contextFile := filepath.Join(contextsDir, "qlik1.yaml")
|
||||
ioutil.WriteFile(contextFile, []byte(contextYaml1), 0777)
|
||||
|
||||
contextFile2 := filepath.Join(contextsDir2, "qlik2.yaml")
|
||||
ioutil.WriteFile(contextFile2, []byte(contextYaml2), 0777)
|
||||
|
||||
contextFile1 := filepath.Join(contextsDir1, "qlik-default.yaml")
|
||||
ioutil.WriteFile(contextFile1, []byte(contextYaml), 0777)
|
||||
|
||||
tearDown := func() {
|
||||
os.RemoveAll(testDir)
|
||||
}
|
||||
return tearDown
|
||||
}
|
||||
|
||||
func TestDeleteContexts(t *testing.T) {
|
||||
type args struct {
|
||||
qlikSenseHome string
|
||||
contextName string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid context",
|
||||
args: args{
|
||||
qlikSenseHome: testDir,
|
||||
contextName: "qlik2",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "default context",
|
||||
args: args{
|
||||
qlikSenseHome: testDir,
|
||||
contextName: "qlik-default",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "non-existent context",
|
||||
args: args{
|
||||
qlikSenseHome: testDir,
|
||||
contextName: "qlik3",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "current context",
|
||||
args: args{
|
||||
qlikSenseHome: testDir,
|
||||
contextName: "qlik1",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
tearDown := setupDeleteContext()
|
||||
defer tearDown()
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
q := New(tt.args.qlikSenseHome)
|
||||
var arg []string
|
||||
arg = append(arg, tt.args.contextName)
|
||||
if err := q.DeleteContextConfig(arg); (err != nil) != tt.wantErr {
|
||||
t.Errorf("DeleteContext() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
type CrdCommandOptions struct {
|
||||
All bool
|
||||
}
|
||||
|
||||
func (q *Qliksense) ViewCrds(opts *CrdCommandOptions) error {
|
||||
//io.WriteString(os.Stdout, q.GetCRDString())
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
fmt.Println("cannot get the current-context cr", err)
|
||||
return err
|
||||
}
|
||||
engineCRD, err := getQliksenseInitCrd(qcr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
customCrd, err := getCustomCrd(qcr)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println(engineCRD)
|
||||
if customCrd != "" {
|
||||
fmt.Println("---")
|
||||
fmt.Println(customCrd)
|
||||
}
|
||||
|
||||
if opts.All {
|
||||
fmt.Println("---")
|
||||
fmt.Printf("%s", q.GetOperatorCRDString())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) InstallCrds(opts *CrdCommandOptions) error {
|
||||
// install qliksense-init crd
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
fmt.Println("cannot get the current-context cr", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if engineCRD, err := getQliksenseInitCrd(qcr); err != nil {
|
||||
return err
|
||||
} else if err = qapi.KubectlApply(engineCRD, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
if customCrd, err := getCustomCrd(qcr); err != nil {
|
||||
return err
|
||||
} else if customCrd != "" {
|
||||
if err = qapi.KubectlApply(customCrd, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.All { // install opeartor crd
|
||||
if err := qapi.KubectlApply(q.GetOperatorCRDString(), ""); err != nil {
|
||||
fmt.Println("cannot do kubectl apply on opeartor CRD", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getQliksenseInitCrd(qcr *qapi.QliksenseCR) (string, error) {
|
||||
var repoPath string
|
||||
var err error
|
||||
|
||||
if qcr.Spec.GetManifestsRoot() != "" {
|
||||
repoPath = qcr.Spec.GetManifestsRoot()
|
||||
} else {
|
||||
if repoPath, err = DownloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
qInitMsPath := filepath.Join(repoPath, Q_INIT_CRD_PATH)
|
||||
if _, err := os.Lstat(qInitMsPath); err != nil {
|
||||
// older version of qliksense-init used
|
||||
qInitMsPath = filepath.Join(repoPath, "manifests/base/manifests/qliksense-init")
|
||||
}
|
||||
qInitByte, err := ExecuteKustomizeBuild(qInitMsPath)
|
||||
if err != nil {
|
||||
fmt.Println("cannot generate crds for qliksense-init", err)
|
||||
return "", err
|
||||
}
|
||||
return string(qInitByte), nil
|
||||
}
|
||||
|
||||
func getCustomCrd(qcr *qapi.QliksenseCR) (string, error) {
|
||||
crdPath := qcr.GetCustomCrdsPath()
|
||||
if crdPath == "" {
|
||||
return "", nil
|
||||
}
|
||||
qInitByte, err := ExecuteKustomizeBuild(crdPath)
|
||||
if err != nil {
|
||||
fmt.Println("cannot generate custom crds", err)
|
||||
return "", err
|
||||
}
|
||||
return string(qInitByte), nil
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
kapi_config "github.com/qlik-oss/k-apis/pkg/config"
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
func TestGetQliksenseInitCrd(t *testing.T) {
|
||||
someTmpRepoPath, err := DownloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
crdFromContextConfig, err := getQliksenseInitCrd(&qapi.QliksenseCR{
|
||||
KApiCr: kapi_config.KApiCr{
|
||||
Spec: &kapi_config.CRSpec{
|
||||
ManifestsRoot: someTmpRepoPath,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
crdFromDownloadedConfig, err := getQliksenseInitCrd(&qapi.QliksenseCR{
|
||||
KApiCr: kapi_config.KApiCr{
|
||||
Spec: &kapi_config.CRSpec{
|
||||
ManifestsRoot: "",
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if crdFromContextConfig != crdFromDownloadedConfig {
|
||||
t.Fatalf("expected %v to equal %v, but they didn't", crdFromContextConfig, crdFromDownloadedConfig)
|
||||
}
|
||||
}
|
||||
@@ -1,273 +1,236 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/v5/copy"
|
||||
"github.com/containers/image/v5/signature"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
imageTypes "github.com/containers/image/v5/types"
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
"github.com/docker/cli/cli/command"
|
||||
cliflags "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/docker/registry"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type imageNameParts struct {
|
||||
name string
|
||||
tag string
|
||||
}
|
||||
|
||||
const (
|
||||
imagesDirName = "images"
|
||||
imageIndexDirName = "index"
|
||||
imageSharedBlobsDirName = "blobs"
|
||||
)
|
||||
|
||||
func (q *Qliksense) PullImages(version, profile string) error {
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
if version != "" {
|
||||
if !qConfig.IsRepoExistForCurrent(version) {
|
||||
if err := q.FetchQK8s(version); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !qcr.IsRepoExist() {
|
||||
return errors.New("ManifestsRoot not found")
|
||||
}
|
||||
if profile != "" {
|
||||
qcr.Spec.Profile = profile
|
||||
if e := qConfig.WriteCR(qcr); e != nil {
|
||||
return e
|
||||
}
|
||||
}
|
||||
return q.PullImagesForCurrentCR()
|
||||
}
|
||||
|
||||
// PullImages ...
|
||||
func (q *Qliksense) PullImagesForCurrentCR() error {
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
version := qcr.GetLabelFromCr("version")
|
||||
profile := qcr.Spec.Profile
|
||||
repoDir := qcr.Spec.ManifestsRoot
|
||||
func (q *Qliksense) PullImages(gitRef, profile string, engine bool) error {
|
||||
var (
|
||||
image, versionFile, imagesDir, homeDir string
|
||||
err error
|
||||
versionOut *VersionOutput
|
||||
)
|
||||
println("getting images list...")
|
||||
|
||||
imagesDir, err := setupImagesDir(q.QliksenseHome)
|
||||
if err != nil {
|
||||
// TODO: get getref and profile from config/cr for About function call
|
||||
if versionOut, err = q.About(gitRef, profile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
versionOut, stored, err := q.readOrGenerateVersionOutput(imagesDir, version, repoDir, profile)
|
||||
if err != nil {
|
||||
if homeDir, err = homedir.Dir(); err != nil {
|
||||
return err
|
||||
}
|
||||
imagesDir = filepath.Join(homeDir, ".qliksense", "images")
|
||||
os.MkdirAll(imagesDir, os.ModePerm)
|
||||
versionFile = filepath.Join(imagesDir, versionOut.QliksenseVersion)
|
||||
|
||||
images := versionOut.Images
|
||||
if err := q.appendAdditionalImages(&images, qcr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, image := range images {
|
||||
if err := pullImage(image, imagesDir); err != nil {
|
||||
fmt.Printf("%v\n", err)
|
||||
return err
|
||||
}
|
||||
fmt.Print("---\n")
|
||||
}
|
||||
|
||||
if version != "" && !stored {
|
||||
if err := q.writeVersionOutput(versionOut, imagesDir, version); err != nil {
|
||||
return err
|
||||
if _, err = os.Stat(versionFile); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if yamlVersion, err := yaml.Marshal(versionOut); err != nil {
|
||||
return err
|
||||
} else if err = ioutil.WriteFile(versionFile, yamlVersion, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return errors.Errorf("Unable to determine About file %v exists", versionFile)
|
||||
}
|
||||
}
|
||||
for _, image = range versionOut.Images {
|
||||
if _, err = q.PullImage(image, engine); err != nil {
|
||||
fmt.Print(err)
|
||||
}
|
||||
println("---")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) appendGitOpsImage(images *[]string, qcr *qapi.QliksenseCR) {
|
||||
if qcr.Spec.GitOps != nil && qcr.Spec.GitOps.Image != "" {
|
||||
*images = append(*images, qcr.Spec.GitOps.Image)
|
||||
// PullImage ...
|
||||
func (q *Qliksense) PullImage(imageName string, engine bool) (map[string]string, error) {
|
||||
if engine {
|
||||
return q.pullDockerImage(imageName)
|
||||
}
|
||||
return q.pullImage(imageName)
|
||||
}
|
||||
|
||||
func (q *Qliksense) appendPreflightImages(images *[]string) {
|
||||
pf := qapi.NewPreflightConfig(q.QliksenseHome)
|
||||
for _, preflightImage := range pf.GetImageMap() {
|
||||
*images = append(*images, preflightImage)
|
||||
func (q *Qliksense) commandTimeoutContext(commandTimeout time.Duration) (context.Context, context.CancelFunc) {
|
||||
ctx := context.Background()
|
||||
var cancel context.CancelFunc = func() {}
|
||||
if commandTimeout > 0 {
|
||||
ctx, cancel = context.WithTimeout(ctx, commandTimeout)
|
||||
}
|
||||
return ctx, cancel
|
||||
}
|
||||
|
||||
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 (q *Qliksense) pullImage(imageName string) (map[string]string, error) {
|
||||
var (
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
srcRef, destRef imageTypes.ImageReference
|
||||
blobDir, targetDir, homeDir string
|
||||
segments []string
|
||||
nameTag []string
|
||||
err error
|
||||
policyContext *signature.PolicyContext
|
||||
)
|
||||
ctx, cancel = q.commandTimeoutContext(0)
|
||||
defer cancel()
|
||||
|
||||
func pullImage(image, imagesDir string) error {
|
||||
srcRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%v", image))
|
||||
if err != nil {
|
||||
return err
|
||||
if srcRef, err = alltransports.ParseImageName("docker://" + imageName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nameTag := getImageNameParts(image)
|
||||
targetDir := filepath.Join(imagesDir, imageIndexDirName, nameTag.name, nameTag.tag)
|
||||
if err := os.MkdirAll(targetDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
segments = strings.Split(imageName, "/")
|
||||
nameTag = strings.Split(segments[len(segments)-1], ":")
|
||||
if len(nameTag) < 2 {
|
||||
nameTag = append(nameTag, "latest")
|
||||
}
|
||||
if homeDir, err = homedir.Dir(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
targetDir = filepath.Join(homeDir, ".qliksense", "images", nameTag[0], nameTag[1])
|
||||
|
||||
fmt.Printf("==> Pulling image %v:%v", nameTag[0], nameTag[1])
|
||||
fmt.Println()
|
||||
os.MkdirAll(targetDir, os.ModePerm)
|
||||
blobDir = filepath.Join(homeDir, ".qliksense", "blobs")
|
||||
os.MkdirAll(blobDir, os.ModePerm)
|
||||
|
||||
if destRef, err = alltransports.ParseImageName("oci:" + targetDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
destRef, err := alltransports.ParseImageName(fmt.Sprintf("oci:%v", targetDir))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
policyContext, err := signature.NewPolicyContext(&signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}})
|
||||
if err != nil {
|
||||
return err
|
||||
if policyContext, err = signature.NewPolicyContext(&signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer policyContext.Destroy()
|
||||
|
||||
fmt.Printf("==> Pulling image from %v\n", srcRef.StringWithinTransport())
|
||||
if _, err := copy.Image(context.Background(), policyContext, destRef, srcRef, ©.Options{
|
||||
_, err = copy.Image(ctx, policyContext, destRef, srcRef, ©.Options{
|
||||
ReportWriter: os.Stdout,
|
||||
SourceCtx: &imageTypes.SystemContext{
|
||||
ArchitectureChoice: "amd64",
|
||||
OSChoice: "linux",
|
||||
},
|
||||
DestinationCtx: &imageTypes.SystemContext{
|
||||
OCISharedBlobDirPath: filepath.Join(imagesDir, imageSharedBlobsDirName),
|
||||
OCISharedBlobDirPath: blobDir,
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
func (q *Qliksense) pullDockerImage(imageName string) (map[string]string, error) {
|
||||
var (
|
||||
cli *command.DockerCli
|
||||
dockerOutput io.Writer
|
||||
response io.ReadCloser
|
||||
pullOptions types.ImagePullOptions
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
ref reference.Named
|
||||
repoInfo *registry.RepositoryInfo
|
||||
authConfig types.AuthConfig
|
||||
encodedAuth string
|
||||
termFd uintptr
|
||||
err error
|
||||
)
|
||||
ctx, cancel = q.commandTimeoutContext(0)
|
||||
defer cancel()
|
||||
|
||||
if cli, err = command.NewDockerCli(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil
|
||||
|
||||
if err = cli.Initialize(cliflags.NewClientOptions()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ref, err = reference.ParseNormalizedNamed(imageName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if repoInfo, err = registry.ParseRepositoryInfo(ref); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authConfig = command.ResolveAuthConfig(ctx, cli, repoInfo.Index)
|
||||
if encodedAuth, err = command.EncodeAuthToBase64(authConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pullOptions = types.ImagePullOptions{
|
||||
RegistryAuth: encodedAuth,
|
||||
}
|
||||
|
||||
if response, err = cli.Client().ImagePull(ctx, imageName, pullOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Close()
|
||||
|
||||
dockerOutput = ioutil.Discard
|
||||
// if b.IsVerbose() {
|
||||
// dockerOutput = b.Out
|
||||
// }
|
||||
dockerOutput = os.Stdout
|
||||
termFd, _ = term.GetFdInfo(dockerOutput)
|
||||
// Setting this to false here because Moby os.Exit(1) all over the place and this fails on WSL (only)
|
||||
// when Term is true.
|
||||
isTerm := false
|
||||
if err = jsonmessage.DisplayJSONMessagesStream(response, dockerOutput, termFd, isTerm, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inspectData, _, err := cli.Client().ImageInspectWithRaw(ctx, imageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return inspectData.ContainerConfig.Labels, nil
|
||||
}
|
||||
|
||||
// TagAndPushImages ...
|
||||
func (q *Qliksense) PushImagesForCurrentCR() error {
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
//TagAndPushImages ...
|
||||
func (q *Qliksense) TagAndPushImages(registry string, engine bool) error {
|
||||
var (
|
||||
image string
|
||||
err error
|
||||
yamlVersion string
|
||||
images VersionOutput
|
||||
)
|
||||
|
||||
if err = yaml.Unmarshal([]byte(yamlVersion), &images); err != nil {
|
||||
return err
|
||||
}
|
||||
version := qcr.GetLabelFromCr("version")
|
||||
profile := qcr.Spec.Profile
|
||||
repoDir := qcr.Spec.ManifestsRoot
|
||||
|
||||
dockerConfigJsonSecret, err := qConfig.GetPushDockerConfigJsonSecret()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
dockerConfigJsonSecret = &qapi.DockerConfigJsonSecret{
|
||||
Uri: qcr.Spec.GetImageRegistry(),
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
imagesDir, err := setupImagesDir(q.QliksenseHome)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
versionOut, stored, err := q.readOrGenerateVersionOutput(imagesDir, version, repoDir, profile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
images := versionOut.Images
|
||||
if err := q.appendAdditionalImages(&images, qcr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, image := range images {
|
||||
if err = pushImage(image, imagesDir, dockerConfigJsonSecret); err != nil {
|
||||
fmt.Printf("%v\n", err)
|
||||
return err
|
||||
}
|
||||
fmt.Print("---\n")
|
||||
}
|
||||
|
||||
if version != "" && !stored {
|
||||
if err := q.writeVersionOutput(versionOut, imagesDir, version); err != nil {
|
||||
return err
|
||||
for _, image = range images.Images {
|
||||
if err = q.TagAndPush(image, registry, engine); err != nil {
|
||||
fmt.Print(err)
|
||||
}
|
||||
println("---")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) appendAdditionalImages(images *[]string, qcr *qapi.QliksenseCR) error {
|
||||
if err := q.appendOperatorImages(images); err != nil {
|
||||
return err
|
||||
}
|
||||
q.appendGitOpsImage(images, qcr)
|
||||
q.appendPreflightImages(images)
|
||||
return nil
|
||||
}
|
||||
|
||||
func pushImage(image, imagesDir string, dockerConfigJsonSecret *qapi.DockerConfigJsonSecret) error {
|
||||
imageNameParts := getImageNameParts(image)
|
||||
srcDir := filepath.Join(imagesDir, imageIndexDirName, imageNameParts.name, imageNameParts.tag)
|
||||
if exists, err := directoryExists(srcDir); err != nil {
|
||||
return err
|
||||
} else if !exists {
|
||||
if err := pullImage(image, imagesDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
srcRef, err := alltransports.ParseImageName(fmt.Sprintf("oci:%v", srcDir))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newImage := fmt.Sprintf("%v/%v:%v", dockerConfigJsonSecret.Uri, imageNameParts.name, imageNameParts.tag)
|
||||
destRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%v", newImage))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
policyContext, err := signature.NewPolicyContext(&signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer policyContext.Destroy()
|
||||
|
||||
destinationCtx := &imageTypes.SystemContext{
|
||||
DockerInsecureSkipTLSVerify: imageTypes.OptionalBoolTrue,
|
||||
}
|
||||
if dockerConfigJsonSecret.Username != "" {
|
||||
destinationCtx.DockerAuthConfig = &imageTypes.DockerAuthConfig{
|
||||
Username: dockerConfigJsonSecret.Username,
|
||||
Password: dockerConfigJsonSecret.Password,
|
||||
}
|
||||
}
|
||||
fmt.Printf("==> Pushing image to: %v\n", destRef.StringWithinTransport())
|
||||
if _, err = copy.Image(context.Background(), policyContext, destRef, srcRef, ©.Options{
|
||||
ReportWriter: os.Stdout,
|
||||
SourceCtx: &imageTypes.SystemContext{
|
||||
OCISharedBlobDirPath: filepath.Join(imagesDir, imageSharedBlobsDirName),
|
||||
},
|
||||
DestinationCtx: destinationCtx,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func directoryExists(path string) (exists bool, err error) {
|
||||
func (q *Qliksense) directoryExists(path string) (exists bool, err error) {
|
||||
if info, err := os.Stat(path); err != nil && os.IsNotExist(err) {
|
||||
exists = false
|
||||
err = nil
|
||||
@@ -282,65 +245,166 @@ func directoryExists(path string) (exists bool, err error) {
|
||||
return exists, err
|
||||
}
|
||||
|
||||
func getImageNameParts(image string) imageNameParts {
|
||||
segments := strings.Split(image, "/")
|
||||
nameTag := strings.Split(segments[len(segments)-1], ":")
|
||||
//TagAndPush ...
|
||||
func (q *Qliksense) TagAndPush(image string, registryName string, engine bool) error {
|
||||
if engine {
|
||||
return q.tagAndDockerPush(image, registryName)
|
||||
}
|
||||
return q.tagAndPush(image, registryName)
|
||||
}
|
||||
|
||||
func (q *Qliksense) tagAndPush(image string, registryName string) error {
|
||||
var (
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
srcRef, destRef imageTypes.ImageReference
|
||||
blobDir, srcDir, homeDir, newName string
|
||||
segments []string
|
||||
nameTag []string
|
||||
err error
|
||||
policyContext *signature.PolicyContext
|
||||
srcExists bool
|
||||
)
|
||||
ctx, cancel = q.commandTimeoutContext(0)
|
||||
defer cancel()
|
||||
|
||||
segments = strings.Split(image, "/")
|
||||
nameTag = strings.Split(segments[len(segments)-1], ":")
|
||||
|
||||
if len(nameTag) < 2 {
|
||||
nameTag = append(nameTag, "latest")
|
||||
}
|
||||
return imageNameParts{
|
||||
name: nameTag[0],
|
||||
tag: nameTag[1],
|
||||
}
|
||||
}
|
||||
|
||||
func setupImagesDir(qliksenseHome string) (string, error) {
|
||||
imagesDir := filepath.Join(qliksenseHome, imagesDirName)
|
||||
|
||||
imageIndexDir := filepath.Join(imagesDir, imageIndexDirName)
|
||||
if err := os.MkdirAll(imageIndexDir, os.ModePerm); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
sharedBlobsDir := filepath.Join(imagesDir, imageSharedBlobsDirName)
|
||||
if err := os.MkdirAll(sharedBlobsDir, os.ModePerm); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return imagesDir, nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) readOrGenerateVersionOutput(imagesDir, version, repoDir, profile string) (versionOut *VersionOutput, stored bool, err error) {
|
||||
if version != "" {
|
||||
versionOut, err = q.readVersionOutput(imagesDir, version)
|
||||
if versionOut != nil {
|
||||
stored = true
|
||||
}
|
||||
}
|
||||
if versionOut == nil {
|
||||
if versionOut, err = q.AboutDir(repoDir, profile); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
return versionOut, stored, nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) readVersionOutput(imagesDir, version string) (*VersionOutput, error) {
|
||||
var versionOut VersionOutput
|
||||
versionFile := filepath.Join(imagesDir, version)
|
||||
if versionOutBytes, err := ioutil.ReadFile(versionFile); err != nil {
|
||||
return nil, err
|
||||
} else if err = yaml.Unmarshal(versionOutBytes, &versionOut); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &versionOut, nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) writeVersionOutput(versionOut *VersionOutput, imagesDir, version string) error {
|
||||
versionFile := filepath.Join(imagesDir, version)
|
||||
if versionOutBytes, err := yaml.Marshal(versionOut); err != nil {
|
||||
if homeDir, err = homedir.Dir(); err != nil {
|
||||
return err
|
||||
} else if err = ioutil.WriteFile(versionFile, versionOutBytes, os.ModePerm); err != nil {
|
||||
}
|
||||
srcDir = filepath.Join(homeDir, ".qliksense", "images", nameTag[0], nameTag[1])
|
||||
if srcExists, err = q.directoryExists(srcDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if !srcExists {
|
||||
if _, err = q.PullImage(image, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if srcRef, err = alltransports.ParseImageName("oci:" + srcDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if segments[0] == "docker.io" {
|
||||
image = strings.Join(segments[1:], "/")
|
||||
}
|
||||
newName = "//" + registryName + "/" + segments[len(segments)-1]
|
||||
|
||||
fmt.Printf("==> Tag and push image to %v", newName)
|
||||
fmt.Println()
|
||||
|
||||
if destRef, err = alltransports.ParseImageName("docker:" + newName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if policyContext, err = signature.NewPolicyContext(&signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}}); err != nil {
|
||||
return err
|
||||
}
|
||||
defer policyContext.Destroy()
|
||||
|
||||
blobDir = filepath.Join(homeDir, ".qliksense", "blobs")
|
||||
os.MkdirAll(blobDir, os.ModePerm)
|
||||
|
||||
_, err = copy.Image(ctx, policyContext, destRef, srcRef, ©.Options{
|
||||
ReportWriter: os.Stdout,
|
||||
SourceCtx: &imageTypes.SystemContext{
|
||||
OCISharedBlobDirPath: blobDir,
|
||||
},
|
||||
DestinationCtx: &imageTypes.SystemContext{
|
||||
DockerDaemonInsecureSkipTLSVerify: true,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// PullImage ...
|
||||
func (q *Qliksense) tagAndDockerPush(image string, registryName string) error {
|
||||
var (
|
||||
cli *command.DockerCli
|
||||
dockerOutput io.Writer
|
||||
response io.ReadCloser
|
||||
pushOptions types.ImagePushOptions
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
newName string
|
||||
segments []string
|
||||
imageList []types.ImageSummary
|
||||
imageListOptions types.ImageListOptions
|
||||
filterArgs filters.Args
|
||||
ref reference.Named
|
||||
repoInfo *registry.RepositoryInfo
|
||||
authConfig types.AuthConfig
|
||||
encodedAuth string
|
||||
termFd uintptr
|
||||
err error
|
||||
)
|
||||
// TODO: Create a real cli config context
|
||||
ctx, cancel = q.commandTimeoutContext(0)
|
||||
defer cancel()
|
||||
if cli, err = command.NewDockerCli(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = cli.Initialize(cliflags.NewClientOptions()); err != nil {
|
||||
return err
|
||||
}
|
||||
segments = strings.Split(image, "/")
|
||||
if segments[0] == "docker.io" {
|
||||
image = strings.Join(segments[1:], "/")
|
||||
}
|
||||
newName = registryName + "/" + segments[len(segments)-1]
|
||||
|
||||
filterArgs = filters.NewArgs()
|
||||
filterArgs.Add("reference", image)
|
||||
imageListOptions = types.ImageListOptions{
|
||||
Filters: filterArgs,
|
||||
}
|
||||
if imageList, err = cli.Client().ImageList(ctx, imageListOptions); err != nil {
|
||||
return err
|
||||
}
|
||||
if imageList == nil || len(imageList) <= 0 {
|
||||
fmt.Printf("Use `qliksense pull`, to pull %v for an air gap push", newName)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = cli.Client().ImageTag(ctx, image, newName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ref, err = reference.ParseNormalizedNamed(image); err != nil {
|
||||
return err
|
||||
}
|
||||
if repoInfo, err = registry.ParseRepositoryInfo(ref); err != nil {
|
||||
return err
|
||||
}
|
||||
authConfig = command.ResolveAuthConfig(ctx, cli, repoInfo.Index)
|
||||
if encodedAuth, err = command.EncodeAuthToBase64(authConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
pushOptions = types.ImagePushOptions{
|
||||
All: true,
|
||||
RegistryAuth: encodedAuth,
|
||||
}
|
||||
|
||||
if response, err = cli.Client().ImagePush(ctx, newName, pushOptions); err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Close()
|
||||
|
||||
dockerOutput = ioutil.Discard
|
||||
// if b.IsVerbose() {
|
||||
// dockerOutput = b.Out
|
||||
// }
|
||||
dockerOutput = os.Stdout
|
||||
termFd, _ = term.GetFdInfo(dockerOutput)
|
||||
// Setting this to false here because Moby os.Exit(1) all over the place and this fails on WSL (only)
|
||||
// when Term is true.
|
||||
isTerm := false
|
||||
if err = jsonmessage.DisplayJSONMessagesStream(response, dockerOutput, termFd, isTerm, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -1,617 +0,0 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
|
||||
"github.com/containers/image/v5/copy"
|
||||
"github.com/containers/image/v5/signature"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
imageTypes "github.com/containers/image/v5/types"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func Test_locateDockerRegistryBinary(t *testing.T) {
|
||||
binary, err := locateDockerRegistryBinary()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
cmd := exec.Command(binary, "--version")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
t.Logf("output: %v\n", string(out))
|
||||
}
|
||||
|
||||
func Test_getSelfSignedCertAndKey(t *testing.T) {
|
||||
host := "andriy.registry.com"
|
||||
validity := time.Hour * 24 * 365
|
||||
selfSignedCert, key, err := getSelfSignedCertAndKey(host, validity)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
fmt.Print(string(selfSignedCert))
|
||||
fmt.Print(string(key))
|
||||
}
|
||||
|
||||
type clientAuthType byte
|
||||
|
||||
const (
|
||||
clientAuthNotProvided clientAuthType = iota
|
||||
clientAuthProvided
|
||||
clientAuthProvidedButIncorrect
|
||||
)
|
||||
|
||||
func Test_Pull_Push_ImagesForCurrentCR(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping pull/push tests in short mode")
|
||||
}
|
||||
var testCases = []struct {
|
||||
name string
|
||||
registryAuth bool
|
||||
clientAuth clientAuthType
|
||||
expectPushSuccess bool
|
||||
}{
|
||||
{
|
||||
name: "registry does not require auth and we do not provide auth",
|
||||
registryAuth: false,
|
||||
clientAuth: clientAuthNotProvided,
|
||||
expectPushSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "registry does not require auth but we provide auth",
|
||||
registryAuth: false,
|
||||
clientAuth: clientAuthProvided,
|
||||
expectPushSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "registry requires auth but we do not provide auth",
|
||||
registryAuth: true,
|
||||
clientAuth: clientAuthNotProvided,
|
||||
expectPushSuccess: false,
|
||||
},
|
||||
{
|
||||
name: "registry requires auth but we provide wrong auth",
|
||||
registryAuth: true,
|
||||
clientAuth: clientAuthProvidedButIncorrect,
|
||||
expectPushSuccess: false,
|
||||
},
|
||||
{
|
||||
name: "registry requires auth and we provide auth",
|
||||
registryAuth: true,
|
||||
clientAuth: clientAuthProvided,
|
||||
expectPushSuccess: true,
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
registryURI := "127.0.0.1:5555"
|
||||
registry, err := setupRegistryV2At(registryURI, testCase.registryAuth)
|
||||
if registry != nil {
|
||||
defer func() {
|
||||
registry.Close()
|
||||
//fmt.Printf("registry stdout:\n%v\n", registry.stdOutBuffer.String())
|
||||
//fmt.Printf("registry stderr:\n%v\n", registry.stdErrBuffer.String())
|
||||
}()
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error setting up local registry: %v", err)
|
||||
}
|
||||
|
||||
tmpQlikSenseHome, err := ioutil.TempDir("", "tmp-qlik-sense-home-")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating tmp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpQlikSenseHome)
|
||||
|
||||
if err := setupQlikSenseHome(t, tmpQlikSenseHome, registry, testCase.clientAuth); err != nil {
|
||||
t.Fatalf("unexpected error setting up qliksense home: %v", err)
|
||||
}
|
||||
q := &Qliksense{
|
||||
QliksenseHome: tmpQlikSenseHome,
|
||||
CrdBox: &packr.Box{},
|
||||
}
|
||||
var versionOut VersionOutput
|
||||
|
||||
if err := q.PullImagesForCurrentCR(); err != nil {
|
||||
t.Fatalf("unexpected pull error: %v", err)
|
||||
} else if versionOutBytes, err := ioutil.ReadFile(path.Join(tmpQlikSenseHome, "images", "foo")); err != nil {
|
||||
t.Fatalf("unexpected error reading version file: %v", err)
|
||||
} else if err = yaml.Unmarshal(versionOutBytes, &versionOut); err != nil {
|
||||
t.Fatalf("unexpected error unmarshalling version file: %v", err)
|
||||
} else if len(versionOut.Images) != 1 || versionOut.Images[0] != "alpine:latest" {
|
||||
t.Fatal(`did not find "alpine:latest"" in the version file`)
|
||||
} else if infos, err := ioutil.ReadDir(path.Join(tmpQlikSenseHome, "images", "index", "alpine", "latest")); err != nil || len(infos) == 0 {
|
||||
t.Fatal("expected images/index/alpine/latest directory to be non-empty")
|
||||
} else if blobInfos, err := ioutil.ReadDir(path.Join(tmpQlikSenseHome, "images", "blobs", "sha256")); err != nil || len(blobInfos) == 0 {
|
||||
t.Fatal("expected images/blobs/sha256 directory to be non-empty")
|
||||
}
|
||||
|
||||
if testCase.expectPushSuccess {
|
||||
if err := q.PushImagesForCurrentCR(); err != nil {
|
||||
t.Fatalf("unexpected push error: %v", err)
|
||||
} else if tmpImagesDir, err := ioutil.TempDir("", "tmp-images-"); err != nil {
|
||||
t.Fatalf("unexpected error creating tmp dir: %v", err)
|
||||
} else if err := testPullImage(fmt.Sprintf("%s/alpine:latest", registryURI), tmpImagesDir, registry); err != nil {
|
||||
t.Fatalf("unexpected error pulling alpine:latest from the local registry: %v", err)
|
||||
} else if infos, err := ioutil.ReadDir(path.Join(tmpImagesDir, "index", "alpine", "latest")); err != nil || len(infos) == 0 {
|
||||
t.Fatal("expected index/alpine/latest directory to be non-empty")
|
||||
} else if blobInfos, err := ioutil.ReadDir(path.Join(tmpImagesDir, "blobs", "sha256")); err != nil || len(blobInfos) == 0 {
|
||||
t.Fatal("expected blobs/sha256 directory to be non-empty")
|
||||
}
|
||||
} else {
|
||||
if err := q.PushImagesForCurrentCR(); err == nil {
|
||||
t.Fatal("unexpected push success")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_appendAdditionalImages(t *testing.T) {
|
||||
tmpQlikSenseHome, err := ioutil.TempDir("", "tmp-qlik-sense-home-")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating tmp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpQlikSenseHome)
|
||||
|
||||
setupQliksenseTestDefaultContext(t, tmpQlikSenseHome, `
|
||||
apiVersion: qlik.com/v1
|
||||
kind: Qliksense
|
||||
metadata:
|
||||
name: qlik-default
|
||||
spec:
|
||||
gitOps:
|
||||
image: some-gitops-image
|
||||
`)
|
||||
|
||||
q := &Qliksense{
|
||||
QliksenseHome: tmpQlikSenseHome,
|
||||
CrdBox: packr.New("crds", "./crds"),
|
||||
}
|
||||
|
||||
pf := api.NewPreflightConfig(q.QliksenseHome)
|
||||
if err := pf.Initialize(); err != nil {
|
||||
t.Fatalf("unexpected error initializing preflight: %v", err)
|
||||
}
|
||||
|
||||
qConfig := api.NewQConfig(q.QliksenseHome)
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error getting current CR: %v", err)
|
||||
}
|
||||
|
||||
images := make([]string, 0)
|
||||
if err := q.appendAdditionalImages(&images, qcr); err != nil {
|
||||
t.Fatalf("unexpected error appending additional images: %v", err)
|
||||
}
|
||||
|
||||
expectedNumberAdditionalImages := 5
|
||||
if len(images) != expectedNumberAdditionalImages {
|
||||
t.Fatalf("unexpected number of additional images: %v, expected: %v", len(images), expectedNumberAdditionalImages)
|
||||
}
|
||||
|
||||
haveMatchingImage := func(test func(string) bool) bool {
|
||||
for _, image := range images {
|
||||
if test(image) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if !haveMatchingImage(func(image string) bool {
|
||||
return strings.Contains(image, "qlik-docker-oss.bintray.io/qliksense-operator:v")
|
||||
}) {
|
||||
t.Fatal("expected to find the operator image in the list, but it wasn't there")
|
||||
}
|
||||
if !haveMatchingImage(func(image string) bool {
|
||||
return image == "some-gitops-image"
|
||||
}) {
|
||||
t.Fatal("expected to find the GitOps image in the list, but it wasn't there")
|
||||
}
|
||||
if !haveMatchingImage(func(image string) bool {
|
||||
return image == "nginx"
|
||||
}) {
|
||||
t.Fatal("expected to find the nginx Preflight image in the list, but it wasn't there")
|
||||
}
|
||||
if !haveMatchingImage(func(image string) bool {
|
||||
return image == "subfuzion/netcat"
|
||||
}) {
|
||||
t.Fatal("expected to find the netcat Preflight image in the list, but it wasn't there")
|
||||
}
|
||||
if !haveMatchingImage(func(image string) bool {
|
||||
return image == "mongo"
|
||||
}) {
|
||||
t.Fatal("expected to find the mongo Preflight image in the list, but it wasn't there")
|
||||
}
|
||||
}
|
||||
|
||||
func setupQlikSenseHome(t *testing.T, tmpQlikSenseHome string, registry *testRegistryV2, clientAuth clientAuthType) error {
|
||||
version := "foo"
|
||||
manifestsRootDir := filepath.ToSlash(path.Join(tmpQlikSenseHome, "contexts", "qlik-default", "repo", version))
|
||||
cr := fmt.Sprintf(`
|
||||
apiVersion: qlik.com/v1
|
||||
kind: Qliksense
|
||||
metadata:
|
||||
name: qlik-default
|
||||
labels:
|
||||
version: %s
|
||||
spec:
|
||||
profile: docker-desktop
|
||||
configs:
|
||||
qliksense:
|
||||
- name: imageRegistry
|
||||
value: %s
|
||||
manifestsRoot: %s
|
||||
rotateKeys: "yes"
|
||||
releaseName: qlik-default
|
||||
`, version, registry.url, manifestsRootDir)
|
||||
setupQliksenseTestDefaultContext(t, tmpQlikSenseHome, cr)
|
||||
|
||||
if clientAuth == clientAuthProvided || clientAuth == clientAuthProvidedButIncorrect {
|
||||
if registry.username == "" || clientAuth == clientAuthProvidedButIncorrect {
|
||||
registry.username = "bad"
|
||||
}
|
||||
if registry.password == "" || clientAuth == clientAuthProvidedButIncorrect {
|
||||
registry.password = "worse"
|
||||
}
|
||||
qConfig := api.NewQConfig(tmpQlikSenseHome)
|
||||
if err := qConfig.SetPushDockerConfigJsonSecret(&api.DockerConfigJsonSecret{
|
||||
Uri: registry.url,
|
||||
Username: registry.username,
|
||||
Password: registry.password,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
profileDir := path.Join(manifestsRootDir, "manifests", "docker-desktop")
|
||||
if err := os.MkdirAll(profileDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(profileDir, "kustomization.yaml"), []byte(`
|
||||
resources:
|
||||
- deployment.yaml
|
||||
`), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(profileDir, "deployment.yaml"), []byte(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: the-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: the-container
|
||||
image: alpine:latest
|
||||
`), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
transformersDir := path.Join(manifestsRootDir, "transformers")
|
||||
if err := os.MkdirAll(transformersDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(transformersDir, "qseokversion.yaml"), []byte(`
|
||||
apiVersion: qlik.com/v1
|
||||
kind: SelectivePatch
|
||||
metadata:
|
||||
name: qseokversion
|
||||
enabled: true
|
||||
patches:
|
||||
- target:
|
||||
kind: HelmChart
|
||||
labelSelector: name!=qliksense-init
|
||||
patch: |-
|
||||
chartName: qliksense
|
||||
chartVersion: 1.21.23
|
||||
`), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type testRegistryV2 struct {
|
||||
cmd *exec.Cmd
|
||||
url string
|
||||
dir string
|
||||
username string
|
||||
password string
|
||||
email string
|
||||
stdOutBuffer *bytes.Buffer
|
||||
stdErrBuffer *bytes.Buffer
|
||||
}
|
||||
|
||||
func locateDockerRegistryBinary() (string, error) {
|
||||
if exePath, err := exec.LookPath("docker-registry"); err != nil {
|
||||
if cwd, err := os.Getwd(); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return path.Join(cwd, "docker-registry"), nil
|
||||
}
|
||||
} else {
|
||||
return exePath, nil
|
||||
}
|
||||
}
|
||||
|
||||
func setupRegistryV2At(url string, auth bool) (*testRegistryV2, error) {
|
||||
reg, err := newTestRegistryV2At(url, auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wait for registry to be ready to serve requests.
|
||||
for i := 0; i != 50; i++ {
|
||||
if err := reg.Ping("http"); err == nil {
|
||||
fmt.Print("registry http ping succeeded\n")
|
||||
break
|
||||
} else {
|
||||
fmt.Printf("registry http ping error: %v\n", err)
|
||||
}
|
||||
if err := reg.Ping("https"); err == nil {
|
||||
fmt.Print("registry https ping succeeded\n")
|
||||
break
|
||||
} else {
|
||||
fmt.Printf("registry https ping error: %v\n", err)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return reg, errors.New("timeout waiting for test registry to become available")
|
||||
}
|
||||
return reg, nil
|
||||
}
|
||||
|
||||
func newTestRegistryV2At(url string, auth bool) (*testRegistryV2, error) {
|
||||
tmp, err := ioutil.TempDir("", "registry-test-")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
template := `version: 0.1
|
||||
loglevel: info
|
||||
storage:
|
||||
filesystem:
|
||||
rootdirectory: %s
|
||||
delete:
|
||||
enabled: true
|
||||
http:
|
||||
addr: %s
|
||||
%s`
|
||||
var (
|
||||
htpasswd string
|
||||
username string
|
||||
password string
|
||||
email string
|
||||
)
|
||||
var env []string
|
||||
if auth {
|
||||
if certificate, key, err := getSelfSignedCertAndKey("localhost", time.Hour*24); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
certPath := filepath.Join(tmp, "domain.crt")
|
||||
if err := ioutil.WriteFile(certPath, certificate, os.FileMode(0644)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyPath := filepath.Join(tmp, "domain.key")
|
||||
if err := ioutil.WriteFile(keyPath, key, os.FileMode(0644)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
env = append(env, fmt.Sprintf("REGISTRY_HTTP_TLS_CERTIFICATE=%v", certPath))
|
||||
env = append(env, fmt.Sprintf("REGISTRY_HTTP_TLS_KEY=%v", keyPath))
|
||||
}
|
||||
|
||||
htpasswdPath := filepath.Join(tmp, "htpasswd")
|
||||
userpasswd := "testuser:$2y$05$sBsSqk0OpSD1uTZkHXc4FeJ0Z70wLQdAX/82UiHuQOKbNbBrzs63m"
|
||||
username = "testuser"
|
||||
password = "testpassword"
|
||||
email = "test@test.org"
|
||||
if err := ioutil.WriteFile(htpasswdPath, []byte(userpasswd), os.FileMode(0644)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
htpasswd = fmt.Sprintf(`auth:
|
||||
htpasswd:
|
||||
realm: basic-realm
|
||||
path: %s
|
||||
`, htpasswdPath)
|
||||
}
|
||||
confPath := filepath.Join(tmp, "config.yaml")
|
||||
config, err := os.Create(confPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := fmt.Fprintf(config, template, tmp, url, htpasswd); err != nil {
|
||||
os.RemoveAll(tmp)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dockerRegistryBinaryPath, err := locateDockerRegistryBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd := exec.Command(dockerRegistryBinaryPath, "serve", confPath)
|
||||
cmd.Env = env
|
||||
stdOutBuf, stdErrBuf, err := consumeAndLogOutputs(fmt.Sprintf("registry-%s", url), cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
os.RemoveAll(tmp)
|
||||
return nil, err
|
||||
}
|
||||
return &testRegistryV2{
|
||||
cmd: cmd,
|
||||
url: url,
|
||||
dir: tmp,
|
||||
username: username,
|
||||
password: password,
|
||||
email: email,
|
||||
stdOutBuffer: stdOutBuf,
|
||||
stdErrBuffer: stdErrBuf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *testRegistryV2) Ping(protocol string) error {
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
|
||||
resp, err := client.Get(fmt.Sprintf("%v://%s/v2/", protocol, t.url))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusUnauthorized {
|
||||
return fmt.Errorf("registry ping replied with an unexpected status code %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *testRegistryV2) Close() {
|
||||
t.cmd.Process.Kill()
|
||||
os.RemoveAll(t.dir)
|
||||
}
|
||||
|
||||
func consumeAndLogOutputStream(id string, f io.ReadCloser) *bytes.Buffer {
|
||||
buff := &bytes.Buffer{}
|
||||
go func() {
|
||||
defer func() {
|
||||
f.Close()
|
||||
fmt.Fprintf(buff, "[%s]: Closed\n", id)
|
||||
}()
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
fmt.Fprintf(buff, "[%s]: waiting\n", id)
|
||||
n, err := f.Read(buf)
|
||||
fmt.Fprintf(buff, "[%s]: got %d,%#v: %s\n", id, n, err, strings.TrimSuffix(string(buf[:n]), "\n"))
|
||||
if n <= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
return buff
|
||||
}
|
||||
|
||||
// consumeAndLogOutputs causes all output to stdout and stderr from an *exec.Cmd to be logged to c
|
||||
func consumeAndLogOutputs(id string, cmd *exec.Cmd) (*bytes.Buffer, *bytes.Buffer, error) {
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return consumeAndLogOutputStream(id+" stdout", stdout), consumeAndLogOutputStream(id+" stderr", stderr), nil
|
||||
}
|
||||
|
||||
func getSelfSignedCertAndKey(hostname string, validity time.Duration) (certificate, key []byte, err error) {
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("ailed to generate serial number: %s", err)
|
||||
}
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"self-signed"},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(validity),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
DNSNames: []string{hostname},
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create certificate: %s", err)
|
||||
}
|
||||
certificate = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
|
||||
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to marshal private key: %v", err)
|
||||
}
|
||||
key = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
|
||||
|
||||
return certificate, key, nil
|
||||
}
|
||||
|
||||
func testPullImage(image, imagesDir string, registry *testRegistryV2) error {
|
||||
srcRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%v", image))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nameTag := getImageNameParts(image)
|
||||
targetDir := filepath.Join(imagesDir, imageIndexDirName, nameTag.name, nameTag.tag)
|
||||
if err := os.MkdirAll(targetDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
destRef, err := alltransports.ParseImageName(fmt.Sprintf("oci:%v", targetDir))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
policyContext, err := signature.NewPolicyContext(&signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer policyContext.Destroy()
|
||||
|
||||
fmt.Printf("==> Test is pulling image from %v\n", srcRef.StringWithinTransport())
|
||||
sourceCtx := &imageTypes.SystemContext{
|
||||
ArchitectureChoice: "amd64",
|
||||
OSChoice: "linux",
|
||||
DockerInsecureSkipTLSVerify: imageTypes.OptionalBoolTrue,
|
||||
}
|
||||
if registry.username != "" {
|
||||
sourceCtx.DockerAuthConfig = &imageTypes.DockerAuthConfig{
|
||||
Username: registry.username,
|
||||
Password: registry.password,
|
||||
}
|
||||
}
|
||||
if _, err := copy.Image(context.Background(), policyContext, destRef, srcRef, ©.Options{
|
||||
ReportWriter: os.Stdout,
|
||||
SourceCtx: sourceCtx,
|
||||
DestinationCtx: &imageTypes.SystemContext{
|
||||
OCISharedBlobDirPath: filepath.Join(imagesDir, imageSharedBlobsDirName),
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,28 +1,13 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing/transport"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
"github.com/google/uuid"
|
||||
kapis_git "github.com/qlik-oss/k-apis/pkg/git"
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
type FetchCommandOptions struct {
|
||||
GitUrl string
|
||||
AccessToken string
|
||||
Version string
|
||||
SecretName string
|
||||
Overwrite bool
|
||||
}
|
||||
|
||||
const (
|
||||
QLIK_GIT_REPO = "https://github.com/qlik-oss/qliksense-k8s"
|
||||
)
|
||||
@@ -32,125 +17,24 @@ func (q *Qliksense) FetchQK8s(version string) error {
|
||||
return fetchAndUpdateCR(qConfig, version)
|
||||
}
|
||||
|
||||
func (q *Qliksense) FetchK8sWithOpts(opts *FetchCommandOptions) error {
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
cr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.AccessToken != "" {
|
||||
encKey, err := qConfig.GetEncryptionKeyFor(cr.GetName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cr.SetFetchAccessToken(opts.AccessToken, encKey); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if opts.SecretName != "" {
|
||||
cr.SetFetchAccessSecretName(opts.SecretName)
|
||||
}
|
||||
if opts.GitUrl != "" {
|
||||
cr.SetFetchUrl(opts.GitUrl)
|
||||
}
|
||||
v := getVersion(opts, cr)
|
||||
if v == "" {
|
||||
return errors.New("Cannot find gitref/tag/branch/version to fetch")
|
||||
}
|
||||
if qConfig.IsRepoExistForCurrent(v) {
|
||||
if opts.Overwrite || getVerionsOverwriteConfirmation(v) == "y" {
|
||||
if err := qConfig.DeleteRepoForCurrent(v); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// nothing to do
|
||||
return nil
|
||||
}
|
||||
}
|
||||
qConfig.WriteCR(cr)
|
||||
return fetchAndUpdateCR(qConfig, v)
|
||||
}
|
||||
|
||||
// fetchAndUpdateCR fetch
|
||||
func fetchAndUpdateCR(qConfig *qapi.QliksenseConfig, version string) error {
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
fmt.Println("cannot get the current-context cr", err)
|
||||
return err
|
||||
}
|
||||
if version == "" {
|
||||
if qcr.GetLabelFromCr("version") == "" {
|
||||
return errors.New("Cannot find gitref/tag/branch/version to fetch")
|
||||
}
|
||||
version = qcr.GetLabelFromCr("version")
|
||||
}
|
||||
encKey, err := qConfig.GetEncryptionKeyFor(qcr.GetName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// downlaod to temp first
|
||||
tempDest, err := fetchToTempDir(qcr.GetFetchUrl(), version, qcr.GetFetchAccessToken(encKey))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
destDir := qConfig.BuildRepoPath(version)
|
||||
fmt.Printf("fetching version [%s] from %s\n", version, qcr.GetFetchUrl())
|
||||
if err := qapi.CopyDirectory(tempDest, destDir); err != nil {
|
||||
if qConfig.IsRepoExistForCurrent(version) {
|
||||
return nil
|
||||
}
|
||||
destDir := qConfig.BuildRepoPath(version)
|
||||
fmt.Printf("fetching version [%s] from %s\n", version, QLIK_GIT_REPO)
|
||||
|
||||
if repo, err := kapis_git.CloneRepository(destDir, QLIK_GIT_REPO, nil); err != nil {
|
||||
return err
|
||||
} else if err = kapis_git.Checkout(repo, version, fmt.Sprintf("%v-by-operator-%v", version, uuid.New().String()), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
qcr.Spec.ManifestsRoot = qConfig.BuildCurrentManifestsRoot(version)
|
||||
qcr.AddLabelToCr("version", version)
|
||||
return qConfig.WriteCurrentContextCR(qcr)
|
||||
}
|
||||
|
||||
func fetchToTempDir(gitUrl, gitRef, accessToken string) (string, error) {
|
||||
tmpDir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
downloadPath := path.Join(tmpDir, "repo")
|
||||
var auth transport.AuthMethod
|
||||
if accessToken != "" {
|
||||
auth = &http.BasicAuth{
|
||||
Username: "something",
|
||||
Password: accessToken,
|
||||
}
|
||||
}
|
||||
if repo, err := kapis_git.CloneRepository(downloadPath, gitUrl, auth); err != nil {
|
||||
return "", err
|
||||
} else if err := kapis_git.Checkout(repo, gitRef, "", auth); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return downloadPath, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getVersion(opts *FetchCommandOptions, qcr *qapi.QliksenseCR) string {
|
||||
if opts.Version == "" {
|
||||
if qcr.GetLabelFromCr("version") != "" {
|
||||
return qcr.GetLabelFromCr("version")
|
||||
}
|
||||
}
|
||||
return opts.Version
|
||||
}
|
||||
|
||||
func getVerionsOverwriteConfirmation(version string) string {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Println("The version [" + version + "] already exist")
|
||||
cfm := "n"
|
||||
for {
|
||||
fmt.Print("Do you want to delete and fetch again [y/N]: ")
|
||||
cfm, _ = reader.ReadString('\n')
|
||||
cfm = strings.Replace(cfm, "\n", "", -1)
|
||||
cfm = strings.TrimSpace(cfm)
|
||||
if cfm == "" {
|
||||
cfm = "n"
|
||||
}
|
||||
cfm = strings.ToLower(cfm)
|
||||
if cfm == "y" || cfm == "n" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return cfm
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
func TestFetchAndUpdateCR(t *testing.T) {
|
||||
tempHome, _ := ioutil.TempDir("", "")
|
||||
|
||||
q := &Qliksense{
|
||||
QliksenseHome: tempHome,
|
||||
}
|
||||
q.SetUpQliksenseContext("test1")
|
||||
qConfig := qapi.NewQConfig(tempHome)
|
||||
if err := fetchAndUpdateCR(qConfig, "v0.0.2"); err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
actualCrFile := filepath.Join(tempHome, "contexts", "test1", "test1.yaml")
|
||||
cr := &qapi.QliksenseCR{}
|
||||
if err := qapi.ReadFromFile(cr, actualCrFile); err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if cr.Spec.ManifestsRoot != "contexts/test1/qlik-k8s/v0.0.2" {
|
||||
t.Log("actual path: " + cr.Spec.ManifestsRoot + ", expected path: contexts/test1/qlik-k8s/v0.0.2")
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/qlik-oss/k-apis/pkg/git"
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
type LsRemoteCmdOptions struct {
|
||||
IncludeBranches bool
|
||||
Limit int
|
||||
}
|
||||
|
||||
func (q *Qliksense) GetInstallableVersions(opts *LsRemoteCmdOptions) error {
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var repoPath string
|
||||
if qcr.Spec.GetManifestsRoot() != "" {
|
||||
repoPath = qcr.Spec.GetManifestsRoot()
|
||||
} else {
|
||||
repoPath, err = DownloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
r, err := git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remoteRefsList, err := git.GetRemoteRefs(r, nil,
|
||||
&git.RemoteRefConstraints{
|
||||
Include: true,
|
||||
Sort: true,
|
||||
SortOrder: git.RefSortOrderDescending,
|
||||
},
|
||||
&git.RemoteRefConstraints{
|
||||
Include: opts.IncludeBranches,
|
||||
Sort: true,
|
||||
SortOrder: git.RefSortOrderAscending,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(remoteRefsList) < 1 {
|
||||
return errors.New("cannot find git remote information in the config repository")
|
||||
}
|
||||
|
||||
var originRemoteRefs *git.RemoteRefs
|
||||
for _, remoteRefs := range remoteRefsList {
|
||||
if remoteRefs.Name == "origin" {
|
||||
originRemoteRefs = remoteRefs
|
||||
break
|
||||
}
|
||||
}
|
||||
if originRemoteRefs == nil {
|
||||
return errors.New(`cannot find git remote called "origin" in the config repository`)
|
||||
}
|
||||
|
||||
tags := originRemoteRefs.Tags
|
||||
if len(tags) > opts.Limit {
|
||||
tags = tags[:opts.Limit]
|
||||
}
|
||||
fmt.Print("Versions:\n")
|
||||
for _, tag := range tags {
|
||||
fmt.Printf(" %s\n", tag)
|
||||
}
|
||||
if opts.IncludeBranches {
|
||||
branches := originRemoteRefs.Branches
|
||||
if len(branches) > opts.Limit {
|
||||
branches = branches[:opts.Limit]
|
||||
}
|
||||
fmt.Print("Branches:\n")
|
||||
for _, branch := range branches {
|
||||
fmt.Printf(" %s\n", branch)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,24 +1,20 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"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, keepPatchFiles bool) error {
|
||||
func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) error {
|
||||
|
||||
// step1: fetch 1.0.0 # pull down qliksense-k8s@1.0.0
|
||||
// step2: operator view | kubectl apply -f # operator manifest (CRD)
|
||||
@@ -27,187 +23,52 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, kee
|
||||
|
||||
// fetch the version
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
if !keepPatchFiles {
|
||||
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
|
||||
fmt.Printf("error removing temporary changes to the config: %v\n", err)
|
||||
}
|
||||
}
|
||||
fetchAndUpdateCR(qConfig, version)
|
||||
|
||||
//TODO: may need to check if CRD already installed, but doing apply does not hurt for now
|
||||
//install crd into cluster
|
||||
fmt.Println("Installing operator CRD")
|
||||
if err := qapi.KubectlApply(q.GetCRDString()); err != nil {
|
||||
fmt.Println("cannot do kubectl apply on opeartor CRD", err)
|
||||
return err
|
||||
}
|
||||
// install generated manifests into cluster
|
||||
fmt.Println("Installing generated manifests into cluster")
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
fmt.Println("cannot get the current-context cr", err)
|
||||
return err
|
||||
}
|
||||
|
||||
qcr.SetEULA("yes")
|
||||
if opts.AcceptEULA != "" {
|
||||
qcr.Spec.AddToConfigs("qliksense", "acceptEULA", opts.AcceptEULA)
|
||||
}
|
||||
if opts.MongoDbUri != "" {
|
||||
qcr.Spec.AddToSecrets("qliksense", "mongoDbUri", opts.MongoDbUri, "")
|
||||
qcr.Spec.AddToSecrets("qliksense", "mongoDbUri", opts.MongoDbUri)
|
||||
}
|
||||
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 operatorControllerString, err := q.getProcessedOperatorControllerString(qcr); err != nil {
|
||||
fmt.Println("error extracting/transforming operator controller", err)
|
||||
return err
|
||||
} else if err := qapi.KubectlApply(operatorControllerString, ""); err != nil {
|
||||
fmt.Println("cannot do kubectl apply on operator controller", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// create patch dependent resoruces
|
||||
fmt.Println("Installing resoruces used kuztomize patch")
|
||||
if err := q.createK8sResoruceBeforePatch(qcr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if qcr.Spec.Git != nil && qcr.Spec.Git.Repository != "" {
|
||||
// fetching and applying manifest will be in the operator controller
|
||||
// get decrypted cr
|
||||
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return q.applyCR(dcr)
|
||||
}
|
||||
}
|
||||
if !qcr.IsRepoExist() {
|
||||
if err := fetchAndUpdateCR(qConfig, version); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
qcr, err = qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
fmt.Println("cannot get the current-context cr", err)
|
||||
return err
|
||||
} else if qcr.Spec.GetManifestsRoot() == "" {
|
||||
return errors.New("cannot get the manifest root. Use qliksense fetch <version> or qliksense set manifestsRoot")
|
||||
}
|
||||
|
||||
// install generated manifests into cluster
|
||||
fmt.Println("Installing generated manifests into cluster")
|
||||
|
||||
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
|
||||
return err
|
||||
} else if err := q.applyConfigToK8s(dcr); err != nil {
|
||||
if err := q.applyConfigToK8s(qcr, "install"); err != nil {
|
||||
fmt.Println("cannot do kubectl apply on manifests")
|
||||
return err
|
||||
} else {
|
||||
return q.applyCR(dcr)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Qliksense) getProcessedOperatorControllerString(qcr *qapi.QliksenseCR) (string, error) {
|
||||
operatorControllerString := q.GetOperatorControllerString()
|
||||
if imageRegistry := qcr.Spec.GetImageRegistry(); imageRegistry != "" {
|
||||
return kustomizeForImageRegistry(operatorControllerString, pullSecretName,
|
||||
path.Join(qliksenseOperatorImageRepo, qliksenseOperatorImageName),
|
||||
path.Join(imageRegistry, qliksenseOperatorImageName))
|
||||
}
|
||||
return operatorControllerString, nil
|
||||
}
|
||||
|
||||
func installOrRemoveImagePullSecret(qConfig *qapi.QliksenseConfig) error {
|
||||
if pullDockerConfigJsonSecret, err := qConfig.GetPullDockerConfigJsonSecret(); err == nil {
|
||||
if dockerConfigJsonSecretYaml, err := pullDockerConfigJsonSecret.ToYaml(""); err != nil {
|
||||
return err
|
||||
} else if err := qapi.KubectlApply(string(dockerConfigJsonSecretYaml), ""); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
deleteDockerConfigJsonSecret := qapi.DockerConfigJsonSecret{
|
||||
Name: pullSecretName,
|
||||
}
|
||||
if deleteDockerConfigJsonSecretYaml, err := deleteDockerConfigJsonSecret.ToYaml(""); err != nil {
|
||||
return err
|
||||
} else if err := qapi.KubectlDelete(string(deleteDockerConfigJsonSecretYaml), ""); err != nil {
|
||||
qapi.LogDebugMessage("failed deleting %v, error: %v\n", pullSecretName, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func kustomizeForImageRegistry(resources, dockerConfigJsonSecretName, name, newName string) (string, error) {
|
||||
fSys := filesys.MakeFsInMemory()
|
||||
if err := fSys.WriteFile("/resources.yaml", []byte(resources)); err != nil {
|
||||
return "", err
|
||||
} else if err := fSys.WriteFile("/addImagePullSecrets.yaml", []byte(fmt.Sprintf(`
|
||||
apiVersion: builtin
|
||||
kind: PatchTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
patch: '[{"op": "add", "path": "/spec/template/spec/imagePullSecrets", "value": [{"name": "%v"}]}]'
|
||||
target:
|
||||
name: .*-operator
|
||||
kind: Deployment
|
||||
`, dockerConfigJsonSecretName))); err != nil {
|
||||
return "", err
|
||||
} else if err := fSys.WriteFile("/kustomization.yaml", []byte(fmt.Sprintf(`
|
||||
resources:
|
||||
- resources.yaml
|
||||
transformers:
|
||||
- addImagePullSecrets.yaml
|
||||
images:
|
||||
- name: %s
|
||||
newName: %s
|
||||
`, name, newName))); err != nil {
|
||||
return "", err
|
||||
} else if out, err := executeKustomizeBuildForFileSystem("/", fSys); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return string(out), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Qliksense) applyCR(cr *qapi.QliksenseCR) error {
|
||||
// install operator cr into cluster
|
||||
//get the current context cr
|
||||
fmt.Println("Install operator CR into cluster")
|
||||
r, err := cr.GetString()
|
||||
r, err := q.getCurrentCRString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := qapi.KubectlApply(r, ""); 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
|
||||
}
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
func TestCreateK8sResoruceBeforePatch(t *testing.T) {
|
||||
td := setup()
|
||||
sampleCr := `
|
||||
apiVersion: qlik.com/v1
|
||||
kind: Qliksense
|
||||
metadata:
|
||||
name: qlik-test3
|
||||
labels:
|
||||
version: v0.0.2
|
||||
spec:
|
||||
git:
|
||||
repository: https://github.com/ffoysal/qliksense-k8s
|
||||
accessToken: abababababababaab
|
||||
userName: "blblbl"
|
||||
gitOps:
|
||||
enabled: "no"
|
||||
schedule: "*/1 * * * *"
|
||||
watchBranch: pr-branch-db1d26d6
|
||||
image: qlik-docker-oss.bintray.io/qliksense-repo-watcher
|
||||
configs:
|
||||
qliksense:
|
||||
- name: acceptEULA
|
||||
value: "yes"
|
||||
secrets:
|
||||
qliksense:
|
||||
- name: mongoDbUri
|
||||
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
|
||||
profile: docker-desktop
|
||||
rotateKeys: "yes"`
|
||||
|
||||
crFile := filepath.Join(testDir, "install_test.yaml")
|
||||
ioutil.WriteFile(crFile, []byte(sampleCr), 0644)
|
||||
q := New(testDir)
|
||||
file, e := os.Open(crFile)
|
||||
if e != nil {
|
||||
t.Log(e)
|
||||
t.FailNow()
|
||||
}
|
||||
if err := q.LoadCr(file, false); err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
qConfig := qapi.NewQConfig(testDir)
|
||||
cr, err := qConfig.GetCR("qlik-test3")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
if err = q.createK8sResoruceBeforePatch(cr); err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
td()
|
||||
}
|
||||
|
||||
func setupQliksenseTestDefaultContext(t *testing.T, tmpQlikSenseHome, CR string) {
|
||||
if err := ioutil.WriteFile(path.Join(tmpQlikSenseHome, "config.yaml"), []byte(`
|
||||
apiVersion: config.qlik.com/v1
|
||||
kind: QliksenseConfig
|
||||
metadata:
|
||||
name: QliksenseConfigMetadata
|
||||
spec:
|
||||
contexts:
|
||||
- name: qlik-default
|
||||
crFile: contexts/qlik-default/qlik-default.yaml
|
||||
currentContext: qlik-default
|
||||
`), os.ModePerm); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
defaultContextDir := path.Join(tmpQlikSenseHome, "contexts", "qlik-default")
|
||||
if err := os.MkdirAll(defaultContextDir, os.ModePerm); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(path.Join(defaultContextDir, "qlik-default.yaml"), []byte(CR), os.ModePerm); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getProcessedOperatorControllerString(t *testing.T) {
|
||||
tmpQlikSenseHome, err := ioutil.TempDir("", "tmp-qlik-sense-home-")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating tmp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpQlikSenseHome)
|
||||
|
||||
registry := "registryFoo"
|
||||
setupQliksenseTestDefaultContext(t, tmpQlikSenseHome, fmt.Sprintf(`
|
||||
apiVersion: qlik.com/v1
|
||||
kind: Qliksense
|
||||
metadata:
|
||||
name: qlik-default
|
||||
spec:
|
||||
configs:
|
||||
qliksense:
|
||||
- name: imageRegistry
|
||||
value: %v
|
||||
`, registry))
|
||||
|
||||
q := &Qliksense{
|
||||
QliksenseHome: tmpQlikSenseHome,
|
||||
CrdBox: packr.New("crds", "./crds"),
|
||||
}
|
||||
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error getting current CR: %v", err)
|
||||
}
|
||||
|
||||
originalOperatorString := q.GetOperatorControllerString()
|
||||
|
||||
processedOperatorString, err := q.getProcessedOperatorControllerString(qcr)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
controllerImageChecks := map[string]func(t *testing.T, controllerImage string){
|
||||
originalOperatorString: func(t *testing.T, controllerImage string) {
|
||||
expectedControllerImagePrefix := fmt.Sprintf("%v/%v:", qliksenseOperatorImageRepo, qliksenseOperatorImageName)
|
||||
if !strings.HasPrefix(controllerImage, expectedControllerImagePrefix) {
|
||||
t.Fatalf("expected controller image: %v to have prefix: %v", controllerImage, expectedControllerImagePrefix)
|
||||
}
|
||||
},
|
||||
processedOperatorString: func(t *testing.T, controllerImage string) {
|
||||
expectedControllerImagePrefix := fmt.Sprintf("%v/%v:", registry, qliksenseOperatorImageName)
|
||||
if !strings.HasPrefix(controllerImage, expectedControllerImagePrefix) {
|
||||
t.Fatalf("expected controller image: %v to have prefix: %v", controllerImage, expectedControllerImagePrefix)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
resourceFactory := resmap.NewFactory(resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl()), nil)
|
||||
for operatorString, controllerImageCheck := range controllerImageChecks {
|
||||
resMap, err := resourceFactory.NewResMapFromBytes([]byte(operatorString))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
res, err := resMap.GetById(resid.NewResId(resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
}, "qliksense-operator"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
controllerImage, err := res.GetString("spec.template.spec.containers[0].image")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
controllerImageCheck(t, controllerImage)
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,22 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // don't delete this line ref: https://github.com/kubernetes/client-go/issues/242
|
||||
"sigs.k8s.io/kustomize/api/filesys"
|
||||
"sigs.k8s.io/kustomize/api/konfig"
|
||||
"sigs.k8s.io/kustomize/api/krusty"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
//ExecuteKustomizeBuild execute kustomize to the directory and return manifest as byte array
|
||||
func ExecuteKustomizeBuild(directory string) ([]byte, error) {
|
||||
return executeKustomizeBuildForFileSystem(directory, filesys.MakeFsOnDisk())
|
||||
}
|
||||
|
||||
func executeKustomizeBuildForFileSystem(directory string, fSys filesys.FileSystem) ([]byte, error) {
|
||||
func executeKustomizeBuild(directory string) ([]byte, error) {
|
||||
log.SetOutput(&nullWriter{})
|
||||
defer func() {
|
||||
log.SetOutput(os.Stderr)
|
||||
}()
|
||||
|
||||
fSys := filesys.MakeFsOnDisk()
|
||||
options := &krusty.Options{
|
||||
DoLegacyResourceSort: false,
|
||||
LoadRestrictions: types.LoadRestrictionsNone,
|
||||
@@ -39,29 +30,3 @@ func executeKustomizeBuildForFileSystem(directory string, fSys filesys.FileSyste
|
||||
}
|
||||
return resMap.AsYaml()
|
||||
}
|
||||
|
||||
func executeKustomizeBuildWithStdoutProgress(path string) (kuzManifest []byte, err error) {
|
||||
result, err := api.ExecuteTaskWithBlinkingStdoutFeedback(func() (interface{}, error) {
|
||||
return ExecuteKustomizeBuild(path)
|
||||
}, "...")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.([]byte), nil
|
||||
}
|
||||
|
||||
//GetYamlsFromMultiDoc filter yaml docs from multiyaml based on kind
|
||||
func GetYamlsFromMultiDoc(multiYaml string, kind string) string {
|
||||
yamlDocs := strings.Split(string(multiYaml), "---")
|
||||
resultDocs := ""
|
||||
for _, doc := range yamlDocs {
|
||||
scanner := bufio.NewScanner(strings.NewReader(doc))
|
||||
for scanner.Scan() {
|
||||
if scanner.Text() == "kind: "+kind {
|
||||
resultDocs = resultDocs + "\n---\n" + doc
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return resultDocs
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
kapis_git "github.com/qlik-oss/k-apis/pkg/git"
|
||||
)
|
||||
|
||||
func Test_ExecuteKustomizeBuild(t *testing.T) {
|
||||
func Test_executeKustomizeBuild(t *testing.T) {
|
||||
tmpDir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v\n", err)
|
||||
@@ -41,7 +41,7 @@ configMapGenerator:
|
||||
t.Fatalf("error writing kustomization file to path: %v error: %v\n", kustomizationYamlFilePath, err)
|
||||
}
|
||||
|
||||
result, err := ExecuteKustomizeBuild(tmpDir)
|
||||
result, err := executeKustomizeBuild(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected kustomize error: %v\n", err)
|
||||
}
|
||||
@@ -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, defaultConfigRepoGitUrl, nil); err != nil {
|
||||
if repo, err := kapis_git.CloneRepository(configPath, defaultGitUrl, 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)
|
||||
@@ -86,7 +86,7 @@ func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
|
||||
|
||||
generateKeys(cr, "won't-use")
|
||||
|
||||
yamlResources, err := ExecuteKustomizeBuild(path.Join(configPath, "manifests", "base", "resources", "users"))
|
||||
yamlResources, err := executeKustomizeBuild(path.Join(configPath, "manifests", "base", "resources", "users"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected kustomize error: %v\n", err)
|
||||
}
|
||||
@@ -139,97 +139,3 @@ func getEjsonKeyDir(defaultKeyDir string) string {
|
||||
}
|
||||
return ejsonKeyDir
|
||||
}
|
||||
|
||||
func Test_GetYamlDocKindFromMultiDoc(t *testing.T) {
|
||||
tmpDir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v\n", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
kustomizationYamlFilePath := path.Join(tmpDir, "kustomization.yaml")
|
||||
testResFileYamlFilePath := path.Join(tmpDir, "test-file.yaml")
|
||||
kustomizationYaml := `
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- test-file.yaml
|
||||
`
|
||||
testYaml := `
|
||||
apiVersion: v1
|
||||
data:
|
||||
foo: bar
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: foo-config
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: qix-sessions
|
||||
chart: qix-sessions-4.0.10
|
||||
heritage: Helm
|
||||
release: qliksense
|
||||
name: qliksense-qix-sessions
|
||||
namespace: default
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: Role
|
||||
metadata:
|
||||
labels:
|
||||
app: chronos
|
||||
chart: chronos-1.5.7
|
||||
heritage: Helm
|
||||
release: qliksense
|
||||
name: qliksense-chronos
|
||||
namespace: default
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- endpoints
|
||||
verbs:
|
||||
- get
|
||||
- update
|
||||
`
|
||||
err = ioutil.WriteFile(kustomizationYamlFilePath, []byte(kustomizationYaml), os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("error writing kustomization file to path: %v error: %v\n", kustomizationYamlFilePath, err)
|
||||
}
|
||||
err = ioutil.WriteFile(testResFileYamlFilePath, []byte(testYaml), os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("error writing test-file to path: %v error: %v\n", testResFileYamlFilePath, err)
|
||||
}
|
||||
result, err := ExecuteKustomizeBuild(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected kustomize error: %v\n", err)
|
||||
}
|
||||
resultYaml := GetYamlsFromMultiDoc(string(result), "Role")
|
||||
|
||||
expectedK8sYaml := `
|
||||
---
|
||||
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: Role
|
||||
metadata:
|
||||
labels:
|
||||
app: chronos
|
||||
chart: chronos-1.5.7
|
||||
heritage: Helm
|
||||
release: qliksense
|
||||
name: qliksense-chronos
|
||||
namespace: default
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- endpoints
|
||||
verbs:
|
||||
- get
|
||||
- update
|
||||
`
|
||||
if resultYaml != expectedK8sYaml {
|
||||
t.Fatalf("expected k8s yaml: [%v] but got: [%v]\n", expectedK8sYaml, resultYaml)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
func (q *Qliksense) LoadCr(reader io.Reader, overwriteExistingContext bool) error {
|
||||
if crBytes, err := ioutil.ReadAll(reader); err != nil {
|
||||
return err
|
||||
} else if crName, err := q.loadCrStringIntoFileSystem(string(crBytes), overwriteExistingContext); err != nil {
|
||||
return err
|
||||
} else {
|
||||
fmt.Println("cr name: [ " + crName + " ] has been loaded")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) IsEulaAcceptedInCrFile(reader io.Reader) (bool, error) {
|
||||
if crBytes, err := ioutil.ReadAll(reader); err != nil {
|
||||
return false, err
|
||||
} else if cr, err := qapi.CreateCRObjectFromString(string(crBytes)); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
return cr.IsEULA(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Qliksense) loadCrStringIntoFileSystem(crstr string, overwriteExistingContext bool) (string, error) {
|
||||
cr, err := qapi.CreateCRObjectFromString(crstr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
if qConfig.IsContextExist(cr.GetName()) {
|
||||
if !overwriteExistingContext {
|
||||
return "", errors.New("Context with name: " + cr.GetName() + " already exists. " +
|
||||
"Please delete the existing context first using the delete-context command or specify the --overwrite flag.")
|
||||
}
|
||||
// else if err := os.RemoveAll(qConfig.GetContextPath(cr.GetName())); err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
}
|
||||
if err := qConfig.CreateContextDirs(cr.GetName()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// encrypt the secrets and do base64 then update the CR
|
||||
encryptionKey, err := qConfig.GetEncryptionKeyFor(cr.GetName())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for svc, nvs := range cr.Spec.Secrets {
|
||||
for _, nv := range nvs {
|
||||
if nv.ValueFrom == nil {
|
||||
skv := &qapi.ServiceKeyValue{
|
||||
Key: nv.Name,
|
||||
Value: nv.Value,
|
||||
SvcName: svc,
|
||||
}
|
||||
if err := q.processSecret(skv, encryptionKey, cr, false); err != nil {
|
||||
return cr.GetName(), err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if cr.Spec.FetchSource != nil && cr.Spec.FetchSource.AccessToken != "" {
|
||||
if err := cr.SetFetchAccessToken(cr.Spec.FetchSource.AccessToken, encryptionKey); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
// update manifestsRoot in case already exist
|
||||
if existingCr, err := qConfig.GetCR(cr.GetName()); err == nil {
|
||||
// cr exists, so update the manifestsRoot if version exist
|
||||
newV := cr.GetLabelFromCr("version")
|
||||
if strings.HasSuffix(existingCr.Spec.ManifestsRoot, newV) {
|
||||
cr.Spec.ManifestsRoot = existingCr.Spec.ManifestsRoot
|
||||
}
|
||||
}
|
||||
// write to disk
|
||||
if err = qConfig.CreateOrWriteCrAndContext(cr); err != nil {
|
||||
return "", err
|
||||
}
|
||||
qConfig.SetCurrentContextName(cr.GetName())
|
||||
qConfig.Write()
|
||||
|
||||
return cr.GetName(), nil
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
func TestLoadCrFile(t *testing.T) {
|
||||
td := setup()
|
||||
setup()
|
||||
sampleCr1 := `
|
||||
apiVersion: qlik.com/v1
|
||||
kind: Qliksense
|
||||
metadata:
|
||||
name: qlik-test
|
||||
labels:
|
||||
version: v0.0.2
|
||||
spec:
|
||||
git:
|
||||
repository: https://github.com/ffoysal/qliksense-k8s
|
||||
accessToken: abababababababaab
|
||||
userName: "blblbl"
|
||||
gitOps:
|
||||
enabled: "no"
|
||||
schedule: "*/1 * * * *"
|
||||
watchBranch: pr-branch-db1d26d6
|
||||
image: qlik-docker-oss.bintray.io/qliksense-repo-watcher
|
||||
configs:
|
||||
qliksense:
|
||||
- name: acceptEULA
|
||||
value: "yes"
|
||||
secrets:
|
||||
qliksense:
|
||||
- name: mongoDbUri
|
||||
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
|
||||
profile: docker-desktop
|
||||
rotateKeys: "yes"`
|
||||
sampleCr2 := `
|
||||
apiVersion: qlik.com/v1
|
||||
kind: Qliksense
|
||||
metadata:
|
||||
name: qlik-test3
|
||||
labels:
|
||||
version: v0.0.2
|
||||
spec:
|
||||
git:
|
||||
repository: https://github.com/ffoysal/qliksense-k8s
|
||||
accessToken: abababababababaab
|
||||
userName: "blblbl"
|
||||
gitOps:
|
||||
enabled: "no"
|
||||
schedule: "*/1 * * * *"
|
||||
watchBranch: pr-branch-db1d26d6
|
||||
image: qlik-docker-oss.bintray.io/qliksense-repo-watcher
|
||||
configs:
|
||||
qliksense:
|
||||
- name: acceptEULA
|
||||
value: "yes"
|
||||
secrets:
|
||||
qliksense:
|
||||
- name: mongoDbUri
|
||||
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
|
||||
profile: docker-desktop
|
||||
rotateKeys: "yes"`
|
||||
|
||||
duplicateCr := `
|
||||
apiVersion: qlik.com/v1
|
||||
kind: Qliksense
|
||||
metadata:
|
||||
name: qlik-default
|
||||
labels:
|
||||
version: v0.0.2
|
||||
spec:
|
||||
git:
|
||||
repository: https://github.com/ffoysal/qliksense-k8s
|
||||
accessToken: abababababababaab
|
||||
userName: "blblbl"`
|
||||
crFile1 := filepath.Join(testDir, "testcr1.yaml")
|
||||
ioutil.WriteFile(crFile1, []byte(sampleCr1), 0644)
|
||||
crFile2 := filepath.Join(testDir, "testcr2.yaml")
|
||||
ioutil.WriteFile(crFile2, []byte(sampleCr2), 0644)
|
||||
|
||||
dupCrFile := filepath.Join(testDir, "dupcr.yaml")
|
||||
ioutil.WriteFile(dupCrFile, []byte(duplicateCr), 0644)
|
||||
|
||||
q := New(testDir)
|
||||
file1, e := os.Open(crFile1)
|
||||
if e != nil {
|
||||
t.Log(e)
|
||||
t.FailNow()
|
||||
}
|
||||
if err := q.LoadCr(file1, false); err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
file2, e := os.Open(crFile2)
|
||||
if e != nil {
|
||||
t.Log(e)
|
||||
t.FailNow()
|
||||
}
|
||||
if err := q.LoadCr(file2, false); err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
qConfig := qapi.NewQConfig(testDir)
|
||||
cr, err := qConfig.GetCR("qlik-test")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
if cr.GetName() != "qlik-test" {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
cr, err = qConfig.GetCR("qlik-test3")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
if cr.GetName() != "qlik-test3" {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if qConfig.Spec.CurrentContext != "qlik-test3" {
|
||||
t.FailNow()
|
||||
}
|
||||
file, e := os.Open(dupCrFile)
|
||||
if e != nil {
|
||||
t.Log(e)
|
||||
t.FailNow()
|
||||
}
|
||||
if err := q.LoadCr(file, false); err == nil {
|
||||
t.FailNow()
|
||||
}
|
||||
td()
|
||||
}
|
||||
@@ -2,40 +2,27 @@ package qliksense
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (q *Qliksense) ViewOperator() error {
|
||||
io.WriteString(os.Stdout, q.GetOperatorCRDString())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Qliksense) ViewOperatorController() error {
|
||||
io.WriteString(os.Stdout, q.GetOperatorControllerString())
|
||||
return nil
|
||||
func (q *Qliksense) ViewOperatorCrd() {
|
||||
io.WriteString(os.Stdout, q.GetCRDString())
|
||||
}
|
||||
|
||||
// this will return crd,deployment,role, rolebinding,serviceaccount for operator
|
||||
func (q *Qliksense) GetOperatorCRDString() string {
|
||||
func (q *Qliksense) GetCRDString() string {
|
||||
result := ""
|
||||
for _, v := range q.getFileList("crd") {
|
||||
result = q.getYamlFromPackrFile(v)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (q *Qliksense) GetOperatorControllerString() string {
|
||||
result := ""
|
||||
for _, v := range q.getFileList("crd-deploy") {
|
||||
result = result + q.getYamlFromPackrFile(v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (q *Qliksense) getYamlFromPackrFile(packrFile string) string {
|
||||
s, err := q.CrdBox.FindString(packrFile)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,21 +2,29 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
)
|
||||
|
||||
// Qliksense is the logic behind the qliksense client
|
||||
type Qliksense struct {
|
||||
QliksenseHome string
|
||||
CrdBox *packr.Box ``
|
||||
QliksenseHome string
|
||||
QliksenseEjsonKeyDir string
|
||||
CrdBox *packr.Box ``
|
||||
}
|
||||
|
||||
// New qliksense client, initialized with useful defaults.
|
||||
func New(qliksenseHome string) *Qliksense {
|
||||
func New(qliksenseHome string) (*Qliksense, error) {
|
||||
qliksenseClient := &Qliksense{
|
||||
QliksenseHome: qliksenseHome,
|
||||
CrdBox: packr.New("crds", "./crds"),
|
||||
}
|
||||
|
||||
return qliksenseClient
|
||||
qliksenseClient.QliksenseEjsonKeyDir = path.Join(qliksenseHome, "ejson", "keys")
|
||||
if err := os.MkdirAll(qliksenseClient.QliksenseEjsonKeyDir, os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return qliksenseClient, nil
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
kapis_git "github.com/qlik-oss/k-apis/pkg/git"
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
func (q *Qliksense) DiscardAllUnstagedChangesFromGitRepo(qConfig *qapi.QliksenseConfig) error {
|
||||
if qcr, err := qConfig.GetCurrentCR(); err != nil {
|
||||
return err
|
||||
} else if version := qcr.GetLabelFromCr("version"); version == "" {
|
||||
return errors.New("version label is not set in CR")
|
||||
} else if qcr.Spec.ManifestsRoot == qConfig.BuildRepoPath(version) {
|
||||
if repo, err := kapis_git.OpenRepository(qcr.Spec.ManifestsRoot); err != nil {
|
||||
return err
|
||||
} else if err = kapis_git.DiscardAllUnstagedChanges(repo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -17,5 +17,5 @@ func (q *Qliksense) UninstallQK8s(contextName string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return qapi.KubectlDelete(str, "")
|
||||
return qapi.KubectlDelete(str)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||
)
|
||||
|
||||
func (q *Qliksense) UpgradeQK8s(keepPatchFiles bool) error {
|
||||
func (q *Qliksense) UpgradeQK8s() error {
|
||||
|
||||
// step1: get CR
|
||||
// step2: run kustomize
|
||||
@@ -14,13 +14,6 @@ func (q *Qliksense) UpgradeQK8s(keepPatchFiles bool) error {
|
||||
|
||||
// fetch the version
|
||||
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||
if !keepPatchFiles {
|
||||
defer func() {
|
||||
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
|
||||
fmt.Printf("error removing temporary changes to the config: %v\n", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
qcr, err := qConfig.GetCurrentCR()
|
||||
if err != nil {
|
||||
@@ -28,20 +21,19 @@ func (q *Qliksense) UpgradeQK8s(keepPatchFiles bool) error {
|
||||
return err
|
||||
}
|
||||
qcr.Spec.RotateKeys = "no"
|
||||
|
||||
dcr, err := qConfig.GetDecryptedCr(qcr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dcr.Spec.Git != nil && dcr.Spec.Git.Repository != "" {
|
||||
// fetching and applying manifest will be in the operator controller
|
||||
// get decrypted cr
|
||||
return q.applyCR(dcr)
|
||||
}
|
||||
err = q.applyConfigToK8s(dcr)
|
||||
if err != nil {
|
||||
if err := q.applyConfigToK8s(qcr, "upgrade"); err != nil {
|
||||
fmt.Println("cannot do kubectl apply on manifests")
|
||||
return err
|
||||
}
|
||||
return q.applyCR(dcr)
|
||||
|
||||
fmt.Println("Install operator CR into cluster")
|
||||
r, err := qcr.GetString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := qapi.KubectlApply(r); err != nil {
|
||||
fmt.Println("cannot do kubectl apply on operator CR")
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user