Compare commits

..

15 Commits

Author SHA1 Message Date
Boris Kuschel
11822db2cb Merge pull request #82 from qlik-oss/fix_Makefile
Make makefile portable
2020-02-08 08:16:30 -05:00
Boris Kuschel
58b027f361 Merge pull request #83 from qlik-oss/portable_paths
Make file paths portable
2020-02-08 08:16:03 -05:00
Boris Kuschel
25fb2c2407 Make file paths portable
Signed-off-by: Boris Kuschel <boris.kuschel@qlik.com>
2020-02-08 08:13:35 -05:00
Boris Kuschel
05b90314e4 Make makefile portable:
Signed-off-by: Boris Kuschel <boris.kuschel@qlik.com>
2020-02-08 07:40:29 -05:00
Andriy Bulynko
5825ba127b Storing/referencing ejson keys at path: ${qliksenseHome}/ejson/keys (#77) 2020-02-07 21:19:28 -05:00
Foysal Iqbal
156f21fab2 Fix default install (#79) 2020-02-07 16:05:11 -05:00
Ashwathi Shiva
835235a109 Add support for rotateKeys and included releaseName as part of CRSpec (#78)
* added support for rotateKeys, and included releaseName as part of CRSpec
2020-02-07 15:05:11 -05:00
Ashwathi Shiva
53127d00d8 Imperative config through cli (#75)
* fixed a regexp bug and another one where qliksense-context was set as default context every time
* modified file creation permissions
2020-02-07 11:17:23 -05:00
Ashwathi Shiva
4415c8e02b Imperative config through cli (#70)
* minor fix
2020-02-06 14:56:20 -05:00
Ashwathi Shiva
c70e123878 Imperative config through cli (#69)
* removed unnecessary check
2020-02-06 13:41:27 -05:00
Ashwathi Shiva
3595d70b7c Imperative config through cli (#66)
qliksense config commands implemented
2020-02-06 00:39:19 -05:00
Foysal Iqbal
cb2001996c Install flags (#68) 2020-02-05 11:59:48 -05:00
Andriy Bulynko
867106afd3 Setting kubeConfigPath based on homedir.Dir() for consistency (#65) 2020-02-04 17:13:16 -05:00
Foysal Iqbal
6c345c9164 Fetch install command (#62) 2020-02-04 16:50:13 -05:00
Andriy Bulynko
ee0a670018 Setting kubeConfigPath based on os.UserHomeDir() (#64) 2020-02-04 15:26:53 -05:00
17 changed files with 748 additions and 55 deletions

4
.gitignore vendored
View File

@@ -1,2 +1,6 @@
bin
.vscode
cmd/qliksense/__debug_bin
pkg/qliksense/crds
pkg/qliksense/packrd
pkg/qliksense/qliksense-packr.go

View File

@@ -56,7 +56,7 @@ $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT):
generate: get-crds packr2
go generate ./...
HAS_PACKR2 := $(shell command -v packr2)
HAS_PACKR2 := $(shell packr2)
packr2:
ifndef HAS_PACKR2
go get -u github.com/gobuffalo/packr/v2/packr2
@@ -69,11 +69,12 @@ clean: clean-packr
clean-packr: packr2
cd pkg/qliksense && packr2 clean
get-crds:
git clone git@github.com:qlik-oss/qliksense-operator.git -b custom_cr /tmp/operator
get-crds:
$(eval TMP := $(shell mktemp -d))
git clone git@github.com:qlik-oss/qliksense-operator.git -b ms-3 $(TMP)/operator
mkdir -p pkg/qliksense/crds/cr
mkdir -p pkg/qliksense/crds/crd
mkdir -p pkg/qliksense/crds/crd-deploy
cp /tmp/operator/deploy/*.yaml pkg/qliksense/crds/crd-deploy
cp /tmp/operator/deploy/crds/*_crd.yaml pkg/qliksense/crds/crd
cp /tmp/operator/deploy/crds/*_cr.yaml pkg/qliksense/crds/cr
cp $(TMP)/operator/deploy/*.yaml pkg/qliksense/crds/crd-deploy
cp $(TMP)/operator/deploy/crds/*_crd.yaml pkg/qliksense/crds/crd
cp $(TMP)/operator/deploy/crds/*_cr.yaml pkg/qliksense/crds/cr

72
cmd/qliksense/context.go Normal file
View File

@@ -0,0 +1,72 @@
package main
import (
"github.com/qlik-oss/sense-installer/pkg/qliksense"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func setContextConfigCmd(q *qliksense.Qliksense) *cobra.Command {
var (
cmd *cobra.Command
)
cmd = &cobra.Command{
Use: "set-context",
Short: "Sets the context in which the Kubernetes cluster and resources live in",
Example: `qliksense config set-context <context_name>`,
RunE: func(cmd *cobra.Command, args []string) error {
log.Debug("In set Context Config Command")
return qliksense.SetContextConfig(q, args)
},
}
return cmd
}
func setOtherConfigsCmd(q *qliksense.Qliksense) *cobra.Command {
var (
cmd *cobra.Command
)
cmd = &cobra.Command{
Use: "set",
Short: "configure a key value pair into the current context",
Example: `qliksense config set <key>=<value>`,
RunE: func(cmd *cobra.Command, args []string) error {
return qliksense.SetOtherConfigs(q, args)
},
}
return cmd
}
func setConfigsCmd(q *qliksense.Qliksense) *cobra.Command {
var (
cmd *cobra.Command
)
cmd = &cobra.Command{
Use: "set-configs",
Short: "set configurations into the qliksense context",
Example: `qliksense config set-configs <key>=<value>`,
RunE: func(cmd *cobra.Command, args []string) error {
return qliksense.SetConfigs(q, args)
},
}
return cmd
}
func setSecretsCmd(q *qliksense.Qliksense) *cobra.Command {
var (
cmd *cobra.Command
)
cmd = &cobra.Command{
Use: "set-secrets",
Short: "set secrets configurations into the qliksense context",
Example: `qliksense config set-secrets <key>=<value> --secret`,
RunE: func(cmd *cobra.Command, args []string) error {
return qliksense.SetSecrets(q, args)
},
}
return cmd
}

View File

@@ -2,7 +2,6 @@ package main
import (
"errors"
"github.com/Masterminds/semver/v3"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
@@ -17,13 +16,10 @@ func fetchCmd(q *qliksense.Qliksense) *cobra.Command {
if len(args) != 1 {
return errors.New("requires a version (i.e. v1.0.0)")
}
if _, err := semver.NewVersion(args[0]); err != nil {
return errors.New("is it not a valid version. should be something like this v1.0.0")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
q.FetchQK8s(args[0])
RunE: func(cmd *cobra.Command, args []string) error {
return q.FetchQK8s(args[0])
},
}
return c

View File

@@ -2,12 +2,12 @@ package main
import (
"errors"
"github.com/Masterminds/semver/v3"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
func installCmd(q *qliksense.Qliksense) *cobra.Command {
opts := &qliksense.InstallCommandOptions{}
c := &cobra.Command{
Use: "install",
Short: "install a qliksense release",
@@ -17,14 +17,18 @@ func installCmd(q *qliksense.Qliksense) *cobra.Command {
if len(args) != 1 {
return errors.New("requires a version (i.e. v1.0.0)")
}
if _, err := semver.NewVersion(args[0]); err != nil {
return errors.New("is it not a valid version. should be something like this v1.0.0")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return q.InstallQK8s(args[0])
return q.InstallQK8s(args[0], opts)
},
}
f := c.Flags()
f.StringVarP(&opts.AcceptEULA, "acceptEULA", "a", "", "AcceptEULA for qliksense")
f.StringVarP(&opts.Namespace, "namespace", "n", "", "Namespace where to install the qliksense")
f.StringVarP(&opts.StorageClass, "storageClass", "s", "", "Storage class for qliksense")
f.StringVarP(&opts.MongoDbUri, "mongoDbUri", "m", "", "mongoDbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
f.StringVarP(&opts.RotateKeys, "rotateKeys", "r", "", "Rotate JWT keys for qliksense (yes:rotate keys/ no:use exising keys from cluster/ None: use default EJSON_KEY from env")
return c
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/mitchellh/go-homedir"
"github.com/qlik-oss/sense-installer/pkg"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@@ -36,7 +37,13 @@ func initAndExecute() error {
log.Fatal(err)
}
if err = rootCmd(qliksense.New(qlikSenseHome)).Execute(); err != nil {
// create dirs and appropriate files for setting up contexts
qliksense.LogDebugMessage("QliksenseHomeDir: %s", qlikSenseHome)
qliksense.SetUpQliksenseDefaultContext(qlikSenseHome)
if qliksenseClient, err := qliksense.New(qlikSenseHome); err != nil {
return err
} else if err := rootCmd(qliksenseClient).Execute(); err != nil {
return err
}
@@ -58,7 +65,11 @@ func setUpPaths() (string, error) {
}
qlikSenseHome = filepath.Join(homeDir, qlikSenseDirVar)
}
os.Mkdir(qlikSenseHome, os.ModePerm)
if err := os.MkdirAll(qlikSenseHome, os.ModePerm); err != nil {
return "", err
}
return qlikSenseHome, nil
}
@@ -112,6 +123,18 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
// add the set-context config command as a sub-command to the app config command
configCmd.AddCommand(setContextConfigCmd(p))
// add the set profile/namespace/storageClassName/git-repository config command as a sub-command to the app config command
configCmd.AddCommand(setOtherConfigsCmd(p))
// add the set ### config command as a sub-command to the app config sub-command
configCmd.AddCommand(setConfigsCmd(p))
// add the set ### config command as a sub-command to the app config sub-command
configCmd.AddCommand(setSecretsCmd(p))
return cmd
}

7
go.mod
View File

@@ -16,13 +16,14 @@ replace (
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8
k8s.io/client-go => k8s.io/client-go v0.0.0-20191016111102-bec269661e48
k8s.io/kubectl => k8s.io/kubectl v0.0.0-20191016120415-2ed914427d51
sigs.k8s.io/kustomize/api => github.com/qlik-oss/kustomize/api v0.3.3-0.20200129153315-09eb26c762c8
sigs.k8s.io/kustomize/api => github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9
)
require (
cloud.google.com/go v0.52.0 // indirect
cloud.google.com/go/storage v1.5.0 // indirect
github.com/Masterminds/semver/v3 v3.0.3
github.com/Shopify/ejson v1.2.1
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
github.com/aws/aws-sdk-go v1.28.9 // indirect
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 // indirect
@@ -52,7 +53,8 @@ require (
github.com/mitchellh/go-homedir v1.1.0
github.com/morikuni/aec v1.0.0 // indirect
github.com/pkg/errors v0.8.1
github.com/qlik-oss/k-apis v0.0.2
github.com/qlik-oss/k-apis v0.0.5
github.com/sirupsen/logrus v1.4.2
github.com/spf13/cobra v0.0.5
github.com/spf13/viper v1.6.1
github.com/theupdateframework/notary v0.6.1 // indirect
@@ -66,6 +68,7 @@ require (
gopkg.in/fatih/pool.v2 v2.0.0 // indirect
gopkg.in/gorethink/gorethink.v3 v3.0.5 // indirect
gopkg.in/yaml.v2 v2.2.8
gopkg.in/yaml.v3 v3.0.0-20190924164351-c8b7dadae555
sigs.k8s.io/kustomize/api v0.3.2
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 // indirect
)

14
go.sum
View File

@@ -708,6 +708,7 @@ github.com/lib/pq v0.0.0-20180201184707-88edab080323/go.mod h1:5WUZQaWbwv1U+lTRe
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
@@ -900,12 +901,20 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/qlik-oss/k-apis v0.0.1 h1:XgKSi2Lu/Jsc0XeBRCObw8urt9vVjtgr7INq6kjJpV0=
github.com/qlik-oss/k-apis v0.0.1/go.mod h1:FDHuThas5MNw192Sk2IGy9fv8S06BLiuz2vfskZ162Q=
github.com/qlik-oss/k-apis v0.0.2 h1:7Sdz7528of+34NijkhxSc964FTOl+RV3IMMJxiScmd0=
github.com/qlik-oss/k-apis v0.0.2/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE=
github.com/qlik-oss/k-apis v0.0.3-0.20200204195649-ea2ee5c630f2 h1:OWdcKP73yCchNf02qJ+xYof7WSHDBeaEyiPRsgRMy3Y=
github.com/qlik-oss/k-apis v0.0.3-0.20200204195649-ea2ee5c630f2/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE=
github.com/qlik-oss/k-apis v0.0.3 h1:LJTQik87Rcsl+rphXfyPtxQc9UgQy+XGdT+epQqi+YY=
github.com/qlik-oss/k-apis v0.0.3/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE=
github.com/qlik-oss/k-apis v0.0.4 h1:fNX1LsGbNZtR7X/0o/2HAnQkuEJs6JcnedHzacBcbfM=
github.com/qlik-oss/k-apis v0.0.4/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE=
github.com/qlik-oss/k-apis v0.0.5 h1:CpiujicAo+clZqy7Pe3CDAqNhTx8cCC3qmzz3ovG7OU=
github.com/qlik-oss/k-apis v0.0.5/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200129153315-09eb26c762c8 h1:WLVkArXf58T+3SHDvSddE8P6OjkeeUtGEgHk8LDdfeo=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200129153315-09eb26c762c8/go.mod h1:OCt7FTrRVHj4kmR2xLJJUIqu00BTr6GeF09hSmM17Kw=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9 h1:iqeqTS4zjp6rPEaxmFB7pemA2CMjOEN5dYSXZaQ82uw=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9/go.mod h1:OCt7FTrRVHj4kmR2xLJJUIqu00BTr6GeF09hSmM17Kw=
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
@@ -940,6 +949,7 @@ github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lz
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=

View File

@@ -3,11 +3,12 @@ package api
import (
"errors"
"fmt"
"github.com/jinzhu/copier"
"gopkg.in/yaml.v2"
"io/ioutil"
"os"
"path/filepath"
"github.com/jinzhu/copier"
"gopkg.in/yaml.v2"
)
// NewQConfig create QliksenseConfig object from file ~/.qliksense/config.yaml
@@ -24,19 +25,14 @@ func NewQConfig(qsHome string) *QliksenseConfig {
fmt.Println("yaml unmarshalling error ", err)
os.Exit(1)
}
qc.QliksenseHomePath = qsHome
return qc
}
// GetCR create a QliksenseCR object for a particular context
// from file ~/.qliksense/contexts/<contx-name>/<contx-name>.yaml
func (qc *QliksenseConfig) GetCR(contextName string) (*QliksenseCR, error) {
crFilePath := ""
for _, ctx := range qc.Spec.Contexts {
if ctx.Name == contextName {
crFilePath = ctx.CrFile
break
}
}
crFilePath := qc.getCRFilePath(contextName)
if crFilePath == "" {
return nil, errors.New("context name " + contextName + " not found")
}
@@ -81,3 +77,65 @@ func getCRObject(crfile string) (*QliksenseCR, error) {
}
return cr, nil
}
func (qc *QliksenseConfig) getCRFilePath(contextName string) string {
crFilePath := ""
for _, ctx := range qc.Spec.Contexts {
if ctx.Name == contextName {
crFilePath = ctx.CrFile
break
}
}
return crFilePath
}
func (qc *QliksenseConfig) IsRepoExist(contextName, version string) bool {
if _, err := os.Lstat(qc.BuildRepoPathForContext(contextName, version)); err != nil {
return false
}
return true
}
func (qc *QliksenseConfig) IsRepoExistForCurrent(version string) bool {
if _, err := os.Lstat(qc.BuildRepoPath(version)); err != nil {
return false
}
return true
}
func (qc *QliksenseConfig) BuildRepoPath(version string) string {
return qc.BuildRepoPathForContext(qc.Spec.CurrentContext, version)
}
func (qc *QliksenseConfig) BuildRepoPathForContext(contextName, version string) string {
return filepath.Join(qc.QliksenseHomePath, "contexts", contextName, "qlik-k8s", version)
}
func (qc *QliksenseConfig) BuildCurrentManifestsRoot(version string) string {
return filepath.Join(qc.BuildRepoPath(version), "manifests")
}
func (qc *QliksenseConfig) WriteCR(cr *QliksenseCR, contextName string) error {
crf := qc.getCRFilePath(contextName)
if crf == "" {
return errors.New("context name " + contextName + " not found")
}
by, err := yaml.Marshal(cr)
if err != nil {
fmt.Println("cannot marshal cr ", err)
return err
}
ioutil.WriteFile(crf, by, 0644)
return nil
}
func (qc *QliksenseConfig) WriteCurrentContextCR(cr *QliksenseCR) error {
return qc.WriteCR(cr, qc.Spec.CurrentContext)
}
func (cr *QliksenseCR) AddLabelToCr(key, value string) error {
if cr.Metadata.Labels == nil {
cr.Metadata.Labels = make(map[string]string)
}
cr.Metadata.Labels[key] = value
return nil
}

View File

@@ -11,8 +11,9 @@ type CommonConfig struct {
// QliksenseConfig is exported
type QliksenseConfig struct {
CommonConfig `json:",inline" yaml:",inline"`
Spec *ContextSpec `json:"spec" yaml:"spec"`
CommonConfig `json:",inline" yaml:",inline"`
Spec *ContextSpec `json:"spec" yaml:"spec"`
QliksenseHomePath string `json:"-" yaml:"-"`
}
// QliksenseCR is exported

View File

@@ -2,8 +2,12 @@ package qliksense
import (
"fmt"
"os"
"path"
"path/filepath"
"github.com/mitchellh/go-homedir"
"github.com/qlik-oss/k-apis/pkg/cr"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
"gopkg.in/yaml.v2"
@@ -22,14 +26,24 @@ func (q *Qliksense) ConfigApplyQK8s() error {
fmt.Println("cannot get the current-context cr", err)
return err
}
return applyConfigToK8s(qcr)
return q.applyConfigToK8s(qcr)
}
func applyConfigToK8s(qcr *qapi.QliksenseCR) error {
func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error {
// apply qliksense-init crd first
mroot := qcr.Spec.GetManifestsRoot()
qInitMsPath := filepath.Join(mroot, Q_INIT_CRD_PATH)
if err := os.Unsetenv("EJSON_KEY"); err != nil {
fmt.Printf("error unsetting EJSON_KEY environment variable: %v\n", err)
return err
}
if err := os.Setenv("EJSON_KEYDIR", q.QliksenseEjsonKeyDir); err != nil {
fmt.Printf("error setting EJSON_KEYDIR environment variable: %v\n", err)
return err
}
qInitByte, err := executeKustomizeBuild(qInitMsPath)
if err != nil {
fmt.Println("cannot generate crds for qliksense-init", err)
@@ -39,8 +53,14 @@ func applyConfigToK8s(qcr *qapi.QliksenseCR) error {
return err
}
userHomeDir, err := homedir.Dir()
if err != nil {
fmt.Printf(`error fetching user's home directory: %v\n`, err)
return err
}
// generate patches
cr.GeneratePatches(qcr.Spec)
cr.GeneratePatches(qcr.Spec, path.Join(userHomeDir, ".kube", "config"))
// apply generated manifests
profilePath := filepath.Join(qcr.Spec.ManifestsRoot, qcr.Spec.Profile)
mByte, err := executeKustomizeBuild(profilePath)

View File

@@ -0,0 +1,366 @@
package qliksense
import (
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/qlik-oss/k-apis/pkg/config"
"github.com/qlik-oss/sense-installer/pkg/api"
yaml "gopkg.in/yaml.v2"
)
const (
// Below are some constants to support qliksense context setup
QliksenseConfigHome = "/.qliksense"
QliksenseConfigContextHome = "/.qliksense/contexts"
QliksenseConfigApiVersion = "config.qlik.com/v1"
QliksenseConfigKind = "QliksenseConfig"
QliksenseMetadataName = "QliksenseConfigMetadata"
QliksenseContextApiVersion = "qlik.com/v1"
QliksenseContextKind = "Qliksense"
QliksenseDefaultProfile = "docker-desktop"
QliksenseConfigFile = "config.yaml"
QliksenseContextsDir = "contexts"
DefaultQliksenseContext = "qlik-default"
DefaultRotateKeys = "yes"
MaxContextNameLength = 17
)
// WriteToFile writes content into specified file
func WriteToFile(content interface{}, targetFile string) {
if content == nil || targetFile == "" {
return
}
file, err := os.OpenFile(targetFile, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
LogDebugMessage("There was an error creating the file: %s, %v", targetFile, err)
log.Fatal(err)
}
defer file.Close()
x, err := yaml.Marshal(content)
if err != nil {
log.Fatalf("An error occurred during marshalling CR: %v", err)
}
LogDebugMessage("Marshalled yaml:\n%s\nWriting to file...", x)
// truncating the file before we write new content
file.Truncate(0)
file.Seek(0, 0)
_, err = file.Write(x)
if err != nil {
log.Fatal(err)
}
LogDebugMessage("Wrote content into %s", targetFile)
}
// ReadFromFile reads content from specified sourcefile
func ReadFromFile(content interface{}, sourceFile string) {
if content == nil || sourceFile == "" {
return
}
contents, err := ioutil.ReadFile(sourceFile)
if err != nil {
LogDebugMessage("There was an error reading from file: %s, %v", sourceFile, err)
log.Fatal(err)
}
if err := yaml.Unmarshal(contents, content); err != nil {
log.Fatalf("An error occurred during unmarshalling: %v", err)
}
}
// AddCommonConfig adds common configs into CRs
func AddCommonConfig(qliksenseCR api.QliksenseCR, contextName string) api.QliksenseCR {
qliksenseCR.ApiVersion = QliksenseContextApiVersion
qliksenseCR.Kind = QliksenseContextKind
if qliksenseCR.Metadata == nil {
qliksenseCR.Metadata = &api.Metadata{}
}
if qliksenseCR.Metadata.Name == "" {
qliksenseCR.Metadata.Name = contextName
}
qliksenseCR.Spec = &config.CRSpec{}
qliksenseCR.Spec.Profile = QliksenseDefaultProfile
qliksenseCR.Spec.ReleaseName = contextName
qliksenseCR.Spec.RotateKeys = DefaultRotateKeys
return qliksenseCR
}
// AddBaseQliksenseConfigs adds configs into config.yaml
func AddBaseQliksenseConfigs(qliksenseConfig api.QliksenseConfig, defaultQliksenseContext string) api.QliksenseConfig {
qliksenseConfig.ApiVersion = QliksenseConfigApiVersion
qliksenseConfig.Kind = QliksenseConfigKind
if qliksenseConfig.Metadata == nil {
qliksenseConfig.Metadata = &api.Metadata{}
}
qliksenseConfig.Metadata.Name = QliksenseMetadataName
if defaultQliksenseContext != "" {
if qliksenseConfig.Spec == nil {
qliksenseConfig.Spec = &api.ContextSpec{}
}
qliksenseConfig.Spec.CurrentContext = defaultQliksenseContext
}
return qliksenseConfig
}
func checkExists(filename string, isFile bool) os.FileInfo {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
if isFile {
LogDebugMessage("File does not exist")
} else {
LogDebugMessage("Dir does not exist")
}
return nil
}
LogDebugMessage("File exists")
return info
}
// FileExists checks if a file exists
func FileExists(filename string) bool {
if fe := checkExists(filename, true); fe != nil && !fe.IsDir() {
return true
}
return false
}
// DirExists checks if a directory exists
func DirExists(dirname string) bool {
if fe := checkExists(dirname, false); fe != nil && fe.IsDir() {
return true
}
return false
}
// LogDebugMessage logs a debug message
func LogDebugMessage(strMessage string, args ...interface{}) {
if os.Getenv("QLIKSENSE_DEBUG") == "true" {
log.Printf(strMessage, args...)
}
}
// SetSecrets - set-secrets <key>=<value> commands
func SetSecrets(q *Qliksense, args []string) error {
// retieve current context from config.yaml
qliksenseCR, qliksenseContextsFile := retrieveCurrentContextInfo(q)
processConfigArgs(args, qliksenseCR.Spec, qliksenseCR.Spec.AddToSecrets)
LogDebugMessage("CR now: %v", qliksenseCR.Spec)
// write modified content into context.yaml
WriteToFile(&qliksenseCR, qliksenseContextsFile)
return nil
}
// SetConfigs - set-configs <key>=<value> commands
func SetConfigs(q *Qliksense, args []string) error {
// retieve current context from config.yaml
qliksenseCR, qliksenseContextsFile := retrieveCurrentContextInfo(q)
processConfigArgs(args, qliksenseCR.Spec, qliksenseCR.Spec.AddToConfigs)
LogDebugMessage("CR now: %v", qliksenseCR.Spec)
// write modified content into context.yaml
WriteToFile(&qliksenseCR, qliksenseContextsFile)
return nil
}
func retrieveCurrentContextInfo(q *Qliksense) (api.QliksenseCR, string) {
var qliksenseConfig api.QliksenseConfig
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
LogDebugMessage("qliksenseConfigFile: %s", qliksenseConfigFile)
ReadFromFile(&qliksenseConfig, qliksenseConfigFile)
currentContext := qliksenseConfig.Spec.CurrentContext
LogDebugMessage("Current-context from config.yaml: %s", currentContext)
if currentContext == "" {
// current-context is empty
log.Fatal(`Please run the "qliksense config set-context <context-name>" first before viewing the current context info`)
}
// read the context.yaml file
var qliksenseCR api.QliksenseCR
if currentContext == "" {
// current-context is empty
log.Fatal(`Please run the "qliksense config set-context <context-name>" first before viewing the current context info`)
}
qliksenseContextsFile := filepath.Join(q.QliksenseHome, QliksenseContextsDir, currentContext, currentContext+".yaml")
if !FileExists(qliksenseContextsFile) {
log.Fatalf("Context file does not exist.\nPlease try re-running `qliksense config set-context <context-name>` and then `qliksense config view` again")
}
ReadFromFile(&qliksenseCR, qliksenseContextsFile)
LogDebugMessage("Read QliksenseCR: %v", qliksenseCR)
LogDebugMessage("Read context file: %s", qliksenseContextsFile)
return qliksenseCR, qliksenseContextsFile
}
func processConfigArgs(args []string, cr *config.CRSpec, updateFn func(string, string, string)) {
// prepare received args
// split args[0] into key and value
if len(args) == 0 {
log.Fatalf("No args were provided. Please provide args to configure the current context")
}
re1 := regexp.MustCompile(`(\w{1,})\[name=(\w{1,})\]=("*[\w\-_/:0-9]+"*)`)
for _, arg := range args {
result := re1.FindStringSubmatch(arg)
LogDebugMessage("finding matches...\n")
LogDebugMessage("Results: %s, %s, %s", result[1], result[2], result[3])
// check if result array's length is == 4 (index 0 - is the full match & indices 1,2,3- are the fields we need)
if len(result) != 4 {
log.Fatal("Please provide valid args for this command")
}
updateFn(result[1], result[2], result[3])
}
}
// SetOtherConfigs - set profile/namespace/storageclassname/git.repository commands
func SetOtherConfigs(q *Qliksense, args []string) error {
// retieve current context from config.yaml
qliksenseCR, qliksenseContextsFile := retrieveCurrentContextInfo(q)
// modify appropriate fields
LogDebugMessage("Command: %s", args[0])
// split args[0] into key and value
if len(args) > 0 {
argsString := strings.Split(args[0], "=")
LogDebugMessage("Split string: %v", argsString)
switch argsString[0] {
case "profile":
LogDebugMessage("Current profile: %s, Incoming profile: %s", qliksenseCR.Spec.Profile, argsString[1])
qliksenseCR.Spec.Profile = argsString[1]
LogDebugMessage("Current profile after modification: %s ", qliksenseCR.Spec.Profile)
case "namespace":
LogDebugMessage("Current namespace: %s, Incoming namespace: %s", qliksenseCR.Spec.NameSpace, argsString[1])
qliksenseCR.Spec.NameSpace = argsString[1]
LogDebugMessage("Current namespace after modification: %s ", qliksenseCR.Spec.NameSpace)
case "git.repository":
LogDebugMessage("Current git.repository: %s, Incoming git.repository: %s", qliksenseCR.Spec.Git.Repository, argsString[1])
qliksenseCR.Spec.Git.Repository = argsString[1]
LogDebugMessage("Current git repository after modification: %s ", qliksenseCR.Spec.Git.Repository)
case "storageClassName":
LogDebugMessage("Current StorageClassName: %s, Incoming StorageClassName: %s", qliksenseCR.Spec.StorageClassName, argsString[1])
qliksenseCR.Spec.StorageClassName = argsString[1]
LogDebugMessage("Current StorageClassName after modification: %s ", qliksenseCR.Spec.StorageClassName)
case "rotateKeys":
LogDebugMessage("Current rotateKeys: %s, Incoming rotateKeys: %s", qliksenseCR.Spec.RotateKeys, argsString[1])
rotateKeys := validateInput(argsString[1])
qliksenseCR.Spec.RotateKeys = rotateKeys
LogDebugMessage("Current rotateKeys after modification: %s ", qliksenseCR.Spec.RotateKeys)
default:
log.Println("As part of the `qliksense config set` command, please enter one of: profile, namespace, storageClassName,rotateKeys or git.repository arguments")
}
} else {
log.Fatalf("No args were provided. Please provide args to configure the current context")
}
// write modified content into context.yaml
WriteToFile(&qliksenseCR, qliksenseContextsFile)
return nil
}
// SetContextConfig - set the context for qliksense kubernetes resources to live in
func SetContextConfig(q *Qliksense, args []string) error {
if len(args) == 1 {
LogDebugMessage("The command received: %s", args)
SetUpQliksenseContext(q.QliksenseHome, args[0], false)
} else {
log.Fatalf("Please provide a name to configure the context with.")
}
return nil
}
// SetUpQliksenseDefaultContext - to setup dir structure for default qliksense context
func SetUpQliksenseDefaultContext(qlikSenseHome string) {
SetUpQliksenseContext(qlikSenseHome, DefaultQliksenseContext, true)
}
// SetUpQliksenseContext - to setup qliksense context
func SetUpQliksenseContext(qlikSenseHome, contextName string, isDefaultContext bool) {
// check the length of the context name entered by the user, it should not exceed 17 chars
if len(contextName) > MaxContextNameLength {
log.Fatal("Please enter a context-name with utmost 17 characters")
}
qliksenseConfigFile := filepath.Join(qlikSenseHome, QliksenseConfigFile)
var qliksenseConfig api.QliksenseConfig
configFileTrack := false
if !FileExists(qliksenseConfigFile) {
qliksenseConfig = AddBaseQliksenseConfigs(qliksenseConfig, contextName)
} else {
ReadFromFile(&qliksenseConfig, qliksenseConfigFile)
if isDefaultContext { // if config file exits but a default context is requested, we want to prevent writing to config file
configFileTrack = true
}
}
// creating a file in the name of the context if it does not exist/ opening it to append/modify content if it already exists
qliksenseContextsDir1 := filepath.Join(qlikSenseHome, QliksenseContextsDir)
if !DirExists(qliksenseContextsDir1) {
if err := os.Mkdir(qliksenseContextsDir1, os.ModePerm); err != nil {
log.Fatalf("Not able to create %s dir: %v", qliksenseContextsDir1, err)
}
}
LogDebugMessage("%s exists", qliksenseContextsDir1)
// creating contexts/qlik-default.yaml file
qliksenseContextFile := filepath.Join(qliksenseContextsDir1, contextName, contextName+".yaml")
var qliksenseCR api.QliksenseCR
defaultContextsDir := filepath.Join(qliksenseContextsDir1, contextName)
if !DirExists(defaultContextsDir) {
if err := os.Mkdir(defaultContextsDir, os.ModePerm); err != nil {
log.Fatalf("Not able to create %s: %v", defaultContextsDir, err)
}
}
LogDebugMessage("%s exists", defaultContextsDir)
if !FileExists(qliksenseContextFile) {
qliksenseCR = AddCommonConfig(qliksenseCR, contextName)
LogDebugMessage("Added Context: %s", contextName)
} else {
ReadFromFile(&qliksenseCR, qliksenseContextFile)
}
WriteToFile(&qliksenseCR, qliksenseContextFile)
ctxTrack := false
if len(qliksenseConfig.Spec.Contexts) > 0 {
for _, ctx := range qliksenseConfig.Spec.Contexts {
if ctx.Name == contextName {
ctx.CrFile = qliksenseContextFile
ctxTrack = true
break
}
}
}
if !ctxTrack {
qliksenseConfig.Spec.Contexts = append(qliksenseConfig.Spec.Contexts, api.Context{
Name: contextName,
CrFile: qliksenseContextFile,
})
}
qliksenseConfig.Spec.CurrentContext = contextName
if !configFileTrack {
WriteToFile(&qliksenseConfig, qliksenseConfigFile)
}
}
func validateInput(input string) string {
validInputs := []string{"yes", "no", "None"}
isValid := false
for _, elem := range validInputs {
if input == elem {
isValid = true
break
}
}
if !isValid {
log.Fatal("Please enter one of: yes, no or None")
}
return input
}

View File

@@ -2,9 +2,37 @@ package qliksense
import (
"fmt"
kapis_git "github.com/qlik-oss/k-apis/pkg/git"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func (q *Qliksense) FetchQK8s(version string) {
//io.WriteString(os.Stdout, q.GetCRDString())
fmt.Println(version)
const (
QLIK_GIT_REPO = "https://github.com/qlik-oss/qliksense-k8s"
)
func (q *Qliksense) FetchQK8s(version string) error {
qConfig := qapi.NewQConfig(q.QliksenseHome)
return fetchAndUpdateCR(qConfig, version)
}
func fetchAndUpdateCR(qConfig *qapi.QliksenseConfig, version string) error {
qcr, err := qConfig.GetCurrentCR()
if err != nil {
fmt.Println("cannot get the current-context cr", err)
return err
}
if qConfig.IsRepoExistForCurrent(version) {
return nil
}
destDir := qConfig.BuildRepoPath(version)
fmt.Printf("fetching version [%s] from %s\n", version, QLIK_GIT_REPO)
if repo, err := kapis_git.CloneRepository(destDir, QLIK_GIT_REPO, nil); err != nil {
return err
} else if err = kapis_git.Checkout(repo, version, version, nil); err != nil {
return err
}
qcr.Spec.ManifestsRoot = qConfig.BuildCurrentManifestsRoot(version)
qcr.AddLabelToCr("version", version)
return qConfig.WriteCurrentContextCR(qcr)
}

View File

@@ -2,21 +2,28 @@ package qliksense
import (
"fmt"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func (q *Qliksense) InstallQK8s(version string) error {
type InstallCommandOptions struct {
AcceptEULA string
Namespace string
StorageClass string
MongoDbUri string
RotateKeys string
}
func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) error {
// step1: fetch 1.0.0 # pull down qliksense-k8s@1.0.0
// step2: operator view | kubectl apply -f # operator manifest (CRD)
// step3: config apply | kubectl apply -f # generates patches (if required) in configuration directory, applies manifest
// step4: config view | kubectl apply -f # generates Custom Resource manifest (CR)
//io.WriteString(os.Stdout, q.GetCRDString())
//fmt.Println(version)
fmt.Println("Fetching " + version)
//qConfig := qapi.NewQConfig(q.QliksenseHome)
//qcr, err := qConfig.GetCurrentCR()
// fetch the version
qConfig := qapi.NewQConfig(q.QliksenseHome)
fetchAndUpdateCR(qConfig, version)
//TODO: may need to check if CRD already installed, but doing apply does not hurt for now
//install crd into cluster
@@ -27,13 +34,28 @@ func (q *Qliksense) InstallQK8s(version string) error {
}
// install generated manifests into cluster
fmt.Println("Installing generated manifests into cluster")
qConfig := qapi.NewQConfig(q.QliksenseHome)
qcr, err := qConfig.GetCurrentCR()
if err != nil {
fmt.Println("cannot get the current-context cr", err)
return err
}
if err := applyConfigToK8s(qcr); err != nil {
if opts.AcceptEULA != "" {
qcr.Spec.AddToConfigs("qliksense", "acceptEULA", opts.AcceptEULA)
}
if opts.MongoDbUri != "" {
qcr.Spec.AddToSecrets("qliksense", "mongoDbUri", opts.MongoDbUri)
}
if opts.StorageClass != "" {
qcr.Spec.StorageClassName = opts.StorageClass
}
if opts.Namespace != "" {
qcr.Spec.NameSpace = opts.Namespace
}
if opts.RotateKeys != "" {
qcr.Spec.RotateKeys = opts.RotateKeys
}
qConfig.WriteCurrentContextCR(qcr)
if err := q.applyConfigToK8s(qcr); err != nil {
fmt.Println("cannot do kubectl apply on manifests")
return err
}

View File

@@ -1,11 +1,22 @@
package qliksense
import (
"bytes"
"encoding/base64"
"io"
"io/ioutil"
"log"
"os"
"path"
"strings"
"testing"
"gopkg.in/yaml.v3"
"github.com/Shopify/ejson"
"github.com/qlik-oss/k-apis/pkg/config"
"github.com/qlik-oss/k-apis/pkg/qust"
kapis_git "github.com/qlik-oss/k-apis/pkg/git"
)
@@ -47,7 +58,7 @@ metadata:
}
}
func Test_executeKustomizeBuild_onQlikConfig_DISABLED(t *testing.T) {
func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
}
@@ -65,7 +76,70 @@ func Test_executeKustomizeBuild_onQlikConfig_DISABLED(t *testing.T) {
t.Fatalf("unexpected error: %v\n", err)
}
if _, err := executeKustomizeBuild(path.Join(configPath, "manifests", "base")); err != nil {
cr := &config.CRSpec{
ManifestsRoot: configPath,
}
if err := os.Setenv("EJSON_KEYDIR", tmpDir); err != nil {
t.Fatalf("unexpected error setting EJSON_KEYDIR environment variable: %v\n", err)
}
if err := os.Unsetenv("EJSON_KEY"); err != nil {
t.Fatalf("unexpected error unsetting EJSON_KEY: %v\n", err)
}
generateKeys(cr, "won't-use")
yamlResources, err := executeKustomizeBuild(path.Join(configPath, "manifests", "base"))
if err != nil {
t.Fatalf("unexpected kustomize error: %v\n", err)
}
decoder := yaml.NewDecoder(bytes.NewReader(yamlResources))
var resource map[string]interface{}
keyIdBase64 := ""
for {
err := decoder.Decode(&resource)
if err != nil {
if err != io.EOF {
t.Fatalf("unexpected yaml decode error: %v\n", err)
}
break
}
if resource["kind"].(string) == "Secret" && strings.Contains(resource["metadata"].(map[string]interface{})["name"].(string), "-users-secrets-") {
keyIdBase64 = resource["data"].(map[string]interface{})["tokenAuthPrivateKeyId"].(string)
break
}
}
untransformedKeyId := `(( (ds "data").kid ))`
if keyIdBase64 == "" {
t.Fatalf("expected keyIdBase64 for users secret to be non empty:\n")
} else if keyId, err := base64.StdEncoding.DecodeString(keyIdBase64); err != nil {
t.Fatalf("unexpected base64 decode error: %v\n", err)
} else if string(keyId) == untransformedKeyId {
t.Fatalf("unexpected users keyId: %v\n", untransformedKeyId)
}
}
func generateKeys(cr *config.CRSpec, defaultKeyDir string) {
log.Println("rotating all keys")
keyDir := getEjsonKeyDir(defaultKeyDir)
if ejsonPublicKey, ejsonPrivateKey, err := ejson.GenerateKeypair(); err != nil {
log.Printf("error generating an ejson key pair: %v\n", err)
} else if err := qust.GenerateKeys(cr, ejsonPublicKey); err != nil {
log.Printf("error generating application keys: %v\n", err)
} else if err := os.MkdirAll(keyDir, os.ModePerm); err != nil {
log.Printf("error makeing sure private key storage directory: %v exists, error: %v\n", keyDir, err)
} else if err := ioutil.WriteFile(path.Join(keyDir, ejsonPublicKey), []byte(ejsonPrivateKey), os.ModePerm); err != nil {
log.Printf("error storing ejson private key: %v\n", err)
}
}
func getEjsonKeyDir(defaultKeyDir string) string {
ejsonKeyDir := os.Getenv("EJSON_KEYDIR")
if ejsonKeyDir == "" {
ejsonKeyDir = defaultKeyDir
}
return ejsonKeyDir
}

View File

@@ -2,6 +2,7 @@ package qliksense
import (
"fmt"
"path/filepath"
"io"
"os"
"strings"
@@ -32,7 +33,7 @@ func (q *Qliksense) getYamlFromPackrFile(packrFile string) string {
func (q *Qliksense) getFileList(resourceType string) []string {
var resList []string
for _, v := range q.CrdBox.List() {
if strings.Contains(v, resourceType+"/") {
if strings.Contains(v, filepath.Join(resourceType, "")) {
resList = append(resList, []string{v}...)
}
}

View File

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