Compare commits

...

73 Commits

Author SHA1 Message Date
Foysal Iqbal
c903529e2d fix go git version
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-04-14 10:02:43 -04:00
Foysal Iqbal
64ddf430b7 fix go git version
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-04-14 08:43:12 -04:00
Andriy Bulynko
0d2e436639 Accounting for imageRegistry CR setting when executing preflight checks (#314) 2020-04-14 06:39:48 -04:00
Foysal Iqbal
ca15145499 update k-api (#315)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-04-13 14:03:03 -04:00
Foysal Iqbal
3274ebd12a fix access token encrypt (#313) 2020-04-13 09:43:35 -04:00
Foysal Iqbal
505b4ef4ce add base64 flag and input pipe (#312)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-04-13 09:21:28 -04:00
Foysal Iqbal
a4e2b0dfe6 Change encryption (#307) 2020-04-09 22:56:05 -04:00
Foysal Iqbal
7cf2b00f0b fix windows copy error (#306) 2020-04-09 13:49:44 -04:00
Ilir Bekteshi
d94454b832 Merge pull request #302 from qlik-oss/buildall
[action] Build for all platforms on PR
2020-04-09 16:21:37 +02:00
Ashwathi Shiva
f4d0bd87f6 Preflight- Provide command into container (#301)
* Supply command as a script into container working
2020-04-09 08:05:43 -04:00
Ilir Bekteshi
645d1496d4 [action] Build for all platforms on PR 2020-04-09 14:04:48 +02:00
Foysal Iqbal
65ce074981 move crds to base/crds folder (#298) 2020-04-08 23:10:12 -04:00
Andriy Bulynko
323014d137 Fixing operator deployment's image renaming if the imageRegistry is set in the CR (#297) 2020-04-08 20:59:33 -04:00
Foysal Iqbal
31262df504 add struct for fetch repo (#287) 2020-04-08 13:37:08 -04:00
Andriy Bulynko
a15fe75b6c Pulling/pushing GitOps and Preflight images (#286) 2020-04-07 19:32:00 -04:00
Ashwathi Shiva
a59bf2d015 mongo image included for preflight check (#288) 2020-04-07 18:40:38 -04:00
Foysal Iqbal
b36b8917da add prefix preflight (#282)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-04-06 02:24:25 -04:00
Ashwathi Shiva
eed4d49665 Removing namespaces from role, rolebinding and sa checks (#280)
* remove namespace from role, rolebinding and SA checks
2020-04-06 00:23:40 -04:00
Foysal Iqbal
34d35909a4 remove regex again (#279)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-04-05 22:13:32 -04:00
Ashwathi Shiva
813bec2377 Preflight commands (#276)
* preflight role, roleBinding, serviceAccount, mongo, all checks working
* Updated readme

Co-authored-by: Foysal Iqbal <mqb@qlik.com>
2020-04-05 19:07:01 -04:00
Foysal Iqbal
97cbfa050c Fix regex (#278) 2020-04-05 15:32:14 -04:00
Foysal Iqbal
44b936a9aa add custom crds (#277)
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-04-04 01:16:13 -04:00
Foysal Iqbal
0e6a1ab18d add first version of editor (#275)
* add first version of editor

Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-04-03 01:22:29 -04:00
Foysal Iqbal
60a77dab5c Merge pull request #274 from qlik-oss/preflight-config
Preflight config
2020-04-02 17:30:24 -04:00
Foysal Iqbal
b041d8be3c Merge branch 'master' into preflight-config 2020-04-02 16:15:11 -04:00
Foysal Iqbal
a73209864c add preflight config in a file for pull/push
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-04-02 16:14:46 -04:00
Jacob Martin
a662e26867 update kustomize to qlik/v.0.0.18 (#273) 2020-04-02 14:14:47 -04:00
Foysal Iqbal
198c631bd1 Merge pull request #271 from qlik-oss/fix-pull
fix pull
2020-04-02 12:10:24 -04:00
Foysal Iqbal
6c38708c9f Merge pull request #272 from qlik-oss/temp-version
change min version
2020-04-02 11:57:02 -04:00
Foysal Iqbal
8c0ffc667d change min version
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-04-02 09:40:09 -04:00
Foysal Iqbal
b8fc1474f8 fix pull
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-04-02 01:04:03 -04:00
Foysal Iqbal
bcb0c44300 fix pull
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-04-02 01:01:50 -04:00
Foysal Iqbal
e2294e48c4 fix pull
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-04-02 00:58:23 -04:00
Foysal Iqbal
ad7861cd13 Merge pull request #270 from qlik-oss/prepare-preflight
prepare for pre-flight
2020-04-01 20:39:19 -04:00
Foysal Iqbal
f17e27f2ef prepare for pre-flight
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-04-01 20:14:31 -04:00
Foysal Iqbal
77bf52e0b0 prepare for pre-flight
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-04-01 20:12:09 -04:00
Ashwathi Shiva
3819f29412 Preflight service, pod and deployment (#268)
* Added preflight deployment, service and pod checks and updated readme
2020-04-01 16:08:49 -04:00
Andriy Bulynko
bdbcc665ae Upgrading qlik-oss/kustomize to version qlik/v0.0.17 (#267) 2020-04-01 12:15:36 -04:00
Ashwathi Shiva
b3d0eff376 troubleshoot-preflight cleaned up and reworked (#263)
* switched to client-go approach to doing preflight-checks, dns check, k8s-version check added, updated readme
2020-03-31 17:07:08 -04:00
Foysal Iqbal
070abea0d8 Merge pull request #265 from qlik-oss/fix-flag-all
fix flag all
2020-03-31 09:44:37 -04:00
Foysal Iqbal
d04defdf13 fix flag all
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-31 09:35:32 -04:00
Ilir Bekteshi
738b934f0e Merge pull request #260 from qlik-oss/license
Add License
2020-03-30 14:43:45 +02:00
Ilir Bekteshi
87f5c740c7 Add License 2020-03-30 12:43:15 +02:00
Foysal Iqbal
1cbc243ca1 Complete apply (#253) 2020-03-30 00:23:33 -04:00
Foysal Iqbal
b944d8a8dd Merge branch 'master' into complete-apply 2020-03-29 23:56:57 -04:00
Foysal Iqbal
6baa8c8a6d fix gomplate secret patch
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-29 23:53:45 -04:00
Andriy Bulynko
315af4d76e Fixing an issue with EULA prompt (#259) 2020-03-29 23:42:01 -04:00
Foysal Iqbal
81862bad30 update k-api
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-29 21:49:34 -04:00
Foysal Iqbal
a58119ef6a fix set context
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-29 21:25:06 -04:00
Foysal Iqbal
be1016400b fix set context
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-29 20:46:15 -04:00
Foysal Iqbal
46b16426df fix version issue
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-29 20:37:18 -04:00
Foysal Iqbal
f873a7e45a fix apply
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-29 20:23:46 -04:00
Foysal Iqbal
3afa9f0c44 fix apply
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-29 20:11:45 -04:00
Foysal Iqbal
7c0df2ec32 Merge branch 'master' into complete-apply 2020-03-29 19:59:33 -04:00
Foysal Iqbal
c619a02691 fix apply
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-29 19:57:20 -04:00
Ashwathi Shiva
66236e1888 Preflight win error (#250)
* Windows yaml file path modified to address error- unsupported protocol schema
2020-03-28 11:21:22 -04:00
Foysal Iqbal
b9b62b2a2e Merge branch 'master' into complete-apply 2020-03-27 23:01:58 -04:00
Foysal Iqbal
31fb9dd532 make path forward slash while saving (#249) 2020-03-27 23:01:31 -04:00
Foysal Iqbal
e0da9621a4 merge conflict 2020-03-27 16:26:39 -04:00
Sanat Nayar
a1be6d6b59 Merge pull request #230 from qlik-oss/cr_validation
Support setting any attribute in Spec (through CR Spec Validation)
2020-03-27 16:18:47 -04:00
Foysal Iqbal
2eaae7bdc3 fix feedback
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-27 16:16:45 -04:00
Foysal Iqbal
a2111be51e Merge branch 'master' into fix-path-slash 2020-03-27 13:53:30 -04:00
Foysal Iqbal
9c1deae17e fix feedback
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-27 13:52:56 -04:00
Foysal Iqbal
5582e2e15d fix feedback
Signed-off-by: Foysal Iqbal <mqb@qlik.com>
2020-03-27 13:46:56 -04:00
Sanat Nayar
f052ff7882 Merge branch 'master' into cr_validation 2020-03-27 11:15:22 -04:00
Sanat Nayar
aec352df32 added additional test cases 2020-03-19 13:43:43 -04:00
Sanat Nayar
c1bee27dff removed debugging printouts 2020-03-19 13:34:44 -04:00
Sanat Nayar
3c464e3316 Merge branch 'master' into cr_validation 2020-03-19 13:04:14 -04:00
Sanat Nayar
a71caf080e pkg/qliksense/context_configs.go 2020-03-19 12:54:03 -04:00
Sanat Nayar
5e9903ef3c init 2020-03-18 17:08:14 -04:00
Sanat Nayar
0adb31360a init 2020-03-18 16:35:27 -04:00
Sanat Nayar
590abfd5bf added crd spec 2020-03-17 14:58:29 -04:00
Sanat Nayar
fa5c854d3a init 2020-03-17 10:17:54 -04:00
55 changed files with 3657 additions and 1296 deletions

View File

@@ -24,4 +24,4 @@ jobs:
shell: bash shell: bash
- run: make test - run: make test
- run: make build - run: make xbuild-all

2
.gitignore vendored
View File

@@ -7,3 +7,5 @@ pkg/qliksense/qliksense-packr.go
pkg/qliksense/docker-registry pkg/qliksense/docker-registry
/pkg/qliksense/tests /pkg/qliksense/tests
.DS_Store .DS_Store
.idea/

191
LICENSE Normal file
View File

@@ -0,0 +1,191 @@
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.

View File

@@ -43,8 +43,8 @@ build: clean generate
go build -ldflags '$(LDFLAGS)' -tags "$(BUILDTAGS)" -o $(BINDIR)/$(MIXIN)$(FILE_EXT) ./cmd/$(MIXIN) go build -ldflags '$(LDFLAGS)' -tags "$(BUILDTAGS)" -o $(BINDIR)/$(MIXIN)$(FILE_EXT) ./cmd/$(MIXIN)
$(MAKE) clean $(MAKE) clean
.PHONY: test .PHONY: test-setup
test: clean generate test-setup: clean generate
ifeq ($(shell ${WHICH} docker-registry 2>${DEVNUL}),) ifeq ($(shell ${WHICH} docker-registry 2>${DEVNUL}),)
$(eval TMP-docker-distribution := $(shell mktemp -d)) $(eval TMP-docker-distribution := $(shell mktemp -d))
git clone https://github.com/docker/distribution.git $(TMP-docker-distribution)/docker-distribution git clone https://github.com/docker/distribution.git $(TMP-docker-distribution)/docker-distribution
@@ -52,9 +52,17 @@ ifeq ($(shell ${WHICH} docker-registry 2>${DEVNUL}),)
cp $(TMP-docker-distribution)/docker-distribution/bin/registry pkg/qliksense/docker-registry cp $(TMP-docker-distribution)/docker-distribution/bin/registry pkg/qliksense/docker-registry
-rm -rf $(TMP-docker-distribution) -rm -rf $(TMP-docker-distribution)
endif endif
.PHONY: test-short
test-short: test-setup
go test -short -count=1 -tags "$(BUILDTAGS)" -v ./... go test -short -count=1 -tags "$(BUILDTAGS)" -v ./...
$(MAKE) clean $(MAKE) clean
.PHONY: test
test: test-setup
go test -count=1 -tags "$(BUILDTAGS)" -v ./...
$(MAKE) clean
xbuild-all: clean generate xbuild-all: clean generate
$(foreach OS, $(SUPPORTED_PLATFORMS), \ $(foreach OS, $(SUPPORTED_PLATFORMS), \
$(foreach ARCH, $(SUPPORTED_ARCHES), \ $(foreach ARCH, $(SUPPORTED_ARCHES), \

View File

@@ -42,3 +42,20 @@ func configViewCmd(q *qliksense.Qliksense) *cobra.Command {
} }
return c 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
}

View File

@@ -3,6 +3,7 @@ package main
import ( import (
"errors" "errors"
"fmt" "fmt"
"os"
qapi "github.com/qlik-oss/sense-installer/pkg/api" qapi "github.com/qlik-oss/sense-installer/pkg/api"
@@ -68,18 +69,30 @@ func setConfigsCmd(q *qliksense.Qliksense) *cobra.Command {
var ( var (
cmd *cobra.Command cmd *cobra.Command
) )
base64Encoded := false
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "set-configs", Use: "set-configs",
Short: "set configurations into the qliksense context as key-value pairs", Short: "set configurations into the qliksense context as key-value pairs",
Example: ` Example: `
qliksense config set-configs <service_name>.<attribute>="<value>" qliksense config set-configs <service_name>.<attribute>="<value>"
- The above configuration will be displayed in the CR - 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
`, `,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return q.SetConfigs(args) if isInputFromPipe() && len(args) == 1 {
return q.SetConfigFromReader(args[0], os.Stdin, base64Encoded)
}
return q.SetConfigs(args, base64Encoded)
}, },
} }
f := cmd.Flags()
f.BoolVarP(&base64Encoded, "base64", "", false, "if the arguments value is base64 encoded")
return cmd return cmd
} }
@@ -88,7 +101,7 @@ func setSecretsCmd(q *qliksense.Qliksense) *cobra.Command {
cmd *cobra.Command cmd *cobra.Command
secret bool secret bool
) )
base64Encoded := false
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "set-secrets", Use: "set-secrets",
Short: "set secrets configurations into the qliksense context as key-value pairs", Short: "set secrets configurations into the qliksense context as key-value pairs",
@@ -101,13 +114,24 @@ qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=true
qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=false qliksense config set-secrets <service_name>.<attribute>="<value>" --secret=false
- Encrypt the secret value and display it in the current context - Encrypt the secret value and display it in the current context
- No secret resource is created - No secret resource is created
- The above configuration will be displayed in the CR `, - 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`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return q.SetSecrets(args, secret) if isInputFromPipe() && len(args) == 1 {
return q.SetSecretsFromReader(args[0], os.Stdin, secret, base64Encoded)
}
return q.SetSecrets(args, secret, base64Encoded)
}, },
} }
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&secret, "secret", false, "Whether secrets should be encrypted as a Kubernetes Secret resource") 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 return cmd
} }

View File

@@ -22,7 +22,7 @@ func crdsViewCmd(q *qliksense.Qliksense) *cobra.Command {
}, },
} }
f := c.Flags() f := c.Flags()
f.BoolVarP(&opts.All, "all", "a", false, "Include All CRDs") f.BoolVarP(&opts.All, "all", "", false, "Include All CRDs")
return c return c
} }
@@ -37,6 +37,6 @@ func crdsInstallCmd(q *qliksense.Qliksense) *cobra.Command {
}, },
} }
f := c.Flags() f := c.Flags()
f.BoolVarP(&opts.All, "all", "a", false, "Include All CRDs") f.BoolVarP(&opts.All, "all", "", false, "Include All CRDs")
return c return c
} }

View File

@@ -85,7 +85,6 @@ func doEnforceEula() {
fmt.Println(eulaText) fmt.Println(eulaText)
fmt.Print(eulaPrompt) fmt.Print(eulaPrompt)
answer := readRuneFromTty() answer := readRuneFromTty()
fmt.Printf("%v\n", answer)
if strings.ToLower(answer) != "y" { if strings.ToLower(answer) != "y" {
fmt.Println(eulaErrorInstruction) fmt.Println(eulaErrorInstruction)
os.Exit(1) os.Exit(1)
@@ -98,9 +97,9 @@ func readRuneFromTty() string {
panic(err) panic(err)
} }
defer t.Close() defer t.Close()
answer, err := t.ReadRune() answer, err := t.ReadString()
if err != nil { if err != nil {
panic(err) panic(err)
} }
return string(answer) return answer
} }

View File

@@ -1,26 +1,30 @@
package main package main
import ( import (
"errors"
"github.com/qlik-oss/sense-installer/pkg/qliksense" "github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func fetchCmd(q *qliksense.Qliksense) *cobra.Command { func fetchCmd(q *qliksense.Qliksense) *cobra.Command {
opts := &qliksense.FetchCommandOptions{}
c := &cobra.Command{ c := &cobra.Command{
Use: "fetch", Use: "fetch",
Short: "fetch a release from qliksense-k8s repo", Short: "fetch a release from qliksense-k8s repo, if version not supplied, will use from context",
Long: `fetch a release from qliksense-k8s repo`, Long: `fetch a release from qliksense-k8s repo, if version not supplied, will use from context`,
Example: `qliksense fetch <version>`, Example: `qliksense fetch [version]`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("requires a version (i.e. v1.0.0)")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return q.FetchQK8s(args[0]) if len(args) == 1 {
opts.Version = args[0]
}
return q.FetchK8sWithOpts(opts)
}, },
} }
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 return c
} }

View File

@@ -20,7 +20,7 @@ func preflightCmd(q *qliksense.Qliksense) *cobra.Command {
return preflightCmd return preflightCmd
} }
func preflightCheckDnsCmd(q *qliksense.Qliksense) *cobra.Command { func pfDnsCheckCmd(q *qliksense.Qliksense) *cobra.Command {
var preflightDnsCmd = &cobra.Command{ var preflightDnsCmd = &cobra.Command{
Use: "dns", Use: "dns",
Short: "perform preflight dns check", Short: "perform preflight dns check",
@@ -28,39 +28,59 @@ func preflightCheckDnsCmd(q *qliksense.Qliksense) *cobra.Command {
Example: `qliksense preflight dns`, Example: `qliksense preflight dns`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q} qp := &preflight.QliksensePreflight{Q: q}
err := qp.DownloadPreflight()
// Preflight DNS check
fmt.Printf("Preflight DNS check\n")
fmt.Println("---------------------")
namespace, kubeConfigContents, err := preflight.InitPreflight()
if err != nil { if err != nil {
err = fmt.Errorf("There has been an error downloading preflight: %+v", err) fmt.Printf("Preflight DNS check FAILED\n")
log.Println(err) log.Fatal(err)
return err
} }
return qp.CheckDns() 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 return preflightDnsCmd
} }
func preflightCheckK8sVersionCmd(q *qliksense.Qliksense) *cobra.Command { func pfK8sVersionCheckCmd(q *qliksense.Qliksense) *cobra.Command {
var preflightCheckK8sVersionCmd = &cobra.Command{ var preflightCheckK8sVersionCmd = &cobra.Command{
Use: "k8s-version", Use: "kube-version",
Short: "check k8s version", Short: "check kubernetes version",
Long: `check minimum valid k8s version on the cluster`, Long: `check minimum valid kubernetes version on the cluster`,
Example: `qliksense preflight k8s-version`, Example: `qliksense preflight kube-version`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q} qp := &preflight.QliksensePreflight{Q: q}
err := qp.DownloadPreflight()
// Preflight Kubernetes minimum version check
fmt.Printf("Preflight kubernetes minimum version check\n")
fmt.Println("------------------------------------------")
namespace, kubeConfigContents, err := preflight.InitPreflight()
if err != nil { if err != nil {
err = fmt.Errorf("There has been an error downloading preflight: %+v", err) fmt.Printf("Preflight kubernetes minimum version check FAILED\n")
log.Println(err) log.Fatal(err)
return err
} }
return qp.CheckK8sVersion() 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 return preflightCheckK8sVersionCmd
} }
func preflightAllChecksCmd(q *qliksense.Qliksense) *cobra.Command { func pfAllChecksCmd(q *qliksense.Qliksense) *cobra.Command {
var mongodbUrl string
var preflightAllChecksCmd = &cobra.Command{ var preflightAllChecksCmd = &cobra.Command{
Use: "all", Use: "all",
Short: "perform all checks", Short: "perform all checks",
@@ -68,14 +88,263 @@ func preflightAllChecksCmd(q *qliksense.Qliksense) *cobra.Command {
Example: `qliksense preflight all`, Example: `qliksense preflight all`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q} qp := &preflight.QliksensePreflight{Q: q}
err := qp.DownloadPreflight()
// Preflight run all checks
fmt.Printf("Running all preflight checks\n")
namespace, kubeConfigContents, err := preflight.InitPreflight()
if err != nil { if err != nil {
err = fmt.Errorf("There has been an error downloading preflight: %+v", err) fmt.Println(err)
log.Println(err) fmt.Printf("Running preflight check suite has FAILED...\n")
return err log.Fatal()
} }
return qp.RunAllPreflightChecks() 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 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
}

View File

@@ -20,28 +20,7 @@ func pullQliksenseImages(q *qliksense.Qliksense) *cobra.Command {
if err != nil { if err != nil {
return err return err
} }
return q.PullImages(version, opts.Profile)
qConfig := qapi.NewQConfig(q.QliksenseHome)
if version == "" {
if qcr, err := qConfig.GetCurrentCR(); err != nil {
return err
} else {
version = qcr.GetLabelFromCr("version")
}
}
if version != "" {
if !qConfig.IsRepoExistForCurrent(version) {
if err := q.FetchQK8s(version); err != nil {
return err
}
}
if err := qConfig.SwitchCurrentCRToVersionAndProfile(version, opts.Profile); err != nil {
return err
}
}
return q.PullImagesForCurrentCR()
}, },
} }
f := cmd.Flags() f := cmd.Flags()
@@ -58,7 +37,7 @@ func pushQliksenseImages(q *qliksense.Qliksense) *cobra.Command {
qConfig := qapi.NewQConfig(q.QliksenseHome) qConfig := qapi.NewQConfig(q.QliksenseHome)
if qcr, err := qConfig.GetCurrentCR(); err != nil { if qcr, err := qConfig.GetCurrentCR(); err != nil {
return err return err
} else if registry := qcr.GetImageRegistry(); registry == "" { } else if registry := qcr.Spec.GetImageRegistry(); registry == "" {
return errors.New("no image registry in config") return errors.New("no image registry in config")
} else { } else {
return q.PushImagesForCurrentCR() return q.PushImagesForCurrentCR()

View File

@@ -100,6 +100,10 @@ func getRootCmd(p *qliksense.Qliksense) *cobra.Command {
if err := p.SetUpQliksenseDefaultContext(); err != nil { if err := p.SetUpQliksenseDefaultContext(); err != nil {
panic(err) panic(err)
} }
pf := api.NewPreflightConfig(p.QliksenseHome)
if err := pf.Initialize(); err != nil {
panic(err)
}
globalEulaPostRun(cmd, p) globalEulaPostRun(cmd, p)
} }
}, },
@@ -185,6 +189,8 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
// add clean-config-repo-patches command as a sub-command to the app config sub-command // add clean-config-repo-patches command as a sub-command to the app config sub-command
configCmd.AddCommand(cleanConfigRepoPatchesCmd(p)) configCmd.AddCommand(cleanConfigRepoPatchesCmd(p))
// open editor for config
configCmd.AddCommand(configEditCmd(p))
// add uninstall command // add uninstall command
cmd.AddCommand(uninstallCmd(p)) cmd.AddCommand(uninstallCmd(p))
@@ -195,11 +201,17 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
// add preflight command // add preflight command
preflightCmd := preflightCmd(p) preflightCmd := preflightCmd(p)
preflightCmd.AddCommand(preflightCheckDnsCmd(p)) preflightCmd.AddCommand(pfDnsCheckCmd(p))
preflightCmd.AddCommand(preflightCheckK8sVersionCmd(p)) preflightCmd.AddCommand(pfK8sVersionCheckCmd(p))
preflightCmd.AddCommand(preflightAllChecksCmd(p)) preflightCmd.AddCommand(pfAllChecksCmd(p))
//preflightCmd.AddCommand(preflightCheckMongoCmd(p)) preflightCmd.AddCommand(pfMongoCheckCmd(p))
//preflightCmd.AddCommand(preflightCheckAllCmd(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(preflightCmd)
cmd.AddCommand(loadCrFile(p)) cmd.AddCommand(loadCrFile(p))

View File

@@ -29,19 +29,20 @@ The expected output should be similar to the one shown below.
```console ```console
$ qliksense preflight dns $ qliksense preflight dns
Creating resources to run preflight checks Preflight DNS check
deployment.apps/qnginx001 created ---------------------
service/qnginx001 created Created deployment "dep-dns-preflight-check"
pod/qnginx001-6db5fc95c5-s9sl2 condition met Created service "svc-dns-pf-check"
Running Preflight checks ⠇ Created pod: pf-pod-1
--- PASS DNS check Fetching pod: pf-pod-1
--- DNS check passed Fetching pod: pf-pod-1
--- PASS cluster-preflight-checks Exec-ing into the container...
PASS Preflight DNS check: PASSED
Completed preflight DNS check
DNS check completed, cleaning up resources now Cleaning up resources...
service "qnginx001" deleted Deleted pod: pf-pod-1
deployment.extensions "qnginx001" deleted Deleted service: svc-dns-pf-check
Deleted deployment: dep-dns-preflight-check
``` ```
@@ -51,50 +52,202 @@ The command to run this check and the expected similar output are as shown below
```console ```console
$ qliksense preflight k8s-version $ qliksense preflight k8s-version
Minimum Kubernetes version supported: 1.11.0 Preflight kubernetes minimum version check
Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.3", GitCommit:"06ad960bfd03b39c8310aaf92d1e7c12ce618213", GitTreeState:"clean", BuildDate:"2020-02-13T18:08:14Z", GoVersion:"go1.13.8", Compiler:"gc", Platform:"darwin/amd64"} ------------------------------------------
Server Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.5", GitCommit:"20c265fef0741dd71a66480e35bd69f18351daea", GitTreeState:"clean", BuildDate:"2019-10-15T19:07:57Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"linux/amd64"} Kubernetes API Server version: v1.15.5
Running Preflight checks ⠇ Current K8s Version: 1.15.5
--- PASS Required Kubernetes Version Current 1.15.5 is greater than minimum required version:1.11.0, hence good to go
--- Good to go. Preflight minimum kubernetes version check: PASSED
--- PASS cluster-preflight-checks Completed Preflight kubernetes minimum version check
PASS
Minimum kubernetes version check completed
``` ```
### 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 ### Running all checks
Run the command shown below to execute all preflight checks. Run the command shown below to execute all preflight checks.
```console ```console
$ qliksense preflight all --mongodb-url=<url> OR
$ qliksense preflight all $ qliksense preflight all
Running all preflight checks Running all preflight checks
Running DNS check... Preflight DNS check
Creating resources to run preflight checks -------------------
deployment.apps/qnginx001 created Created deployment "dep-dns-preflight-check"
service/qnginx001 created Created service "svc-dns-pf-check"
pod/qnginx001-6db5fc95c5-grwv2 condition met Created pod: pf-pod-1
Running Preflight checks ⠇ Fetching pod: pf-pod-1
--- PASS DNS check Fetching pod: pf-pod-1
--- DNS check passed Exec-ing into the container...
--- PASS cluster-preflight-checks Preflight DNS check: PASSED
PASS Completed preflight DNS check
Cleaning up resources...
Deleted pod: pf-pod-1
Deleted service: svc-dns-pf-check
Deleted deployment: dep-dns-preflight-check
DNS check completed, cleaning up resources now Preflight kubernetes minimum version check
service "qnginx001" deleted ------------------------------------------
deployment.extensions "qnginx001" deleted Kubernetes API Server version: v1.15.5
Current K8s Version: 1.15.5
Running minimum kubernetes version check... Current 1.15.5 is greater than minimum required version:1.11.0, hence good to go
Minimum Kubernetes version supported: 1.11.0 Preflight minimum kubernetes version check: PASSED
Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.3", GitCommit:"06ad960bfd03b39c8310aaf92d1e7c12ce618213", GitTreeState:"clean", BuildDate:"2020-02-13T18:08:14Z", GoVersion:"go1.13.8", Compiler:"gc", Platform:"darwin/amd64"} Completed Preflight kubernetes minimum version check
Server Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.5", GitCommit:"20c265fef0741dd71a66480e35bd69f18351daea", GitTreeState:"clean", BuildDate:"2019-10-15T19:07:57Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"linux/amd64"} ...
Running Preflight checks ⠧ ...
--- PASS Required Kubernetes Version All preflight checks have PASSED
--- Good to go.
--- PASS cluster-preflight-checks
PASS
Minimum kubernetes version check completed
Completed running all preflight checks Completed running all preflight checks
``` ```

11
go.mod
View File

@@ -10,12 +10,13 @@ replace (
k8s.io/client-go => k8s.io/client-go v0.0.0-20191016111102-bec269661e48 k8s.io/client-go => k8s.io/client-go v0.0.0-20191016111102-bec269661e48
k8s.io/kubectl => k8s.io/kubectl v0.0.0-20191016120415-2ed914427d51 k8s.io/kubectl => k8s.io/kubectl v0.0.0-20191016120415-2ed914427d51
sigs.k8s.io/kustomize/api => github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9 sigs.k8s.io/kustomize/api => github.com/qlik-oss/kustomize/api v0.3.3-0.20200402170547-2e8140160c36
) )
require ( require (
cloud.google.com/go v0.52.0 // indirect cloud.google.com/go v0.52.0 // indirect
cloud.google.com/go/storage v1.5.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/Shopify/ejson v1.2.1
github.com/aws/aws-sdk-go v1.28.9 // indirect github.com/aws/aws-sdk-go v1.28.9 // indirect
github.com/bugsnag/bugsnag-go v1.5.3 // indirect github.com/bugsnag/bugsnag-go v1.5.3 // indirect
@@ -25,13 +26,13 @@ require (
github.com/docker/cli v0.0.0-20191212191748-ebca1413117a // indirect github.com/docker/cli v0.0.0-20191212191748-ebca1413117a // indirect
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7 // indirect github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7 // indirect
github.com/docker/go-metrics v0.0.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/envy v1.9.0 // indirect
github.com/gobuffalo/logger v1.0.3 // indirect github.com/gobuffalo/logger v1.0.3 // indirect
github.com/gobuffalo/packd v1.0.0 // indirect github.com/gobuffalo/packd v1.0.0 // indirect
github.com/gobuffalo/packr/v2 v2.7.1 github.com/gobuffalo/packr/v2 v2.7.1
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.3.3 // indirect github.com/golang/protobuf v1.3.3 // indirect
github.com/google/uuid v1.1.1
github.com/gorilla/mux v1.7.3 // indirect github.com/gorilla/mux v1.7.3 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
@@ -40,8 +41,9 @@ require (
github.com/mattn/go-tty v0.0.3 github.com/mattn/go-tty v0.0.3
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/otiai10/copy v1.1.1
github.com/pkg/errors v0.8.1 github.com/pkg/errors v0.8.1
github.com/qlik-oss/k-apis v0.0.22 github.com/qlik-oss/k-apis v0.1.1
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/rogpeppe/go-internal v1.5.2 // indirect github.com/rogpeppe/go-internal v1.5.2 // indirect
github.com/spf13/cobra v0.0.6 github.com/spf13/cobra v0.0.6
@@ -49,8 +51,7 @@ require (
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31 github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 // indirect golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 // indirect
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a // indirect golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a // indirect
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b golang.org/x/net v0.0.0-20200301022130-244492dfa37a
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect
golang.org/x/tools v0.0.0-20200312194400-c312e98713c2 // indirect golang.org/x/tools v0.0.0-20200312194400-c312e98713c2 // indirect
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b // indirect google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b // indirect
google.golang.org/grpc v1.27.0 // indirect google.golang.org/grpc v1.27.0 // indirect

43
go.sum
View File

@@ -131,6 +131,8 @@ 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.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 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/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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= 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/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
@@ -205,6 +207,7 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc
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-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/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.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 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -297,6 +300,12 @@ 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-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/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-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 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-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= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
@@ -561,6 +570,8 @@ 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.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 h1:DMo4fmknnz0E0evoNYnV48RjWndOsmd6OW+09R3cEP8=
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= 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.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
@@ -568,6 +579,7 @@ 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.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 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 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-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= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
@@ -662,6 +674,7 @@ 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/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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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/lib/pq v1.1.1/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 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@@ -755,6 +768,7 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 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/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/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/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs= github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs=
github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
@@ -787,6 +801,14 @@ github.com/opencontainers/selinux v1.3.0 h1:xsI95WzPZu5exzA6JzkLSfdr/DilzOhCJOqG
github.com/opencontainers/selinux v1.3.0/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs= github.com/opencontainers/selinux v1.3.0/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913 h1:TnbXhKzrTOyuvWrjI8W6pcoI9XPbLHFXCdN2dtUw7Rw= 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/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.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@@ -847,10 +869,14 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/qlik-oss/k-apis v0.0.22 h1:tntQEeRqDYkBi2Ku5+xt7ABGMeFPck7+DOKrHUnpzwI= github.com/qlik-oss/k-apis v0.0.39 h1:fIGCC7f9kU7319VTSJKr3fLoA9E4MjusRFmOjX3ypis=
github.com/qlik-oss/k-apis v0.0.22/go.mod h1:DNiWYqCqPIN216l7+1rccArNIYPb1Le7kYDcPSyNp+Q= github.com/qlik-oss/k-apis v0.0.39/go.mod h1:yoYGgPJ/H0t9H3NSq64dWfyQY6QWi2L9c+hCJoVO03U=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9 h1:iqeqTS4zjp6rPEaxmFB7pemA2CMjOEN5dYSXZaQ82uw= github.com/qlik-oss/k-apis v0.1.0 h1:uMl1316SNYy5Hm6jy1U7wiCMkut0tKqdP8mBpSuXXp8=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9/go.mod h1:OCt7FTrRVHj4kmR2xLJJUIqu00BTr6GeF09hSmM17Kw= 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/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
@@ -934,6 +960,8 @@ github.com/spf13/viper v1.6.1 h1:VPZzIkznI1YhVMRi6vNFLHSwhnhReBfgTxIPccpfdZk=
github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= 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 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= 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.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.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
@@ -961,6 +989,7 @@ 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 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 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 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 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
@@ -995,6 +1024,8 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMx
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 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/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 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 h1:p7OofyZ509h8DmPLh8Hn+EIIZm/xYhdZHJ9GnXHdr6U=
github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= 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= github.com/yvasiyarov/gorelic v0.0.6 h1:qMJQYPNdtJ7UNYHjX38KXZtltKTqimMuoQjNnSVIuJg=
@@ -1046,6 +1077,7 @@ golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U= golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-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 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA=
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1126,6 +1158,8 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjut
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b 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-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-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1289,6 +1323,7 @@ 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-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 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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=

View File

@@ -1,11 +1,9 @@
package api package api
import ( import (
"crypto/rsa"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -23,6 +21,7 @@ const (
qliksenseContextsDirName = "contexts" qliksenseContextsDirName = "contexts"
qliksenseSecretsDirName = "secrets" qliksenseSecretsDirName = "secrets"
qliksenseEjsonDirName = "ejson" qliksenseEjsonDirName = "ejson"
QLIK_GIT_REPO = "https://github.com/qlik-oss/qliksense-k8s"
) )
// NewQConfig create QliksenseConfig object from file ~/.qliksense/config.yaml // NewQConfig create QliksenseConfig object from file ~/.qliksense/config.yaml
@@ -133,6 +132,77 @@ func (qc *QliksenseConfig) GetCRFilePath(contextName string) string {
} }
return crFilePath 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 { func (qc *QliksenseConfig) IsRepoExist(contextName, version string) bool {
if _, err := os.Lstat(qc.BuildRepoPathForContext(contextName, version)); err != nil { if _, err := os.Lstat(qc.BuildRepoPathForContext(contextName, version)); err != nil {
return false return false
@@ -147,6 +217,11 @@ func (qc *QliksenseConfig) IsRepoExistForCurrent(version string) bool {
return true return true
} }
func (qc *QliksenseConfig) DeleteRepoForCurrent(version string) error {
path := qc.BuildRepoPath(version)
return os.RemoveAll(path)
}
func (qc *QliksenseConfig) BuildRepoPath(version string) string { func (qc *QliksenseConfig) BuildRepoPath(version string) string {
return qc.BuildRepoPathForContext(qc.Spec.CurrentContext, version) return qc.BuildRepoPathForContext(qc.Spec.CurrentContext, version)
} }
@@ -159,10 +234,10 @@ func (qc *QliksenseConfig) BuildCurrentManifestsRoot(version string) string {
return qc.BuildRepoPath(version) return qc.BuildRepoPath(version)
} }
func (qc *QliksenseConfig) WriteCR(cr *QliksenseCR, contextName string) error { func (qc *QliksenseConfig) WriteCR(cr *QliksenseCR) error {
crf := qc.GetCRFilePath(contextName) crf := qc.GetCRFilePath(cr.GetName())
if crf == "" { if crf == "" {
return errors.New("context name " + contextName + " not found") return errors.New("context name " + cr.GetName() + " not found")
} }
return qc.TransformAndWriteCr(cr, crf) return qc.TransformAndWriteCr(cr, crf)
@@ -183,7 +258,7 @@ func (qc *QliksenseConfig) CreateOrWriteCrAndContext(cr *QliksenseCR) error {
crf = filepath.Join(cDir, cr.GetName()+".yaml") crf = filepath.Join(cDir, cr.GetName()+".yaml")
ctx := Context{ ctx := Context{
Name: cr.GetName(), Name: cr.GetName(),
CrFile: filepath.Join("contexts", cr.GetName(), cr.GetName()+".yaml"), CrFile: "contexts/" + cr.GetName() + "/" + cr.GetName() + ".yaml", //filepath.Join("contexts", cr.GetName(), cr.GetName()+".yaml"),
} }
qc.AddToContexts(ctx) qc.AddToContexts(ctx)
@@ -198,6 +273,8 @@ func (qc *QliksenseConfig) CreateOrWriteCrAndContext(cr *QliksenseCR) error {
func (qc *QliksenseConfig) TransformAndWriteCr(cr *QliksenseCR, file string) error { func (qc *QliksenseConfig) TransformAndWriteCr(cr *QliksenseCR, file string) error {
if strings.HasPrefix(cr.Spec.ManifestsRoot, qc.QliksenseHomePath) { 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, qc.QliksenseHomePath+"\\", "", 1)
cr.Spec.ManifestsRoot = strings.Replace(cr.Spec.ManifestsRoot, "\\", "/", -1)
} }
if err := WriteToFile(cr, file); err != nil { if err := WriteToFile(cr, file); err != nil {
return err return err
@@ -214,7 +291,7 @@ func (qc *QliksenseConfig) AddToContexts(ctx Context) error {
return nil return nil
} }
func (qc *QliksenseConfig) WriteCurrentContextCR(cr *QliksenseCR) error { func (qc *QliksenseConfig) WriteCurrentContextCR(cr *QliksenseCR) error {
return qc.WriteCR(cr, qc.Spec.CurrentContext) return qc.WriteCR(cr)
} }
func (qc *QliksenseConfig) IsContextExist(ctxName string) bool { func (qc *QliksenseConfig) IsContextExist(ctxName string) bool {
@@ -245,9 +322,9 @@ func (qc *QliksenseConfig) GetCurrentContextSecretsDir() (string, error) {
func (qc *QliksenseConfig) setDockerConfigJsonSecret(filename string, dockerConfigJsonSecret *DockerConfigJsonSecret) error { func (qc *QliksenseConfig) setDockerConfigJsonSecret(filename string, dockerConfigJsonSecret *DockerConfigJsonSecret) error {
if secretsDir, err := qc.GetCurrentContextSecretsDir(); err != nil { if secretsDir, err := qc.GetCurrentContextSecretsDir(); err != nil {
return err return err
} else if publicKey, _, err := qc.GetCurrentContextEncryptionKeyPair(); err != nil { } else if encryptionKey, err := qc.GetEncryptionKeyForCurrent(); err != nil {
return err return err
} else if dockerConfigJsonSecretYaml, err := dockerConfigJsonSecret.ToYaml(publicKey); err != nil { } else if dockerConfigJsonSecretYaml, err := dockerConfigJsonSecret.ToYaml(encryptionKey); err != nil {
return err return err
} else if err := os.MkdirAll(secretsDir, os.ModePerm); err != nil { } else if err := os.MkdirAll(secretsDir, os.ModePerm); err != nil {
return err return err
@@ -294,9 +371,9 @@ func (qc *QliksenseConfig) getDockerConfigJsonSecret(name string) (*DockerConfig
return nil, err return nil, err
} else if dockerConfigJsonSecretYaml, err := ioutil.ReadFile(filepath.Join(secretsDir, name)); err != nil { } else if dockerConfigJsonSecretYaml, err := ioutil.ReadFile(filepath.Join(secretsDir, name)); err != nil {
return nil, err return nil, err
} else if _, privateKey, err := qc.GetCurrentContextEncryptionKeyPair(); err != nil { } else if encryptionKey, err := qc.GetEncryptionKeyForCurrent(); err != nil {
return nil, err return nil, err
} else if err := dockerConfigJsonSecret.FromYaml(dockerConfigJsonSecretYaml, privateKey); err != nil { } else if err := dockerConfigJsonSecret.FromYaml(dockerConfigJsonSecretYaml, encryptionKey); err != nil {
return nil, err return nil, err
} }
return dockerConfigJsonSecret, nil return dockerConfigJsonSecret, nil
@@ -307,11 +384,11 @@ func (qc *QliksenseConfig) getCurrentContextEncryptionKeyPairLocation() (string,
if qcr, err := qc.GetCurrentCR(); err != nil { if qcr, err := qc.GetCurrentCR(); err != nil {
return "", err return "", err
} else { } else {
return qc.getContextEncryptionKeyPairLocation(qcr.GetName()) return qc.getContextEncryptionKeyLocation(qcr.GetName())
} }
} }
func (qc *QliksenseConfig) getContextEncryptionKeyPairLocation(contextName string) (string, error) { func (qc *QliksenseConfig) getContextEncryptionKeyLocation(contextName string) (string, error) {
// Check env var: QLIKSENSE_KEY_LOCATION to determine location to store keypair // Check env var: QLIKSENSE_KEY_LOCATION to determine location to store keypair
var secretKeyPairLocation string var secretKeyPairLocation string
if os.Getenv("QLIKSENSE_KEY_LOCATION") != "" { if os.Getenv("QLIKSENSE_KEY_LOCATION") != "" {
@@ -321,9 +398,9 @@ func (qc *QliksenseConfig) getContextEncryptionKeyPairLocation(contextName strin
// QLIKSENSE_KEY_LOCATION has not been set, hence storing key pair in default location: // QLIKSENSE_KEY_LOCATION has not been set, hence storing key pair in default location:
// /.qliksense/secrets/contexts/<current-context>/secrets/ // /.qliksense/secrets/contexts/<current-context>/secrets/
secretKeyPairLocation = filepath.Join(qc.QliksenseHomePath, qliksenseSecretsDirName, qliksenseContextsDirName, contextName, qliksenseSecretsDirName) secretKeyPairLocation = filepath.Join(qc.QliksenseHomePath, qliksenseSecretsDirName, qliksenseContextsDirName, contextName, qliksenseSecretsDirName)
} }
return secretKeyPairLocation, nil
return secretKeyPairLocation, os.MkdirAll(secretKeyPairLocation, os.ModePerm)
} }
func (qc *QliksenseConfig) GetCurrentContextEjsonKeyDir() (string, error) { func (qc *QliksenseConfig) GetCurrentContextEjsonKeyDir() (string, error) {
@@ -338,52 +415,25 @@ func (qc *QliksenseConfig) GetCurrentContextEjsonKeyDir() (string, error) {
} }
} }
func (qc *QliksenseConfig) GetCurrentContextEncryptionKeyPair() (*rsa.PublicKey, *rsa.PrivateKey, error) { func (qc *QliksenseConfig) GetEncryptionKeyForCurrent() (string, error) {
if qcr, err := qc.GetCurrentCR(); err != nil { if qcr, err := qc.GetCurrentCR(); err != nil {
return nil, nil, err return "", err
} else { } else {
return qc.GetContextEncryptionKeyPair(qcr.GetName()) return qc.GetEncryptionKeyFor(qcr.GetName())
} }
} }
func (qc *QliksenseConfig) GetContextEncryptionKeyPair(contextName string) (*rsa.PublicKey, *rsa.PrivateKey, error) { func (qc *QliksenseConfig) GetEncryptionKeyFor(contextName string) (string, error) {
secretKeyPairLocation, err := qc.getContextEncryptionKeyPairLocation(contextName) secretKeyLocation, err := qc.getContextEncryptionKeyLocation(contextName)
if err != nil { if err != nil {
return nil, nil, err return "", err
} }
key, err := LoadSecretKey(secretKeyLocation)
publicKeyFilePath := filepath.Join(secretKeyPairLocation, QliksensePublicKey) if key != "" {
privateKeyFilePath := filepath.Join(secretKeyPairLocation, QliksensePrivateKey) return key, nil
// try to create the dir if it doesn't exist
if !FileExists(publicKeyFilePath) || !FileExists(privateKeyFilePath) {
LogDebugMessage("Qliksense secretKeyLocation dir does not exist, creating it now: %s", secretKeyPairLocation)
if err := os.MkdirAll(secretKeyPairLocation, os.ModePerm); err != nil {
err = fmt.Errorf("Not able to create %s dir: %v", secretKeyPairLocation, err)
log.Println(err)
return nil, nil, err
}
// generating and storing key-pair
err1 := GenerateAndStoreSecretKeypair(secretKeyPairLocation)
if err1 != nil {
err1 = fmt.Errorf("Not able to generate and store key pair for encryption")
log.Println(err1)
return nil, nil, err1
}
}
if publicKeyBytes, err := ReadKeys(publicKeyFilePath); err != nil {
LogDebugMessage("Not able to read public key")
return nil, nil, err
} else if privateKeyBytes, err := ReadKeys(privateKeyFilePath); err != nil {
LogDebugMessage("Not able to read private key")
return nil, nil, err
} else if rsaPublicKey, err := DecodeToPublicKey(publicKeyBytes); err != nil {
return nil, nil, err
} else if rsaPrivateKey, err := DecodeToPrivateKey(privateKeyBytes); err != nil {
return nil, nil, err
} else {
return rsaPublicKey, rsaPrivateKey, nil
} }
fmt.Println("Generating new encryption key for the context: " + contextName)
return GenerateAndStoreSecretKey(secretKeyLocation)
} }
func (cr *QliksenseCR) AddLabelToCr(key, value string) { func (cr *QliksenseCR) AddLabelToCr(key, value string) {
@@ -408,17 +458,6 @@ func (cr *QliksenseCR) GetString() (string, error) {
return string(out), nil return string(out), nil
} }
func (cr *QliksenseCR) GetImageRegistry() string {
for _, nameValues := range cr.Spec.Configs {
for _, nameValue := range nameValues {
if nameValue.Name == "imageRegistry" {
return nameValue.Value
}
}
}
return ""
}
func (cr *QliksenseCR) GetK8sSecretsFolder(qlikSenseHomeDir string) string { func (cr *QliksenseCR) GetK8sSecretsFolder(qlikSenseHomeDir string) string {
return filepath.Join(qlikSenseHomeDir, qliksenseContextsDirName, cr.GetName(), qliksenseSecretsDirName) return filepath.Join(qlikSenseHomeDir, qliksenseContextsDirName, cr.GetName(), qliksenseSecretsDirName)
} }
@@ -440,11 +479,23 @@ func (cr *QliksenseCR) SetEULA(value string) {
cr.Spec.AddToConfigs("qliksense", "acceptEULA", value) 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 // GetDecryptedCr it decrypts all the encrypted value and return a new CR
func (qc *QliksenseConfig) GetDecryptedCr(cr *QliksenseCR) (*QliksenseCR, error) { func (qc *QliksenseConfig) GetDecryptedCr(cr *QliksenseCR) (*QliksenseCR, error) {
newCr := &QliksenseCR{} newCr := &QliksenseCR{}
copier.Copy(newCr, cr) copier.Copy(newCr, cr)
_, rsaPrivateKey, err := qc.GetCurrentContextEncryptionKeyPair() encryptionKey, err := qc.GetEncryptionKeyFor(cr.GetName())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -457,7 +508,7 @@ func (qc *QliksenseConfig) GetDecryptedCr(cr *QliksenseCR) (*QliksenseCR, error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
db, err := Decrypt(b, rsaPrivateKey) db, err := DecryptData(b, encryptionKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -470,6 +521,11 @@ func (qc *QliksenseConfig) GetDecryptedCr(cr *QliksenseCR) (*QliksenseCR, error)
finalSecrets[k] = newNvs finalSecrets[k] = newNvs
} }
newCr.Spec.Secrets = finalSecrets 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 return newCr, nil
} }

View File

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

8
pkg/api/copy.go Normal file
View File

@@ -0,0 +1,8 @@
package api
import "github.com/otiai10/copy"
//copy source directory to destination
func CopyDirectory(source string, dest string) error {
return copy.Copy(source, dest)
}

101
pkg/api/copy_test.go Normal file
View File

@@ -0,0 +1,101 @@
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()
}

View File

@@ -1,7 +1,6 @@
package api package api
import ( import (
"crypto/rsa"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
@@ -27,14 +26,14 @@ func (kdcjt *k8sDockerConfigJsonType) GenerateAuth() {
} }
type DockerConfigJsonSecret struct { type DockerConfigJsonSecret struct {
Name string Name string
Uri string Uri string
Username string Username string
Password string Password string
Email string Email string
} }
func (d *DockerConfigJsonSecret) ToYaml(encryptionKey *rsa.PublicKey) ([]byte, error) { func (d *DockerConfigJsonSecret) ToYaml(encryptionKey string) ([]byte, error) {
k8sDockerConfigJson := k8sDockerConfigJsonType{ k8sDockerConfigJson := k8sDockerConfigJsonType{
Username: d.Username, Username: d.Username,
Password: d.Password, Password: d.Password,
@@ -51,8 +50,8 @@ func (d *DockerConfigJsonSecret) ToYaml(encryptionKey *rsa.PublicKey) ([]byte, e
return nil, err return nil, err
} }
var k8sDockerConfigJsonMapMaybeEncryptedBytes []byte var k8sDockerConfigJsonMapMaybeEncryptedBytes []byte
if encryptionKey != nil { if encryptionKey != "" {
if k8sDockerConfigJsonMapMaybeEncryptedBytes, err = Encrypt(k8sDockerConfigJsonMapBytes, encryptionKey); err != nil { if k8sDockerConfigJsonMapMaybeEncryptedBytes, err = EncryptData(k8sDockerConfigJsonMapBytes, encryptionKey); err != nil {
return nil, err return nil, err
} }
} else { } else {
@@ -65,7 +64,7 @@ func (d *DockerConfigJsonSecret) ToYaml(encryptionKey *rsa.PublicKey) ([]byte, e
Kind: "Secret", Kind: "Secret",
}, },
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: d.Name, Name: d.Name,
}, },
Type: v1.SecretTypeDockerConfigJson, Type: v1.SecretTypeDockerConfigJson,
Data: map[string][]byte{ Data: map[string][]byte{
@@ -76,7 +75,7 @@ func (d *DockerConfigJsonSecret) ToYaml(encryptionKey *rsa.PublicKey) ([]byte, e
return K8sSecretToYaml(k8sSecret) return K8sSecretToYaml(k8sSecret)
} }
func (d *DockerConfigJsonSecret) FromYaml(secretBytes []byte, decryptionKey *rsa.PrivateKey) error { func (d *DockerConfigJsonSecret) FromYaml(secretBytes []byte, decryptionKey string) error {
k8sDockerConfigJsonMap := k8sDockerConfigJsonMapType{} k8sDockerConfigJsonMap := k8sDockerConfigJsonMapType{}
if k8sSecret, err := K8sSecretFromYaml(secretBytes); err != nil { if k8sSecret, err := K8sSecretFromYaml(secretBytes); err != nil {
return err return err
@@ -86,7 +85,7 @@ func (d *DockerConfigJsonSecret) FromYaml(secretBytes []byte, decryptionKey *rsa
return errors.New("not a kubernetes.io/dockerconfigjson type") return errors.New("not a kubernetes.io/dockerconfigjson type")
} else if k8sDockerConfigJsonMapEncryptedBytes, ok := k8sSecret.Data[".dockerconfigjson"]; !ok { } else if k8sDockerConfigJsonMapEncryptedBytes, ok := k8sSecret.Data[".dockerconfigjson"]; !ok {
return errors.New("secret data is missing a value for the .dockerconfigjson key") return errors.New("secret data is missing a value for the .dockerconfigjson key")
} else if k8sDockerConfigJsonMapBytes, err := Decrypt(k8sDockerConfigJsonMapEncryptedBytes, decryptionKey); err != nil { } else if k8sDockerConfigJsonMapBytes, err := DecryptData(k8sDockerConfigJsonMapEncryptedBytes, decryptionKey); err != nil {
return errors.New("secret data is missing a value for the .dockerconfigjson key") return errors.New("secret data is missing a value for the .dockerconfigjson key")
} else if err := json.Unmarshal(k8sDockerConfigJsonMapBytes, &k8sDockerConfigJsonMap); err != nil { } else if err := json.Unmarshal(k8sDockerConfigJsonMapBytes, &k8sDockerConfigJsonMap); err != nil {
return err return err

View File

@@ -1,8 +1,6 @@
package api package api
import ( import (
"crypto/rand"
"crypto/rsa"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
@@ -14,21 +12,21 @@ import (
func TestDockerConfigJsonSecret(t *testing.T) { func TestDockerConfigJsonSecret(t *testing.T) {
dockerConfigJsonSecret := DockerConfigJsonSecret{ dockerConfigJsonSecret := DockerConfigJsonSecret{
Name: "some-name", Name: "some-name",
Uri: "some-uri", Uri: "some-uri",
Username: "some-username", Username: "some-username",
Password: "some-password", Password: "some-password",
Email: "some-email", Email: "some-email",
} }
dockerConfigJsonSecretFromYaml := DockerConfigJsonSecret{} dockerConfigJsonSecretFromYaml := DockerConfigJsonSecret{}
validYamlMap := map[string]interface{}{} validYamlMap := map[string]interface{}{}
privateKey, err := rsa.GenerateKey(rand.Reader, RSA_KEY_LENGTH) encryptionKey, err := GenerateKey()
if err != nil { if err != nil {
t.Fatalf("error generating RSA private key: %v\n", err) t.Fatalf("error generating RSA private key: %v\n", err)
} }
dockerConfigJsonSecretYamlBytes, err := dockerConfigJsonSecret.ToYaml(&privateKey.PublicKey) dockerConfigJsonSecretYamlBytes, err := dockerConfigJsonSecret.ToYaml(encryptionKey)
dockerConfigJsonMap := map[string]interface{}{} dockerConfigJsonMap := map[string]interface{}{}
if err != nil { if err != nil {
t.Fatalf("error converting secret to yaml: %v", err) t.Fatalf("error converting secret to yaml: %v", err)
@@ -43,7 +41,7 @@ func TestDockerConfigJsonSecret(t *testing.T) {
t.Fatalf("no .dockerconfigjson data key in the secret yaml: %v", string(dockerConfigJsonSecretYamlBytes)) t.Fatalf("no .dockerconfigjson data key in the secret yaml: %v", string(dockerConfigJsonSecretYamlBytes))
} else if dockerConfigJsonEncryptedBytes, err := base64.StdEncoding.DecodeString(dockerConfigJsonBytesBase64.(string)); err != nil { } else if dockerConfigJsonEncryptedBytes, err := base64.StdEncoding.DecodeString(dockerConfigJsonBytesBase64.(string)); err != nil {
t.Fatalf("error decoding dockerConfigJsonBytes from base64: %v", err) t.Fatalf("error decoding dockerConfigJsonBytes from base64: %v", err)
} else if dockerConfigJsonBytes, err := Decrypt(dockerConfigJsonEncryptedBytes, privateKey); err != nil { } else if dockerConfigJsonBytes, err := DecryptData(dockerConfigJsonEncryptedBytes, encryptionKey); err != nil {
t.Fatalf("error decrypting dockerConfigJsonBytes: %v", err) t.Fatalf("error decrypting dockerConfigJsonBytes: %v", err)
} else if err := json.Unmarshal(dockerConfigJsonBytes, &dockerConfigJsonMap); err != nil { } else if err := json.Unmarshal(dockerConfigJsonBytes, &dockerConfigJsonMap); err != nil {
t.Fatalf("error unmarshalling dockerConfigJson from json: %v", err) t.Fatalf("error unmarshalling dockerConfigJson from json: %v", err)
@@ -63,7 +61,7 @@ func TestDockerConfigJsonSecret(t *testing.T) {
} }
t.Logf("dockerConfigJsonSecretYaml: \n%v\n", string(dockerConfigJsonSecretYamlBytes)) t.Logf("dockerConfigJsonSecretYaml: \n%v\n", string(dockerConfigJsonSecretYamlBytes))
if err := dockerConfigJsonSecretFromYaml.FromYaml(dockerConfigJsonSecretYamlBytes, privateKey); err != nil { if err := dockerConfigJsonSecretFromYaml.FromYaml(dockerConfigJsonSecretYamlBytes, encryptionKey); err != nil {
t.Fatalf("error reading secret in from yaml: %v", err) t.Fatalf("error reading secret in from yaml: %v", err)
} else if !reflect.DeepEqual(dockerConfigJsonSecret, dockerConfigJsonSecretFromYaml) { } else if !reflect.DeepEqual(dockerConfigJsonSecret, dockerConfigJsonSecretFromYaml) {
t.Fatalf("secret: %v does not equal secret: %v", dockerConfigJsonSecret, dockerConfigJsonSecretFromYaml) t.Fatalf("secret: %v does not equal secret: %v", dockerConfigJsonSecret, dockerConfigJsonSecretFromYaml)

View File

@@ -1,58 +1,42 @@
package api package api
import ( import (
"crypto/aes"
"crypto/cipher"
"crypto/rand" "crypto/rand"
"crypto/rsa" "encoding/hex"
"crypto/x509" "errors"
"encoding/pem"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"log" "log"
"path/filepath" "path/filepath"
) )
const ( const (
RSA_KEY_LENGTH = 4096 key_file_name = "user_secret_key"
QliksensePublicKey = "qliksensePub"
QliksensePrivateKey = "qliksensePriv"
) )
// GenerateAndStoreSecretKeypair generates and stores key pairs // GenerateAndStoreSecretKey generates and stores key
func GenerateAndStoreSecretKeypair(secretsPath string) error { func GenerateAndStoreSecretKey(secretsDir string) (string, error) {
LogDebugMessage("%s exists", secretsPath) // creating contexts/qlik-default/secrets/user_secret_key
// creating contexts/qlik-default/secrets/qliksensePub and contexts/qlik-default/secrets/qliksensePriv files keyFile := filepath.Join(secretsDir, key_file_name)
publicKeyFilePath := filepath.Join(secretsPath, QliksensePublicKey) key, err := GenerateKey()
privateKeyFilePath := filepath.Join(secretsPath, QliksensePrivateKey)
LogDebugMessage("Generating public-private key pair.....")
GenerateRSAEncryptionKeys(publicKeyFilePath, privateKeyFilePath)
LogDebugMessage("Generated public-private key pairs")
return nil
}
// GenerateRSAEncryptionKeys is used to generate a new public-private key pair
func GenerateRSAEncryptionKeys(publicKeyFilePath, privateKeyFilePath string) error {
LogDebugMessage("Generating new RSA key pair")
privateKey, err := rsa.GenerateKey(rand.Reader, RSA_KEY_LENGTH)
if err != nil { if err != nil {
log.Printf("error generating RSA private key: %v\n", err) return "", err
return err
} }
if err := writeContentToFile([]byte(key), keyFile); err != nil {
privateKeyPEM := EncodePrivateKey(privateKey) return "", err
if err := writeContentToFile(privateKeyPEM, privateKeyFilePath); err != nil {
return err
} }
pubKeyPEM, err2 := EncodePublicKey(&privateKey.PublicKey) return key, nil
if err2 != nil { }
log.Printf("error occurred when encoding public key: %v\n", err2) func LoadSecretKey(secretsDir string) (string, error) {
return err2 keyFile := filepath.Join(secretsDir, key_file_name)
by, err := ioutil.ReadFile(keyFile)
if err != nil {
return "", err
} }
if err := writeContentToFile(pubKeyPEM, publicKeyFilePath); err != nil { return string(by), nil
return err
}
return nil
} }
// writeContentToFile writes keys to a file // writeContentToFile writes keys to a file
@@ -65,104 +49,54 @@ func writeContentToFile(keyData []byte, fileName string) error {
return nil return nil
} }
// Encrypt encrypts data with public key func GenerateKey() (string, error) {
func Encrypt(pt []byte, pub *rsa.PublicKey) ([]byte, error) { salt := make([]byte, 32)
//hash := sha512.New() if _, err := rand.Read(salt); err != nil {
//ct, err := rsa.EncryptOAEP(hash, rand.Reader, pub, pt, nil) return "", err
ct, err := rsa.EncryptPKCS1v15(rand.Reader, pub, pt) }
s := fmt.Sprintf("%x", salt)
return s, nil
}
func EncryptData(plaintext []byte, userKey string) ([]byte, error) {
key, _ := hex.DecodeString(userKey)
block, err := aes.NewCipher(key)
if err != nil { if err != nil {
log.Println(err)
return nil, err return nil, err
} }
return ct, nil
}
// Decrypt decrypts data with private key aesgcm, err := cipher.NewGCM(block)
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 { if err != nil {
log.Println(err)
return nil, err return nil, err
} }
return pt, nil 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
} }
// EncodePrivateKey private key to bytes func DecryptData(ciphertext []byte, userKey string) ([]byte, error) {
func EncodePrivateKey(priv *rsa.PrivateKey) []byte { key, _ := hex.DecodeString(userKey)
privBytes := pem.EncodeToMemory( block, err := aes.NewCipher(key)
&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 { if err != nil {
log.Println(err)
return nil, err return nil, err
} }
pubBytes := pem.EncodeToMemory(&pem.Block{ aesgcm, err := cipher.NewGCM(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 { if err != nil {
log.Println(err)
return nil, err return nil, err
} }
return key, nil nonceSize := aesgcm.NonceSize()
} if len(ciphertext) < nonceSize {
return nil, errors.New("ciphertext too short")
// 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) nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
if err != nil { if err != nil {
log.Println(err)
return nil, err return nil, err
} }
key, ok := iface.(*rsa.PublicKey) return plaintext, nil
if !ok {
err := fmt.Errorf("Unable to decode public key")
log.Println(err)
return nil, err
}
return key, nil
} }

View File

@@ -1,128 +1,29 @@
package api package api
import ( import (
"encoding/base64"
"log"
"os"
"testing" "testing"
) )
func Test_generateRSAEncryptionKeys(t *testing.T) { func Test_encrypt_decrypt(t *testing.T) {
tests := []struct { key, err := GenerateKey()
name string if err != nil {
wantErr bool t.Log(err)
}{ t.FailNow()
{
name: "valid case",
wantErr: false,
},
} }
for _, tt := range tests { testData := "this is a secret value"
t.Run(tt.name, func(t *testing.T) { enc, err := EncryptData([]byte(testData), key)
if err := GenerateAndStoreSecretKeypair(os.TempDir()); (err != nil) != tt.wantErr { if err != nil {
t.Errorf("generateRSAEncryptionKeys() error = %v, wantErr %v", err, tt.wantErr) t.Log(err)
} t.FailNow()
}) }
} dec, err := DecryptData(enc, key)
} if err != nil {
t.Log(err)
func Test_encryption_decryption(t *testing.T) { t.FailNow()
privKeyBytes := []byte(`-----BEGIN RSA PRIVATE KEY----- }
MIIJJwIBAAKCAgEAwUCimKCidbF3UxEHPy8K+hvhklRB9JYhj5sJy0if4lTVibkK if testData != string(dec) {
1MrYCykOnmC40pPU9GLY1b8HxAg9tvyRn0YHUxOra6vVQaVcOVJhTM8D18d+lSr3 t.Log("expected: " + testData)
Lp1yiX+UGT4nzWI9+R1CCbwXrqeQVoZs6QZKynEXMkFI9/wNMOwPOvQFOSTuoEoC t.Log("actual: " + string(dec))
O+zyTyUWEkNbUq825ELUQdIsjgmlWUOONudxsAr7ESRXW9QTHVh6uWmr3VRKZHby t.Fail()
1JdU3I/wjdlGg5M2dDuXy5nQO9w/nYLjJXiw+zzOetZ/+t7/VOkOpNTeJQhwTM1W
F7Y2VLetbi9FHgyzHatrduh07+XEiTbgDf3GIx2bp2p6oh0G3N2zpiLcK/aZj8ro
uWWydfFfsU3MZ4FfJDP8I6b9awxjmKYqIr6hiPQCJaLBED8mwK+I5evIbnKv6E6u
K+BApWA/R7ElragoFYbqQ1VpvntVMtJt9Dy5ZrI+IQARdXD3bb34oh0IPBhClnvv
MUc1cWxDoXEX6oJ4I+LzxE87Zkwnan9qOwengolMVKFwPx1o37qrbmrXID21kKt7
FL6xN4HxHLkItr1fKzdyWDFRHgASTAWfx5BIwvPuUW0vZHkvO80VyV2L63whVhPn
PASmFkbviomrBttYfpr2aGQqF/qR1Nlxe834MFxk1pS9LMa/WnzvFr0gWakCAwEA
AQKCAgARSp9B2N2wejibDiL/3E23I1eDqFZedDB8kPrHXbAwqDaTJCN79spt9TaB
pVXkQaYEV/Pe7EDdoX8kKGU/QxzUqiXkdHOYdBtUZbKfFMbbP9ZrsnR7j0r4UpoF
yDH3hprU93E5PcNAtW2M0GpeT1nR01yn+n908PCdOAIE3GC7RDq1zOl2QzVLL55R
9ATv2Q2oTvJ/ETc7XlGVMx4+e2cIwXLFjeLjLI6pSYlxnarrGuetJZeEviWxto9n
odFVZI6yx8JFTXX8ZTCr/1IjwDDVyhMPmrHI2Lsv9cqBpSpbVe32cUkKxhsGaYjz
GvesQKamOPhco2ATNxPm0yopFlPsGKMfVl0BK0J6BqFh1BvU/SYJmXfnFuUNO3vV
4u2Saa0q1iddxV0rXDwIqUfn+S6rwzK0G7y8bH2yvpB2VwiG3TFPnULep4wsefNq
Fj92kqFBjacGpQLEEslUY0CMgeZ2+NuBQSUTscP3wBRsottMR6YXJtINdvfHBx+e
EcN71z8D00w3mYqIQ7qb4Ml6HOqknunn58g43L9sACMUMTlEBXa9pUnScNYgWBAz
W2q2mH37cIydM2JRZPpA8B4yTHt5ugJmChwyNFM7941arjKrebH+6AzLkofGedOP
zg+vZQuPEXWs+3MBBnkWoyJW3Y0fbQdjsuQTtnd+7iyoxoBroQKCAQEA4dIiFlIS
MDfRhQQWSiDvaw9aneDEJ3uo63ZRH5tm/IynLgtjYgEm/ZxlBCQgqRKLYELBxhu8
SaF0uPK8pmpFJt0mIwSlsdeVhuE2obQeKUCczaqrKeaHS3PdWLjTlwph81BGRkHy
qfqtNylyyMxrdEbnR51EtsWgFq6anTUAui1Q09JMuMNZRMOzDs1F4gExgD22rc0V
c9YQ+jHJRxBGtNKMpMEqc8cvaxBidbItrN9SMTSWog7uYPBuEuaJ6K9vpgyJMOzJ
SYcQEFGqgIqIDCg+ABE4d/4YROMKZ1DV/bJCind9brUHSx6XALsF0nC5c1Q9TnUL
qI2khOwts4KYKwKCAQEA2xRC6Az97Vkdzu7BjLJ1FKmx4S2nEEgVS12ds82U+5Xf
BHKAJnjqlqmmpzzJG+d77IYktz0+mey1QCNkqlm2fhuKs8LZMnpZRf0l8VcoBsUP
/xKz7wfiE7RRFZtLJhPp4hhe43GzX5/JFMWMnC6UykwQbj4t1E/GNM/Suqwvg12M
wktAJ6nqLgfhjQSO4xWo+nPzcbX+fNtrPCZVrBhYXihhcwRRNImWUCGJ6J4LMdPY
Y9Z59qhOvE9cReH/Xw1av46omyiSyAqlgPyZ/kzA2IJSqYCjiQR/2+RD/g13jpcJ
jatXLVZ8MJSL5OTS40G/HHTNNpNHbKKh0GOyxBA3ewKCAQBAn8UXhCcmW2L/YPsL
/b7mcX9qPP+FmRLvR23R0MQ5M/tH5wRq8I969n3GIJykJeVzB8eybQ+GNslTgEvS
iAkAJTubu+G7MkndTqg2wHf9MDtvdA8Fr646Po8yq7oJuHPtkKR7yLWsRUu6xIbP
xgheP0hCq1QVxhqZQyCGKrvpi7xc0gsYuPbcAfFFJCOCmPrUi1SzCkTAYJt9LjA+
wP6rErIjGBCRD4iXaBn1OqdtmH9KC5WsDP/VCBlIGWeQCly2NVIxiSHVg+xp7yUP
IhXq/L05gbQaSsIhPKQmivCiaJg4The8TdwneDqYf+0bmxzHT203/bD3bImPbJNr
ksz/AoIBAEwu4Y1cZzkQUmNRd5D7xecnk6ngfEYXKwCIT3zlMrfCSEl9n77BMaKu
4Dsr0iuX9eosQ7xM2eYhAG6LYEg05lc4MKWOToVVMpI6E+W3Dz47bPKgiF3I+f8s
Jz5CQIG/TwfGvciOE3hfUkec4ua09BzdEqGjkcBQ9XYMBxXPJr6h2379OBQS7FKR
fwfQ2/dv4tElXTTfut2kV8gU9Jnh5Wjo1epvR+XjKpg28YQo4W+0YX1magcyRB8L
4eSTUIC3XiVa8Jr0IwbZXPBb5xkdi7o+p4w2JahSHjxTRqmj+T1mnHXdbXVgq9Mg
9Pzl7cgFZvX4UBx4XtASRf73jITNtt0CggEADH9K+O7FrIOSQly0sMvsRCMtejp3
o+MDh1Q+vEg2kEgNXjS4ZFVljUpM2kg1OdUz7feS4dLXUJiIQ8ZWtZPedcq7wjHd
02he5+s06l0jPifN3tX1ADfXGpXg5R2fbkrIzakkPP5/RO/aDxIUo7qhklNsVTXO
VlGGfWLdk0ekA4upKm02Q1+YOlbIcAicEYYY8K7IffUwnohzKwL9yfuGi1VKTXpE
4fzdegsHI03FSqR7V+LvtBpIupQ7RO4kuBmCEyI4E9FVknchg4te4gO3qwd9y0rJ
Gu7HNIOrwOHzviI7J6Nd/l9MmeKqklHSgJvko/f5TmiXuQQ8xDZf84rcjQ==
-----END RSA PRIVATE KEY-----
`)
publicKeyBytes := []byte(`-----BEGIN RSA PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwUCimKCidbF3UxEHPy8K
+hvhklRB9JYhj5sJy0if4lTVibkK1MrYCykOnmC40pPU9GLY1b8HxAg9tvyRn0YH
UxOra6vVQaVcOVJhTM8D18d+lSr3Lp1yiX+UGT4nzWI9+R1CCbwXrqeQVoZs6QZK
ynEXMkFI9/wNMOwPOvQFOSTuoEoCO+zyTyUWEkNbUq825ELUQdIsjgmlWUOONudx
sAr7ESRXW9QTHVh6uWmr3VRKZHby1JdU3I/wjdlGg5M2dDuXy5nQO9w/nYLjJXiw
+zzOetZ/+t7/VOkOpNTeJQhwTM1WF7Y2VLetbi9FHgyzHatrduh07+XEiTbgDf3G
Ix2bp2p6oh0G3N2zpiLcK/aZj8rouWWydfFfsU3MZ4FfJDP8I6b9awxjmKYqIr6h
iPQCJaLBED8mwK+I5evIbnKv6E6uK+BApWA/R7ElragoFYbqQ1VpvntVMtJt9Dy5
ZrI+IQARdXD3bb34oh0IPBhClnvvMUc1cWxDoXEX6oJ4I+LzxE87Zkwnan9qOwen
golMVKFwPx1o37qrbmrXID21kKt7FL6xN4HxHLkItr1fKzdyWDFRHgASTAWfx5BI
wvPuUW0vZHkvO80VyV2L63whVhPnPASmFkbviomrBttYfpr2aGQqF/qR1Nlxe834
MFxk1pS9LMa/WnzvFr0gWakCAwEAAQ==
-----END RSA PUBLIC KEY-----
`)
origStr := "Value1234"
pubKey, err := DecodeToPublicKey(publicKeyBytes)
if err != nil {
t.Error(err)
t.FailNow()
}
privKey, err := DecodeToPrivateKey(privKeyBytes)
if err != nil {
t.Error(err)
t.FailNow()
}
encData, err := Encrypt([]byte(origStr), pubKey)
if err != nil {
t.Error(err)
t.FailNow()
}
encDataStr := base64.StdEncoding.EncodeToString(encData)
log.Println("Encoded text:", encDataStr)
dec, _ := base64.StdEncoding.DecodeString(encDataStr)
data, err := Decrypt(dec, privKey)
if err != nil {
t.Error(err)
t.FailNow()
}
if string(data) != origStr {
t.Error("original string and decrypted string don't match")
t.FailNow()
} }
} }

View File

@@ -98,20 +98,21 @@ func kubectlOperation(manifests string, oprName string, namespace string) error
return nil return nil
} }
func KubectlDirectOps(opr []string, namespace string) error { func KubectlDirectOps(opr []string, namespace string) (string, error) {
arguments := []string{} arguments := []string{}
if namespace != "" { if namespace != "" {
arguments = append(arguments, "-n", namespace) arguments = append(arguments, "-n", namespace)
} }
arguments = append(arguments, opr...) arguments = append(arguments, opr...)
var out bytes.Buffer
cmd := exec.Command("kubectl", arguments...) cmd := exec.Command("kubectl", arguments...)
LogDebugMessage("Kubectl command: %s %v\n", "kubectl", arguments) LogDebugMessage("Kubectl command: %s %v\n", "kubectl", arguments)
sterrBuffer := &bytes.Buffer{} sterrBuffer := &bytes.Buffer{}
cmd.Stderr = sterrBuffer cmd.Stderr = sterrBuffer
cmd.Stdout = os.Stdout cmd.Stdout = &out
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return fmt.Errorf("kubectl %v failed with: %v, %v\n", opr, err, sterrBuffer.String()) return "", fmt.Errorf("kubectl %v failed with: %v, %v\n", opr, err, sterrBuffer.String())
} }
return nil s := out.String()
return s, nil
} }

View File

@@ -24,7 +24,7 @@ func TestKubectlDirectOps(t *testing.T) {
ns := GetKubectlNamespace() ns := GetKubectlNamespace()
opr := fmt.Sprintf("version") opr := fmt.Sprintf("version")
opr1 := strings.Fields(opr) opr1 := strings.Fields(opr)
err := KubectlDirectOps(opr1, ns) _, err := KubectlDirectOps(opr1, ns)
if err != nil { if err != nil {
t.Log(err) t.Log(err)
t.Fail() t.Fail()

124
pkg/api/preflight_apis.go Normal file
View File

@@ -0,0 +1,124 @@
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()
}

View File

@@ -0,0 +1,138 @@
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)
}
}

View File

@@ -4,6 +4,7 @@ import (
"archive/tar" "archive/tar"
"archive/zip" "archive/zip"
"compress/gzip" "compress/gzip"
b64 "encoding/base64"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@@ -11,7 +12,6 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"time" "time"
@@ -62,27 +62,38 @@ func ReadKeys(keyFile string) ([]byte, error) {
} }
// ProcessConfigArgs processes args and returns an service, key, value slice // ProcessConfigArgs processes args and returns an service, key, value slice
func ProcessConfigArgs(args []string) ([]*ServiceKeyValue, error) { func ProcessConfigArgs(args []string, base64Encoded bool) ([]*ServiceKeyValue, error) {
// prepare received args // prepare received args
// split args[0] into key and value // split args[0] into key and value
if len(args) == 0 { if len(args) == 0 {
err := fmt.Errorf("No args were provided. Please provide args to configure the current context") err := fmt.Errorf("No args were provided. Please provide args to configure the current context")
return nil, err return nil, err
} }
notValidErr := fmt.Errorf("Please provide valid args for this command")
resultSvcKV := make([]*ServiceKeyValue, len(args)) resultSvcKV := make([]*ServiceKeyValue, len(args))
re1 := regexp.MustCompile(`(\w{1,}).(\w{1,})=("*[\w\-?=_/:0-9]+"*)`) // qliksense.mongodb=somethig
for i, arg := range args { for i, arg := range args {
LogDebugMessage("Arg received: %s", arg) LogDebugMessage("Arg received: %s", arg)
result := re1.FindStringSubmatch(arg) first := strings.SplitN(arg, "=", 2)
// 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(first) != 2 {
if len(result) != 4 { return nil, notValidErr
err := fmt.Errorf("Please provide valid args for this command") }
return nil, err 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 ")
}
} }
resultSvcKV[i] = &ServiceKeyValue{ resultSvcKV[i] = &ServiceKeyValue{
SvcName: result[1], SvcName: second[0],
Key: result[2], Key: second[1],
Value: strings.ReplaceAll(result[3], `"`, ""), Value: resultValue,
} }
} }
return resultSvcKV, nil return resultSvcKV, nil

47
pkg/api/utils_test.go Normal file
View File

@@ -0,0 +1,47 @@
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
}

View File

@@ -4,13 +4,104 @@ import (
"fmt" "fmt"
) )
func (qp *QliksensePreflight) RunAllPreflightChecks() error { func (qp *QliksensePreflight) RunAllPreflightChecks(namespace string, kubeConfigContents []byte, mongodbUrl string) {
//run all preflight checks
fmt.Println("Running all preflight checks") checkCount := 0
fmt.Printf("\nRunning DNS check...\n") totalCount := 0
qp.CheckDns() // Preflight minimum kuberenetes version check
fmt.Printf("\nRunning minimum kubernetes version check...\n") fmt.Printf("\nPreflight kubernetes minimum version check\n")
qp.CheckK8sVersion() 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") fmt.Println("Completed running all preflight checks")
return nil
} }

View File

@@ -0,0 +1,128 @@
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
}

View File

@@ -1,128 +1,92 @@
package preflight package preflight
import ( import (
"bytes"
"fmt" "fmt"
"html/template" "strings"
"io/ioutil"
"path/filepath"
"github.com/qlik-oss/sense-installer/pkg/api"
) )
const dnsCheckYAML = ` const (
apiVersion: troubleshoot.replicated.com/v1beta1 nginx = "nginx"
kind: Preflight netcat = "netcat"
metadata: )
name: cluster-preflight-checks
namespace: {{ . }}
spec:
collectors:
- run:
collectorName: spin-up-pod
args: ["-z", "-v", "-w 1", "qnginx001", "80"]
command: ["nc"]
image: subfuzion/netcat:latest
imagePullPolicy: IfNotPresent
name: spin-up-pod-check-dns
namespace: {{ . }}
timeout: 30s
analyzers: func (qp *QliksensePreflight) CheckDns(namespace string, kubeConfigContents []byte) error {
- textAnalyze: clientset, _, err := getK8SClientSet(kubeConfigContents, "")
checkName: DNS check
collectorName: spin-up-pod-check-dns
fileName: spin-up-pod.txt
regex: succeeded
outcomes:
- fail:
message: DNS check failed
- pass:
message: DNS check passed
`
func (qp *QliksensePreflight) CheckDns() error {
// retrieve namespace
namespace := api.GetKubectlNamespace()
api.LogDebugMessage("Namespace: %s\n", namespace)
tmpl, err := template.New("dnsCheckYAML").Parse(dnsCheckYAML)
if err != nil {
fmt.Printf("cannot parse template: %v", err)
return err
}
tempYaml, err := ioutil.TempFile("", "")
if err != nil {
fmt.Printf("cannot create file: %v", err)
return err
}
api.LogDebugMessage("Temp Yaml file: %s\n", tempYaml.Name())
b := bytes.Buffer{}
err = tmpl.Execute(&b, namespace)
if err != nil { if err != nil {
err = fmt.Errorf("error: unable to create a kubernetes client: %v\n", err)
fmt.Println(err) fmt.Println(err)
return err return err
} }
tempYaml.WriteString(b.String()) // creating deployment
depName := "dep-dns-preflight-check"
// creating Kubectl resources nginxImageName, err := qp.GetPreflightConfigObj().GetImageName(nginx, true)
appName := "qnginx001"
const PreflightChecksDirName = "preflight_checks"
fmt.Println("Creating resources to run preflight checks")
// kubectl create deployment
opr := fmt.Sprintf("create deployment %s --image=nginx", appName)
err = initiateK8sOps(opr, namespace)
if err != nil { 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) fmt.Println(err)
return err return err
} }
defer func() { waitForPodToDie(clientset, namespace, dnsPod)
// Deleting deployment..
opr = fmt.Sprintf("delete deployment %s", appName)
// we want to delete the k8s resource here, we dont care a lot about an error here
_ = initiateK8sOps(opr, namespace)
api.LogDebugMessage("delete deployment executed")
}()
// create service logStr, err := getPodLogs(clientset, dnsPod)
opr = fmt.Sprintf("create service clusterip %s --tcp=80:80", appName)
err = initiateK8sOps(opr, namespace)
if err != nil { if err != nil {
err = fmt.Errorf("error: unable to execute dns check in the cluster: %v", err)
fmt.Println(err) fmt.Println(err)
return err return err
} }
defer func() { if strings.HasSuffix(strings.TrimSpace(logStr), "succeeded!") {
// delete service fmt.Println("Preflight DNS check: PASSED")
opr = fmt.Sprintf("delete service %s", appName) } else {
// we want to delete the k8s resource here, we dont care a lot about an error here err = fmt.Errorf("Expected response not found\n")
_ = initiateK8sOps(opr, namespace)
api.LogDebugMessage("delete service executed")
}()
//kubectl -n $namespace wait --for=condition=ready pod -l app=$appName --timeout=120s
opr = fmt.Sprintf("wait --for=condition=ready pod -l app=%s --timeout=120s", appName)
err = initiateK8sOps(opr, namespace)
if err != nil {
fmt.Println(err)
return err return err
} }
api.LogDebugMessage("kubectl wait executed")
// call preflight fmt.Println("Completed preflight DNS check")
preflightCommand := filepath.Join(qp.Q.QliksenseHome, PreflightChecksDirName, preflightFileName) fmt.Println("Cleaning up resources...")
err = invokePreflight(preflightCommand, tempYaml)
if err != nil {
fmt.Println(err)
return err
}
fmt.Println("DNS check completed, cleaning up resources now")
return nil return nil
} }

View File

@@ -0,0 +1,87 @@
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
}

View File

@@ -3,139 +3,68 @@ package preflight
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"io/ioutil"
"log" "log"
"os"
"os/exec"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
"time"
"github.com/qlik-oss/sense-installer/pkg/qliksense" "github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/qlik-oss/sense-installer/pkg/api" "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 { type QliksensePreflight struct {
Q *qliksense.Qliksense Q *qliksense.Qliksense
} }
const ( func (qp *QliksensePreflight) GetPreflightConfigObj() *api.PreflightConfig {
// preflight releases have the same version return api.NewPreflightConfig(qp.Q.QliksenseHome)
preflightRelease = "v0.9.26"
preflightLinuxFile = "preflight_linux_amd64.tar.gz"
preflightMacFile = "preflight_darwin_amd64.tar.gz"
preflightWindowsFile = "preflight_windows_amd64.zip"
PreflightChecksDirName = "preflight_checks"
preflightFileName = "preflight"
)
var preflightBaseURL = fmt.Sprintf("https://github.com/replicatedhq/troubleshoot/releases/download/%s/", preflightRelease)
func (qp *QliksensePreflight) DownloadPreflight() error {
const preflightExecutable = "preflight"
preflightInstallDir := filepath.Join(qp.Q.QliksenseHome, PreflightChecksDirName)
platform := runtime.GOOS
exists, err := checkInstalled(preflightInstallDir, preflightExecutable)
if err != nil {
err = fmt.Errorf("There has been an error when trying to determine if preflight installer exists")
log.Println(err)
return err
}
if exists {
// preflight exist, no need to download again.
api.LogDebugMessage("Preflight already exists, proceeding to perform checks")
return nil
}
// Create the Preflight-check directory, download and install preflight
if !api.DirExists(preflightInstallDir) {
api.LogDebugMessage("%s does not exist, creating now\n", preflightInstallDir)
if err := os.Mkdir(preflightInstallDir, os.ModePerm); err != nil {
err = fmt.Errorf("Not able to create %s dir: %v", preflightInstallDir, err)
log.Println(err)
return nil
}
}
api.LogDebugMessage("Preflight-checks install Dir: %s exists", preflightInstallDir)
preflightUrl, preflightFile, err := determinePlatformSpecificUrls(platform)
if err != nil {
err = fmt.Errorf("There was an error when trying to determine platform specific paths")
return err
}
// Download Preflight
err = downloadAndExplode(preflightUrl, preflightInstallDir, preflightFile)
if err != nil {
return err
}
fmt.Println("Downloaded Preflight")
return nil
} }
func checkInstalled(preflightInstallDir, preflightExecutable string) (bool, error) { func InitPreflight() (string, []byte, error) {
installerExists := true api.LogDebugMessage("Reading .kube/config file...")
preflightInstaller := filepath.Join(preflightInstallDir, preflightExecutable)
if api.DirExists(preflightInstallDir) {
if !api.FileExists(preflightInstaller) {
installerExists = false
api.LogDebugMessage("Preflight install directory exists, but preflight installer does not exist")
}
} else {
installerExists = false
}
return installerExists, nil
}
func downloadAndExplode(url, installDir, file string) error { homeDir, err := homedir.Dir()
err := api.DownloadFile(url, installDir, file)
if err != nil { if err != nil {
return err err = fmt.Errorf("Unable to deduce home dir\n")
return "", nil, err
} }
api.LogDebugMessage("Downloaded File: %s", file) api.LogDebugMessage("Kube config location: %s\n\n", filepath.Join(homeDir, ".kube", "config"))
fileToUntar := filepath.Join(installDir, file) kubeConfig := filepath.Join(homeDir, ".kube", "config")
api.LogDebugMessage("File to explode: %s", file) kubeConfigContents, err := ioutil.ReadFile(kubeConfig)
err = api.ExplodePackage(installDir, fileToUntar)
if err != nil { if err != nil {
return err err = fmt.Errorf("Unable to deduce home dir\n")
return "", nil, err
} }
// retrieve namespace
return nil namespace := api.GetKubectlNamespace()
} // if namespace comes back empty, we will run checks in the default namespace
if namespace == "" {
func determinePlatformSpecificUrls(platform string) (string, string, error) { namespace = "default"
var preflightUrl, preflightFile string
if runtime.GOARCH != `amd64` {
err := fmt.Errorf("%s architecture is not supported", runtime.GOARCH)
return "", "", err
} }
api.LogDebugMessage("Namespace: %s\n", namespace)
switch platform { return namespace, kubeConfigContents, nil
case "windows":
preflightFile = preflightWindowsFile
case "darwin":
preflightFile = preflightMacFile
case "linux":
preflightFile = preflightLinuxFile
default:
err := fmt.Errorf("Unable to download the preflight executable for the underlying platform\n")
return "", "", err
}
preflightUrl = fmt.Sprintf("%s%s", preflightBaseURL, preflightFile)
return preflightUrl, preflightFile, nil
} }
func initiateK8sOps(opr, namespace string) error { func initiateK8sOps(opr, namespace string) error {
opr1 := strings.Fields(opr) opr1 := strings.Fields(opr)
err := api.KubectlDirectOps(opr1, namespace) _, err := api.KubectlDirectOps(opr1, namespace)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return err return err
@@ -143,45 +72,574 @@ func initiateK8sOps(opr, namespace string) error {
return nil return nil
} }
func invokePreflight(preflightCommand string, yamlFile *os.File) error { func int32Ptr(i int32) *int32 { return &i }
arguments := []string{}
arguments = append(arguments, yamlFile.Name(), "--interactive=false")
cmd := exec.Command(preflightCommand, arguments...)
sterrBuffer := &bytes.Buffer{} func retryOnError(mf func() error) error {
cmd.Stdout = sterrBuffer return retry.OnError(wait.Backoff{
cmd.Stderr = sterrBuffer Duration: 1 * time.Second,
if err := cmd.Run(); err != nil { Factor: 1,
return fmt.Errorf("Error when running preflight command: %v\n", err) 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
}
} }
ind := strings.Index(sterrBuffer.String(), "---") clientset, err := kubernetes.NewForConfig(clientConfig)
output := sterrBuffer.String() if err != nil {
if ind > -1 { err = errors.Wrap(err, "Unable to create clientset")
output = fmt.Sprintf("%s\n%s", output[:ind], output[ind:]) fmt.Println(err)
return nil, nil, err
} }
fmt.Printf("%v\n", output) return clientset, clientConfig, nil
}
// Maybe good to retain this part in case we need to process the output in future. func createPreflightTestDeployment(clientset *kubernetes.Clientset, namespace string, depName string, imageName string) (*appsv1.Deployment, error) {
// We are going to look for the first occurance of PASS or FAIL from the end deploymentsClient := clientset.AppsV1().Deployments(namespace)
// there are also some space-like deceiving characters which are being hard to get by 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,
},
},
},
},
},
},
},
}
//outputArr := strings.Fields(strings.TrimSpace(output)) // Create Deployment
//trackSuccess := false var result *appsv1.Deployment
//trackPrg := false 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())
//for i := len(outputArr) - 1; i >= 0; i-- { return deployment, nil
// if strings.TrimSpace(outputArr[i]) != "" { }
// if outputArr[i] == "PASS" {
// trackSuccess = true
// trackPrg = true
// } else if outputArr[i] == "FAIL" {
// trackPrg = true
// }
// }
// if trackPrg {
// break
// }
//}
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 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)
}

View File

@@ -20,7 +20,7 @@ func Test_initiateK8sOps(t *testing.T) {
name: "valid case", name: "valid case",
args: args{ args: args{
opr: fmt.Sprintf("version"), opr: fmt.Sprintf("version"),
namespace: "ash-ns", namespace: "test-ns",
}, },
wantErr: false, wantErr: false,
}, },
@@ -28,7 +28,7 @@ func Test_initiateK8sOps(t *testing.T) {
name: "invalid case", name: "invalid case",
args: args{ args: args{
opr: fmt.Sprintf("versions"), opr: fmt.Sprintf("versions"),
namespace: "ash-ns", namespace: "test-ns",
}, },
wantErr: true, wantErr: true,
}, },
@@ -41,50 +41,3 @@ func Test_initiateK8sOps(t *testing.T) {
}) })
} }
} }
func Test_determinePlatformSpecificUrls(t *testing.T) {
type args struct {
platform string
}
tests := []struct {
name string
args args
want string
want1 string
wantErr bool
}{
{
name: "valid platform",
args: args{
platform: "windows",
},
want: fmt.Sprintf("%s%s", preflightBaseURL, preflightWindowsFile),
want1: preflightWindowsFile,
wantErr: false,
},
{
name: "invalid platform",
args: args{
platform: "unix",
},
want: "",
want1: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1, err := determinePlatformSpecificUrls(tt.args.platform)
if (err != nil) != tt.wantErr {
t.Errorf("determinePlatformSpecificUrls() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("determinePlatformSpecificUrls() got = %v, want %v", got, tt.want)
}
if got1 != tt.want1 {
t.Errorf("determinePlatformSpecificUrls() got1 = %v, want %v", got1, tt.want1)
}
})
}
}

137
pkg/preflight/role_check.go Normal file
View File

@@ -0,0 +1,137 @@
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
}

View File

@@ -1,84 +1,56 @@
package preflight package preflight
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil"
"path/filepath"
"text/template"
"github.com/qlik-oss/sense-installer/pkg/api" "github.com/Masterminds/semver/v3"
"k8s.io/apimachinery/pkg/version"
) )
const minK8sVersion = "1.11.0" func (qp *QliksensePreflight) CheckK8sVersion(namespace string, kubeConfigContents []byte) error {
const checkVersionYAML = `
apiVersion: troubleshoot.replicated.com/v1beta1
kind: Preflight
metadata:
name: cluster-preflight-checks
namespace: {{ .namespace }}
spec:
analyzers:
- clusterVersion:
outcomes:
- fail:
when: "< {{ .minK8sVersion }}"
message: The application requires at least Kubernetes {{ .minK8sVersion }} or later.
uri: https://www.kubernetes.io
- pass:
when: ">= {{ .minK8sVersion }}"
message: Good to go.
`
func (qp *QliksensePreflight) CheckK8sVersion() error { var currentVersion *semver.Version
// retrieve namespace
namespace := api.GetKubectlNamespace()
api.LogDebugMessage("Namespace: %s\n", namespace) clientset, _, err := getK8SClientSet(kubeConfigContents, "")
tmpl, err := template.New("checkVersionYAML").Parse(checkVersionYAML)
if err != nil { if err != nil {
fmt.Printf("cannot parse template: %v", err) err = fmt.Errorf("Unable to create clientset: %v\n", err)
return err return err
} }
tempYaml, err := ioutil.TempFile("", "") var serverVersion *version.Info
if err != nil { if err := retryOnError(func() (err error) {
fmt.Printf("cannot create file: %v", err) serverVersion, err = clientset.ServerVersion()
return err
}); err != nil {
err = fmt.Errorf("Unable to get server version: %v\n", err)
//fmt.Println(err)
return err return err
} }
api.LogDebugMessage("Temp Yaml file: %s\n", tempYaml.Name()) fmt.Printf("Kubernetes API Server version: %s\n", serverVersion.String())
b := bytes.Buffer{} // Compare K8s version on the cluster with minimum supported k8s version
err = tmpl.Execute(&b, map[string]string{ currentVersion, err = semver.NewVersion(serverVersion.String())
"namespace": namespace,
"minK8sVersion": minK8sVersion,
})
if err != nil { 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) fmt.Println(err)
return err return err
} }
tempYaml.WriteString(b.String()) if currentVersion.GreaterThan(minK8sVersionSemver) {
//api.LogDebugMessage("Temp yaml contents: %s", b.String()) //fmt.Printf("\n\nCurrent %s Component version: %s is less than minimum required version:%s\n", component, currentComponentVersion, componentVersionFromDependenciesYaml)
fmt.Printf("Minimum Kubernetes version supported: %s\n", minK8sVersion) fmt.Printf("Current %s is greater than minimum required version:%s\n", currentVersion, minK8sVersionSemver)
fmt.Println("Preflight minimum kubernetes version check: PASSED")
// current kubectl version } else {
opr := fmt.Sprintf("version") fmt.Printf("Current %s is less than minimum required version:%s\n", currentVersion, minK8sVersionSemver)
err = initiateK8sOps(opr, namespace) fmt.Println("Preflight minimum kubernetes version check: FAILED")
if err != nil {
fmt.Println(err)
return err
} }
fmt.Printf("Completed Preflight kubernetes minimum version check\n")
// call preflight
preflightCommand := filepath.Join(qp.Q.QliksenseHome, PreflightChecksDirName, preflightFileName)
err = invokePreflight(preflightCommand, tempYaml)
if err != nil {
fmt.Println(err)
return err
}
fmt.Println("Minimum kubernetes version check completed")
return nil return nil
} }

View File

@@ -94,7 +94,7 @@ func (q *Qliksense) getConfigDirectory(gitUrl, gitRef, profileEntered string) (d
} }
if gitRef != "" { if gitRef != "" {
if dir, err = downloadFromGitRepoToTmpDir(gitUrl, gitRef); err != nil { if dir, err = DownloadFromGitRepoToTmpDir(gitUrl, gitRef); err != nil {
return "", false, "", err return "", false, "", err
} else { } else {
return dir, true, profile, nil return dir, true, profile, nil
@@ -120,14 +120,15 @@ func (q *Qliksense) getConfigDirectory(gitUrl, gitRef, profileEntered string) (d
return dir, false, profile, nil return dir, false, profile, nil
} }
if dir, err = downloadFromGitRepoToTmpDir(gitUrl, "master"); err != nil { if dir, err = DownloadFromGitRepoToTmpDir(gitUrl, "master"); err != nil {
return "", false, "", err return "", false, "", err
} else { } else {
return dir, true, profile, nil return dir, true, profile, nil
} }
} }
func downloadFromGitRepoToTmpDir(gitUrl, gitRef string) (string, error) { //DownloadFromGitRepoToTmpDir download git repo to a temporary directory
func DownloadFromGitRepoToTmpDir(gitUrl, gitRef string) (string, error) {
if tmpDir, err := ioutil.TempDir("", ""); err != nil { if tmpDir, err := ioutil.TempDir("", ""); err != nil {
return "", err return "", err
} else { } else {
@@ -193,8 +194,10 @@ func getImageList(yamlContent []byte) ([]string, error) {
}) })
} }
var sortedImageList []string var sortedImageList []string
for image, _ := range imageMap { for image, v := range imageMap {
sortedImageList = append(sortedImageList, image) sortedImageList = append(sortedImageList, image)
// a warning "simplify range expression" if written like this 'for image _ :=range imageMap'
_ = v
} }
sort.Strings(sortedImageList) sort.Strings(sortedImageList)
return sortedImageList, nil return sortedImageList, nil

View File

@@ -525,12 +525,16 @@ func Test_About_getConfigDirectory(t *testing.T) {
if err := q.SetUpQliksenseDefaultContext(); err != nil { if err := q.SetUpQliksenseDefaultContext(); err != nil {
t.Fatalf("error setting up default context in the tmp dir: %v\n", err) t.Fatalf("error setting up default context in the tmp dir: %v\n", err)
return nil, "", "", "" return nil, "", "", ""
} else if err := q.FetchQK8s("master"); err != nil { } else if qConfig, err := qapi.NewQConfigE(q.QliksenseHome); err != nil {
t.Fatalf("error fetching master config to the tmp dir: %v\n", err) t.Fatalf("cannot initiallize qConfig: %v\n", err)
return nil, "", "", "" return nil, "", "", ""
} else { } else if !qConfig.IsRepoExistForCurrent("master") {
return q, "no-git-clone-for-you", "", "" if err := q.FetchQK8s("master"); err != nil {
t.Fatalf("error fetching master config to the tmp dir: %v\n", err)
return nil, "", "", ""
}
} }
return q, "no-git-clone-for-you", "", ""
} }
}, },
verify: func(q *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) { verify: func(q *Qliksense, configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {

View File

@@ -15,8 +15,32 @@ func (q *Qliksense) ApplyCRFromReader(r io.Reader, opts *InstallCommandOptions,
if err != nil { if err != nil {
return err return err
} }
if err := q.InstallQK8s(cr.GetLabelFromCr("version"), opts, keepPatchFiles); err != nil { if IsQliksenseInstalled(cr.GetName()) {
return err // 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 nil 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
} }

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -17,7 +18,7 @@ import (
) )
const ( const (
Q_INIT_CRD_PATH = "manifests/base/manifests/qliksense-init" Q_INIT_CRD_PATH = "manifests/base/crds"
agreementTempalte = ` agreementTempalte = `
Please read the agreement at https://www.qlik.com/us/legal/license-terms Please read the agreement at https://www.qlik.com/us/legal/license-terms
Accept the end user license agreement by providing acceptEULA=yes Accept the end user license agreement by providing acceptEULA=yes
@@ -88,7 +89,7 @@ func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error {
cr.GeneratePatches(&qcr.KApiCr, path.Join(userHomeDir, ".kube", "config")) cr.GeneratePatches(&qcr.KApiCr, path.Join(userHomeDir, ".kube", "config"))
// apply generated manifests // apply generated manifests
profilePath := filepath.Join(qcr.Spec.GetManifestsRoot(), qcr.Spec.GetProfileDir()) profilePath := filepath.Join(qcr.Spec.GetManifestsRoot(), qcr.Spec.GetProfileDir())
mByte, err := executeKustomizeBuild(profilePath) mByte, err := ExecuteKustomizeBuild(profilePath)
if err != nil { if err != nil {
fmt.Println("cannot generate manifests for "+profilePath, err) fmt.Println("cannot generate manifests for "+profilePath, err)
return err return err
@@ -145,21 +146,76 @@ func (q *Qliksense) getCurrentCrDependentResourceAsString() (string, error) {
var crString strings.Builder var crString strings.Builder
for svcName, v := range qcr.Spec.Secrets { for svcName, v := range qcr.Spec.Secrets {
hasFile := false
for _, item := range v { for _, item := range v {
if item.ValueFrom != nil && item.ValueFrom.SecretKeyRef != nil { if item.ValueFrom != nil && item.ValueFrom.SecretKeyRef != nil {
secretFilePath := filepath.Join(q.QliksenseHome, QliksenseContextsDir, qcr.GetName(), QliksenseSecretsDir, svcName+".yaml") hasFile = true
break
if api.FileExists(secretFilePath) { }
secretFile, err := ioutil.ReadFile(secretFilePath) }
if err != nil { if hasFile {
return "", err secretFilePath := filepath.Join(q.QliksenseHome, QliksenseContextsDir, qcr.GetName(), QliksenseSecretsDir, svcName+".yaml")
} if api.FileExists(secretFilePath) {
crString.WriteString("\n---\n") secretFile, err := ioutil.ReadFile(secretFilePath)
crString.Write(secretFile) if err != nil {
return "", err
} }
crString.WriteString("\n---\n")
crString.Write(secretFile)
} }
} }
} }
crString.WriteString("\n---\n") crString.WriteString("\n---\n")
return crString.String(), nil 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
}

View File

@@ -1,19 +1,21 @@
package qliksense package qliksense
import ( import (
"crypto/rsa" "errors"
"fmt" "fmt"
"io"
"github.com/qlik-oss/k-apis/pkg/config"
"github.com/robfig/cron/v3"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"github.com/robfig/cron/v3"
"github.com/qlik-oss/k-apis/pkg/config"
b64 "encoding/base64" b64 "encoding/base64"
ansi "github.com/mattn/go-colorable" ansi "github.com/mattn/go-colorable"
@@ -32,48 +34,58 @@ const (
MaxContextNameLength = 17 MaxContextNameLength = 17
QliksenseSecretsDir = "secrets" QliksenseSecretsDir = "secrets"
imageRegistryConfigKey = "imageRegistry" imageRegistryConfigKey = "imageRegistry"
pullSecretName = "artifactory-docker-secret" 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 // SetSecrets - set-secrets <key>=<value> commands
func (q *Qliksense) SetSecrets(args []string, isSecretSet bool) error { func (q *Qliksense) SetSecrets(args []string, isSecretSet bool, base64Encoded bool) error {
qConfig := api.NewQConfig(q.QliksenseHome) qConfig := api.NewQConfig(q.QliksenseHome)
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q) qliksenseCR, err := qConfig.GetCurrentCR()
if err != nil { if err != nil {
return err return err
} }
// Metadata name in qliksense CR is the name of the current context // Metadata name in qliksense CR is the name of the current context
api.LogDebugMessage("Current context: %s", qliksenseCR.GetName()) api.LogDebugMessage("Current context: %s", qliksenseCR.GetName())
rsaPublicKey, _, err := qConfig.GetCurrentContextEncryptionKeyPair() encryptionKey, err := qConfig.GetEncryptionKeyForCurrent()
if err != nil { if err != nil {
return err return err
} }
resultArgs, err := api.ProcessConfigArgs(args) resultArgs, err := api.ProcessConfigArgs(args, base64Encoded)
if err != nil { if err != nil {
return err return err
} }
for _, ra := range resultArgs { for _, ra := range resultArgs {
api.LogDebugMessage("value args to be encrypted: %s", ra.Value) api.LogDebugMessage("value args to be encrypted: %s", ra.Value)
if err := q.processSecret(ra, rsaPublicKey, qliksenseCR, isSecretSet); err != nil { if err := q.processSecret(ra, encryptionKey, qliksenseCR, isSecretSet); err != nil {
return err return err
} }
} }
// write modified content into context-yaml // write modified content into context-yaml
api.WriteToFile(&qliksenseCR, qliksenseContextsFile) return qConfig.WriteCR(qliksenseCR)
return nil
} }
func (q *Qliksense) processSecret(ra *api.ServiceKeyValue, rsaPublicKey *rsa.PublicKey, qliksenseCR *api.QliksenseCR, isSecretSet bool) error { func (q *Qliksense) processSecret(ra *api.ServiceKeyValue, encryptionKey string, qliksenseCR *api.QliksenseCR, isSecretSet bool) error {
// encrypt value with RSA key pair cipherText, e2 := api.EncryptData([]byte(ra.Value), encryptionKey)
valueBytes := []byte(ra.Value)
cipherText, e2 := api.Encrypt(valueBytes, rsaPublicKey)
if e2 != nil { if e2 != nil {
return e2 return e2
} }
base64EncodedSecret := b64.StdEncoding.EncodeToString(cipherText)
secretName := "" secretName := ""
if isSecretSet { if isSecretSet {
secretFolder := qliksenseCR.GetK8sSecretsFolder(q.QliksenseHome) secretFolder := qliksenseCR.GetK8sSecretsFolder(q.QliksenseHome)
@@ -105,7 +117,8 @@ func (q *Qliksense) processSecret(ra *api.ServiceKeyValue, rsaPublicKey *rsa.Pub
if k8sSecret.Data == nil { if k8sSecret.Data == nil {
k8sSecret.Data = map[string][]byte{} k8sSecret.Data = map[string][]byte{}
} }
k8sSecret.Data[ra.Key] = []byte(base64EncodedSecret) // v1.Secret does enconding, so no need to encode again
k8sSecret.Data[ra.Key] = []byte(cipherText)
// Write secret to file // Write secret to file
k8sSecretBytes, err := api.K8sSecretToYaml(k8sSecret) k8sSecretBytes, err := api.K8sSecretToYaml(k8sSecret)
@@ -118,25 +131,36 @@ func (q *Qliksense) processSecret(ra *api.ServiceKeyValue, rsaPublicKey *rsa.Pub
return err return err
} }
api.LogDebugMessage("Created a Kubernetes secret") api.LogDebugMessage("Created a Kubernetes secret")
// Prepare args to update CR in the next step
base64EncodedSecret = ""
} }
base64EncodedSecret := b64.StdEncoding.EncodeToString([]byte(cipherText))
// write into CR the keyref of the secret // write into CR the keyref of the secret
qliksenseCR.Spec.AddToSecrets(ra.SvcName, ra.Key, base64EncodedSecret, secretName) qliksenseCR.Spec.AddToSecrets(ra.SvcName, ra.Key, base64EncodedSecret, secretName)
return nil 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 // SetConfigs - set-configs <key>=<value> commands
func (q *Qliksense) SetConfigs(args []string) error { func (q *Qliksense) SetConfigs(args []string, base64Encoded bool) error {
// retieve current context from config.yaml // retieve current context from config.yaml
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q) qConfig := api.NewQConfig(q.QliksenseHome)
qliksenseCR, err := qConfig.GetCurrentCR()
if err != nil { if err != nil {
return err return err
} }
resultArgs, err := api.ProcessConfigArgs(args) resultArgs, err := api.ProcessConfigArgs(args, base64Encoded)
if err != nil { if err != nil {
return err return err
} }
@@ -144,54 +168,66 @@ func (q *Qliksense) SetConfigs(args []string) error {
qliksenseCR.Spec.AddToConfigs(ra.SvcName, ra.Key, ra.Value) qliksenseCR.Spec.AddToConfigs(ra.SvcName, ra.Key, ra.Value)
} }
// write modified content into context.yaml // write modified content into context.yaml
api.WriteToFile(&qliksenseCR, qliksenseContextsFile) return qConfig.WriteCR(qliksenseCR)
return nil
} }
func retrieveCurrentContextInfo(q *Qliksense) (*api.QliksenseCR, string, error) { func caseInsenstiveFieldByName(v reflect.Value, name string) reflect.Value {
var qliksenseConfig api.QliksenseConfig name = strings.ToLower(name)
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile) return v.FieldByNameFunc(func(n string) bool { return strings.ToLower(n) == name })
if err := api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile); err != nil { }
log.Println(err)
return nil, "", err
}
currentContext := qliksenseConfig.Spec.CurrentContext
api.LogDebugMessage("Current-context from config.yaml: %s", currentContext)
if currentContext == "" {
// current-context is empty
err := fmt.Errorf(`Please run the "qliksense config set-context <context-name>" first before viewing the current context info`)
log.Println(err)
return nil, "", err
}
// read the context.yaml file
qliksenseCR := &api.QliksenseCR{}
if currentContext == "" {
// current-context is empty
err := fmt.Errorf(`Please run the "qliksense config set-context <context-name>" first before viewing the current context info`)
log.Println(err)
return nil, "", err
}
qliksenseContextsFile := filepath.Join(q.QliksenseHome, QliksenseContextsDir, currentContext, currentContext+".yaml")
if !api.FileExists(qliksenseContextsFile) {
err := fmt.Errorf("Context file does not exist.\nPlease try re-running `qliksense config set-context <context-name>` and then `qliksense config view` again")
log.Println(err)
return nil, "", err
}
if err := api.ReadFromFile(qliksenseCR, qliksenseContextsFile); err != nil {
log.Println(err)
return nil, "", err
}
api.LogDebugMessage("Read context file: %s, Read QliksenseCR: %v", qliksenseContextsFile, qliksenseCR) func validateCR(key string, keySub string, value string, crSpec *api.QliksenseCR) (bool, *api.QliksenseCR) {
cr := reflect.ValueOf(crSpec.Spec)
return qliksenseCR, qliksenseContextsFile, nil 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
}
}
}
// 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{}
}
}
}
return true, crSpec
} }
// SetOtherConfigs - set profile/storageclassname/git.repository/manifestRoot commands // SetOtherConfigs - set profile/storageclassname/git.repository/manifestRoot commands
func (q *Qliksense) SetOtherConfigs(args []string) error { func (q *Qliksense) SetOtherConfigs(args []string) error {
// retieve current context from config.yaml // retieve current context from config.yaml
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q) qConfig := api.NewQConfig(q.QliksenseHome)
qliksenseCR, err := qConfig.GetCurrentCR()
if err != nil { if err != nil {
return err return err
} }
@@ -204,72 +240,127 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
} }
for _, arg := range args { for _, arg := range args {
argsString := strings.Split(arg, "=") if strings.HasPrefix(arg, "fetchSource.") {
switch argsString[0] { if err := q.processSetFetchSource(arg, qliksenseCR); err != nil {
case "profile":
qliksenseCR.Spec.Profile = argsString[1]
api.LogDebugMessage("Current profile after modification: %s ", qliksenseCR.Spec.Profile)
case "git.repository":
if qliksenseCR.Spec.Git == nil {
qliksenseCR.Spec.Git = &config.Repo{}
}
qliksenseCR.Spec.Git.Repository = argsString[1]
api.LogDebugMessage("Current git repository after modification: %s ", qliksenseCR.Spec.Git.Repository)
case "storageClassName":
qliksenseCR.Spec.StorageClassName = argsString[1]
api.LogDebugMessage("Current StorageClassName after modification: %s ", qliksenseCR.Spec.StorageClassName)
case "manifestsRoot":
qliksenseCR.Spec.ManifestsRoot = argsString[1]
case "rotateKeys":
rotateKeys, err := validateInput(argsString[1])
if err != nil {
return err return err
} }
qliksenseCR.Spec.RotateKeys = rotateKeys } else if strings.HasPrefix(arg, "git.") {
api.LogDebugMessage("Current rotateKeys after modification: %s ", qliksenseCR.Spec.RotateKeys) if err := q.processSetGit(arg, qliksenseCR); err != nil {
case "gitops.enabled":
if qliksenseCR.Spec.GitOps == nil {
qliksenseCR.Spec.GitOps = &config.GitOps{}
}
if strings.EqualFold(argsString[1], "yes") || strings.EqualFold(argsString[1], "no") {
qliksenseCR.Spec.GitOps.Enabled = argsString[1]
api.LogDebugMessage("Current gitOps enabled status : %s ", qliksenseCR.Spec.GitOps.Enabled)
} else {
err := fmt.Errorf("Please use yes or no")
log.Println(err)
return err return err
} }
case "gitops.schedule": } else if strings.HasPrefix(arg, "gitOps.") {
if qliksenseCR.Spec.GitOps == nil { if err := q.processSetGitOps(arg, qliksenseCR); err != nil {
qliksenseCR.Spec.GitOps = &config.GitOps{}
}
if _, err := cron.ParseStandard(argsString[1]); err != nil {
err := fmt.Errorf("Please enter string with standard cron scheduling syntax ")
return err return err
} }
qliksenseCR.Spec.GitOps.Schedule = argsString[1] } else {
api.LogDebugMessage("Current gitOps schedule is : %s ", qliksenseCR.Spec.GitOps.Schedule) if err := processSetSingleArg(arg, qliksenseCR); err != nil {
case "gitops.watchbranch": return err
if qliksenseCR.Spec.GitOps == nil {
qliksenseCR.Spec.GitOps = &config.GitOps{}
} }
qliksenseCR.Spec.GitOps.WatchBranch = argsString[1] }
api.LogDebugMessage("Current gitOps watchbranch is : %s ", qliksenseCR.Spec.GitOps.WatchBranch) fmt.Println(chalk.Green.Color("Successfully added to Custom Resource Spec"))
case "gitops.image": }
if qliksenseCR.Spec.GitOps == nil {
qliksenseCR.Spec.GitOps = &config.GitOps{} // write modified content into context.yaml
return qConfig.WriteCR(qliksenseCR)
}
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
} }
qliksenseCR.Spec.GitOps.Image = argsString[1] }
api.LogDebugMessage("Current gitOps image is : %s ", qliksenseCR.Spec.GitOps.Image) if !valid {
default: return errors.New("please povide rotateKeys=yes|no|None")
err := fmt.Errorf("Please enter one of: profile, storageClassName,rotateKeys, manifestRoot, git.repository or gitops arguments to configure the current context") }
log.Println(err) 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 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")
} }
// write modified content into context.yaml return nil
api.WriteToFile(&qliksenseCR, qliksenseContextsFile) }
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 return nil
} }
@@ -374,6 +465,12 @@ func (q *Qliksense) DeleteContextConfig(args []string) error {
// SetUpQliksenseDefaultContext - to setup dir structure for default qliksense context // SetUpQliksenseDefaultContext - to setup dir structure for default qliksense context
func (q *Qliksense) SetUpQliksenseDefaultContext() error { func (q *Qliksense) SetUpQliksenseDefaultContext() error {
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)
} }
@@ -404,7 +501,8 @@ func (q *Qliksense) SetUpQliksenseContext(contextName string) error {
} }
if qliksenseConfig.IsContextExist(contextName) { if qliksenseConfig.IsContextExist(contextName) {
return nil qliksenseConfig.Spec.CurrentContext = contextName
return qliksenseConfig.Write()
} }
qliksenseCR := &api.QliksenseCR{} qliksenseCR := &api.QliksenseCR{}
qliksenseCR.AddCommonConfig(contextName) qliksenseCR.AddCommonConfig(contextName)
@@ -414,7 +512,7 @@ func (q *Qliksense) SetUpQliksenseContext(contextName string) error {
} }
// set the encrypted default mongo // set the encrypted default mongo
return q.SetSecrets([]string{`qliksense.mongoDbUri="mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"`}, false) return q.SetSecrets([]string{`qliksense.mongoDbUri="mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"`}, false, false)
} }
func validateInput(input string) (string, error) { func validateInput(input string) (string, error) {
@@ -435,7 +533,8 @@ func validateInput(input string) (string, error) {
return input, err return input, err
} }
// PrepareK8sSecret decodes and decrypts the secret value in the secret.yaml file and returns a B64encoded string // 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) { func (q *Qliksense) PrepareK8sSecret(targetFile string) (string, error) {
// check if targetFile exists // check if targetFile exists
if !api.FileExists(targetFile) { if !api.FileExists(targetFile) {
@@ -444,7 +543,7 @@ func (q *Qliksense) PrepareK8sSecret(targetFile string) (string, error) {
return "", err return "", err
} }
qConfig := api.NewQConfig(q.QliksenseHome) qConfig := api.NewQConfig(q.QliksenseHome)
_, rsaPrivateKey, err := qConfig.GetCurrentContextEncryptionKeyPair() encryptionKey, err := qConfig.GetEncryptionKeyForCurrent()
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -462,17 +561,13 @@ func (q *Qliksense) PrepareK8sSecret(targetFile string) (string, error) {
dataMap := k8sSecret1.Data dataMap := k8sSecret1.Data
var resultMap = make(map[string][]byte) var resultMap = make(map[string][]byte)
for k, v := range dataMap { for k, v := range dataMap {
ba, err := b64.StdEncoding.DecodeString(string(v)) //k8s secrets has already base64 decoed value
if err != nil { decryptedString, err := api.DecryptData(v, encryptionKey)
err := fmt.Errorf("Not able to decode message: %v", err)
return "", err
}
decryptedString, err := api.Decrypt(ba, rsaPrivateKey)
if err != nil { if err != nil {
err := fmt.Errorf("Not able to decrypt message: %v", err) err := fmt.Errorf("Not able to decrypt message: %v", err)
return "", err return "", err
} }
resultMap[k] = decryptedString resultMap[k] = []byte(decryptedString)
} }
// putting the above map back into the k8sSecret struct // putting the above map back into the k8sSecret struct
@@ -496,7 +591,7 @@ func readTargetfile(targetFile string) ([]byte, error) {
func (q *Qliksense) SetImageRegistry(registry, pushUsername, pushPassword, pullUsername, pullPassword string) error { func (q *Qliksense) SetImageRegistry(registry, pushUsername, pushPassword, pullUsername, pullPassword string) error {
qConfig := api.NewQConfig(q.QliksenseHome) qConfig := api.NewQConfig(q.QliksenseHome)
qliksenseCR, qliksenseContextsFile, err := retrieveCurrentContextInfo(q) qliksenseCR, err := qConfig.GetCurrentCR()
if err != nil { if err != nil {
return err return err
} }
@@ -523,7 +618,7 @@ func (q *Qliksense) SetImageRegistry(registry, pushUsername, pushPassword, pullU
} }
qliksenseCR.Spec.AddToConfigs("qliksense", imageRegistryConfigKey, registry) qliksenseCR.Spec.AddToConfigs("qliksense", imageRegistryConfigKey, registry)
return api.WriteToFile(&qliksenseCR, qliksenseContextsFile) return qConfig.WriteCR(qliksenseCR)
} }
func (q *Qliksense) SetEulaAccepted() error { func (q *Qliksense) SetEulaAccepted() error {

View File

@@ -35,119 +35,35 @@ metadata:
name: testctx-qliksense-senseinstaller name: testctx-qliksense-senseinstaller
type: Opaque type: Opaque
` `
var encText = "SFpVZ2t5SGsrN2lLQjlTYm9rbFUxSDFRcmVYdUxhTW9MUHlQOGtGditxMEcwZTlIZDl1dVRrV0tEYm5qUURSWFp3dStuNklueGk3anI2c1djSVdsbWlKTHdWQUJwdUg0a1NXd3llMUlMa2oxK3FRSFlMM2dQUExvN1pBYkVDeDROMUVvam12M0t0NmQwbkdhSXlWWEpmWWJUVVFDM1Y4L0ZTVXBVN0JUb0l4OVZWdmlPam5HTHk4RlF2a3RUaHJxWTUvZEh2N3pVUmhiOTc2Q2YwbEovZ3I2L2NwRk9RMUFXVXdodVhrTG9lYjVzNFdtTEZzNldqT3k0bWlKM1J6VllLaWVUSFJ2SE85eDB6dUthanRwSGEzWEZkaE5QNnpySVJJNTRFalUyblVYYUNlYXVnWnZEOUxjdWluOFhFcjExbkFINURCUDAycXhoZk5BejVoMlV2eFNWVmR0aW1QTDBhMVBJTUxGQTgyWUkrQkFOQkhkSUNnZGU5SkxIRFBoTzR6c0llaE1LRmhVQkNoOUhQa3kyRnhTeDJ3YWp3M1UycEsvcFJVZUxDazRUbkhmL25LN3h5ekdpV3dSUFFFZHdsWE5JbUhjVlVPV3gvNWh4WlJCUTZtb3pGYk1HbXR1Mkh5Z3RVV2gzNFYzd1BhS01TNFRsa0hyODFjRjVCWVpxenBFK1pKWnVyLy8zbzJsU0tFMjMxTG1pcGk1K0FqbXZvUVcyWHBocjFNVWJQY1pXUkJFRkkyQXBCM0FhQXFPa0k1MkRqNG43Mko5bCtaMzdydTk1aHk5K1lzY0FxMjZVbExYRlc0S3RUUkRLSjlMNnVmdlIrUUNudER3em5UTFRHUnEwZU5COWt6S0Q4MFlUdXozeHNXK3cxdjlHbDJaMnBZMTZWTCtEV1k9"
var decText = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false" var decText = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
func setupTargetFileAndPrivateKey() ([]byte, []byte, string, error) { func setupTargetFileAndPrivateKey() (string, string, error) {
targetFileString := fmt.Sprintf(targetFileStringTemplate, encText)
privKeyBytes := []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIJJwIBAAKCAgEApFf3qCQhAr2QLRRZdhLyB8exLjrQiXLr8hwDe0xHSLJX3w7v
5z4ujJWrHUulQ2/hvS8uffxMVrp2YqeA4sjy/ku1KqZVQv/WNTdL2v9Z1ewbRnBj
DQvmkWDKZ+cP8VPdHGzQ4iM2z6BZ4RQTkdQMKqsVwUsLO9amI2TOny/M696eFRW0
pk4+W3QZZRawT0HqJPvKKXKqoO2+62W54rOV8glJi29Do06e4S6CZUl1hBUy0VlL
trLlRSOHTois0dF2a9f7+GGgU11MHO6w1k1NesSlfZ0vnrrkW8WFLqewk+Jj+w1x
eQnYHOeHjx6zi9f9DC96eSylSB3iJ71NmXcMc0IEZ2LiQIqTL83BLOgMBCsK3FSl
GMakQUR2GJ+M0I/selYkRMhid6eOmhlsTNMPbpcTHxZ+ReIzS+5B1X0FZ7RIL+jS
L9mFcxmD3dxurrrt/DkLpXcuWdi1s1bpVn3jIQhU0+bgA6hT0k8Kj2f3Q9QnvkHS
Eff+XyLvLwQeSsSAcnM+1I7fNSPEo2cq0au6ZtjHcirXmMminAQ6cKW1XrEvJBef
HHibtjJjIM4bHH7MKRA5H3km/J4CCwI1VogSTcE05Z6ypAFU2TCrnec9c9VXkRDP
94h0GuoG8sdhmQudqvghr/8T7CV+sGRQbdeqrXwanfnGPrjcMVIO/dSOxOUCAwEA
AQKCAgA5b2TmJnpC8u0IVCxPz582iNurRHLNFpTPMGsnFCl1hp6fHiFJt7mc+FGt
E1rWjqtd6rdc4Gfth40IPXIV0BTcOqk+FpOFrtO2FXU1PDixQqrlmzGCxb324NTc
KyyvMpf77yuxXI0zUt8WgmW0eV8nKlOYEhoC96lohTqQ96uuY0bsJ4HS/VVdsN2P
Lra/fFHQSw8EHUb0pyIqMoscZ5bn18cUK/Z/hGKSYCbCL0Iavy3bbFHBsBPgbeJD
2BBN4953Iiy1Sak2eUy4b9LtkmaZmVAc7mpOFxLn38gD3icgB+bZPoGBw6b7sw71
Pc2R+hI9x/oNj0TUR11adhZApBJ9RhBbnSCUt8OUt9U5prNj+9qs8cHJGywtz1da
ZT1M6mn2MFSsaOyOlJPzGUzSf4AhI7HiouDpLHtHDqLmc8Vv4rZUqrcFw6kZTCY5
564yE8hh/UimOgQr7467/hADHZ0kBsupFEDWRqQ3qTIikHmGhTYZehDrSGL/3BMG
rvsFdv0krUHyW4FfHqPN09jfP3LTqd5vVbzRhxcGsoGmP/1kXIDtO8Cp1s/K6Mse
tInRCRla8ttZ3CZZ+Vf8HLi/n8XSRfbnMGYi7lVVxnp6kNsTEBgosbdU/r1zbMRJ
8mMMHygyugaRLHmOD/8fkWLfyR88cxPH8u9NufTfvJgiFpZboQKCAQEA0uSU6IGZ
pXIVZdmDWt4mxpS5T2UYarw3V9z/Isd3kkUU5YrC2XrvPRwmx5Jh9GXl9WENYJAR
wH7PaJT0HpBwpxJa1RqHHDSka7DnDcy44oRXyM7e2AmcW8QvcDty/0HPo4oZq4IT
m/+ot1R+bIpmJOweGRhVauzxJEUlQyt+kiH/ad8GiOS6LwqFPq42alnUxPQ106wF
EZZ2WQdzkyV6tF9aMG18AT1fJsGwNjCLRxJ52t/aEUP5mYwlL2UTT5Acn8KbtrTO
fFLAxGuB9LDdT1tGgIpzsXmxAaaeuPvSK4TDFdQyLAUdQJdz0GD9j9ciMPQH3UPe
Vjt6qtpfY6QK2wKCAQEAx36Vys5BlQI0TG6qORI0fiOYpLG1GqmdbCNRgBUsMj5T
LFe7uSd4qnDvGmns4MdkSSOlpF17bQiWhWKbjKRQpT0U/46zcIT4pWyajXJe+i9H
M/DpSRkMq2kGkx6KX2u9L66QBzcxJjtS17amdSpDAfsrvJgOWkxxInzw9n1u6aTe
ZjRDXdVX0KjPebEPOaoJToxne21Od3t+47TnDsQPsO1dvvrXX76IfH8cAlD5+0C/
b2YvDqWDmh9ICjKShwuDWgi4KjCV5PMHCIxH0FQ2L6mSbwIb9YgGin3wjN3KbWqz
dgEu7MeDxEwxZSSg4OstYVLQVgM39G/2ZA4YVJEbPwKCAQAo9FjymhBzb6c2Izp+
D/wpvkIKaBCI0cpRlso5P9E5p466UOsr/tKs5GWnhgbdxlgVAebuJKw93KJ8pciO
kvA9kbPwBHnOgW6Ytz73kBUrcBX4GixueddSftPTkMfxSB+Bm9UGWHlkZw6lo5P1
kh7p9qyVpQMZg7AEoiTtWWn4CQAn2DbVqM17Syi7Fmvc1VsbcG1vkM1fMAAFpAvO
vI2Kr6W9F9XoC7oJtb15mI3DnJPrbGNVzQSQzAWAoblRTyQv5kQFBDHBNPTYcCRJ
l3sy6P/VAI4dHgvAzVGvjL+w0dRszct8fvXCUGceRWeYYmfyZ8GLN53a0ywsN8Ik
gHvXAoIBACee5HEa9bt6bJihgf1DuFk1CKPtB2L8PN+1RAKEMfrolexAoG/tfvGa
7GH6l6ks8KX2BnfWeST2h66GHw6Xs8ydjQYUeV7nidqQ70EYbfSSXznZpvt1liaU
/VFKx4CcDT7jFIfaVlCZh6KADB9I/XXvRIh4SqF0fSO0XMcXsmeE7watapPAQ2iV
nl804yk4tBB9oi/JTcQ9Kr5et2UfW15wRiYf+5ZwaPsQ46cyHfPgsCSXztDB3plF
jTE5ShC4IKZJBQqcC6kk+0ifU8P0da6RpxuU96iUE3h9+sB/bCy+/FV7dq5gEbNy
znygAbOqAaFKqUXr7bkGY5ELm5lwGFECggEACcyaF9mMqLGghR55ew+cMmdeYdK3
meMLi5nrgtbQpVLlz+IV7Vdmrv7lZjeTr4nvU/5miU+p+If14CCFBiSucGq3Kmyp
OSM5cNCjDhw8uIDfY2qWCrZ2NSMR3qaAoBAQyQ2ER1IL98TDF/Qui0ZatbPiM4Ns
GErhkBZh3MCDSt24yiVKcUB79BxatWB4K7h7y8wqpX4Rj7rpfJMF7wz/I1cgyuCE
7XFpRwj7F1B2MmXnvV3KAgAD0EqrJDLeM0vIlDhpOUEaFUkuqmQyeB8qQkWfyXbD
jzloS3cNq0MBijB8oixwD2b4dVhBM7z8vQMX6OntN+97luWgO8OIukoYAg==
-----END RSA PRIVATE KEY-----
`)
publicKeyBytes := []byte(`-----BEGIN RSA PUBLIC KEY----- secretKeyLocation := filepath.Join(testDir, secrets, contexts, qlikDefaultContext, secrets)
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApFf3qCQhAr2QLRRZdhLy if err := os.MkdirAll(secretKeyLocation, 0777); err != nil {
B8exLjrQiXLr8hwDe0xHSLJX3w7v5z4ujJWrHUulQ2/hvS8uffxMVrp2YqeA4sjy
/ku1KqZVQv/WNTdL2v9Z1ewbRnBjDQvmkWDKZ+cP8VPdHGzQ4iM2z6BZ4RQTkdQM
KqsVwUsLO9amI2TOny/M696eFRW0pk4+W3QZZRawT0HqJPvKKXKqoO2+62W54rOV
8glJi29Do06e4S6CZUl1hBUy0VlLtrLlRSOHTois0dF2a9f7+GGgU11MHO6w1k1N
esSlfZ0vnrrkW8WFLqewk+Jj+w1xeQnYHOeHjx6zi9f9DC96eSylSB3iJ71NmXcM
c0IEZ2LiQIqTL83BLOgMBCsK3FSlGMakQUR2GJ+M0I/selYkRMhid6eOmhlsTNMP
bpcTHxZ+ReIzS+5B1X0FZ7RIL+jSL9mFcxmD3dxurrrt/DkLpXcuWdi1s1bpVn3j
IQhU0+bgA6hT0k8Kj2f3Q9QnvkHSEff+XyLvLwQeSsSAcnM+1I7fNSPEo2cq0au6
ZtjHcirXmMminAQ6cKW1XrEvJBefHHibtjJjIM4bHH7MKRA5H3km/J4CCwI1VogS
TcE05Z6ypAFU2TCrnec9c9VXkRDP94h0GuoG8sdhmQudqvghr/8T7CV+sGRQbdeq
rXwanfnGPrjcMVIO/dSOxOUCAwEAAQ==
-----END RSA PUBLIC KEY-----
`)
targetFile := filepath.Join(testDir, "targetfile.yaml")
// tests/config.yaml exists
err := ioutil.WriteFile(targetFile, []byte(targetFileString), 0777)
if err != nil {
log.Printf("Error while creating file: %v", err)
return nil, nil, "", err
}
secretKeyPairDir := filepath.Join(testDir, secrets, contexts, qlikDefaultContext, secrets)
if err := os.MkdirAll(secretKeyPairDir, 0777); err != nil {
err = fmt.Errorf("Not able to create directories") err = fmt.Errorf("Not able to create directories")
log.Fatal(err) log.Fatal(err)
} }
os.Setenv("QLIKSENSE_KEY_LOCATION", secretKeyPairDir) os.Setenv("QLIKSENSE_KEY_LOCATION", secretKeyLocation)
privKeyFile := filepath.Join(secretKeyPairDir, "qliksensePriv") //privKeyFile := filepath.Join(secretKeyLocation, "user_secret_key")
// construct and write priv key file into secretsDir location key, err := api.LoadSecretKey(secretKeyLocation)
err = ioutil.WriteFile(privKeyFile, privKeyBytes, 0777) 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 { if err != nil {
log.Printf("Error while creating file: %v", err) log.Printf("Error while creating file: %v", err)
return nil, nil, "", err return "", "", err
} }
pubKeyFile := filepath.Join(secretKeyPairDir, "qliksensePub")
api.LogDebugMessage("Test setup - \npub key path: %s\n, priv key path: %s\n", pubKeyFile, privKeyFile)
// construct and write pub key file into secretsDir location
err = ioutil.WriteFile(pubKeyFile, publicKeyBytes, 0777)
if err != nil {
log.Printf("Error while creating file: %v", err)
return nil, nil, "", err
}
return publicKeyBytes, privKeyBytes, targetFile, nil
}
func removePrivateKey() { return targetFile, key, err
err := os.Remove(filepath.Join(testDir, secrets, contexts, qlikDefaultContext, secrets, "qliksensePriv"))
if err != nil {
log.Fatalf("Could not delete private key %v", err)
}
return
} }
func setup() func() { func setup() func() {
@@ -224,7 +140,8 @@ func Test_retrieveCurrentContextInfo(t *testing.T) {
q := &Qliksense{ q := &Qliksense{
QliksenseHome: testDir, QliksenseHome: testDir,
} }
_, _, err := retrieveCurrentContextInfo(q) qConfig := api.NewQConfig(q.QliksenseHome)
_, err := qConfig.GetCurrentCR()
if err != nil { if err != nil {
t.FailNow() t.FailNow()
} }
@@ -327,7 +244,7 @@ func TestSetOtherConfigs(t *testing.T) {
q: &Qliksense{ q: &Qliksense{
QliksenseHome: testDir, QliksenseHome: testDir,
}, },
args: []string{"profile=minikube", "rotateKeys=yes", "storageClassName=efs", "gitops.enabled=yes", "gitops.schedule=30 * * * *"}, args: []string{"profile=minikube", "rotateKeys=yes", "storageClassName=efs", "gitOps.enabled=yes", "gitOps.schedule=30 * * * *", "git.repository=master", "git.userName=foo", "git.accessToken=1234"},
}, },
wantErr: false, wantErr: false,
}, },
@@ -337,7 +254,7 @@ func TestSetOtherConfigs(t *testing.T) {
q: &Qliksense{ q: &Qliksense{
QliksenseHome: testDir, QliksenseHome: testDir,
}, },
args: []string{"someconfig=somevalue, gitops.schedule=bar", "gitops.enabled=bar"}, args: []string{"someconfig=somevalue, gitOps.schedule=bar", "gitOps.enabled=bar", "git.foo=bar", "rotateKeys=bar"},
}, },
wantErr: true, wantErr: true,
}, },
@@ -388,7 +305,7 @@ func TestSetConfigs(t *testing.T) {
defer tearDown() defer tearDown()
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if err := tt.args.q.SetConfigs(tt.args.args); (err != nil) != tt.wantErr { if err := tt.args.q.SetConfigs(tt.args.args, false); (err != nil) != tt.wantErr {
t.Errorf("SetConfigs() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("SetConfigs() error = %v, wantErr %v", err, tt.wantErr)
} }
}) })
@@ -507,9 +424,14 @@ spec:
}) })
} }
} }
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) { func Test_PrepareK8sSecret(t *testing.T) {
type fields struct { type fields struct {
QliksenseHome string QliksenseHome string
} }
@@ -529,7 +451,7 @@ func Test_PrepareK8sSecret(t *testing.T) {
wantErr: false, wantErr: false,
setup: func() (string, func()) { setup: func() (string, func()) {
tearDown := setup() tearDown := setup()
_, _, targetFile, _ := setupTargetFileAndPrivateKey() targetFile, _, _ := setupTargetFileAndPrivateKey()
return targetFile, tearDown return targetFile, tearDown
}, },
}, },
@@ -542,7 +464,7 @@ func Test_PrepareK8sSecret(t *testing.T) {
wantErr: true, wantErr: true,
setup: func() (string, func()) { setup: func() (string, func()) {
tearDown := setup() tearDown := setup()
_, _, targetFile, _ := setupTargetFileAndPrivateKey() targetFile, _, _ := setupTargetFileAndPrivateKey()
removePrivateKey() removePrivateKey()
return targetFile, tearDown return targetFile, tearDown
}, },
@@ -556,8 +478,7 @@ func Test_PrepareK8sSecret(t *testing.T) {
wantErr: true, wantErr: true,
setup: func() (string, func()) { setup: func() (string, func()) {
tearDown := setup() tearDown := setup()
_, _, _, _ = setupTargetFileAndPrivateKey() setupTargetFileAndPrivateKey()
removePrivateKey()
return "", tearDown return "", tearDown
}, },
}, },
@@ -637,6 +558,7 @@ func Test_SetSecrets(t *testing.T) {
type args struct { type args struct {
args []string args []string
isSecretSet bool isSecretSet bool
base64 bool
} }
tests := []struct { tests := []struct {
name string name string
@@ -655,6 +577,18 @@ func Test_SetSecrets(t *testing.T) {
}, },
wantErr: 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", name: "test1 valid secret secrets=true",
fields: fields{ fields: fields{
@@ -690,22 +624,17 @@ func Test_SetSecrets(t *testing.T) {
}, },
} }
tearDown := setup() tearDown := setup()
_, privateKeyBytes, _, err := setupTargetFileAndPrivateKey() _, encryptionKey, err := setupTargetFileAndPrivateKey()
if err != nil { if err != nil {
t.FailNow() t.FailNow()
} }
defer tearDown() defer tearDown()
privKey, err := api.DecodeToPrivateKey(privateKeyBytes)
if err != nil {
t.FailNow()
}
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
q := &Qliksense{ q := &Qliksense{
QliksenseHome: tt.fields.QliksenseHome, QliksenseHome: tt.fields.QliksenseHome,
} }
if err := q.SetSecrets(tt.args.args, tt.args.isSecretSet); (err != nil) != tt.wantErr { 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.Errorf("SetSecrets() error = %v, wantErr %v", err, tt.wantErr)
t.FailNow() t.FailNow()
} }
@@ -716,7 +645,10 @@ func Test_SetSecrets(t *testing.T) {
// extract the value for testing // extract the value for testing
testValueArr := strings.SplitN(tt.args.args[0], "=", 2) testValueArr := strings.SplitN(tt.args.args[0], "=", 2)
testValue := strings.ReplaceAll(testValueArr[1], "\"", "") testValue := strings.ReplaceAll(testValueArr[1], "\"", "")
if tt.args.base64 {
d, _ := b64.StdEncoding.DecodeString(testValue)
testValue = strings.Trim(string(d), "\n ")
}
qliksenseCR, err := readCRFile() qliksenseCR, err := readCRFile()
if err != nil { if err != nil {
err = fmt.Errorf("Not able to read from context file: %v", err) err = fmt.Errorf("Not able to read from context file: %v", err)
@@ -733,13 +665,7 @@ func Test_SetSecrets(t *testing.T) {
log.Printf("decode error: %v", err) log.Printf("decode error: %v", err)
t.FailNow() t.FailNow()
} }
decodedValue, err := b64.StdEncoding.DecodeString(valToBeEncrypted) decryptedVal, err := api.DecryptData([]byte(valToBeEncrypted), encryptionKey)
if err != nil {
err := fmt.Errorf("Error occurred while decoding: %v", err)
log.Printf("decode error: %v", err)
t.FailNow()
}
decryptedVal, err := api.Decrypt(decodedValue, privKey)
if err != nil { if err != nil {
err := fmt.Errorf("Error occurred while testing decryption: %v", err) err := fmt.Errorf("Error occurred while testing decryption: %v", err)
log.Printf("No Data in Secret: %v", err) log.Printf("No Data in Secret: %v", err)
@@ -780,7 +706,8 @@ func getValueToBeDecodedForSetSecrets(item config.NameValue, qliksenseCR *api.Ql
} }
// secret=false // secret=false
if item.Value != "" { if item.Value != "" {
return item.Value, nil b, err := b64.RawStdEncoding.DecodeString(item.Value)
return string(b), err
} }
err := fmt.Errorf("Both Value and ValueFrom are empty") err := fmt.Errorf("Both Value and ValueFrom are empty")
return "", err return "", err

View File

@@ -2,6 +2,7 @@ package qliksense
import ( import (
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
qapi "github.com/qlik-oss/sense-installer/pkg/api" qapi "github.com/qlik-oss/sense-installer/pkg/api"
@@ -19,12 +20,24 @@ func (q *Qliksense) ViewCrds(opts *CrdCommandOptions) error {
fmt.Println("cannot get the current-context cr", err) fmt.Println("cannot get the current-context cr", err)
return err return err
} }
if engineCRD, err := getQliksenseInitCrd(qcr); err != nil { engineCRD, err := getQliksenseInitCrd(qcr)
if err != nil {
return err return err
} else if opts.All { }
fmt.Printf("%s\n%s", q.GetOperatorCRDString(), engineCRD) customCrd, err := getCustomCrd(qcr)
} else { if err != nil {
fmt.Printf("%s", engineCRD) return nil
}
fmt.Println(engineCRD)
if customCrd != "" {
fmt.Println("---")
fmt.Println(customCrd)
}
if opts.All {
fmt.Println("---")
fmt.Printf("%s", q.GetOperatorCRDString())
} }
return nil return nil
} }
@@ -43,6 +56,14 @@ func (q *Qliksense) InstallCrds(opts *CrdCommandOptions) error {
} else if err = qapi.KubectlApply(engineCRD, ""); err != nil { } else if err = qapi.KubectlApply(engineCRD, ""); err != nil {
return err 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 opts.All { // install opeartor crd
if err := qapi.KubectlApply(q.GetOperatorCRDString(), ""); err != nil { if err := qapi.KubectlApply(q.GetOperatorCRDString(), ""); err != nil {
fmt.Println("cannot do kubectl apply on opeartor CRD", err) fmt.Println("cannot do kubectl apply on opeartor CRD", err)
@@ -59,16 +80,33 @@ func getQliksenseInitCrd(qcr *qapi.QliksenseCR) (string, error) {
if qcr.Spec.GetManifestsRoot() != "" { if qcr.Spec.GetManifestsRoot() != "" {
repoPath = qcr.Spec.GetManifestsRoot() repoPath = qcr.Spec.GetManifestsRoot()
} else { } else {
if repoPath, err = downloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master"); err != nil { if repoPath, err = DownloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master"); err != nil {
return "", err return "", err
} }
} }
qInitMsPath := filepath.Join(repoPath, Q_INIT_CRD_PATH) qInitMsPath := filepath.Join(repoPath, Q_INIT_CRD_PATH)
qInitByte, err := executeKustomizeBuild(qInitMsPath) 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 { if err != nil {
fmt.Println("cannot generate crds for qliksense-init", err) fmt.Println("cannot generate crds for qliksense-init", err)
return "", err return "", err
} }
return string(qInitByte), nil 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
}

View File

@@ -8,7 +8,7 @@ import (
) )
func TestGetQliksenseInitCrd(t *testing.T) { func TestGetQliksenseInitCrd(t *testing.T) {
someTmpRepoPath, err := downloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master") someTmpRepoPath, err := DownloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master")
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }

View File

@@ -1,6 +1,7 @@
package qliksense package qliksense
import ( import (
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@@ -27,6 +28,31 @@ const (
imageSharedBlobsDirName = "blobs" 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 ... // PullImages ...
func (q *Qliksense) PullImagesForCurrentCR() error { func (q *Qliksense) PullImagesForCurrentCR() error {
qConfig := qapi.NewQConfig(q.QliksenseHome) qConfig := qapi.NewQConfig(q.QliksenseHome)
@@ -49,7 +75,7 @@ func (q *Qliksense) PullImagesForCurrentCR() error {
} }
images := versionOut.Images images := versionOut.Images
if err := q.appendOperatorImages(&images); err != nil { if err := q.appendAdditionalImages(&images, qcr); err != nil {
return err return err
} }
@@ -69,6 +95,19 @@ func (q *Qliksense) PullImagesForCurrentCR() error {
return nil 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)
}
}
func (q *Qliksense) appendPreflightImages(images *[]string) {
pf := qapi.NewPreflightConfig(q.QliksenseHome)
for _, preflightImage := range pf.GetImageMap() {
*images = append(*images, preflightImage)
}
}
func (q *Qliksense) appendOperatorImages(images *[]string) error { func (q *Qliksense) appendOperatorImages(images *[]string) error {
if operatorImages, err := getImageList([]byte(q.GetOperatorControllerString())); err != nil { if operatorImages, err := getImageList([]byte(q.GetOperatorControllerString())); err != nil {
return err return err
@@ -131,7 +170,7 @@ func (q *Qliksense) PushImagesForCurrentCR() error {
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
dockerConfigJsonSecret = &qapi.DockerConfigJsonSecret{ dockerConfigJsonSecret = &qapi.DockerConfigJsonSecret{
Uri: qcr.GetImageRegistry(), Uri: qcr.Spec.GetImageRegistry(),
} }
} else { } else {
return err return err
@@ -149,7 +188,7 @@ func (q *Qliksense) PushImagesForCurrentCR() error {
} }
images := versionOut.Images images := versionOut.Images
if err := q.appendOperatorImages(&images); err != nil { if err := q.appendAdditionalImages(&images, qcr); err != nil {
return err return err
} }
@@ -170,6 +209,15 @@ func (q *Qliksense) PushImagesForCurrentCR() error {
return nil 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 { func pushImage(image, imagesDir string, dockerConfigJsonSecret *qapi.DockerConfigJsonSecret) error {
imageNameParts := getImageNameParts(image) imageNameParts := getImageNameParts(image)
srcDir := filepath.Join(imagesDir, imageIndexDirName, imageNameParts.name, imageNameParts.tag) srcDir := filepath.Join(imagesDir, imageIndexDirName, imageNameParts.name, imageNameParts.tag)

View File

@@ -68,6 +68,9 @@ const (
) )
func Test_Pull_Push_ImagesForCurrentCR(t *testing.T) { func Test_Pull_Push_ImagesForCurrentCR(t *testing.T) {
if testing.Short() {
t.Skip("Skipping pull/push tests in short mode")
}
var testCases = []struct { var testCases = []struct {
name string name string
registryAuth bool registryAuth bool
@@ -131,7 +134,7 @@ func Test_Pull_Push_ImagesForCurrentCR(t *testing.T) {
} }
q := &Qliksense{ q := &Qliksense{
QliksenseHome: tmpQlikSenseHome, QliksenseHome: tmpQlikSenseHome,
CrdBox: packr.New("crds", "./crds"), CrdBox: &packr.Box{},
} }
var versionOut VersionOutput var versionOut VersionOutput
@@ -170,29 +173,88 @@ func Test_Pull_Push_ImagesForCurrentCR(t *testing.T) {
} }
} }
func setupQlikSenseHome(t *testing.T, tmpQlikSenseHome string, registry *testRegistryV2, clientAuth clientAuthType) error { func Test_appendAdditionalImages(t *testing.T) {
if err := ioutil.WriteFile(path.Join(tmpQlikSenseHome, "config.yaml"), []byte(` tmpQlikSenseHome, err := ioutil.TempDir("", "tmp-qlik-sense-home-")
apiVersion: config.qlik.com/v1 if err != nil {
kind: QliksenseConfig t.Fatalf("unexpected error creating tmp dir: %v", err)
}
defer os.RemoveAll(tmpQlikSenseHome)
setupQliksenseTestDefaultContext(t, tmpQlikSenseHome, `
apiVersion: qlik.com/v1
kind: Qliksense
metadata: metadata:
name: QliksenseConfigMetadata name: qlik-default
spec: spec:
contexts: gitOps:
- name: qlik-default image: some-gitops-image
crFile: contexts/qlik-default/qlik-default.yaml `)
currentContext: qlik-default
`), os.ModePerm); err != nil { q := &Qliksense{
return err QliksenseHome: tmpQlikSenseHome,
CrdBox: packr.New("crds", "./crds"),
} }
defaultContextDir := path.Join(tmpQlikSenseHome, "contexts", "qlik-default") pf := api.NewPreflightConfig(q.QliksenseHome)
if err := os.MkdirAll(defaultContextDir, os.ModePerm); err != nil { if err := pf.Initialize(); err != nil {
return err 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" version := "foo"
manifestsRootDir := fmt.Sprintf("%s/repo/%s", defaultContextDir, version) manifestsRootDir := filepath.ToSlash(path.Join(tmpQlikSenseHome, "contexts", "qlik-default", "repo", version))
if err := ioutil.WriteFile(path.Join(defaultContextDir, "qlik-default.yaml"), []byte(fmt.Sprintf(` cr := fmt.Sprintf(`
apiVersion: qlik.com/v1 apiVersion: qlik.com/v1
kind: Qliksense kind: Qliksense
metadata: metadata:
@@ -208,9 +270,8 @@ spec:
manifestsRoot: %s manifestsRoot: %s
rotateKeys: "yes" rotateKeys: "yes"
releaseName: qlik-default releaseName: qlik-default
`, version, registry.url, manifestsRootDir)), os.ModePerm); err != nil { `, version, registry.url, manifestsRootDir)
return err setupQliksenseTestDefaultContext(t, tmpQlikSenseHome, cr)
}
if clientAuth == clientAuthProvided || clientAuth == clientAuthProvidedButIncorrect { if clientAuth == clientAuthProvided || clientAuth == clientAuthProvidedButIncorrect {
if registry.username == "" || clientAuth == clientAuthProvidedButIncorrect { if registry.username == "" || clientAuth == clientAuthProvidedButIncorrect {

View File

@@ -1,13 +1,28 @@
package qliksense package qliksense
import ( import (
"bufio"
"errors"
"fmt" "fmt"
"io/ioutil"
"os"
"path"
"strings"
"github.com/google/uuid" "github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
kapis_git "github.com/qlik-oss/k-apis/pkg/git" kapis_git "github.com/qlik-oss/k-apis/pkg/git"
qapi "github.com/qlik-oss/sense-installer/pkg/api" qapi "github.com/qlik-oss/sense-installer/pkg/api"
) )
type FetchCommandOptions struct {
GitUrl string
AccessToken string
Version string
SecretName string
Overwrite bool
}
const ( const (
QLIK_GIT_REPO = "https://github.com/qlik-oss/qliksense-k8s" QLIK_GIT_REPO = "https://github.com/qlik-oss/qliksense-k8s"
) )
@@ -17,24 +32,125 @@ func (q *Qliksense) FetchQK8s(version string) error {
return fetchAndUpdateCR(qConfig, version) 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 { func fetchAndUpdateCR(qConfig *qapi.QliksenseConfig, version string) error {
qcr, err := qConfig.GetCurrentCR() qcr, err := qConfig.GetCurrentCR()
if err != nil { if err != nil {
fmt.Println("cannot get the current-context cr", err) fmt.Println("cannot get the current-context cr", err)
return err return err
} }
if qConfig.IsRepoExistForCurrent(version) { if version == "" {
return nil 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, QLIK_GIT_REPO)
if repo, err := kapis_git.CloneRepository(destDir, QLIK_GIT_REPO, nil); err != nil { destDir := qConfig.BuildRepoPath(version)
return err fmt.Printf("fetching version [%s] from %s\n", version, qcr.GetFetchUrl())
} else if err = kapis_git.Checkout(repo, version, fmt.Sprintf("%v-by-operator-%v", version, uuid.New().String()), nil); err != nil { if err := qapi.CopyDirectory(tempDest, destDir); err != nil {
return err return nil
} }
qcr.Spec.ManifestsRoot = qConfig.BuildCurrentManifestsRoot(version) qcr.Spec.ManifestsRoot = qConfig.BuildCurrentManifestsRoot(version)
qcr.AddLabelToCr("version", version) qcr.AddLabelToCr("version", version)
return qConfig.WriteCurrentContextCR(qcr) 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
}

View File

@@ -24,7 +24,7 @@ func (q *Qliksense) GetInstallableVersions(opts *LsRemoteCmdOptions) error {
if qcr.Spec.GetManifestsRoot() != "" { if qcr.Spec.GetManifestsRoot() != "" {
repoPath = qcr.Spec.GetManifestsRoot() repoPath = qcr.Spec.GetManifestsRoot()
} else { } else {
repoPath, err = downloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master") repoPath, err = DownloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master")
if err != nil { if err != nil {
return err return err
} }

View File

@@ -3,6 +3,7 @@ package qliksense
import ( import (
"errors" "errors"
"fmt" "fmt"
"path"
"path/filepath" "path/filepath"
"github.com/qlik-oss/k-apis/pkg/config" "github.com/qlik-oss/k-apis/pkg/config"
@@ -27,11 +28,9 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, kee
// fetch the version // fetch the version
qConfig := qapi.NewQConfig(q.QliksenseHome) qConfig := qapi.NewQConfig(q.QliksenseHome)
if !keepPatchFiles { if !keepPatchFiles {
defer func() { if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil {
if err := q.DiscardAllUnstagedChangesFromGitRepo(qConfig); err != nil { fmt.Printf("error removing temporary changes to the config: %v\n", err)
fmt.Printf("error removing temporary changes to the config: %v\n", err) }
}
}()
} }
qcr, err := qConfig.GetCurrentCR() qcr, err := qConfig.GetCurrentCR()
@@ -66,16 +65,11 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, kee
//CRD will be installed outside of operator //CRD will be installed outside of operator
//install operator controller into the namespace //install operator controller into the namespace
fmt.Println("Installing operator controller") fmt.Println("Installing operator controller")
operatorControllerString := q.GetOperatorControllerString() if operatorControllerString, err := q.getProcessedOperatorControllerString(qcr); err != nil {
if imageRegistry := qcr.GetImageRegistry(); imageRegistry != "" { fmt.Println("error extracting/transforming operator controller", err)
operatorControllerString, err = kustomizeForImageRegistry(operatorControllerString, pullSecretName, return err
"qlik/qliksense-operator", fmt.Sprintf("%v/qliksense-operator", imageRegistry)) } else if err := qapi.KubectlApply(operatorControllerString, ""); err != nil {
if err != nil { fmt.Println("cannot do kubectl apply on operator controller", err)
return err
}
}
if err := qapi.KubectlApply(operatorControllerString, ""); err != nil {
fmt.Println("cannot do kubectl apply on opeartor controller", err)
return err return err
} }
@@ -94,7 +88,7 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, kee
return q.applyCR(dcr) return q.applyCR(dcr)
} }
} }
if version != "" { // no need to fetch manifest root already set by some other way if !qcr.IsRepoExist() {
if err := fetchAndUpdateCR(qConfig, version); err != nil { if err := fetchAndUpdateCR(qConfig, version); err != nil {
return err return err
} }
@@ -121,9 +115,19 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, kee
} }
} }
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 { func installOrRemoveImagePullSecret(qConfig *qapi.QliksenseConfig) error {
if pullDockerConfigJsonSecret, err := qConfig.GetPullDockerConfigJsonSecret(); err == nil { if pullDockerConfigJsonSecret, err := qConfig.GetPullDockerConfigJsonSecret(); err == nil {
if dockerConfigJsonSecretYaml, err := pullDockerConfigJsonSecret.ToYaml(nil); err != nil { if dockerConfigJsonSecretYaml, err := pullDockerConfigJsonSecret.ToYaml(""); err != nil {
return err return err
} else if err := qapi.KubectlApply(string(dockerConfigJsonSecretYaml), ""); err != nil { } else if err := qapi.KubectlApply(string(dockerConfigJsonSecretYaml), ""); err != nil {
return err return err
@@ -132,7 +136,7 @@ func installOrRemoveImagePullSecret(qConfig *qapi.QliksenseConfig) error {
deleteDockerConfigJsonSecret := qapi.DockerConfigJsonSecret{ deleteDockerConfigJsonSecret := qapi.DockerConfigJsonSecret{
Name: pullSecretName, Name: pullSecretName,
} }
if deleteDockerConfigJsonSecretYaml, err := deleteDockerConfigJsonSecret.ToYaml(nil); err != nil { if deleteDockerConfigJsonSecretYaml, err := deleteDockerConfigJsonSecret.ToYaml(""); err != nil {
return err return err
} else if err := qapi.KubectlDelete(string(deleteDockerConfigJsonSecretYaml), ""); err != nil { } else if err := qapi.KubectlDelete(string(deleteDockerConfigJsonSecretYaml), ""); err != nil {
qapi.LogDebugMessage("failed deleting %v, error: %v\n", pullSecretName, err) qapi.LogDebugMessage("failed deleting %v, error: %v\n", pullSecretName, err)

View File

@@ -1,11 +1,20 @@
package qliksense package qliksense
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings"
"testing" "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" qapi "github.com/qlik-oss/sense-installer/pkg/api"
) )
@@ -63,3 +72,106 @@ spec:
} }
td() 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)
}
}

View File

@@ -1,8 +1,10 @@
package qliksense package qliksense
import ( import (
"bufio"
"log" "log"
"os" "os"
"strings"
"github.com/qlik-oss/sense-installer/pkg/api" "github.com/qlik-oss/sense-installer/pkg/api"
@@ -13,7 +15,8 @@ import (
"sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/api/types"
) )
func executeKustomizeBuild(directory string) ([]byte, error) { //ExecuteKustomizeBuild execute kustomize to the directory and return manifest as byte array
func ExecuteKustomizeBuild(directory string) ([]byte, error) {
return executeKustomizeBuildForFileSystem(directory, filesys.MakeFsOnDisk()) return executeKustomizeBuildForFileSystem(directory, filesys.MakeFsOnDisk())
} }
@@ -39,10 +42,26 @@ func executeKustomizeBuildForFileSystem(directory string, fSys filesys.FileSyste
func executeKustomizeBuildWithStdoutProgress(path string) (kuzManifest []byte, err error) { func executeKustomizeBuildWithStdoutProgress(path string) (kuzManifest []byte, err error) {
result, err := api.ExecuteTaskWithBlinkingStdoutFeedback(func() (interface{}, error) { result, err := api.ExecuteTaskWithBlinkingStdoutFeedback(func() (interface{}, error) {
return executeKustomizeBuild(path) return ExecuteKustomizeBuild(path)
}, "...") }, "...")
if err != nil { if err != nil {
return nil, err return nil, err
} }
return result.([]byte), nil 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
}

View File

@@ -20,7 +20,7 @@ import (
kapis_git "github.com/qlik-oss/k-apis/pkg/git" 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("", "") tmpDir, err := ioutil.TempDir("", "")
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v\n", err) 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) 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 { if err != nil {
t.Fatalf("unexpected kustomize error: %v\n", err) t.Fatalf("unexpected kustomize error: %v\n", err)
} }
@@ -86,7 +86,7 @@ func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
generateKeys(cr, "won't-use") 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 { if err != nil {
t.Fatalf("unexpected kustomize error: %v\n", err) t.Fatalf("unexpected kustomize error: %v\n", err)
} }
@@ -139,3 +139,97 @@ func getEjsonKeyDir(defaultKeyDir string) string {
} }
return ejsonKeyDir 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)
}
}

View File

@@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "strings"
qapi "github.com/qlik-oss/sense-installer/pkg/api" qapi "github.com/qlik-oss/sense-installer/pkg/api"
) )
@@ -41,16 +41,17 @@ func (q *Qliksense) loadCrStringIntoFileSystem(crstr string, overwriteExistingCo
if !overwriteExistingContext { if !overwriteExistingContext {
return "", errors.New("Context with name: " + cr.GetName() + " already exists. " + 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.") "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
} }
// else if err := os.RemoveAll(qConfig.GetContextPath(cr.GetName())); err != nil {
// return "", err
// }
} }
if err := qConfig.CreateContextDirs(cr.GetName()); err != nil { if err := qConfig.CreateContextDirs(cr.GetName()); err != nil {
return "", err return "", err
} }
// encrypt the secrets and do base64 then update the CR // encrypt the secrets and do base64 then update the CR
rsaPublicKey, _, err := qConfig.GetContextEncryptionKeyPair(cr.GetName()) encryptionKey, err := qConfig.GetEncryptionKeyFor(cr.GetName())
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -62,15 +63,26 @@ func (q *Qliksense) loadCrStringIntoFileSystem(crstr string, overwriteExistingCo
Value: nv.Value, Value: nv.Value,
SvcName: svc, SvcName: svc,
} }
if err := q.processSecret(skv, rsaPublicKey, cr, false); err != nil { if err := q.processSecret(skv, encryptionKey, cr, false); err != nil {
return cr.GetName(), err 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 // write to disk
if err = qConfig.CreateOrWriteCrAndContext(cr); err != nil { if err = qConfig.CreateOrWriteCrAndContext(cr); err != nil {
return "", err return "", err
} }

View File

@@ -28,21 +28,20 @@ func (q *Qliksense) UpgradeQK8s(keepPatchFiles bool) error {
return err return err
} }
qcr.Spec.RotateKeys = "no" qcr.Spec.RotateKeys = "no"
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
return err
} else if err := q.applyConfigToK8s(dcr); err != nil {
fmt.Println("cannot do kubectl apply on manifests")
return err
}
fmt.Println("Install operator CR into cluster") dcr, err := qConfig.GetDecryptedCr(qcr)
r, err := qcr.GetString()
if err != nil { if err != nil {
return err return err
} }
if err := qapi.KubectlApply(r, ""); err != nil { if dcr.Spec.Git != nil && dcr.Spec.Git.Repository != "" {
fmt.Println("cannot do kubectl apply on operator CR") // fetching and applying manifest will be in the operator controller
// get decrypted cr
return q.applyCR(dcr)
} }
return nil err = q.applyConfigToK8s(dcr)
if err != nil {
fmt.Println("cannot do kubectl apply on manifests")
return err
}
return q.applyCR(dcr)
} }