Compare commits

...

15 Commits

Author SHA1 Message Date
Ashwathi Shiva
26955d749e fixed typo 2020-05-08 16:33:30 -04:00
Ashwathi Shiva
3e678d9056 fixed typo 2020-05-08 16:32:38 -04:00
Ashwathi Shiva
ec9423330d updated docs and typo in help text 2020-05-08 16:18:26 -04:00
Ashwathi Shiva
c5b6443179 updated doc 2020-05-08 16:14:00 -04:00
Ashwathi Shiva
a228757e0b need for both priv key and pub key in the client cert 2020-05-08 15:35:11 -04:00
Ashwathi Shiva
8f2e464d0e Issue-85- replaced github-errors with golang equivalent 2020-05-08 00:34:30 -04:00
Ashwathi Shiva
9d547bfe0c mongo check working when ca cert and client cert put in same file 2020-05-08 00:09:25 -04:00
Ashwathi Shiva
7525c2e698 Preflight mongo mutual tls (#357)
* preflight mongo mutual tls working when cert is specified in CR
2020-05-04 09:42:43 -04:00
Andriy Bulynko
60763e034a Not deleting docker pull secret (#356) 2020-04-29 09:33:41 -04:00
Ashwathi Shiva
ce4081a422 Preflight mongo version check (#353)
* mongo minimum version check working
2020-04-28 16:51:02 -04:00
Sanat Nayar
dd503a40c1 Merge pull request #347 from qlik-oss/upgrade_libraries
upgrade/standardize yaml and semver
2020-04-28 09:02:10 -04:00
Sanat Nayar
b790419fc2 Merge branch 'master' into upgrade_libraries 2020-04-27 16:03:25 -04:00
Ashwathi Shiva
55f9c07c21 Preflight clean cmd (#345)
* Preflight clean as a command and cleanup before and after individual preflight checks
2020-04-27 10:26:50 -04:00
Sanat Nayar
ef77ea3a5f init 2020-04-27 08:49:42 -04:00
Sanat Nayar
7f70bfc7de init 2020-04-27 08:04:13 -04:00
19 changed files with 550 additions and 196 deletions

View File

@@ -3,21 +3,26 @@ package main
import (
"fmt"
. "github.com/logrusorgru/aurora"
ansi "github.com/mattn/go-colorable"
"github.com/qlik-oss/sense-installer/pkg/preflight"
. "github.com/logrusorgru/aurora"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
func preflightCmd(q *qliksense.Qliksense) *cobra.Command {
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var preflightCmd = &cobra.Command{
Use: "preflight",
Short: "perform preflight checks on the cluster",
Long: `perform preflight checks on the cluster`,
Example: `qliksense preflight <preflight_check_to_run>`,
}
f := preflightCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return preflightCmd
}
@@ -44,7 +49,7 @@ func pfDnsCheckCmd(q *qliksense.Qliksense) *cobra.Command {
if namespace == "" {
namespace = "default"
}
if err = qp.CheckDns(namespace, kubeConfigContents); err != nil {
if err = qp.CheckDns(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight DNS check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
@@ -147,7 +152,7 @@ func pfDeploymentCheckCmd(q *qliksense.Qliksense) *cobra.Command {
}
var pfDeploymentCheckCmd = &cobra.Command{
Use: "deployment",
Short: "perform preflight deploymwnt check",
Short: "perform preflight deployment 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 {
@@ -163,7 +168,7 @@ func pfDeploymentCheckCmd(q *qliksense.Qliksense) *cobra.Command {
if namespace == "" {
namespace = "default"
}
if err = qp.CheckDeployment(namespace, kubeConfigContents); err != nil {
if err = qp.CheckDeployment(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight deployment check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
@@ -202,7 +207,7 @@ func pfServiceCheckCmd(q *qliksense.Qliksense) *cobra.Command {
if namespace == "" {
namespace = "default"
}
if err = qp.CheckService(namespace, kubeConfigContents); err != nil {
if err = qp.CheckService(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight service check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
@@ -240,7 +245,7 @@ func pfPodCheckCmd(q *qliksense.Qliksense) *cobra.Command {
if namespace == "" {
namespace = "default"
}
if err = qp.CheckPod(namespace, kubeConfigContents); err != nil {
if err = qp.CheckPod(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight pod check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
@@ -275,7 +280,7 @@ func pfCreateRoleCheckCmd(q *qliksense.Qliksense) *cobra.Command {
fmt.Printf("Error: %v\n", err)
return nil
}
if err = qp.CheckCreateRole(namespace); err != nil {
if err = qp.CheckCreateRole(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight role check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
@@ -310,7 +315,7 @@ func pfCreateRoleBindingCheckCmd(q *qliksense.Qliksense) *cobra.Command {
fmt.Printf("Error: %v\n", err)
return nil
}
if err = qp.CheckCreateRoleBinding(namespace); err != nil {
if err = qp.CheckCreateRoleBinding(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight rolebinding check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
@@ -332,7 +337,7 @@ func pfCreateServiceAccountCheckCmd(q *qliksense.Qliksense) *cobra.Command {
var preflightServiceAccountCmd = &cobra.Command{
Use: "serviceaccount",
Short: "preflight create ServiceAccount check",
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 {
@@ -345,7 +350,7 @@ func pfCreateServiceAccountCheckCmd(q *qliksense.Qliksense) *cobra.Command {
fmt.Printf("Error: %v\n", err)
return nil
}
if err = qp.CheckCreateServiceAccount(namespace); err != nil {
if err = qp.CheckCreateServiceAccount(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight ServiceAccount check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
@@ -417,7 +422,7 @@ func pfMongoCheckCmd(q *qliksense.Qliksense) *cobra.Command {
if namespace == "" {
namespace = "default"
}
if err = qp.CheckMongo(kubeConfigContents, namespace, preflightOpts); err != nil {
if err = qp.CheckMongo(kubeConfigContents, namespace, preflightOpts, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight mongo check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
@@ -436,3 +441,42 @@ func pfMongoCheckCmd(q *qliksense.Qliksense) *cobra.Command {
f.BoolVar(&preflightOpts.MongoOptions.Tls, "tls", false, "enable tls?")
return preflightMongoCmd
}
func pfCleanupCmd(q *qliksense.Qliksense) *cobra.Command {
out := ansi.NewColorableStdout()
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var pfCleanCmd = &cobra.Command{
Use: "clean",
Short: "perform preflight clean",
Long: `perform preflight clean to ensure that all resources are cleared up in the cluster`,
Example: `qliksense preflight clean`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts}
// Preflight clean
namespace, kubeConfigContents, err := preflight.InitPreflight()
if err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight cleanup FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if namespace == "" {
namespace = "default"
}
if err = qp.Cleanup(namespace, kubeConfigContents); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight cleanup FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("Preflight cleanup complete"))
return nil
},
}
f := pfCleanCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return pfCleanCmd
}

View File

@@ -8,6 +8,7 @@ import (
"path/filepath"
"strings"
. "github.com/logrusorgru/aurora"
ansi "github.com/mattn/go-colorable"
"github.com/mitchellh/go-homedir"
"github.com/qlik-oss/sense-installer/pkg"
@@ -15,7 +16,6 @@ import (
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
"github.com/spf13/viper"
. "github.com/logrusorgru/aurora"
)
// To run this project in debug mode, run:
@@ -46,7 +46,7 @@ func initAndExecute() error {
log.Fatal(err)
}
// create dirs and appropriate files for setting up contexts
api.LogDebugMessage("QliksenseHomeDir: %s", qlikSenseHome)
api.LogDebugMessage("QliksenseHomeDir: %s\n", qlikSenseHome)
qliksenseClient := qliksense.New(qlikSenseHome)
cmd := rootCmd(qliksenseClient)
@@ -195,7 +195,6 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
// add clean-config-repo-patches command as a sub-command to the app config sub-command
configCmd.AddCommand(cleanConfigRepoPatchesCmd(p))
// open editor for config
configCmd.AddCommand(configEditCmd(p))
@@ -220,6 +219,7 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
preflightCmd.AddCommand(pfCreateRoleBindingCheckCmd(p))
preflightCmd.AddCommand(pfCreateServiceAccountCheckCmd(p))
preflightCmd.AddCommand(pfCreateAuthCheckCmd(p))
preflightCmd.AddCommand(pfCleanupCmd(p))
cmd.AddCommand(preflightCmd)
cmd.AddCommand(loadCrFile(p))

View File

@@ -23,6 +23,12 @@ Run the following command to execute a specific check
qliksense preflight dns
```
#### Running cleanup
Run the following command to cleanup entities created for preflight checks that were left behind on the cluster.
```
qliksense preflight clean
```
### qliksense load
`qliksense load` command takes input from a file or from pipe

View File

@@ -16,19 +16,29 @@ Examples:
qliksense preflight <preflight_check_to_run>
Available Commands:
all perform all checks
dns perform preflight dns check
k8s-version check k8s version
all perform all checks
authcheck preflight authcheck
clean perform preflight clean
deployment perform preflight deployment check
dns perform preflight dns check
kube-version check kubernetes version
mongo preflight mongo OR preflight mongo --url=<url>
pod perform preflight pod check
role preflight create role check
rolebinding preflight create rolebinding check
service perform preflight service check
serviceaccount preflight create ServiceAccount check
Flags:
-h, --help help for preflight
-v, --verbose verbose mode
```
### DNS check
Run the following command to perform preflight DNS check. We setup a kubernetes deployment and try to reach it as part of establishing DNS connectivity in this check.
The expected output should be similar to the one shown below.
```shell
$ qliksense preflight dns
$ qliksense preflight dns -v
Preflight DNS check
---------------------
@@ -51,7 +61,7 @@ Deleted deployment: dep-dns-preflight-check
We check the version of the target kubernetes cluster and ensure that it falls in the valid range of kubernetes versions that are supported by qliksense.
The command to run this check and the expected similar output are as shown below:
```shell
$ qliksense preflight k8s-version
$ qliksense preflight k8s-version -v
Preflight kubernetes minimum version check
------------------------------------------
@@ -66,7 +76,7 @@ Completed Preflight kubernetes minimum version check
### Service check
We use the commmand below to test if we are able to create a service in the cluster.
```shell
$ qliksense preflight service
$ qliksense preflight service -v
Preflight service check
-----------------------
@@ -82,7 +92,7 @@ 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.
```shell
$ qliksense preflight deployment
$ qliksense preflight deployment -v
Preflight deployment check
-----------------------
@@ -97,7 +107,7 @@ Completed preflight deployment check
### Pod check
We use the commmand below to test if we are able to create a pod in the cluster.
```shell
$ qliksense preflight pod
$ qliksense preflight pod -v
Preflight pod check
--------------------
@@ -110,61 +120,61 @@ Deleted pod: pod-pf-check
Completed preflight pod check
```
### Create-Role check
### 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
$ qliksense preflight role -v
Preflight role check
---------------------------
Preflight create-role check:
Preflight role check:
Created role: role-preflight-check
Preflight create-role check: PASSED
Preflight role check: PASSED
Cleaning up resources...
Deleted role: role-preflight-check
Completed preflight create-role check
Completed preflight role check
```
### Create-RoleBinding check
### 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
$ qliksense preflight rolebinding -v
Preflight create roleBinding check
Preflight rolebinding check
---------------------------
Preflight createRoleBinding check:
Preflight rolebinding check:
Created RoleBinding: role-binding-preflight-check
Preflight createRoleBinding check: PASSED
Preflight rolebinding check: PASSED
Cleaning up resources...
Deleting RoleBinding: role-binding-preflight-check
Deleted RoleBinding: role-binding-preflight-check
Completed preflight createRoleBinding check
Completed preflight rolebinding 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
$ qliksense preflight serviceaccount -v
Preflight create ServiceAccount check
Preflight ServiceAccount check
-------------------------------------
Preflight createServiceAccount check:
Preflight serviceaccount check:
Created Service Account: preflight-check-test-serviceaccount
Preflight createServiceAccount check: PASSED
Preflight serviceaccount check: PASSED
Cleaning up resources...
Deleting ServiceAccount: preflight-check-test-serviceaccount
Deleted ServiceAccount: preflight-check-test-serviceaccount
Completed preflight createServiceAccount check
Completed preflight serviceaccount check
```
### CreateRB check
### Auth check
We use the command below to combine creation of role, role binding, and service account tests
```shell
$ qliksense preflight createRB
$ qliksense preflight authcheck -v
Preflight createRB check
Preflight auth check
-------------------------------------
Preflight create-role check:
Created role: role-preflight-check
@@ -189,18 +199,18 @@ Cleaning up resources...
Deleted ServiceAccount: preflight-check-test-serviceaccount
Completed preflight createServiceAccount check
Completed preflight CreateRB check
Completed preflight auth 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
qliksense preflight mongo --url=<mongo-server url> --ca-cert=<path to ca-cert file>
qliksense preflight mongo --url=<url> -v OR
qliksense preflight mongo -v
qliksense preflight mongo --url=<mongo-server url> --ca-cert=<path to ca-cert file> -v
```
```shell
Preflight mongo check
---------------------
Preflight mongodb check:
@@ -216,13 +226,35 @@ Deleted pod: pf-mongo-pod
Completed preflight mongodb check
```
#### Mongodb check with mutual tls
In order to perform mutual tls with mongo we need to:
- append client certificate to the beginning/end of CA certificate. Make sure to include the beginning and end tags on each certificate.
The CA certificate file should look like this in the end:
```shell
<existing contents of CA cert>
...
-----BEGIN RSA PRIVATE KEY-----
<private key>
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
<public key>
-----END CERTIFICATE-----
```
- Run the command below to set the ca certificate into the CR
```shell
cat <path_to_ca.crt> | base64 | qliksense config set-secrets qliksense.caCertificates --base64
```
Next, run:
```shell
qliksense preflight mongo -v
```
### Running all checks
Run the command shown below to execute all preflight checks.
```shell
$ qliksense preflight all --mongodb-url=<url> OR
$ qliksense preflight all --mongodb-url=<mongo-server url> --mongodb-ca-cert=<path to ca-cert file>
$ qliksense preflight all --mongodb-url=<url> -v OR
$ qliksense preflight all --mongodb-url=<mongo-server url> --mongodb-ca-cert=<path to ca-cert file> -v
Running all preflight checks
@@ -253,4 +285,23 @@ Completed Preflight kubernetes minimum version check
All preflight checks have PASSED
Completed running all preflight checks
```
```
### Clean
Run the command below to cleanup entities that were created for the purpose of running preflight checks and left behind in the cluster.
```shell
$ qliksense preflight clean -v
Preflight clean
----------------
Removing deployment...
Removing service...
Removing pod...
Removing role...
Removing rolebinding...
Removing serviceaccount...
Removing DNS check components...
Removing mongo check components...
Preflight cleanup complete
```

5
go.mod
View File

@@ -16,7 +16,7 @@ replace (
require (
cloud.google.com/go v0.52.0 // indirect
cloud.google.com/go/storage v1.5.0 // indirect
github.com/Masterminds/semver/v3 v3.0.3
github.com/Masterminds/semver/v3 v3.1.0
github.com/Shopify/ejson v1.2.1
github.com/aws/aws-sdk-go v1.28.9 // indirect
github.com/bugsnag/bugsnag-go v1.5.3 // indirect
@@ -34,9 +34,7 @@ require (
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381
github.com/mattn/go-colorable v0.1.4
github.com/mattn/go-tty v0.0.3
github.com/mitchellh/go-homedir v1.1.0
@@ -53,7 +51,6 @@ require (
golang.org/x/tools v0.0.0-20200312194400-c312e98713c2 // indirect
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b // indirect
gopkg.in/yaml.v2 v2.2.8
gopkg.in/yaml.v3 v3.0.0-20190924164351-c8b7dadae555
k8s.io/api v0.17.2
k8s.io/apimachinery v0.17.2
k8s.io/client-go v11.0.0+incompatible

2
go.sum
View File

@@ -72,6 +72,8 @@ github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RP
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.0.3 h1:znjIyLfpXEDQjOIEWh+ehwpTU14UzUPub3c3sm36u14=
github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/sprig/v3 v3.0.2 h1:wz22D0CiSctrliXiI9ZO3HoNApweeRGftyDN+BQa3B8=
github.com/Masterminds/sprig/v3 v3.0.2/go.mod h1:oesJ8kPONMONaZgtiHNzUShJbksypC5kWczhZAf6+aU=
github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=

View File

@@ -96,7 +96,7 @@ func WriteToFile(content interface{}, targetFile string) error {
log.Println(err)
return err
}
LogDebugMessage("Wrote content into %s", targetFile)
LogDebugMessage("Wrote content into %s\n", targetFile)
return nil
}

View File

@@ -7,7 +7,7 @@ import (
"reflect"
"testing"
"gopkg.in/yaml.v3"
"gopkg.in/yaml.v2"
)
func TestDockerConfigJsonSecret(t *testing.T) {
@@ -34,10 +34,10 @@ func TestDockerConfigJsonSecret(t *testing.T) {
t.Fatalf("error unmarshalling yaml string: %v, error: %v", string(dockerConfigJsonSecretYamlBytes), err)
} else if validYamlMap["apiVersion"] != "v1" ||
validYamlMap["kind"] != "Secret" ||
validYamlMap["metadata"].(map[string]interface{})["name"] != dockerConfigJsonSecret.Name ||
validYamlMap["metadata"].(map[interface {}]interface {})["name"] != dockerConfigJsonSecret.Name ||
validYamlMap["type"] != "kubernetes.io/dockerconfigjson" {
t.Fatalf("error verifying validity of secret yaml: %v", string(dockerConfigJsonSecretYamlBytes))
} else if dockerConfigJsonBytesBase64, ok := validYamlMap["data"].(map[string]interface{})[".dockerconfigjson"]; !ok {
} else if dockerConfigJsonBytesBase64, ok := validYamlMap["data"].(map[interface {}]interface {})[".dockerconfigjson"]; !ok {
t.Fatalf("no .dockerconfigjson data key in the secret yaml: %v", string(dockerConfigJsonSecretYamlBytes))
} else if dockerConfigJsonEncryptedBytes, err := base64.StdEncoding.DecodeString(dockerConfigJsonBytesBase64.(string)); err != nil {
t.Fatalf("error decoding dockerConfigJsonBytes from base64: %v", err)
@@ -45,14 +45,14 @@ func TestDockerConfigJsonSecret(t *testing.T) {
t.Fatalf("error decrypting dockerConfigJsonBytes: %v", err)
} else if err := json.Unmarshal(dockerConfigJsonBytes, &dockerConfigJsonMap); err != nil {
t.Fatalf("error unmarshalling dockerConfigJson from json: %v", err)
} else if dockerConfigJson, ok := dockerConfigJsonMap["auths"].(map[string]interface{})[dockerConfigJsonSecret.Uri]; !ok {
} else if dockerConfigJson, ok := dockerConfigJsonMap["auths"].(map[string]interface {})[dockerConfigJsonSecret.Uri]; !ok {
t.Fatalf("dockerConfigJson map does not contain data for the registry: %v", dockerConfigJsonSecret.Uri)
} else if dockerConfigJson.(map[string]interface{})["username"] != dockerConfigJsonSecret.Username ||
dockerConfigJson.(map[string]interface{})["password"] != dockerConfigJsonSecret.Password ||
dockerConfigJson.(map[string]interface{})["email"] != dockerConfigJsonSecret.Email {
} else if dockerConfigJson.(map[string]interface {})["username"] != dockerConfigJsonSecret.Username ||
dockerConfigJson.(map[string]interface {})["password"] != dockerConfigJsonSecret.Password ||
dockerConfigJson.(map[string]interface {})["email"] != dockerConfigJsonSecret.Email {
t.Fatal("dockerConfigJson map does not contain expected values")
} else {
authBase64 := dockerConfigJson.(map[string]interface{})["auth"]
authBase64 := dockerConfigJson.(map[string]interface {})["auth"]
if auth, err := base64.StdEncoding.DecodeString(authBase64.(string)); err != nil {
t.Fatal("error base64 decoding auth value")
} else if string(auth) != fmt.Sprintf("%s:%s", dockerConfigJsonSecret.Username, dockerConfigJsonSecret.Password) {

View File

@@ -17,8 +17,9 @@ type PreflightConfig struct {
}
type PreflightSpec struct {
MinK8sVersion string `json:"minK8sVersion,omitempty" yaml:"minK8sVersion,omitempty"`
Images map[string]string `json:"images,omitempty" yaml:"images,omitempty"`
MinK8sVersion string `json:"minK8sVersion,omitempty" yaml:"minK8sVersion,omitempty"`
MinMongoVersion string `json:"minMongoVersion,omitempty" yaml:"minMongoVersion,omitempty"`
Images map[string]string `json:"images,omitempty" yaml:"images,omitempty"`
}
//NewPreflightConfigEmpty create empty PreflightConfig object
@@ -74,6 +75,13 @@ func (p *PreflightConfig) AddMinK8sV(version string) {
p.Spec.MinK8sVersion = version
}
func (p *PreflightConfig) AddMinMongoV(version string) {
if p.Spec == nil {
p.Spec = &PreflightSpec{}
}
p.Spec.MinMongoVersion = version
}
func (p *PreflightConfig) AddImage(imageFor, imageName string) {
if p.Spec.Images == nil {
p.Spec.Images = make(map[string]string)
@@ -101,6 +109,11 @@ func (p *PreflightConfig) GetImageName(imageFor string, accountForImageRegistry
func (p *PreflightConfig) GetMinK8sVersion() string {
return p.Spec.MinK8sVersion
}
func (p *PreflightConfig) GetMinMongoVersion() string {
return p.Spec.MinMongoVersion
}
func (p *PreflightConfig) IsExistOnDisk() bool {
if _, err := os.Lstat(p.GetConfigFilePath()); err != nil {
return false
@@ -117,6 +130,7 @@ func (p *PreflightConfig) Initialize() error {
return nil
}
p.AddMinK8sV("1.15")
p.AddMinMongoV("3.6")
p.AddImage("nginx", "nginx")
p.AddImage("netcat", "subfuzion/netcat")
p.AddImage("mongo", "mongo")

View File

@@ -23,7 +23,7 @@ func checkExists(filename string) os.FileInfo {
if os.IsNotExist(err) {
return nil
}
LogDebugMessage("File exists")
LogDebugMessage("File exists\n")
return info
}
@@ -73,7 +73,7 @@ func ProcessConfigArgs(args []string, base64Encoded bool) ([]*ServiceKeyValue, e
resultSvcKV := make([]*ServiceKeyValue, len(args))
// qliksense.mongodb=somethig
for i, arg := range args {
LogDebugMessage("Arg received: %s", arg)
LogDebugMessage("Arg received: %s\n", arg)
first := strings.SplitN(arg, "=", 2)
if len(first) != 2 {
return nil, notValidErr

View File

@@ -3,9 +3,9 @@ package preflight
import (
"fmt"
. "github.com/logrusorgru/aurora"
ansi "github.com/mattn/go-colorable"
"github.com/pkg/errors"
. "github.com/logrusorgru/aurora"
)
func (qp *QliksensePreflight) RunAllPreflightChecks(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions) error {
@@ -24,7 +24,7 @@ func (qp *QliksensePreflight) RunAllPreflightChecks(kubeConfigContents []byte, n
totalCount++
// Preflight deployment check
if err := qp.CheckDeployment(namespace, kubeConfigContents); err != nil {
if err := qp.CheckDeployment(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight deployment check FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
@@ -34,7 +34,7 @@ func (qp *QliksensePreflight) RunAllPreflightChecks(kubeConfigContents []byte, n
totalCount++
// Preflight service check
if err := qp.CheckService(namespace, kubeConfigContents); err != nil {
if err := qp.CheckService(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight service check FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
@@ -44,7 +44,7 @@ func (qp *QliksensePreflight) RunAllPreflightChecks(kubeConfigContents []byte, n
totalCount++
// Preflight pod check
if err := qp.CheckPod(namespace, kubeConfigContents); err != nil {
if err := qp.CheckPod(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight pod check FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
@@ -54,7 +54,7 @@ func (qp *QliksensePreflight) RunAllPreflightChecks(kubeConfigContents []byte, n
totalCount++
// Preflight role check
if err := qp.CheckCreateRole(namespace); err != nil {
if err := qp.CheckCreateRole(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight role check FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
@@ -64,7 +64,7 @@ func (qp *QliksensePreflight) RunAllPreflightChecks(kubeConfigContents []byte, n
totalCount++
// Preflight rolebinding check
if err := qp.CheckCreateRoleBinding(namespace); err != nil {
if err := qp.CheckCreateRoleBinding(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red(" Preflight rolebinding check FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
@@ -74,7 +74,7 @@ func (qp *QliksensePreflight) RunAllPreflightChecks(kubeConfigContents []byte, n
totalCount++
// Preflight serviceaccount check
if err := qp.CheckCreateServiceAccount(namespace); err != nil {
if err := qp.CheckCreateServiceAccount(namespace, false); err != nil {
fmt.Fprintf(out, "%s\n", Red(" Preflight serviceaccount check FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
@@ -84,7 +84,7 @@ func (qp *QliksensePreflight) RunAllPreflightChecks(kubeConfigContents []byte, n
totalCount++
// Preflight mongo check
if err := qp.CheckMongo(kubeConfigContents, namespace, preflightOpts); err != nil {
if err := qp.CheckMongo(kubeConfigContents, namespace, preflightOpts, false); err != nil {
fmt.Fprintf(out, "%s\n", Red(" Preflight mongo check FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
@@ -94,7 +94,7 @@ func (qp *QliksensePreflight) RunAllPreflightChecks(kubeConfigContents []byte, n
totalCount++
// Preflight DNS check
if err := qp.CheckDns(namespace, kubeConfigContents); err != nil {
if err := qp.CheckDns(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red(" Preflight DNS check FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {

View File

@@ -6,7 +6,7 @@ import (
"k8s.io/client-go/kubernetes"
)
func (qp *QliksensePreflight) CheckDeployment(namespace string, kubeConfigContents []byte) error {
func (qp *QliksensePreflight) CheckDeployment(namespace string, kubeConfigContents []byte, cleanup bool) error {
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
if err != nil {
err = fmt.Errorf("Kube config error: %v\n", err)
@@ -14,63 +14,80 @@ func (qp *QliksensePreflight) CheckDeployment(namespace string, kubeConfigConten
}
// Deployment check
qp.P.LogVerboseMessage("Preflight deployment check: \n")
qp.P.LogVerboseMessage("--------------------------- \n")
err = qp.checkPfDeployment(clientset, namespace, "deployment-preflight-check")
if !cleanup {
qp.P.LogVerboseMessage("Preflight deployment check: \n")
qp.P.LogVerboseMessage("--------------------------- \n")
}
err = qp.checkPfDeployment(clientset, namespace, cleanup)
if err != nil {
qp.P.LogVerboseMessage("Preflight Deployment check: FAILED\n")
return err
}
qp.P.LogVerboseMessage("Completed preflight deployment check\n")
if !cleanup {
qp.P.LogVerboseMessage("Completed preflight deployment check\n")
}
return nil
}
func (qp *QliksensePreflight) CheckService(namespace string, kubeConfigContents []byte) error {
func (qp *QliksensePreflight) CheckService(namespace string, kubeConfigContents []byte, cleanup bool) error {
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
if err != nil {
err = fmt.Errorf("unable to create a kubernetes client: %v\n", err)
return err
}
// Service check
qp.P.LogVerboseMessage("Preflight service check: \n")
qp.P.LogVerboseMessage("------------------------ \n")
err = qp.checkPfService(clientset, namespace)
if !cleanup {
qp.P.LogVerboseMessage("Preflight service check: \n")
qp.P.LogVerboseMessage("------------------------ \n")
}
err = qp.checkPfService(clientset, namespace, cleanup)
if err != nil {
qp.P.LogVerboseMessage("Preflight Service check: FAILED\n")
return err
}
qp.P.LogVerboseMessage("Completed preflight service check\n")
if !cleanup {
qp.P.LogVerboseMessage("Completed preflight service check\n")
}
return nil
}
func (qp *QliksensePreflight) CheckPod(namespace string, kubeConfigContents []byte) error {
func (qp *QliksensePreflight) CheckPod(namespace string, kubeConfigContents []byte, cleanup bool) error {
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
if err != nil {
err = fmt.Errorf("error: unable to create a kubernetes client: %v\n", err)
return err
}
// Pod check
qp.P.LogVerboseMessage("Preflight pod check: \n")
qp.P.LogVerboseMessage("-------------------- \n")
err = qp.checkPfPod(clientset, namespace)
if !cleanup {
qp.P.LogVerboseMessage("Preflight pod check: \n")
qp.P.LogVerboseMessage("-------------------- \n")
}
err = qp.checkPfPod(clientset, namespace, cleanup)
if err != nil {
qp.P.LogVerboseMessage("Preflight Pod check: FAILED\n")
return err
}
qp.P.LogVerboseMessage("Completed preflight pod check\n")
if !cleanup {
qp.P.LogVerboseMessage("Completed preflight pod check\n")
}
return nil
}
func (qp *QliksensePreflight) checkPfPod(clientset *kubernetes.Clientset, namespace string) error {
// create a pod
func (qp *QliksensePreflight) checkPfPod(clientset *kubernetes.Clientset, namespace string, cleanup bool) error {
// delete the pod we are going to create, if it already exists in the cluster
podName := "pod-pf-check"
qp.deletePod(clientset, namespace, podName)
if cleanup {
return nil
}
commandToRun := []string{}
imageName, err := qp.GetPreflightConfigObj().GetImageName(nginx, true)
if err != nil {
return err
}
// create a pod
pod, err := qp.createPreflightTestPod(clientset, namespace, podName, imageName, nil, commandToRun)
if err != nil {
err = fmt.Errorf("unable to create pod - %v\n", err)
@@ -87,9 +104,14 @@ func (qp *QliksensePreflight) checkPfPod(clientset *kubernetes.Clientset, namesp
return nil
}
func (qp *QliksensePreflight) checkPfService(clientset *kubernetes.Clientset, namespace string) error {
// creating service
func (qp *QliksensePreflight) checkPfService(clientset *kubernetes.Clientset, namespace string, cleanup bool) error {
// delete the service we are going to create, if it already exists in the cluster
serviceName := "svc-pf-check"
qp.deleteService(clientset, namespace, serviceName)
if cleanup {
return nil
}
// creating service
pfService, err := qp.createPreflightTestService(clientset, namespace, serviceName)
if err != nil {
err = fmt.Errorf("unable to create service - %v\n", err)
@@ -106,7 +128,14 @@ func (qp *QliksensePreflight) checkPfService(clientset *kubernetes.Clientset, na
return nil
}
func (qp *QliksensePreflight) checkPfDeployment(clientset *kubernetes.Clientset, namespace, depName string) error {
func (qp *QliksensePreflight) checkPfDeployment(clientset *kubernetes.Clientset, namespace string, cleanup bool) error {
// delete the deployment we are going to create, if it already exists in the cluster
depName := "deployment-preflight-check"
qp.deleteDeployment(clientset, namespace, depName)
if cleanup {
return nil
}
// check if we are able to create a deployment
imageName, err := qp.GetPreflightConfigObj().GetImageName(nginx, true)
if err != nil {

View File

@@ -3,6 +3,8 @@ package preflight
import (
"fmt"
"strings"
"k8s.io/client-go/kubernetes"
)
const (
@@ -10,21 +12,32 @@ const (
netcat = "netcat"
)
func (qp *QliksensePreflight) CheckDns(namespace string, kubeConfigContents []byte) error {
qp.P.LogVerboseMessage("Preflight DNS check: \n")
qp.P.LogVerboseMessage("------------------- \n")
func (qp *QliksensePreflight) CheckDns(namespace string, kubeConfigContents []byte, cleanup bool) error {
depName := "dep-dns-preflight-check"
serviceName := "svc-dns-pf-check"
podName := "pf-pod-1"
if !cleanup {
qp.P.LogVerboseMessage("Preflight DNS check: \n")
qp.P.LogVerboseMessage("------------------- \n")
}
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
if err != nil {
err = fmt.Errorf("unable to create a kubernetes client: %v\n", err)
return err
}
// delete the deployment we are going to create, if it already exists in the cluster
qp.runDNSCleanup(clientset, namespace, podName, serviceName, depName)
if cleanup {
return nil
}
// creating deployment
depName := "dep-dns-preflight-check"
nginxImageName, err := qp.GetPreflightConfigObj().GetImageName(nginx, true)
if err != nil {
return err
}
dnsDeployment, err := qp.createPreflightTestDeployment(clientset, namespace, depName, nginxImageName)
if err != nil {
err = fmt.Errorf("unable to create deployment: %v\n", err)
@@ -37,7 +50,6 @@ func (qp *QliksensePreflight) CheckDns(namespace string, kubeConfigContents []by
}
// creating service
serviceName := "svc-dns-pf-check"
dnsService, err := qp.createPreflightTestService(clientset, namespace, serviceName)
if err != nil {
err = fmt.Errorf("unable to create service : %s, %s\n", serviceName, err)
@@ -46,13 +58,13 @@ func (qp *QliksensePreflight) CheckDns(namespace string, kubeConfigContents []by
defer qp.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 {
err = fmt.Errorf("unable to retrieve image : %v\n", err)
return err
}
dnsPod, err := qp.createPreflightTestPod(clientset, namespace, podName, netcatImageName, nil, commandToRun)
if err != nil {
err = fmt.Errorf("unable to create pod : %s, %s\n", podName, err)
@@ -83,9 +95,16 @@ func (qp *QliksensePreflight) CheckDns(namespace string, kubeConfigContents []by
err = fmt.Errorf("Expected response not found\n")
return err
}
qp.P.LogVerboseMessage("Completed preflight DNS check\n")
qp.P.LogVerboseMessage("Cleaning up resources...\n")
if !cleanup {
qp.P.LogVerboseMessage("Completed preflight DNS check\n")
qp.P.LogVerboseMessage("Cleaning up resources...\n")
}
return nil
}
func (qp *QliksensePreflight) runDNSCleanup(clientset *kubernetes.Clientset, namespace, podName, serviceName, depName string) {
qp.deleteDeployment(clientset, namespace, depName)
qp.deletePod(clientset, namespace, podName)
qp.deleteService(clientset, namespace, serviceName)
}

View File

@@ -1,10 +1,15 @@
package preflight
import (
"encoding/pem"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/Masterminds/semver/v3"
"github.com/pkg/errors"
"github.com/qlik-oss/sense-installer/pkg/api"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
apiv1 "k8s.io/api/core/v1"
@@ -15,11 +20,12 @@ const (
mongo = "mongo"
)
func (qp *QliksensePreflight) CheckMongo(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions) error {
qp.P.LogVerboseMessage("Preflight mongodb check: \n")
qp.P.LogVerboseMessage("------------------------ \n")
if preflightOpts.MongoOptions.MongodbUrl == "" {
func (qp *QliksensePreflight) CheckMongo(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions, cleanup bool) error {
if !cleanup {
qp.P.LogVerboseMessage("Preflight mongodb check: \n")
qp.P.LogVerboseMessage("------------------------ \n")
}
if preflightOpts != nil && preflightOpts.MongoOptions.MongodbUrl == "" && !cleanup {
// infer mongoDbUrl from currentCR
qp.P.LogVerboseMessage("MongoDbUri is empty, infer from CR\n")
qConfig := qapi.NewQConfig(qp.Q.QliksenseHome)
@@ -37,45 +43,108 @@ func (qp *QliksensePreflight) CheckMongo(kubeConfigContents []byte, namespace st
qp.P.LogVerboseMessage("An error occurred while retrieving mongodbUrl from current CR: %v\n", err)
return err
}
preflightOpts.MongoOptions.MongodbUrl = decryptedCR.Spec.GetFromSecrets("qliksense", "mongoDbUri")
}
preflightOpts.MongoOptions.MongodbUrl = strings.TrimSpace(decryptedCR.Spec.GetFromSecrets("qliksense", "mongoDbUri"))
tmpDir := os.TempDir()
caCrtFile := filepath.Join(tmpDir, "rootCA.crt")
clientCrtFile := filepath.Join(tmpDir, "mongoClient.crt")
caCertStr := decryptedCR.Spec.GetFromSecrets("qliksense", "caCertificates")
clientCertStr := decryptedCR.Spec.GetFromSecrets("qliksense", "mongoDbClientCrt")
qp.P.LogVerboseMessage("MongodbUrl: %s\n", preflightOpts.MongoOptions.MongodbUrl)
if err := qp.mongoConnCheck(kubeConfigContents, namespace, preflightOpts); err != nil {
return err
if preflightOpts.MongoOptions.CaCertFile == "" && caCertStr != "" {
api.LogDebugMessage("received ca crt: %s\n", caCertStr)
if err := ioutil.WriteFile(caCrtFile, []byte(caCertStr), 0644); err != nil {
return fmt.Errorf("unable to write CA crt to file: %v", err)
}
preflightOpts.MongoOptions.CaCertFile = caCrtFile
}
if preflightOpts.MongoOptions.ClientCertFile == "" && clientCertStr != "" {
api.LogDebugMessage("received client crt: %s\n", clientCertStr)
if err := ioutil.WriteFile(clientCrtFile, []byte(clientCertStr), 0644); err != nil {
return fmt.Errorf("unable to write client crt to file: %v", err)
}
preflightOpts.MongoOptions.ClientCertFile = clientCrtFile
}
}
if !cleanup {
qp.P.LogVerboseMessage("MongodbUrl: %s\n", preflightOpts.MongoOptions.MongodbUrl)
// if mongoDbUrl is empty, abort check
if preflightOpts.MongoOptions.MongodbUrl == "" {
qp.P.LogVerboseMessage("Mongodb Url is empty, hence aborting preflight check\n")
return errors.New("MongoDbUrl is empty")
}
}
var privKeys []string
var err error
if preflightOpts.MongoOptions.CaCertFile != "" && preflightOpts.MongoOptions.ClientCertFile == "" {
privKeys, err = qp.extractPrivateKeysFromCA(preflightOpts.MongoOptions.CaCertFile)
if err != nil {
return fmt.Errorf("unable to parse CA cert: %v", err)
}
}
privKeyCount := len(privKeys)
if privKeyCount == 0 {
api.LogDebugMessage("no private keys extrated from CA, hence proceeding with usual flow\n")
if err := qp.mongoConnCheck(kubeConfigContents, namespace, preflightOpts, cleanup); err != nil {
return err
}
} else {
api.LogDebugMessage("found %d private keys\n", privKeyCount)
successCount := 0
for _, privKey := range privKeys {
preflightOpts.MongoOptions.ClientCertFile = privKey
if err1 := qp.mongoConnCheck(kubeConfigContents, namespace, preflightOpts, cleanup); err1 != nil {
err = err1
continue
}
successCount++
break
}
if successCount == 0 {
return err
}
}
if !cleanup {
qp.P.LogVerboseMessage("Completed preflight mongodb check\n")
}
qp.P.LogVerboseMessage("Completed preflight mongodb check\n")
return nil
}
func (qp *QliksensePreflight) mongoConnCheck(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions) error {
var caCertSecretName, clientCertSecretName string
func (qp *QliksensePreflight) mongoConnCheck(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions, cleanup bool) error {
caCertSecretName := "preflight-mongo-test-cacert"
clientCertSecretName := "preflight-mongo-test-clientcert"
mongoPodName := "pf-mongo-pod"
clientset, _, err := getK8SClientSet(kubeConfigContents, "")
if err != nil {
err = fmt.Errorf("unable to create a kubernetes client: %v\n", err)
return err
}
// cleanup before starting check
qp.runMongoCleanup(clientset, namespace, mongoPodName, caCertSecretName, clientCertSecretName)
if cleanup {
return nil
}
var secrets []string
if preflightOpts.MongoOptions.CaCertFile != "" {
caCertSecretName = "preflight-mongo-test-cacert"
caCertSecret, err := qp.createSecret(clientset, namespace, preflightOpts.MongoOptions.CaCertFile, caCertSecretName)
if err != nil {
err = fmt.Errorf("unable to create a ca cert kubernetes secret: %v\n", err)
return err
}
defer qp.deleteK8sSecret(clientset, namespace, caCertSecret)
defer qp.deleteK8sSecret(clientset, namespace, caCertSecret.Name)
secrets = append(secrets, caCertSecretName)
}
if preflightOpts.MongoOptions.ClientCertFile != "" {
clientCertSecretName = "preflight-mongo-test-clientcert"
clientCertSecret, err := qp.createSecret(clientset, namespace, preflightOpts.MongoOptions.ClientCertFile, clientCertSecretName)
if err != nil {
err = fmt.Errorf("unable to create a client cert kubernetes secret: %v\n", err)
return err
}
defer qp.deleteK8sSecret(clientset, namespace, clientCertSecret)
defer qp.deleteK8sSecret(clientset, namespace, clientCertSecret.Name)
secrets = append(secrets, clientCertSecretName)
}
@@ -89,7 +158,7 @@ func (qp *QliksensePreflight) mongoConnCheck(kubeConfigContents []byte, namespac
mongoCommand.WriteString(fmt.Sprintf(" --password %s", preflightOpts.MongoOptions.Password))
api.LogDebugMessage("Adding username and password\n")
}
if preflightOpts.MongoOptions.Tls || preflightOpts.MongoOptions.CaCertFile != "" || preflightOpts.MongoOptions.ClientCertFile != "" {
if preflightOpts.MongoOptions.Tls || preflightOpts.MongoOptions.ClientCertFile != "" {
mongoCommand.WriteString(" --tls")
api.LogDebugMessage("Adding --tls: Mongo command: %s\n", mongoCommand.String())
}
@@ -107,18 +176,17 @@ func (qp *QliksensePreflight) mongoConnCheck(kubeConfigContents []byte, namespac
api.LogDebugMessage("Mongo command: %s\n", strings.Join(commandToRun, " "))
// create a pod
podName := "pf-mongo-pod"
imageName, err := qp.GetPreflightConfigObj().GetImageName(mongo, true)
if err != nil {
err = fmt.Errorf("unable to retrieve image : %v\n", err)
return err
}
mongoPod, err := qp.createPreflightTestPod(clientset, namespace, podName, imageName, secrets, commandToRun)
mongoPod, err := qp.createPreflightTestPod(clientset, namespace, mongoPodName, imageName, secrets, commandToRun)
if err != nil {
err = fmt.Errorf("unable to create pod : %v\n", err)
return err
}
defer qp.deletePod(clientset, namespace, podName)
defer qp.deletePod(clientset, namespace, mongoPodName)
if err := waitForPod(clientset, namespace, mongoPod); err != nil {
return err
@@ -134,6 +202,13 @@ func (qp *QliksensePreflight) mongoConnCheck(kubeConfigContents []byte, namespac
return err
}
// check mongo server version
ok, err := qp.checkMongoVersion(logStr)
if !ok || err != nil {
return err
}
// check if connection succeeded
stringToCheck := "Implicit session:"
if strings.Contains(logStr, stringToCheck) {
qp.P.LogVerboseMessage("Preflight mongo check: PASSED\n")
@@ -144,6 +219,43 @@ func (qp *QliksensePreflight) mongoConnCheck(kubeConfigContents []byte, namespac
return nil
}
func (qp *QliksensePreflight) checkMongoVersion(logStr string) (bool, error) {
// check mongo server version
api.LogDebugMessage("Minimum required mongo version: %s\n", qp.GetPreflightConfigObj().GetMinMongoVersion())
mongoVersionStrToCheck := "MongoDB server version:"
if strings.Contains(logStr, mongoVersionStrToCheck) {
logLines := strings.Split(logStr, "\n")
for _, eachline := range logLines {
if strings.Contains(eachline, mongoVersionStrToCheck) {
mongoVersionLog := strings.Split(eachline, ":")
if len(mongoVersionLog) < 2 {
continue
}
mongoVersionStr := strings.TrimSpace(mongoVersionLog[1])
api.LogDebugMessage("Extracted mongo version from pod log: %s\n", mongoVersionStr)
currentMongoVersionSemver, err := semver.NewVersion(mongoVersionStr)
if err != nil {
err = fmt.Errorf("Unable to convert minimum mongo version into semver version:%v\n", err)
return false, err
}
minMongoVersionSemver, err := semver.NewVersion(qp.GetPreflightConfigObj().GetMinMongoVersion())
if err != nil {
err = fmt.Errorf("Unable to convert required minimum mongo version into semver version:%v\n", err)
return false, err
}
if currentMongoVersionSemver.GreaterThan(minMongoVersionSemver) || currentMongoVersionSemver.Equal(minMongoVersionSemver) {
qp.P.LogVerboseMessage("Current mongodb server version %s is greater than or equal to minimum required mongodb version: %s\n", currentMongoVersionSemver, minMongoVersionSemver)
return true, nil
}
err = fmt.Errorf("Current mongodb server version %s is less than minimum required mongodb version: %s", currentMongoVersionSemver, minMongoVersionSemver)
return false, err
}
}
}
err := errors.New("Unable to infer mongodb server version")
return false, err
}
func (qp *QliksensePreflight) createSecret(clientset *kubernetes.Clientset, namespace, certFile, certSecretName string) (*apiv1.Secret, error) {
certBytes, err := ioutil.ReadFile(certFile)
if err != nil {
@@ -152,8 +264,52 @@ func (qp *QliksensePreflight) createSecret(clientset *kubernetes.Clientset, name
certSecret, err := qp.createPreflightTestSecret(clientset, namespace, certSecretName, certBytes)
if err != nil {
err = fmt.Errorf("unable to create secret with ca cert : %v\n", err)
err = fmt.Errorf("unable to create secret with cert : %v\n", err)
return nil, err
}
return certSecret, nil
}
func (qp *QliksensePreflight) runMongoCleanup(clientset *kubernetes.Clientset, namespace, mongoPodName, caCertSecretName, clientCertSecretName string) {
qp.deletePod(clientset, namespace, mongoPodName)
qp.deleteK8sSecret(clientset, namespace, caCertSecretName)
qp.deleteK8sSecret(clientset, namespace, clientCertSecretName)
}
func (qp *QliksensePreflight) extractPrivateKeysFromCA(cafile string) ([]string, error) {
api.LogDebugMessage("extracting private keys from CA file: %s\n", cafile)
raw, err := ioutil.ReadFile(cafile)
if err != nil {
return nil, err
}
dirPath := os.TempDir()
count := 0
var files []string
for {
block, rest := pem.Decode(raw)
if block == nil {
break
}
if block.Type != "CERTIFICATE" {
api.LogDebugMessage("found a private key\n")
privFile := filepath.Join(dirPath, fmt.Sprintf("mongo_priv_%d.key", count+1))
api.LogDebugMessage("creating a private key file: %s\n", privFile)
keyData := pem.EncodeToMemory(block)
block, rest = pem.Decode(rest)
if block != nil && block.Type == "CERTIFICATE" {
api.LogDebugMessage("block type: %s\n", block.Type)
keyData = append(keyData, pem.EncodeToMemory(block)...)
}
api.LogDebugMessage("key data: %s\n", keyData)
if err := ioutil.WriteFile(privFile, keyData, 0600); err != nil {
return nil, fmt.Errorf("error writing private key to file: \"%s\"", privFile)
}
api.LogDebugMessage("successfully wrote contents to the private key file\n")
files = append(files, privFile)
count++
}
raw = rest
}
api.LogDebugMessage("extracted private key files: %v\n", files)
return files, nil
}

View File

@@ -12,7 +12,6 @@ import (
"time"
"github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/qlik-oss/sense-installer/pkg/api"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
appsv1 "k8s.io/api/apps/v1"
@@ -115,13 +114,13 @@ func getK8SClientSet(kubeconfig []byte, contextName string) (*kubernetes.Clients
if len(kubeconfig) == 0 {
clientConfig, err = rest.InClusterConfig()
if err != nil {
err = errors.Wrap(err, "Unable to load in-cluster kubeconfig")
err = fmt.Errorf("Unable to load in-cluster kubeconfig: %w", err)
return nil, nil, err
}
} else {
config, err := clientcmd.Load(kubeconfig)
if err != nil {
err = errors.Wrap(err, "Unable to load kubeconfig")
err = fmt.Errorf("Unable to load kubeconfig: %w", err)
return nil, nil, err
}
if contextName != "" {
@@ -129,13 +128,13 @@ func getK8SClientSet(kubeconfig []byte, contextName string) (*kubernetes.Clients
}
clientConfig, err = clientcmd.NewDefaultClientConfig(*config, &clientcmd.ConfigOverrides{}).ClientConfig()
if err != nil {
err = errors.Wrap(err, "Unable to create client config from config")
err = fmt.Errorf("Unable to create client config from config: %w", err)
return nil, nil, err
}
}
clientset, err := kubernetes.NewForConfig(clientConfig)
if err != nil {
err = errors.Wrap(err, "Unable to create clientset")
err = fmt.Errorf("Unable to create clientset: %w", err)
return nil, nil, err
}
return clientset, clientConfig, nil
@@ -186,7 +185,7 @@ func (qp *QliksensePreflight) createPreflightTestDeployment(clientset *kubernete
result, err = deploymentsClient.Create(deployment)
return err
}); err != nil {
err = errors.Wrapf(err, "unable to create deployments in the %s namespace", namespace)
err = fmt.Errorf("unable to create deployments in the %s namespace: %w", namespace, err)
return nil, err
}
qp.P.LogVerboseMessage("Created deployment %q\n", result.GetObjectMeta().GetName())
@@ -201,7 +200,7 @@ func getDeployment(clientset *kubernetes.Clientset, namespace, depName string) (
deployment, err = deploymentsClient.Get(depName, v1.GetOptions{})
return err
}); err != nil {
err = errors.Wrapf(err, "unable to get deployments in the %s namespace", namespace)
err = fmt.Errorf("unable to get deployments in the %s namespace: %w", namespace, err)
api.LogDebugMessage("%v\n", err)
return nil, err
}
@@ -271,7 +270,7 @@ func getService(clientset *kubernetes.Clientset, namespace, svcName string) (*ap
svc, err = servicesClient.Get(svcName, v1.GetOptions{})
return err
}); err != nil {
err = errors.Wrapf(err, "unable to get services in the %s namespace", namespace)
err = fmt.Errorf("unable to get services in the %s namespace: %w", namespace, err)
return nil, err
}
@@ -288,7 +287,6 @@ func (qp *QliksensePreflight) deleteService(clientset *kubernetes.Clientset, nam
if err := retryOnError(func() (err error) {
return servicesClient.Delete(name, &deleteOptions)
}); err != nil {
fmt.Println(err)
return err
}
qp.P.LogVerboseMessage("Deleted service: %s\n", name)
@@ -568,18 +566,20 @@ func (qp *QliksensePreflight) createPfRole(clientset *kubernetes.Clientset, name
return role, nil
}
func (qp *QliksensePreflight) deleteRole(clientset *kubernetes.Clientset, namespace string, role *v1beta1.Role) {
func (qp *QliksensePreflight) deleteRole(clientset *kubernetes.Clientset, namespace string, roleName string) error {
rolesClient := clientset.RbacV1beta1().Roles(namespace)
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}
err := rolesClient.Delete(role.GetName(), &deleteOptions)
err := rolesClient.Delete(roleName, &deleteOptions)
if err != nil {
log.Fatal(err)
log.Printf("Error: %v\n", err)
return err
}
qp.P.LogVerboseMessage("Deleted role: %s\n\n", role.Name)
qp.P.LogVerboseMessage("Deleted role: %s\n\n", roleName)
return nil
}
func (qp *QliksensePreflight) createPfRoleBinding(clientset *kubernetes.Clientset, namespace, roleBindingName string) (*v1beta1.RoleBinding, error) {
@@ -619,18 +619,20 @@ func (qp *QliksensePreflight) createPfRoleBinding(clientset *kubernetes.Clientse
return roleBinding, nil
}
func (qp *QliksensePreflight) deleteRoleBinding(clientset *kubernetes.Clientset, namespace string, roleBinding *v1beta1.RoleBinding) {
func (qp *QliksensePreflight) deleteRoleBinding(clientset *kubernetes.Clientset, namespace string, roleBindingName string) error {
roleBindingClient := clientset.RbacV1beta1().RoleBindings(namespace)
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}
err := roleBindingClient.Delete(roleBinding.GetName(), &deleteOptions)
err := roleBindingClient.Delete(roleBindingName, &deleteOptions)
if err != nil {
log.Fatal(err)
log.Printf("Error: %v\n", err)
return err
}
qp.P.LogVerboseMessage("Deleted RoleBinding: %s\n\n", roleBinding.Name)
qp.P.LogVerboseMessage("Deleted RoleBinding: %s\n\n", roleBindingName)
return nil
}
func (qp *QliksensePreflight) createPfServiceAccount(clientset *kubernetes.Clientset, namespace, serviceAccountName string) (*apiv1.ServiceAccount, error) {
@@ -657,18 +659,20 @@ func (qp *QliksensePreflight) createPfServiceAccount(clientset *kubernetes.Clien
return serviceAccount, nil
}
func (qp *QliksensePreflight) deleteServiceAccount(clientset *kubernetes.Clientset, namespace string, serviceAccount *apiv1.ServiceAccount) {
func (qp *QliksensePreflight) deleteServiceAccount(clientset *kubernetes.Clientset, namespace string, serviceAccountName string) error {
serviceAccountClient := clientset.CoreV1().ServiceAccounts(namespace)
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}
err := serviceAccountClient.Delete(serviceAccount.GetName(), &deleteOptions)
err := serviceAccountClient.Delete(serviceAccountName, &deleteOptions)
if err != nil {
log.Fatal(err)
log.Printf("Error: %v\n", err)
return err
}
qp.P.LogVerboseMessage("Deleted ServiceAccount: %s\n\n", serviceAccount.Name)
qp.P.LogVerboseMessage("Deleted ServiceAccount: %s\n\n", serviceAccountName)
return nil
}
func (qp *QliksensePreflight) createPreflightTestSecret(clientset *kubernetes.Clientset, namespace, secretName string, secretData []byte) (*apiv1.Secret, error) {
@@ -699,16 +703,42 @@ func (qp *QliksensePreflight) createPreflightTestSecret(clientset *kubernetes.Cl
return secret, nil
}
func (qp *QliksensePreflight) deleteK8sSecret(clientset *kubernetes.Clientset, namespace string, k8sSecret *apiv1.Secret) {
func (qp *QliksensePreflight) deleteK8sSecret(clientset *kubernetes.Clientset, namespace string, secretName string) error {
secretClient := clientset.CoreV1().Secrets(namespace)
deletePolicy := v1.DeletePropagationForeground
deleteOptions := v1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}
err := secretClient.Delete(k8sSecret.GetName(), &deleteOptions)
err := secretClient.Delete(secretName, &deleteOptions)
if err != nil {
log.Fatal(err)
return err
}
qp.P.LogVerboseMessage("Deleted Secret: %s\n", k8sSecret.Name)
qp.P.LogVerboseMessage("Deleted Secret: %s\n", secretName)
return nil
}
func (qp *QliksensePreflight) Cleanup(namespace string, kubeConfigContents []byte) error {
qp.P.LogVerboseMessage("Preflight clean\n")
qp.P.LogVerboseMessage("----------------\n")
qp.P.LogVerboseMessage("Removing deployment...\n")
qp.CheckDeployment(namespace, kubeConfigContents, true)
qp.P.LogVerboseMessage("Removing service...\n")
qp.CheckService(namespace, kubeConfigContents, true)
qp.P.LogVerboseMessage("Removing pod...\n")
qp.CheckPod(namespace, kubeConfigContents, true)
qp.P.LogVerboseMessage("Removing role...\n")
qp.CheckCreateRole(namespace, true)
qp.P.LogVerboseMessage("Removing rolebinding...\n")
qp.CheckCreateRoleBinding(namespace, true)
qp.P.LogVerboseMessage("Removing serviceaccount...\n")
qp.CheckCreateServiceAccount(namespace, true)
qp.P.LogVerboseMessage("Removing DNS check components...\n")
qp.CheckDns(namespace, kubeConfigContents, true)
qp.P.LogVerboseMessage("Removing mongo check components...\n")
qp.CheckMongo(kubeConfigContents, namespace, &PreflightOptions{MongoOptions: &MongoOptions{}}, true)
return nil
}

View File

@@ -11,48 +11,59 @@ import (
"github.com/qlik-oss/sense-installer/pkg/qliksense"
)
var resultYamlBytes = []byte("")
func (qp *QliksensePreflight) CheckCreateRole(namespace string) error {
func (qp *QliksensePreflight) CheckCreateRole(namespace string, cleanup bool) error {
// create a Role
qp.P.LogVerboseMessage("Preflight role check: \n")
qp.P.LogVerboseMessage("--------------------- \n")
err := qp.checkCreateEntity(namespace, "Role")
if !cleanup {
qp.P.LogVerboseMessage("Preflight role check: \n")
qp.P.LogVerboseMessage("--------------------- \n")
}
err := qp.checkCreateEntity(namespace, "Role", cleanup)
if err != nil {
return err
}
qp.P.LogVerboseMessage("Completed preflight role check\n")
if !cleanup {
qp.P.LogVerboseMessage("Completed preflight role check\n")
}
return nil
}
func (qp *QliksensePreflight) CheckCreateRoleBinding(namespace string) error {
func (qp *QliksensePreflight) CheckCreateRoleBinding(namespace string, cleanup bool) error {
// create a RoleBinding
qp.P.LogVerboseMessage("Preflight rolebinding check: \n")
qp.P.LogVerboseMessage("---------------------------- \n")
err := qp.checkCreateEntity(namespace, "RoleBinding")
if !cleanup {
qp.P.LogVerboseMessage("Preflight rolebinding check: \n")
qp.P.LogVerboseMessage("---------------------------- \n")
}
err := qp.checkCreateEntity(namespace, "RoleBinding", cleanup)
if err != nil {
return err
}
qp.P.LogVerboseMessage("Completed preflight rolebinding check\n")
if !cleanup {
qp.P.LogVerboseMessage("Completed preflight rolebinding check\n")
}
return nil
}
func (qp *QliksensePreflight) CheckCreateServiceAccount(namespace string) error {
func (qp *QliksensePreflight) CheckCreateServiceAccount(namespace string, cleanup bool) error {
// create a service account
qp.P.LogVerboseMessage("Preflight serviceaccount check: \n")
qp.P.LogVerboseMessage("------------------------------- \n")
err := qp.checkCreateEntity(namespace, "ServiceAccount")
if !cleanup {
qp.P.LogVerboseMessage("Preflight serviceaccount check: \n")
qp.P.LogVerboseMessage("------------------------------- \n")
}
err := qp.checkCreateEntity(namespace, "ServiceAccount", cleanup)
if err != nil {
return err
}
qp.P.LogVerboseMessage("Completed preflight serviceaccount check\n")
if !cleanup {
qp.P.LogVerboseMessage("Completed preflight serviceaccount check\n")
}
return nil
}
func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string) error {
func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string, cleanup bool) error {
qConfig := qapi.NewQConfig(qp.Q.QliksenseHome)
var currentCR *qapi.QliksenseCR
mfroot := ""
kusDir := ""
resultYamlBytes := []byte("")
var err error
currentCR, err = qConfig.GetCurrentCR()
if err != nil {
@@ -89,6 +100,12 @@ func (qp *QliksensePreflight) checkCreateEntity(namespace, entityToTest string)
}
namespace = "" // namespace is handled when generating the manifests
// check if entity already exists in the cluster, if so - delete it
api.KubectlDeleteVerbose(sa, namespace, qp.P.Verbose)
if cleanup {
return nil
}
defer func() {
qp.P.LogVerboseMessage("Cleaning up resources...\n")
err := api.KubectlDeleteVerbose(sa, namespace, qp.P.Verbose)
@@ -113,7 +130,7 @@ func (qp *QliksensePreflight) CheckCreateRB(namespace string, kubeConfigContents
qp.P.LogVerboseMessage("Preflight createRole check: \n")
qp.P.LogVerboseMessage("--------------------------- \n")
errStr := strings.Builder{}
err1 := qp.checkCreateEntity(namespace, "Role")
err1 := qp.checkCreateEntity(namespace, "Role", false)
if err1 != nil {
errStr.WriteString(err1.Error())
errStr.WriteString("\n")
@@ -125,7 +142,7 @@ func (qp *QliksensePreflight) CheckCreateRB(namespace string, kubeConfigContents
// create a roleBinding
qp.P.LogVerboseMessage("Preflight rolebinding check: \n")
qp.P.LogVerboseMessage("---------------------------- \n")
err2 := qp.checkCreateEntity(namespace, "RoleBinding")
err2 := qp.checkCreateEntity(namespace, "RoleBinding", false)
if err2 != nil {
errStr.WriteString(err2.Error())
errStr.WriteString("\n")
@@ -137,7 +154,7 @@ func (qp *QliksensePreflight) CheckCreateRB(namespace string, kubeConfigContents
// create a service account
qp.P.LogVerboseMessage("Preflight serviceaccount check: \n")
qp.P.LogVerboseMessage("------------------------------- \n")
err3 := qp.checkCreateEntity(namespace, "ServiceAccount")
err3 := qp.checkCreateEntity(namespace, "ServiceAccount", false)
if err3 != nil {
errStr.WriteString(err3.Error())
errStr.WriteString("\n")

View File

@@ -18,12 +18,12 @@ import (
b64 "encoding/base64"
. "github.com/logrusorgru/aurora"
ansi "github.com/mattn/go-colorable"
"github.com/qlik-oss/sense-installer/pkg/api"
_ "gopkg.in/yaml.v2"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
. "github.com/logrusorgru/aurora"
)
const (
@@ -62,7 +62,7 @@ func (q *Qliksense) SetSecrets(args []string, isSecretSet bool, base64Encoded bo
}
// Metadata name in qliksense CR is the name of the current context
api.LogDebugMessage("Current context: %s", qliksenseCR.GetName())
api.LogDebugMessage("Current context: %s\n", qliksenseCR.GetName())
encryptionKey, err := qConfig.GetEncryptionKeyForCurrent()
if err != nil {
return err
@@ -72,7 +72,7 @@ func (q *Qliksense) SetSecrets(args []string, isSecretSet bool, base64Encoded bo
return err
}
for _, ra := range resultArgs {
api.LogDebugMessage("value args to be encrypted: %s", ra.Value)
api.LogDebugMessage("value args to be encrypted: %s\n", ra.Value)
if err := q.processSecret(ra, encryptionKey, qliksenseCR, isSecretSet); err != nil {
return err
}
@@ -411,7 +411,7 @@ func (q *Qliksense) DeleteContextConfig(args []string, flag bool) error {
out := ansi.NewColorableStdout()
switch args[0] {
case qliksenseConfig.Spec.CurrentContext:
fmt.Fprintln(out,Yellow("Please switch contexts to be able to delete this context."))
fmt.Fprintln(out, Yellow("Please switch contexts to be able to delete this context."))
err := fmt.Errorf(Red("Cannot delete current context - %s").String(), White(Bold(qliksenseConfig.Spec.CurrentContext)))
return err
case DefaultQliksenseContext:
@@ -452,7 +452,7 @@ func (q *Qliksense) DeleteContextConfig(args []string, flag bool) error {
if ans == true {
api.WriteToFile(&qliksenseConfig, qliksenseConfigFile)
fmt.Fprintln(out, Yellow(Underline("Warning: Active resources may still be running in-cluster")))
fmt.Fprintln(out, Green("Successfully deleted context: "),Bold(args[0]))
fmt.Fprintln(out, Green("Successfully deleted context: "), Bold(args[0]))
} else {
return nil
}

View File

@@ -53,9 +53,7 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions, kee
}
qConfig.WriteCurrentContextCR(qcr)
//if the docker pull secret exists on disk, install it in the cluster
//if it doesn't exist on disk, remove it in the cluster
if err := installOrRemoveImagePullSecret(qConfig); err != nil {
if err := applyImagePullSecret(qConfig); err != nil {
return err
}
@@ -132,22 +130,13 @@ func (q *Qliksense) getProcessedOperatorControllerString(qcr *qapi.QliksenseCR)
return operatorControllerString, nil
}
func installOrRemoveImagePullSecret(qConfig *qapi.QliksenseConfig) error {
func applyImagePullSecret(qConfig *qapi.QliksenseConfig) error {
if pullDockerConfigJsonSecret, err := qConfig.GetPullDockerConfigJsonSecret(); err == nil {
if dockerConfigJsonSecretYaml, err := pullDockerConfigJsonSecret.ToYaml(""); err != nil {
return err
} else if err := qapi.KubectlApply(string(dockerConfigJsonSecretYaml), ""); err != nil {
return err
}
} else {
deleteDockerConfigJsonSecret := qapi.DockerConfigJsonSecret{
Name: pullSecretName,
}
if deleteDockerConfigJsonSecretYaml, err := deleteDockerConfigJsonSecret.ToYaml(""); err != nil {
return err
} else if err := qapi.KubectlDelete(string(deleteDockerConfigJsonSecretYaml), ""); err != nil {
qapi.LogDebugMessage("failed deleting %v, error: %v\n", pullSecretName, err)
}
}
return nil
}

View File

@@ -22,7 +22,7 @@ import (
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
"gopkg.in/yaml.v3"
"gopkg.in/yaml.v2"
"github.com/Shopify/ejson"
"github.com/qlik-oss/k-apis/pkg/config"
@@ -310,8 +310,8 @@ func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
}
break
}
if resource["kind"].(string) == "Secret" && strings.Contains(resource["metadata"].(map[string]interface{})["name"].(string), "users-secrets-") {
keyIdBase64 = resource["data"].(map[string]interface{})["tokenAuthPrivateKeyId"].(string)
if resource["kind"].(string) == "Secret" && strings.Contains(resource["metadata"].(map[interface {}]interface {})["name"].(string), "users-secrets-") {
keyIdBase64 = resource["data"].(map[interface {}]interface {})["tokenAuthPrivateKeyId"].(string)
break
}
}