Compare commits

..

5 Commits

Author SHA1 Message Date
Boris Kuschel
35bd8835df Update README.md 2020-01-20 08:37:20 -05:00
Boris Kuschel
c1799deeef Add issue about kustomize speed 2020-01-20 08:36:11 -05:00
Boris Kuschel
837b88f3c8 Formatting fixups 2020-01-20 08:31:55 -05:00
Boris Kuschel
880f7cb29c Update README.md 2020-01-20 08:28:55 -05:00
Boris Kuschel
c7d93e2652 Add README.md
Signed-off-by: Boris Kuschel <boris.kuschel@qlik.com>
2020-01-20 08:27:19 -05:00
35 changed files with 2448 additions and 2992 deletions

View File

@@ -9,7 +9,6 @@ jobs:
working_directory: /go/src/github.com/qlik-oss/sense-installer
steps:
- checkout
- run: make test
- run: make build
build_release:
docker:
@@ -17,27 +16,45 @@ jobs:
working_directory: /go/src/github.com/qlik-oss/sense-installer
steps:
- checkout
- run: make test
- run: make xbuild-all
- run:
name: "Build latest master from porter repo"
command: |
export GO111MODULE=off
go get -u get.porter.sh/porter || true
cd /go/src/get.porter.sh/porter
# store porter master commit
git rev-parse HEAD > /go/porter-master-commit.txt
make xbuild-all VERSION=latest
cp -r bin/latest/* /go/src/github.com/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/bin/${CIRCLE_TAG}/
- run:
name: "Publish Release on GitHub"
command: |
go get github.com/tcnksm/ghr
ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${CIRCLE_TAG} /go/src/github.com/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/bin/${CIRCLE_TAG}/
# VERSION=v$(./artifacts/qliksense-linux-amd64 version | sed -nre 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*/\1/p')
PORTER_REPO_COMMIT=$(cat /go/porter-master-commit.txt)
ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -b "porter build based on commit: https://github.com/deislabs/porter/commit/${PORTER_REPO_COMMIT}" -delete ${CIRCLE_TAG} /go/src/github.com/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/bin/${CIRCLE_TAG}/
workflows:
version: 2
commit:
jobs:
- build:
filters:
tags:
ignore: /^v.*/
branches:
only: master
build_release:
jobs:
- build_release:
- build:
filters:
tags:
only: /^v.*/
branches:
ignore: /.*/
tags:
only: /v.*/
- build_release:
requires:
- build
filters:
branches:
ignore: /.*/
tags:
only: /v.*/

628
.qliksense/porter-runtime Normal file

File diff suppressed because one or more lines are too long

628
.qliksense/porter.exe Normal file

File diff suppressed because one or more lines are too long

View File

@@ -4,14 +4,13 @@ PKG = github.com/qlik-oss/sense-installer
MAKE_OPTS ?= --no-print-directory
LDFLAGS = -w -X $(PKG)/pkg.Version=$(VERSION) -X $(PKG)/pkg.Commit=$(COMMIT) -X "$(PKG)/pkg.CommitDate=$(COMMIT_DATE)"
XBUILD = CGO_ENABLED=0 go build -a -tags "$(BUILDTAGS)" -ldflags '$(LDFLAGS)'
XBUILD = CGO_ENABLED=0 go build -a -tags netgo -ldflags '$(LDFLAGS)'
BINDIR = bin
COMMIT ?= $(shell git rev-parse --short HEAD)
COMMIT_DATE ?= $(shell git show --no-patch --no-notes --pretty='%cd' $(COMMIT) --date=iso)
VERSION ?= $(shell git describe --tags 2> /dev/null || echo v0)
PERMALINK ?= $(shell git describe --tags --exact-match &> /dev/null && echo latest || echo canary)
BUILDTAGS = netgo containers_image_ostree_stub exclude_graphdriver_devicemapper exclude_graphdriver_btrfs containers_image_openpgp
CLIENT_PLATFORM ?= $(shell go env GOOS)
CLIENT_ARCH ?= $(shell go env GOARCH)
@@ -32,48 +31,18 @@ FILE_EXT=
endif
.PHONY: build
build: clean generate
build:
mkdir -p $(BINDIR)
go build -ldflags '$(LDFLAGS)' -tags "$(BUILDTAGS)" -o $(BINDIR)/$(MIXIN)$(FILE_EXT) ./cmd/$(MIXIN)
$(MAKE) clean
go build -ldflags '$(LDFLAGS)' -o $(BINDIR)/$(MIXIN)$(FILE_EXT) ./cmd/$(MIXIN)
.PHONY: test
test:
go test -short -count=1 -tags "$(BUILDTAGS)" -v ./...
xbuild-all: clean generate
xbuild-all:
$(foreach OS, $(SUPPORTED_PLATFORMS), \
$(foreach ARCH, $(SUPPORTED_ARCHES), \
$(MAKE) $(MAKE_OPTS) CLIENT_PLATFORM=$(OS) CLIENT_ARCH=$(ARCH) MIXIN=$(MIXIN) xbuild; \
))
$(MAKE) clean
xbuild: $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
$(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT):
mkdir -p $(dir $@)
GOOS=$(CLIENT_PLATFORM) GOARCH=$(CLIENT_ARCH) $(XBUILD) -o $@ ./cmd/$(MIXIN)
generate: get-crds packr2
go generate ./...
HAS_PACKR2 := $(shell command -v packr2)
packr2:
ifndef HAS_PACKR2
go get -u github.com/gobuffalo/packr/v2/packr2
endif
clean: clean-packr
-rm -rf /tmp/operator
-rm -fr pkg/qliksense/crds
clean-packr: packr2
cd pkg/qliksense && packr2 clean
get-crds:
git clone git@github.com:qlik-oss/qliksense-operator.git -b custom_cr /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

View File

@@ -6,7 +6,6 @@
- [Getting Started](#getting-started)
- [Requirements](#requirements)
- [Download](#download)
- [Porter CLI](#porter-cli)
- [Generate Credentials from published bundle](#generate-credentials-from-published-bundle)
- [Qlik Sense version and image list](#qliksense-version-and-image-list)
- [Optional: Pulling images in manifest locally, "air gap"](#optional-pulling-images-in-manifest-locally-%22air-gap%22)
@@ -18,11 +17,11 @@
## About
The Qlik Sense installer CLI (sense-installer) provides an imperitive interface to many of the configurations that need to be applied against the declaritive structure described in [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s).
The Qlik Sense installer CLI (sense-installer) provides an imperitive interface to many of the configurations that need to be applied against the declaritive structure described in (https://github.com/qlik-oss/qliksense-k8s)[https://github.com/qlik-oss/qliksense-k8s].
This is a technology preview that uses [porter](https://porter.sh) to execute "actions" (operations) and bundle versions of the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository.
This is a technology preview that uses (porter)[https://porter.sh] to execute "actions" (operations) and bundle versions of the (qliksense-k8s)[https://github.com/qlik-oss/qliksense-k8s] repository.
These bundles are posted to [docker hub](https://hub.docker.com/) at the following location: [qliksense-cnab-bundle](https://hub.docker.com/r/qlik/qliksense-cnab-bundle/tags).
These bundles are posted to (docker hub)[https://hub.docker.com/] at the following location: (https://hub.docker.com/r/qlik/qliksense-cnab-bundle)[https://hub.docker.com/r/qlik/qliksense-cnab-bundle/tags].
For each version of a qliksense sense edge build there should be a corresponding release current posted on docker hub. ex. `qlik/qliksense-cnab-bundle:v1.21.23-edge` for `v1.21.23` edge release of qliksense. The latest version posted will also be labelled as `latest`
@@ -37,83 +36,51 @@ For each version of a qliksense sense edge build there should be a corresponding
### Requirements
- Docker Client connected to a docker engine into which images can built, pulled and pushed.
- Docker Client connected to a docker engine into which images can built, pulled and pushed.
- (Docker Desktop setup tested for these instructions)
### Download
- Download the appropriate executable for your platform from the [releases page](https://github.com/qlik-oss/sense-installer/releases).
- To allow the CLI to download and initialize dependencies (including porter and it's associated mixins), simply execute `qliksense` with no arguments
- `qliksense`
#### Porter CLI
- *Optional*: If wanting to use porter CLI directly, two environment variables will need to be set so as not to conflict with an existing porter installation:
- Download the appropriate executable for your platform from the [releases page](https://github.com/qlik-oss/sense-installer/releases).
- Two environment variables will need to be set so as not to conflict with an existing porter installation:
- _Bash_
```shell
bash# export PORTER_HOME="$HOME\.qliksense"
bash# export PATH="$HOME\.qliksense;$PATH"
```
- _PowerShell_
```shell
PS> $Env:PORTER_HOME="$Env:USERPROFILE\.qliksense"
PS> $Env:PATH="$Env:USERPROFILE\.qliksense;$Env:PATH"
```
- To allow the CLI to download and initialize dependencies (including porter and it's associated mixins), simply execute `qliksense` with no arguments
- `qliksense`
### Generate Credentials from published bundle
- Ensure connectivity to the target cluster create a kubeconfig credential for a target bundle.
- generating a file as follows, replace `<credential_name>` with a name of your choosing.
- _Bash_
```shell
bash# CREDENTIAL_NAME=<credential_name>
bash# cat <<EOF > $HOME/.qliksense/credentials/$CREDENTIAL_NAME.yaml
name: $CREDENTIAL_NAME
credentials:
- name: kubeconfig
source:
path: $HOME/.kube/config
EOF
```
- _PowerShell_
```shell
PS> $CREDENTIAL_NAME="<credential_name>"
PS> Add-Content -Value @"
name: $CREDENTIAL_NAME
credentials:
- name: kubeconfig
source:
path: $Env:USERPROFILE\.kube\config
"@ -Path $Env:USERPROFILE\.qliksense\credentials\$CREDENTIAL_NAME".yaml"
```
- credentials can also be created using the [porter](https://porter.sh) CLI *(the correct environmental variable need to have been set up as shown in [Porter CLI](#porter-cli) above)*
- `porter cred generate <credential_name> --tag qlik/qliksense-cnab-bundle:v1.21.23-edge`, replace `<credential_name>` with a name of your choosing.
- Select `file path` and specify full path to a kube config file ex. _Bash_: `/home/user/.kube/config` or _PowerShell_ `C:\Users\user\.kube\config`
- Ensure connectivity to the target cluster create a kubeconfig credential for a target bundle run ex.
- `porter cred generate <credential_name> --tag qlik/qliksense-cnab-bundle:v1.21.23-edge`
, replace `<credential_name>` with a name of your choosing.
- Select `file path` and specify full path to a kube config file ex. _Bash_:
- `/home/user/.kube/config` or _PowerShell_ `C:\Users\user\.kube\config `
### Qlik Sense version and image list
It is possible verify the version of the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository bundled into the `qlik/qliksense-cnab-bundle` image and retreive the list of images included in that release. (This operation can take a minute or so)<https://github.com/qlik-oss/kustomize/issues/13> as the entire manifests needs to be rendered:
It is possible verify the version of the (qliksense-k8s)[https://github.com/qlik-oss/qliksense-k8s] repository bundled into the `qlik/qliksense-cnab-bundle` image and retreive the list of images included in that release. (This operation can take a minute or so)<https://github.com/qlik-oss/kustomize/issues/13> as the entire manifests needs to be rendered:
- `qliksense about --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
### Optional: Pulling images in manifest locally, "air gap"
If the `dockerRegistry` parameter is specified as the private docker registry to be used by the kubernetes cluster hosting qliksense, it is possible to pull images to the local docker engine for an eventual push during a `qliksense install` or `qliksense upgrade`
- `qliksense pull --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
### Running Preflight checks
You can run preflight checks to ensure that the cluster is in a healthy state before installing Qliksense.
You can run preflight checks to ensure that the cluster is in a healthy state before installing Qliksense.
- `qliksense preflight -c <credential_name> --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
The above command runs the checks in the default namespace. If you want to specify the namespace to run preflight checks on:
- `qliksense preflight --param namespace=<value> -c <credential_name> --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
### Installation
- Install the bundle : `qliksense install --param acceptEULA=yes -c <credential_name> --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
#### Supported Parameters during install
@@ -140,27 +107,17 @@ idpConfigs=[{"discoveryUrl":"http://keycloak-insecure:8089/keycloak/realms/maste
Then pass that file during install command like this
```shell
qliksense install --param acceptEULA=yes -c <credential_name> --param-file idpconfigs.txt --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
porter install --param acceptEULA=yes -c <credential_name> --param-file idpconfigs.txt --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
```
## Packaging a Custom bundle
If files need to be added to the [qliksense-k8s repository](https://github.com/qlik-oss/qliksense-k8s) in order to perform advanced configuration outside the scope of the what the operator provides, a custom bundle needs to be built.
## Packaging a Custom bundle
If files need to be added to the (qliksense-k8s repository)[https://github.com/qlik-oss/qliksense-k8s] in order to perform advanced configuration outside the scope of the what the operator provides, a custom bundle needs to be built.
Packaging of Qlik Sense on Kubernetes is done through a [Porter](https://porter.sh/) definition in the [Qlik Sense on Kubernetes configuration repository](https://github.com/qlik-oss/qliksense-k8s/blob/master/porter.yaml), the resulting bundle published on DockerHub as a [Cloud Natvie Application Bundle](https://cnab.io/) called [qliksense-cnab-bundle](https://hub.docker.com/r/qlik/qliksense-cnab-bundle)
To start, clone [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and modify the repo as desired, once finished make sure to be in the `qliksense-k8s` directory from which the porter bundle can be built:
```shell
git clone git@github.com:qlik-oss/qliksense-k8s.git
cd qliksense-k8s
qliksense build
```
To start, clone (https://github.com/qlik-oss/qliksense-k8s)[https://github.com/qlik-oss/qliksense-k8s] and modify the repo as desired, once finished make sure to be in the `qliksense-k8s` directory from which the porter bundle can be built:
- `porter build`
Once built, all of the `porter` command that were used with `--tag` can be now be used without this flag provided that porter is executed with the `qliksense-k8s` directory. `porter` will automatically use the qliksense-k8s (and the porter.yaml) in the current directory.
## List of Commands
- [qliksense about](action_about.md)

View File

@@ -1,41 +0,0 @@
# qliksense about
About action will display inside information regarding [qliksense-k8](https://github.com/qlik-oss/qliksense-k8s) release.
it will support following flags
- `qliksense about 1.0.0` display default profile for tag `1.0.0`.
- `qliksense about 1.0.0 --profile=docker-desktop`
- `qliksense about`
- assuming current directory has `manifests/docker-desktop`
- or get version information from pull of `qliksense-k8s` `master`
using other supported commands user might have built the CR into the location `~/.qliksense/myqliksense.yaml`
```yaml
apiVersion: qlik.com/v1
kind: QlikSense
metadata:
name: myqliksense
spec:
profile: docker-desktop
manifestsRoot: /Usr/ddd/my-k8-repo/manifests
namespace: myqliksense
storageClassName: efs
configs:
qliksense:
- name: acceptEULA
value: "yes"
secrets:
qliksense:
- name: mongoDbUri
value: "mongo://mongo:3307"
- name: messagingPassword
valueFromKey: messagingPassword
```
In that case the command would be
- `qliksense about`
- display from `/Usr/ddd/my-k8-repo/manifests/docker-desktop` location
- pull from `master` if directory invalid/empty

View File

@@ -1,65 +0,0 @@
package main
import (
"errors"
"fmt"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
"strings"
)
type aboutCommandOptions struct {
Profile string
}
func about(q *qliksense.Qliksense) *cobra.Command {
opts := &aboutCommandOptions{}
c := &cobra.Command{
Use: "about ref",
Short: "About Qlik Sense",
Long: "Gives the version of QLik Sense on Kubernetes and versions of images.",
Example: `
qliksense about 1.0.0
- display default profile (docker-desktop) for Git ref 1.0.0 in the qliksense-k8s repo
qliksense about 1.0.0 --profile=docker-desktop
- specifying profile
qliksense about
qliksense about --profile=test
- if no Git ref is provided, then get version information from the configuration on disk:
- if user's current directory has a subdirectory "manifests/${profile}",
then get version information from that
- if using other supported commands the user has built a CR in ~/.qliksense,
then get version information based on the path derived like so:
- ${spec.manifestsRoot}/${spec.profile} # if no profile flag provided
- ${spec.manifestsRoot}/${profile} # if profile is provided using the --profile command flag
- if no config found on disk in locations described above,
then get version information based on the default profile in the qliksense-k8s repo master
`,
RunE: func(cmd *cobra.Command, args []string) error {
if gitRef, err := getAboutCommandGitRef(args); err != nil {
return err
} else if vout, err := q.About(gitRef, opts.Profile); err != nil {
return err
} else if out, err := yaml.Marshal(vout); err != nil {
return err
} else if _, err := fmt.Println(out); err != nil {
return err
}
return nil
},
}
f := c.Flags()
f.StringVar(&opts.Profile, "profile", "", "Configuration profile")
return c
}
func getAboutCommandGitRef(args []string) (string, error) {
if len(args) > 1 {
return "", errors.New("too many arguments, only 1 expected")
} else if len(args) == 1 {
return strings.TrimSpace(args[0]), nil
}
return "", nil
}

412
cmd/qliksense/aliases.go Normal file
View File

@@ -0,0 +1,412 @@
package main
import (
"bufio"
"os"
"strings"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
func buildAliasCommands(porterCmd *cobra.Command, q *qliksense.Qliksense) []*cobra.Command {
return []*cobra.Command{
buildBuildAlias(porterCmd),
buildInstallAlias(porterCmd, q),
buildUpgradeAlias(porterCmd, q),
buildAboutAlias(porterCmd),
buildPreflightAlias(porterCmd, q),
buildUninstallAlias(porterCmd, q),
}
}
func buildBuildAlias(porterCmd *cobra.Command) *cobra.Command {
var (
c *cobra.Command
)
c = &cobra.Command{
Use: "build",
Short: "Build a bundle",
Long: "Builds the bundle in the current directory by generating a Dockerfile and a CNAB bundle.json, and then building the invocation image.",
DisableFlagParsing: true,
RunE: func(cmd *cobra.Command, args []string) error {
return porterCmd.RunE(porterCmd, append([]string{"build"}, args...))
},
Annotations: map[string]string{
"group": "alias",
},
}
return c
}
type paramOptions struct {
aboutOptions
Params []string
ParamFiles []string
Name string
InsecureRegistry bool
// CredentialIdentifiers is a list of credential names or paths to make available to the bundle.
CredentialIdentifiers []string
Driver string
Force bool
Insecure bool
}
func buildInstallAlias(porterCmd *cobra.Command, q *qliksense.Qliksense) *cobra.Command {
var (
c *cobra.Command
opts *paramOptions
registry *string
)
opts = &paramOptions{}
c = &cobra.Command{
Use: "install [INSTANCE]",
Short: "Install qliksense",
Long: `Install a new instance of a bundle.
The first argument is the bundle instance name to create for the installation. This defaults to the name of the bundle.
Porter uses the Docker driver as the default runtime for executing a bundle's invocation image, but an alternate driver may be supplied via '--driver/-d'.
For example, the 'debug' driver may be specified, which simply logs the info given to it and then exits.`,
Example: ` qliksense install
qliksense install --version v1.0.0
qliksense install --insecure
qliksense install qliksense --file qliksense/bundle.json
qliksense install --param-file base-values.txt --param-file dev-values.txt --param test-mode=true --param header-color=blue
qliksense install --cred kubernetes
qliksense install --driver debug
qliksense install MyAppFromTag --tag qlik/qliksense-cnab-bundle:v1.0.0
`,
//DisableFlagParsing: true,
RunE: func(cmd *cobra.Command, args []string) error {
// Push images here.
// TODO: Need to get the private reg from params
args = append(os.Args[2:], opts.getTagValue(args)...)
if registry = opts.findKey("dockerRegistry"); registry != nil {
if len(*registry) > 0 {
q.TagAndPushImages(*registry)
}
}
return porterCmd.RunE(porterCmd, append([]string{"install"}, args...))
},
Annotations: map[string]string{
"group": "alias",
},
}
f := c.Flags()
f.StringVarP(&opts.Version, "version", "v", "latest",
"Version of Qlik Sense to install")
f.BoolVar(&opts.Insecure, "insecure", true,
"Allow working with untrusted bundles")
f.StringVarP(&opts.File, "file", "f", "",
"Path to the porter manifest file. Defaults to the bundle in the current directory.")
f.StringVar(&opts.CNABFile, "cnab-file", "",
"Path to the CNAB bundle.json file.")
f.StringSliceVar(&opts.ParamFiles, "param-file", nil,
"Path to a parameters definition file for the bundle, each line in the form of NAME=VALUE. May be specified multiple times.")
f.StringSliceVar(&opts.Params, "param", nil,
"Define an individual parameter in the form NAME=VALUE. Overrides parameters set with the same name using --param-file. May be specified multiple times.")
f.StringSliceVarP(&opts.CredentialIdentifiers, "cred", "c", nil,
"Credential to use when installing the bundle. May be either a named set of credentials or a filepath, and specified multiple times.")
f.StringVarP(&opts.Driver, "driver", "d", "docker",
"Specify a driver to use. Allowed values: docker, debug")
f.StringVarP(&opts.Tag, "tag", "t", "",
"Use a bundle in an OCI registry specified by the given tag")
f.BoolVar(&opts.InsecureRegistry, "insecure-registry", false,
"Don't require TLS for the registry")
f.BoolVar(&opts.Force, "force", false,
"Force a fresh pull of the bundle and all dependencies")
return c
}
func buildUpgradeAlias(porterCmd *cobra.Command, q *qliksense.Qliksense) *cobra.Command {
var (
c *cobra.Command
opts *paramOptions
registry *string
)
opts = &paramOptions{}
c = &cobra.Command{
Use: "upgrade [INSTANCE]",
Short: "Upgrade qliksense",
Long: `Upgrade to a new instance of a bundle.
The first argument is the bundle instance name to upgrade for the installation. This defaults to the name of the bundle.
Porter uses the Docker driver as the default runtime for executing a bundle's invocation image, but an alternate driver may be supplied via '--driver/-d'.
For example, the 'debug' driver may be specified, which simply logs the info given to it and then exits.`,
Example: ` qliksense upgrade
qliksense upgrade --version v1.0.0
qliksense upgrade --insecure
qliksense upgrade qliksense --file qliksense/bundle.json
qliksense upgrade --param-file base-values.txt --param-file dev-values.txt --param test-mode=true --param header-color=blue
qliksense upgrade --cred kubernetes
qliksense upgrade --driver debug
qliksense upgrade MyAppFromTag --tag qlik/qliksense-cnab-bundle:v1.0.0
`,
//DisableFlagParsing: true,
RunE: func(cmd *cobra.Command, args []string) error {
// Push images here.
// TODO: Need to get the private reg from params
args = append(os.Args[2:], opts.getTagValue(args)...)
if registry = opts.findKey("dockerRegistry"); registry != nil {
if len(*registry) > 0 {
q.TagAndPushImages(*registry)
}
}
return porterCmd.RunE(porterCmd, append([]string{"upgrade"}, args...))
},
Annotations: map[string]string{
"group": "alias",
},
}
f := c.Flags()
f.StringVarP(&opts.Version, "version", "v", "latest",
"Version of Qlik Sense to upgrade to")
f.BoolVar(&opts.Insecure, "insecure", true,
"Allow working with untrusted bundles")
f.StringVarP(&opts.File, "file", "f", "",
"Path to the porter manifest file. Defaults to the bundle in the current directory.")
f.StringVar(&opts.CNABFile, "cnab-file", "",
"Path to the CNAB bundle.json file.")
f.StringSliceVar(&opts.ParamFiles, "param-file", nil,
"Path to a parameters definition file for the bundle, each line in the form of NAME=VALUE. May be specified multiple times.")
f.StringSliceVar(&opts.Params, "param", nil,
"Define an individual parameter in the form NAME=VALUE. Overrides parameters set with the same name using --param-file. May be specified multiple times.")
f.StringSliceVarP(&opts.CredentialIdentifiers, "cred", "c", nil,
"Credential to use when installing the bundle. May be either a named set of credentials or a filepath, and specified multiple times.")
f.StringVarP(&opts.Driver, "driver", "d", "docker",
"Specify a driver to use. Allowed values: docker, debug")
f.StringVarP(&opts.Tag, "tag", "t", "",
"Use a bundle in an OCI registry specified by the given tag")
f.BoolVar(&opts.InsecureRegistry, "insecure-registry", false,
"Don't require TLS for the registry")
f.BoolVar(&opts.Force, "force", false,
"Force a fresh pull of the bundle and all dependencies")
return c
}
func buildUninstallAlias(porterCmd *cobra.Command, q *qliksense.Qliksense) *cobra.Command {
var (
c *cobra.Command
opts *paramOptions
)
opts = &paramOptions{}
c = &cobra.Command{
Use: "uninstall [INSTANCE]",
Short: "Uninstall a bundle instance",
Long: `Uninstall a bundle instance
The first argument is the bundle instance name to uninstall. This defaults to the name of the bundle.
Porter uses the Docker driver as the default runtime for executing a bundle's invocation image, but an alternate driver may be supplied via '--driver/-d'.
For example, the 'debug' driver may be specified, which simply logs the info given to it and then exits.`,
Example: ` qliksense uninstall
qliksense uninstall --insecure
qliksense uninstall MyAppInDev --file myapp/bundle.json
qliksense uninstall --param-file base-values.txt --param-file dev-values.txt --param test-mode=true --param header-color=blue
qliksense uninstall --cred azure --cred kubernetes
qliksense uninstall --driver debug
qliksense uninstall MyAppFromTag --tag deislabs/porter-kube-bundle:v1.0
`,
RunE: func(cmd *cobra.Command, args []string) error {
return porterCmd.RunE(porterCmd, append([]string{"uninstall"}, os.Args[2:]...))
},
Annotations: map[string]string{
"group": "alias",
},
}
f := c.Flags()
f.BoolVar(&opts.Insecure, "insecure", true,
"Allow working with untrusted bundles")
f.StringVarP(&opts.File, "file", "f", "",
"Path to the porter manifest file. Defaults to the bundle in the current directory. Optional unless a newer version of the bundle should be used to uninstall the bundle.")
f.StringVar(&opts.CNABFile, "cnab-file", "",
"Path to the CNAB bundle.json file.")
f.StringSliceVar(&opts.ParamFiles, "param-file", nil,
"Path to a parameters definition file for the bundle, each line in the form of NAME=VALUE. May be specified multiple times.")
f.StringSliceVar(&opts.Params, "param", nil,
"Define an individual parameter in the form NAME=VALUE. Overrides parameters set with the same name using --param-file. May be specified multiple times.")
f.StringSliceVarP(&opts.CredentialIdentifiers, "cred", "c", nil,
"Credential to use when uninstalling the bundle. May be either a named set of credentials or a filepath, and specified multiple times.")
f.StringVarP(&opts.Driver, "driver", "d", "docker",
"Specify a driver to use. Allowed values: docker, debug")
f.StringVarP(&opts.Tag, "tag", "t", "",
"Use a bundle in an OCI registry specified by the given tag")
f.BoolVar(&opts.InsecureRegistry, "insecure-registry", false,
"Don't require TLS for the registry")
f.BoolVar(&opts.Force, "force", false,
"Force a fresh pull of the bundle and all dependencies")
return c
}
func (o *aboutOptions) getTagDefaults(args []string) []string {
args = append(args, o.getTagValue(args)...)
return args
}
func (o *aboutOptions) getTagValue(args []string) []string {
tagArr := []string{}
if len(o.Tag) > 1 {
tagArr = []string{"--tag", o.Tag}
}
if len(o.Tag) <= 0 && len(o.File) <= 0 && len(o.CNABFile) <= 0 {
if _, err := os.Stat("porter.yaml"); err != nil {
tagArr = []string{"--tag", "qlik/qliksense-cnab-bundle:" + o.Version}
}
}
return tagArr
}
type aboutOptions struct {
Version string
Tag string
File string
CNABFile string
}
func buildAboutAlias(porterCmd *cobra.Command) *cobra.Command {
var (
c *cobra.Command
opts *aboutOptions
)
opts = &aboutOptions{}
c = &cobra.Command{
Use: "about",
Short: "About Qlik Sense",
Long: "Gives the verion of QLik Sense on Kuberntetes and versions of images.",
RunE: func(cmd *cobra.Command, args []string) error {
args = opts.getTagDefaults(args)
return porterCmd.RunE(porterCmd, append([]string{"invoke", "--action", "about"}, args...))
},
Annotations: map[string]string{
"group": "alias",
},
}
f := c.Flags()
f.StringVarP(&opts.Version, "version", "v", "latest",
"Version of Qlik Sense to install")
f.StringVarP(&opts.Tag, "tag", "t", "",
"Use a bundle in an OCI registry specified by the given tag")
f.StringVarP(&opts.File, "file", "f", "",
"Path to the porter manifest file. Defaults to the bundle in the current directory.")
f.StringVar(&opts.CNABFile, "cnab-file", "",
"Path to the CNAB bundle.json file.")
return c
}
func buildPreflightAlias(porterCmd *cobra.Command, q *qliksense.Qliksense) *cobra.Command {
var (
c *cobra.Command
opts *paramOptions
)
opts = &paramOptions{}
c = &cobra.Command{
Use: "preflight",
Short: "Preflight Checks",
Long: "Perform Preflight Checks",
RunE: func(cmd *cobra.Command, args []string) error {
args = append(os.Args[1:], opts.getTagDefaults(args)...)
return porterCmd.RunE(porterCmd, append([]string{"invoke", "--action", "preflight"}, args...))
},
Annotations: map[string]string{
"group": "alias",
},
}
f := c.Flags()
f.StringSliceVar(&opts.Params, "param", nil,
"Define an individual parameter in the form NAME=VALUE. Overrides parameters set with the same name using --param-file. May be specified multiple times.")
f.StringSliceVar(&opts.ParamFiles, "param-file", nil,
"Path to a parameters definition file for the bundle, each line in the form of NAME=VALUE. May be specified multiple times.")
f.StringVarP(&opts.Tag, "tag", "t", "",
"Use a bundle in an OCI registry specified by the given tag")
f.StringVarP(&opts.Version, "version", "v", "latest",
"Version of Qlik Sense to install")
f.StringSliceVarP(&opts.CredentialIdentifiers, "cred", "c", nil,
"Credential to use when installing the bundle. May be either a named set of credentials or a filepath, and specified multiple times.")
return c
}
func (o *paramOptions) findKey(param string) *string {
var (
value *string
)
if value = o.findParams(param); value != nil {
return value
}
if value = o.findParamFiles(param); value != nil {
return value
}
return nil
}
// parsedParams parses the variable assignments in Params.
func (o *paramOptions) findParams(param string) *string {
return o.findVariableKey(param, o.Params)
}
// parseParamFiles parses the variable assignments in ParamFiles.
func (o *paramOptions) findParamFiles(param string) *string {
var (
path string
retStr *string
)
for _, path = range o.ParamFiles {
retStr = o.findParamFile(param, path)
}
return retStr
}
func (o *paramOptions) findParamFile(param string, path string) *string {
var (
f *os.File
err error
scanner *bufio.Scanner
lines []string
retStr *string
)
if f, err = os.Open(path); err == nil {
defer f.Close()
scanner = bufio.NewScanner(f)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
retStr = o.findVariableKey(param, lines)
}
return retStr
}
func (o *paramOptions) findVariableKey(param string, params []string) *string {
var (
variable, value string
)
for _, p := range params {
parts := strings.SplitN(p, "=", 2)
if len(parts) >= 2 {
variable = strings.TrimSpace(parts[0])
if variable == param {
value = strings.TrimSpace(parts[1])
return &value
}
}
}
return nil
}

View File

@@ -1,42 +0,0 @@
package main
import (
"fmt"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
var configCmd = &cobra.Command{
Use: "config",
Short: "do operations on/around CR",
Long: `do operations on/around CR`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Use like: config view or config apply")
},
}
func configApplyCmd(q *qliksense.Qliksense) *cobra.Command {
c := &cobra.Command{
Use: "apply",
Short: "generate the patchs and apply manifests to k8s",
Long: `generate patches based on CR and apply manifests to k8s`,
Example: `qliksense config apply`,
RunE: func(cmd *cobra.Command, args []string) error {
return q.ConfigApplyQK8s()
},
}
return c
}
func configViewCmd(q *qliksense.Qliksense) *cobra.Command {
c := &cobra.Command{
Use: "view",
Short: "view the qliksense operator CR",
Long: `display the operator CR, that has been created for the current context`,
Example: `qliksense config view`,
RunE: func(cmd *cobra.Command, args []string) error {
return q.ConfigViewCR()
},
}
return c
}

View File

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

View File

@@ -1,30 +0,0 @@
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 {
c := &cobra.Command{
Use: "install",
Short: "install a qliksense release",
Long: `install a qliksesne release`,
Example: `qliksense install <version>`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("requires a version (i.e. v1.0.0)")
}
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 c
}

View File

@@ -1,28 +0,0 @@
package main
import (
"fmt"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
var operatorCmd = &cobra.Command{
Use: "operator",
Short: "Configuration for operator",
Long: `Configuration for operator`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("User like: operator view")
},
}
func operatorViewCmd(q *qliksense.Qliksense) *cobra.Command {
c := &cobra.Command{
Use: "view",
Short: "View Configuration for operator",
Long: `View Configuration for operator`,
Run: func(cmd *cobra.Command, args []string) {
q.ViewOperatorCrd()
},
}
return c
}

31
cmd/qliksense/porter.go Normal file
View File

@@ -0,0 +1,31 @@
package main
import (
"fmt"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
"strings"
)
func porter(q *qliksense.Qliksense) *cobra.Command {
return &cobra.Command{
Use: "porter",
Short: "Execute a porter command",
DisableFlagParsing: true,
RunE: func(cobCmd *cobra.Command, args []string) error {
var (
err error
)
if _, err = q.CallPorter(args,
func(x string) (out *string) {
out = new(string)
*out = strings.ReplaceAll(x, "porter", "qliksense porter")
fmt.Println(*out)
return
}); err != nil {
return err
}
return nil
},
}
}

View File

@@ -1,27 +1,33 @@
package main
import (
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
func pullQliksenseImages(q *qliksense.Qliksense) *cobra.Command {
opts := &aboutCommandOptions{}
cmd := &cobra.Command{
Use: "pull",
Short: "Pull docke images for offline install",
Example: `qliksense pull`,
RunE: func(cmd *cobra.Command, args []string) error {
if gitRef, err := getAboutCommandGitRef(args); err != nil {
return err
} else if err = q.PullImages(gitRef, opts.Profile, false); err != nil {
return err
}
return nil
},
}
f := cmd.Flags()
f.StringVar(&opts.Profile, "profile", "", "Configuration profile")
return cmd
}
package main
import (
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
func pullQliksenseImages(q *qliksense.Qliksense) *cobra.Command {
var (
cmd *cobra.Command
opts *aboutOptions
)
opts = &aboutOptions{}
cmd = &cobra.Command{
Use: "pull",
Short: "Pull docke images for offline install",
Example: `qliksense pull`,
RunE: func(cmd *cobra.Command, args []string) error {
return q.PullImages(opts.getTagDefaults(args))
},
}
f := cmd.Flags()
f.StringVarP(&opts.Version, "version", "v", "latest",
"From version of Qlik Sense The images will be pulled")
f.StringVarP(&opts.Tag, "tag", "t", "",
"Use a bundle in an OCI registry specified by the given tag")
f.StringVarP(&opts.File, "file", "f", "",
"Path to the porter manifest file. Defaults to the bundle in the current directory.")
f.StringVar(&opts.CNABFile, "cnab-file", "",
"Path to the CNAB bundle.json file.")
return cmd
}

View File

@@ -3,10 +3,11 @@ package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/mitchellh/go-homedir"
@@ -16,39 +17,57 @@ import (
"github.com/spf13/viper"
)
// To run this project in ddebug mode, run:
// export QLIKSENSE_DEBUG=true
// qliksense <command>
const (
// porterURLBase = "https://deislabs.blob.core.windows.net/porter"
porterURLBase = "https://github.com/qlik-oss/sense-installer/releases/download"
porterHomeVar = "PORTER_HOME"
qlikSenseHomeVar = "QLIKSENSE_HOME"
qlikSenseDirVar = ".qliksense"
mixinDirVar = "mixins"
porterRuntime = "porter-runtime"
)
func initAndExecute() error {
var (
qlikSenseHome string
err error
porterExe string
err error
)
qlikSenseHome, err = setUpPaths()
if err != nil {
log.Fatal(err)
if porterExe, err = installPorter(); err != nil {
return err
}
if err = rootCmd(qliksense.New(qlikSenseHome)).Execute(); err != nil {
if err := rootCmd(qliksense.New(porterExe)).Execute(); err != nil {
return err
}
return nil
}
func setUpPaths() (string, error) {
func installPorter() (string, error) {
var (
homeDir, qlikSenseHome string
err error
porterPermaLink = pkg.Version
//porterPermaLink = "v0.3.0"
destination, homeDir, mixin, mixinOpts, qlikSenseHome, porterExe, ext string
mixinsVar = map[string]string{
"kustomize": "-v 0.2-beta-3-0e19ca4 --url https://github.com/donmstewart/porter-kustomize/releases/download",
"qliksense": "-v v0.14.0 --url https://github.com/qlik-oss/porter-qliksense/releases/download",
"exec": "-v latest",
"kubernetes": "-v latest",
"helm": "-v latest",
"azure": "-v latest",
"terraform": "-v latest",
"az": "-v latest",
"aws": "-v latest",
"gcloud": "-v latest",
}
downloadMixins map[string]string
downloadPorter bool
err error
cmd *exec.Cmd
)
porterExe = "porter"
if runtime.GOOS == "windows" {
porterExe = porterExe + ".exe"
}
if qlikSenseHome = os.Getenv(qlikSenseHomeVar); qlikSenseHome == "" {
if homeDir, err = homedir.Dir(); err != nil {
return "", err
@@ -58,8 +77,75 @@ func setUpPaths() (string, error) {
}
qlikSenseHome = filepath.Join(homeDir, qlikSenseDirVar)
}
os.Mkdir(qlikSenseHome, os.ModePerm)
return qlikSenseHome, nil
os.Setenv(porterHomeVar, qlikSenseHome)
//TODO: Check if porter version is one alreadu is one for this build
porterExe = filepath.Join(qlikSenseHome, porterExe)
if _, err = os.Stat(qlikSenseHome); err != nil {
if os.IsNotExist(err) {
downloadPorter = true
} else {
return "", err
}
} else {
if _, err = os.Stat(porterExe); err != nil {
if os.IsNotExist(err) {
downloadPorter = true
} else {
return "", err
}
}
}
if downloadPorter {
os.Mkdir(qlikSenseHome, os.ModePerm)
destination = filepath.Join(qlikSenseHome, porterRuntime)
if err = downloadFile(porterURLBase+"/"+porterPermaLink+"/porter-linux-amd64", destination); err != nil {
return "", err
}
os.Chmod(destination, 0755)
if runtime.GOOS == "linux" && runtime.GOARCH == "amd64" {
if _, err = copy(filepath.Join(qlikSenseHome, porterRuntime), porterExe); err != nil {
return "", err
}
os.Chmod(porterExe, 0755)
} else {
if runtime.GOOS == "windows" {
ext = ".exe"
}
if err = downloadFile(porterURLBase+"/"+porterPermaLink+"/"+"porter-"+runtime.GOOS+"-"+runtime.GOARCH+ext, porterExe); err != nil {
return "", err
}
os.Chmod(porterExe, 0755)
}
}
if _, err = os.Stat(filepath.Join(qlikSenseHome, mixinDirVar)); err != nil {
if os.IsNotExist(err) {
downloadMixins = mixinsVar
} else {
return "", err
}
} else {
downloadMixins = make(map[string]string)
for mixin, mixinOpts = range mixinsVar {
if _, err = os.Stat(filepath.Join(qlikSenseHome, mixinDirVar, mixin)); err != nil {
if os.IsNotExist(err) {
downloadMixins[mixin] = mixinOpts
} else {
return "", err
}
}
}
}
for mixin, mixinOpts = range downloadMixins {
cmd = exec.Command(porterExe, append([]string{"mixin", "install", mixin}, strings.Split(mixinOpts, " ")...)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err = cmd.Run(); err != nil {
return "", err
}
}
return porterExe, nil
}
var versionCmd = &cobra.Command{
@@ -73,7 +159,7 @@ var versionCmd = &cobra.Command{
func rootCmd(p *qliksense.Qliksense) *cobra.Command {
var (
cmd *cobra.Command
cmd, porterCmd, alias *cobra.Command
)
cmd = &cobra.Command{
@@ -92,24 +178,13 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
// For qliksense overrides/commands
cmd.AddCommand(pullQliksenseImages(p))
cmd.AddCommand(about(p))
porterCmd = porter(p)
cmd.AddCommand(porterCmd)
for _, alias = range buildAliasCommands(porterCmd, p) {
cmd.AddCommand(alias)
}
// add version command
cmd.AddCommand(versionCmd)
// add operator command
cmd.AddCommand(operatorCmd)
operatorCmd.AddCommand(operatorViewCmd(p))
//add fetch command
cmd.AddCommand(fetchCmd(p))
// add install command
cmd.AddCommand(installCmd(p))
// add config command
cmd.AddCommand(configCmd)
configCmd.AddCommand(configApplyCmd(p))
configCmd.AddCommand(configViewCmd(p))
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
return cmd

61
go.mod
View File

@@ -12,62 +12,65 @@ replace (
// github.com/jaguilar/vt100 => github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305
// golang.org/x/crypto v0.0.0-20190129210102-0709b304e793 => golang.org/x/crypto v0.0.0-20180904163835-0709b304e793
golang.org/x/sys => golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a
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
)
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
get.porter.sh/porter v0.22.0-beta.1
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Microsoft/hcsshim v0.8.7 // indirect
github.com/PaesslerAG/jsonpath v0.1.1 // indirect
github.com/PuerkitoBio/goquery v1.5.0 // indirect
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
github.com/aws/aws-sdk-go v1.28.9 // indirect
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 // indirect
github.com/bitly/go-simplejson v0.5.0 // indirect
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/bugsnag/bugsnag-go v1.5.3 // indirect
github.com/bugsnag/panicwrap v1.2.0 // indirect
github.com/carolynvs/datetime-printer v0.2.0 // indirect
github.com/cbroglie/mustache v1.0.1 // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cloudflare/cfssl v1.4.1 // indirect
github.com/containerd/containerd v1.3.2 // indirect
github.com/containerd/continuity v0.0.0-20191214063359-1097c8bae83b // indirect
github.com/containers/image/v5 v5.1.0
github.com/deislabs/cnab-go v0.7.1-beta1 // indirect
github.com/docker/cli v0.0.0-20191212191748-ebca1413117a
github.com/docker/cnab-to-oci v0.3.0-beta2 // indirect
github.com/docker/distribution v2.7.1+incompatible
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7
github.com/docker/go v1.5.1-1 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/gobuffalo/packr/v2 v2.7.1
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.3.3 // indirect
github.com/gorilla/mux v1.7.3 // indirect
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 // indirect
github.com/gobuffalo/packr/v2 v2.7.1 // indirect
github.com/gofrs/uuid v3.2.0+incompatible // indirect
github.com/google/go-containerregistry v0.0.0-20191216221554-74b082017bc4 // indirect
github.com/gophercloud/gophercloud v0.7.0 // indirect
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/hashicorp/go-hclog v0.10.0 // indirect
github.com/hashicorp/go-plugin v1.0.1 // indirect
github.com/imdario/mergo v0.3.8 // indirect
github.com/jinzhu/gorm v1.9.11 // indirect
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/lib/pq v1.2.0 // indirect
github.com/mattn/go-sqlite3 v2.0.1+incompatible // indirect
github.com/miekg/pkcs11 v1.0.3 // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/morikuni/aec v1.0.0 // indirect
github.com/pkg/errors v0.8.1
github.com/qlik-oss/k-apis v0.0.2
github.com/mmcdole/gofeed v1.0.0-beta2 // indirect
github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf // indirect
github.com/olekukonko/tablewriter v0.0.4 // indirect
github.com/opencontainers/runc v0.1.1 // indirect
github.com/pivotal/image-relocation v0.0.0-20191111101224-e94aff6df06c // indirect
github.com/qri-io/jsonschema v0.1.1 // indirect
github.com/spf13/cobra v0.0.5
github.com/spf13/viper v1.6.1
github.com/theupdateframework/notary v0.6.1 // indirect
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 // indirect
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a // indirect
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 // indirect
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b // indirect
google.golang.org/grpc v1.27.0 // indirect
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553
gopkg.in/AlecAivazis/survey.v1 v1.8.7 // indirect
gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect
gopkg.in/fatih/pool.v2 v2.0.0 // indirect
gopkg.in/gorethink/gorethink.v3 v3.0.5 // indirect
gopkg.in/yaml.v2 v2.2.8
sigs.k8s.io/kustomize/api v0.3.2
gopkg.in/yaml.v2 v2.2.7
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 // indirect
)
exclude github.com/Azure/go-autorest v12.0.0+incompatible

955
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -1,83 +0,0 @@
package api
import (
"errors"
"fmt"
"github.com/jinzhu/copier"
"gopkg.in/yaml.v2"
"io/ioutil"
"os"
"path/filepath"
)
// NewQConfig create QliksenseConfig object from file ~/.qliksense/config.yaml
func NewQConfig(qsHome string) *QliksenseConfig {
configFile := filepath.Join(qsHome, "config.yaml")
data, err := ioutil.ReadFile(configFile)
if err != nil {
fmt.Println("Cannot read config file from: "+configFile, err)
os.Exit(1)
}
qc := &QliksenseConfig{}
err = yaml.Unmarshal(data, qc)
if err != nil {
fmt.Println("yaml unmarshalling error ", err)
os.Exit(1)
}
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
}
}
if crFilePath == "" {
return nil, errors.New("context name " + contextName + " not found")
}
return getCRObject(crFilePath)
}
// GetCurrentCR create a QliksenseCR object for current context
func (qc *QliksenseConfig) GetCurrentCR() (*QliksenseCR, error) {
return qc.GetCR(qc.Spec.CurrentContext)
}
// SetCrLocation sets the CR location for a context. Helpful during test
func (qc *QliksenseConfig) SetCrLocation(contextName, filepath string) (*QliksenseConfig, error) {
tempQc := &QliksenseConfig{}
copier.Copy(tempQc, qc)
found := false
tempQc.Spec.Contexts = []Context{}
for _, c := range qc.Spec.Contexts {
if c.Name == contextName {
c.CrFile = filepath
found = true
}
tempQc.Spec.Contexts = append(tempQc.Spec.Contexts, []Context{c}...)
}
if found {
return tempQc, nil
}
return nil, errors.New("cannot find the context")
}
func getCRObject(crfile string) (*QliksenseCR, error) {
data, err := ioutil.ReadFile(crfile)
if err != nil {
fmt.Println("Cannot read config file from: "+crfile, err)
return nil, err
}
cr := &QliksenseCR{}
err = yaml.Unmarshal(data, cr)
if err != nil {
fmt.Println("cannot unmarshal cr ", err)
return nil, err
}
return cr, nil
}

View File

@@ -1,85 +0,0 @@
package api
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
const tempPermissionCode os.FileMode = 0777
func setup() (func(), string) {
dir, _ := ioutil.TempDir("", "testing_path")
config :=
`
apiVersion: config.qlik.com/v1
kind: QliksenseConfig
metadata:
name: whatever
spec:
contexts:
- name: contx1
crLocation: /Users/mqb/.qliksense/contexts/contx1
- name: cotx2
crLocation: /root/.qliksense/contexts/cotx2.yaml
currentContext: contx1
`
configFile := filepath.Join(dir, "config.yaml")
ioutil.WriteFile(configFile, []byte(config), tempPermissionCode)
tearDown := func() {
os.RemoveAll(dir)
}
return tearDown, dir
}
func createCRFile(homeDir string) {
cr :=
`
apiVersion: qlik.com/v1
kind: QlikSense
metadata:
name: contx1
labels:
version: v1.0.0
spec:
profile: docker-desktop
manifestsRoot: /Users/mqb/.qliksense/contexts/contx1/qlik-k8s/v0.0.1/manifests
namespace: myqliksense
storageClassName: efs
configs:
qliksense:
- name: acceptEULA
value: "yes"
`
ctx1Dir := filepath.Join(homeDir, "contexts", "contx1")
crFile := filepath.Join(ctx1Dir, "contx1.yaml")
os.MkdirAll(ctx1Dir, tempPermissionCode)
ioutil.WriteFile(crFile, []byte(cr), tempPermissionCode)
}
func TestGetCR(t *testing.T) {
td, dir := setup()
qc := NewQConfig(dir)
if qc.Spec.CurrentContext != "contx1" {
t.Fail()
}
// create CR
createCRFile(dir)
crFile := filepath.Join(dir, "contexts", "contx1", "contx1.yaml")
qct, e := qc.SetCrLocation("contx1", crFile)
if e != nil {
t.Fail()
t.Log(e)
}
qcr, err := qct.GetCurrentCR()
if err != nil {
t.Fail()
t.Log(err)
}
if qcr.Spec.Profile != "docker-desktop" {
t.Fail()
}
td()
}

View File

@@ -1,29 +0,0 @@
package api
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
)
func KubectlApply(manifests string) error {
tempYaml, err := ioutil.TempFile("", "")
if err != nil {
fmt.Println("cannot create file ", err)
return err
}
tempYaml.WriteString(manifests)
cmd := exec.Command("kubectl", "apply", "-f", tempYaml.Name(), "--validate=false")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
fmt.Printf("kubectl apply failed with %s\n", err)
fmt.Println("temp CRD file: " + tempYaml.Name())
return err
}
os.Remove(tempYaml.Name())
return nil
}

View File

@@ -1,40 +0,0 @@
package api
import "github.com/qlik-oss/k-apis/pkg/config"
// CommonConfig is exported
type CommonConfig struct {
ApiVersion string `json:"apiVersion" yaml:"apiVersion"`
Kind string `json:"kind" yaml:"kind"`
Metadata *Metadata `json:"metadata" yaml:"metadata"`
}
// QliksenseConfig is exported
type QliksenseConfig struct {
CommonConfig `json:",inline" yaml:",inline"`
Spec *ContextSpec `json:"spec" yaml:"spec"`
}
// QliksenseCR is exported
type QliksenseCR struct {
CommonConfig `json:",inline" yaml:",inline"`
Spec *config.CRSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
}
// ContextSpec is exported
type ContextSpec struct {
Contexts []Context `json:"contexts" yaml:"contexts"`
CurrentContext string `json:"currentContext" yaml:"currentContext"`
}
// Context is exported
type Context struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
CrFile string `json:"crFile,omitempty" yaml:"crFile,omitempty"`
}
// Metadata is exported
type Metadata struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
}

View File

@@ -1,238 +0,0 @@
package qliksense
import (
"bytes"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"reflect"
"sort"
kapis_git "github.com/qlik-oss/k-apis/pkg/git"
"gopkg.in/yaml.v2"
)
type patch struct {
Target struct {
Kind string `yaml:"kind"`
LabelSelector string `yaml:"labelSelector"`
} `yaml:"target"`
Patch string `yaml:"patch"`
}
type selectivePatch struct {
APIVersion string `yaml:"apiVersion"`
Metadata struct {
Name string `yaml:"name"`
} `yaml:"metadata"`
Enabled bool `yaml:"enabled"`
Patches []patch `yaml:"patches"`
}
type helmChart struct {
APIVersion string `yaml:"apiVersion"`
Kind string `yaml:"kind"`
Metadata struct {
Name string `yaml:"name"`
} `yaml:"metadata"`
ReleaseNamespace string `yaml:"releaseNamespace"`
ChartHome string `yaml:"chartHome"`
ChartRepo string `yaml:"chartRepo"`
ChartName string `yaml:"chartName"`
ChartVersion string `yaml:"chartVersion"`
}
type VersionOutput struct {
QliksenseVersion string `yaml:"qlikSenseVersion"`
Images []string `yaml:"images"`
}
type nullWriter struct {
}
func (nw *nullWriter) Write(p []byte) (n int, err error) {
return len(p), nil
}
const (
defaultProfile = "docker-desktop"
defaultGitUrl = "https://github.com/qlik-oss/qliksense-k8s"
)
func (p *Qliksense) About(gitRef, profile string) (*VersionOutput, error) {
configDirectory, isTemporary, profile, err := getConfigDirectory(defaultGitUrl, gitRef, profile)
if err != nil {
return nil, err
}
if isTemporary {
defer os.RemoveAll(configDirectory)
}
chartVersion, err := getChartVersion(filepath.Join(configDirectory, "transformers", "qseokversion.yaml"), "qliksense")
if err != nil {
return nil, err
}
kuzManifest, err := executeKustomizeBuild(filepath.Join(configDirectory, "manifests", profile))
if err != nil {
return nil, err
}
images, err := getImageList(kuzManifest)
if err != nil {
return nil, err
}
return &VersionOutput{
QliksenseVersion: chartVersion,
Images: images,
}, nil
}
func getConfigDirectory(gitUrl, gitRef, profileEntered string) (dir string, isTemporary bool, profile string, err error) {
profile = profileEntered
if profile == "" {
profile = defaultProfile
}
if gitRef != "" {
if dir, err = downloadFromGitRepoToTmpDir(gitUrl, gitRef); err != nil {
return "", false, "", err
} else {
return dir, true, profile, nil
}
}
var exists bool
exists, dir, err = configExistsInCurrentDirectory(profile)
if err != nil {
return "", false, "", err
} else if exists {
return dir, false, profile, nil
}
var profileFromCR string
exists, dir, profileFromCR, err = configExistsInCR()
if err != nil {
return "", false, "", err
} else if exists {
if profileEntered == "" {
profile = profileFromCR
}
return dir, false, profile, nil
}
if dir, err = downloadFromGitRepoToTmpDir(gitUrl, "master"); err != nil {
return "", false, "", err
} else {
return dir, true, profile, nil
}
}
func downloadFromGitRepoToTmpDir(gitUrl, gitRef string) (string, error) {
if tmpDir, err := ioutil.TempDir("", ""); err != nil {
return "", err
} else {
downloadPath := path.Join(tmpDir, "repo")
if err := downloadFromGitRepo(gitUrl, gitRef, downloadPath); err != nil {
_ = os.RemoveAll(tmpDir)
return "", err
} else {
return downloadPath, nil
}
}
}
func downloadFromGitRepo(gitUrl, gitRef, destDir string) error {
if repo, err := kapis_git.CloneRepository(destDir, gitUrl, nil); err != nil {
return err
} else {
return kapis_git.Checkout(repo, gitRef, "", nil)
}
}
func configExistsInCurrentDirectory(profile string) (exists bool, currentDirectory string, err error) {
currentDirectory, err = os.Getwd()
if err == nil {
info, err := os.Stat(path.Join(currentDirectory, "manifests", profile))
if err == nil && info.IsDir() {
exists = true
}
}
return exists, currentDirectory, err
}
func configExistsInCR() (exists bool, directory string, profile string, err error) {
return exists, directory, profile, err
}
func getImageList(yamlContent []byte) ([]string, error) {
decoder := yaml.NewDecoder(bytes.NewReader(yamlContent))
var resource map[string]interface{}
imageMap := make(map[string]bool)
for {
err := decoder.Decode(&resource)
if err != nil {
if err != io.EOF {
return nil, err
}
break
}
traverseYamlDecodedMapRecursively(reflect.ValueOf(resource), []string{}, func(path []string, val interface{}) {
if len(path) >= 2 && path[len(path)-1] == "image" &&
(path[len(path)-2] == "containers" || path[len(path)-2] == "initContainers") {
if image, ok := val.(string); ok {
imageMap[image] = true
}
}
})
}
var sortedImageList []string
for image, _ := range imageMap {
sortedImageList = append(sortedImageList, image)
}
sort.Strings(sortedImageList)
return sortedImageList, nil
}
func traverseYamlDecodedMapRecursively(val reflect.Value, path []string, visitorFunc func(path []string, val interface{})) {
kind := val.Kind()
switch kind {
case reflect.Interface:
traverseYamlDecodedMapRecursively(val.Elem(), path, visitorFunc)
case reflect.Slice:
for i := 0; i < val.Len(); i++ {
traverseYamlDecodedMapRecursively(val.Index(i), path, visitorFunc)
}
case reflect.Map:
for _, key := range val.MapKeys() {
traverseYamlDecodedMapRecursively(val.MapIndex(key), append(path, key.Interface().(string)), visitorFunc)
}
default:
if kind != reflect.Invalid {
visitorFunc(path, val.Interface())
}
}
}
func getChartVersion(versionFile, chartName string) (string, error) {
var patchInst patch
var selPatch selectivePatch
var chart helmChart
if bytes, err := ioutil.ReadFile(versionFile); err != nil {
return "", err
} else if err = yaml.Unmarshal(bytes, &selPatch); err != nil {
return "", err
}
for _, patchInst = range selPatch.Patches {
if err := yaml.Unmarshal([]byte(patchInst.Patch), &chart); err == nil {
if chart.ChartName == chartName {
return chart.ChartVersion, nil
}
}
}
return "", nil
}

View File

@@ -1,505 +0,0 @@
package qliksense
import (
"fmt"
"os"
"path"
"reflect"
"testing"
)
func Test_About_getImageList(t *testing.T) {
var testCases = []struct {
name string
k8sYaml string
expectedImages []string
}{
{
name: "base",
k8sYaml: `
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
name: nginx-deployment-1
spec:
selector:
matchLabels:
app: nginx
replicas: 2 # tells deployment to run 2 pods matching the template
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
---
apiVersion: v1
kind: Secret
metadata:
creationTimestamp: 2018-11-15T20:46:46Z
name: mysecret
namespace: default
resourceVersion: "7579"
uid: 91460ecb-e917-11e8-98f2-025000000001
type: Opaque
data:
username: YWRtaW5pc3RyYXRvcg==
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
name: nginx-deployment-2
spec:
selector:
matchLabels:
app: nginx
replicas: 2 # tells deployment to run 2 pods matching the template
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx # has to match .spec.template.metadata.labels
serviceName: "nginx"
replicas: 3 # by default is 1
template:
metadata:
labels:
app: nginx # has to match .spec.selector.matchLabels
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: k8s.gcr.io/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "my-storage-class"
resources:
requests:
storage: 1Gi
---
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
spec:
containers:
- name: pi
image: perl
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never
backoffLimit: 4
---
apiVersion: v1
kind: Pod
metadata:
name: init-demo
spec:
containers:
- name: nginx
image: nginx
env:
- name: FOO
value: null
ports:
- containerPort: 80
volumeMounts:
- name: workdir
mountPath: /usr/share/nginx/html
# These containers are run during pod initialization
initContainers:
- name: install
image: busybox
command:
- wget
- "-O"
- "/work-dir/index.html"
- http://kubernetes.io
volumeMounts:
- name: workdir
mountPath: "/work-dir"
dnsPolicy: Default
volumes:
- name: workdir
emptyDir: {}
`,
expectedImages: []string{"busybox", "k8s.gcr.io/nginx-slim:0.8", "nginx", "nginx:1.7.9", "perl"},
},
{
name: "works for custom resources and CronJobs",
k8sYaml: `
apiVersion: "qixmanager.qlik.com/v1"
kind: "Engine"
metadata:
name: release-name-engine-reload
spec:
metadata:
labels:
qix-engine: qix-engine
annotations:
prometheus.io/scrape: "true"
workloadType: "reload"
podSpec:
imagePullSecrets:
- name: artifactory-docker-secret
dnsConfig:
options:
- name: timeout
value: "1"
- name: single-request-reopen
containers:
- name: engine-reload
image: another-engine
---
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox2
args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure
`,
expectedImages: []string{"another-engine", "busybox2"},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
images, err := getImageList([]byte(testCase.k8sYaml))
if err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
if !reflect.DeepEqual(images, testCase.expectedImages) {
t.Fatalf("expected %v, but got: %v\n", testCase.expectedImages, images)
}
})
}
}
func Test_About_getConfigDirectory(t *testing.T) {
verifyAsdBranch := func(configDir string) (ok bool, reason string) {
tmpDir := os.TempDir()
if (path.Clean(path.Dir(path.Dir(configDir))) != path.Clean(tmpDir)) || (path.Base(configDir) != "repo") {
return false, fmt.Sprintf("expected config directory path: %v to be under: %v and terminate with repo", configDir, tmpDir)
}
if info, err := os.Stat(path.Join(configDir, "asdczxc")); err != nil || !info.Mode().IsRegular() {
return false, fmt.Sprintf(`expected to find file: "asdczxc" under directory: %v`, configDir)
}
return true, ""
}
verifyMasterBranch := func(configDir string) (ok bool, reason string) {
tmpDir := os.TempDir()
if (path.Clean(path.Dir(path.Dir(configDir))) != path.Clean(tmpDir)) || (path.Base(configDir) != "repo") {
return false, fmt.Sprintf("expected config directory path: %v to be under: %v and terminate with repo", configDir, tmpDir)
}
if _, err := os.Stat(path.Join(configDir, "asdczxc")); err == nil || !os.IsNotExist(err) {
return false, fmt.Sprintf(`expected to NOT find file: "asdczxc"" under directory: %v`, configDir)
}
if info, err := os.Stat(path.Join(configDir, "sad")); err != nil || !info.Mode().IsRegular() {
return false, fmt.Sprintf(`expected to find file: "sad"" under directory: %v`, configDir)
}
return true, ""
}
var testCases = []struct {
name string
setup func(t *testing.T) (gitUrl, gitRef, profileEntered string)
verify func(configDir string, isTemporary bool, profile string) (ok bool, reason string, err error)
cleanup func(configDir string) error
}{
{
name: "config in current directory and default profile",
setup: func(t *testing.T) (gitUrl, gitRef, profileEntered string) {
currentDirectory, err := os.Getwd()
if err != nil {
t.Fatalf("error obtaining current directory: %v\n", err)
}
defaultProfilePath := path.Join(currentDirectory, "manifests", "docker-desktop")
err = os.MkdirAll(defaultProfilePath, os.ModePerm)
if err != nil {
t.Fatalf("error making path: %v, err: %v\n", defaultProfilePath, err)
}
return "no-clone-for-you", "", ""
},
verify: func(configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
currentDirectory, err := os.Getwd()
if err != nil {
return false, "", err
}
if configDir != currentDirectory {
return false, fmt.Sprintf("expected config directory: %v to equal current directory: %v", configDir, currentDirectory), nil
}
if isTemporary {
return false, "expected isTemporary to be false", nil
}
if profile != "docker-desktop" {
return false, fmt.Sprintf("expected profile to be: docker-desktop, but it was: %v", profile), nil
}
return true, "", nil
},
cleanup: func(configDir string) error {
if currentDirectory, err := os.Getwd(); err != nil {
return err
} else if err := os.RemoveAll(path.Join(currentDirectory, "manifests")); err != nil {
return err
}
return nil
},
},
{
name: "config in current directory and profile specified",
setup: func(t *testing.T) (gitUrl, gitRef, profileEntered string) {
currentDirectory, err := os.Getwd()
if err != nil {
t.Fatalf("error obtaining current directory: %v\n", err)
}
profileEntered = "foo"
defaultProfilePath := path.Join(currentDirectory, "manifests", profileEntered)
err = os.MkdirAll(defaultProfilePath, os.ModePerm)
if err != nil {
t.Fatalf("error making path: %v, err: %v\n", defaultProfilePath, err)
}
return "no-clone-for-you", "", profileEntered
},
verify: func(configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
currentDirectory, err := os.Getwd()
if err != nil {
return false, "", err
}
if configDir != currentDirectory {
return false, fmt.Sprintf("expected config directory: %v to equal current directory: %v", configDir, currentDirectory), nil
}
if isTemporary {
return false, "expected isTemporary to be false", nil
}
if profile != "foo" {
return false, fmt.Sprintf("expected profile to be: foo, but it was: %v", profile), nil
}
return true, "", nil
},
cleanup: func(configDir string) error {
if currentDirectory, err := os.Getwd(); err != nil {
return err
} else if err := os.RemoveAll(path.Join(currentDirectory, "manifests")); err != nil {
return err
}
return nil
},
},
{
name: "config downloaded from git based on specific git ref and default profile used",
setup: func(t *testing.T) (gitUrl, gitRef, profileEntered string) {
return "https://github.com/test/HelloWorld", "asd", ""
},
verify: func(configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
ok, reason = verifyAsdBranch(configDir)
if !ok {
return ok, reason, nil
}
if !isTemporary {
return false, "expected isTemporary to be true", nil
}
if profile != "docker-desktop" {
return false, fmt.Sprintf("expected profile to be: docker-desktop, but it was: %v", profile), nil
}
return true, "", nil
},
cleanup: func(configDir string) error {
tmpDir := os.TempDir()
if path.Clean(path.Dir(path.Dir(configDir))) == path.Clean(tmpDir) && path.Base(configDir) == "repo" {
tmpTmpDir := path.Dir(configDir)
if err := os.RemoveAll(tmpTmpDir); err != nil {
return err
}
}
return nil
},
},
{
name: "config downloaded from git based on specific git ref and profile specified",
setup: func(t *testing.T) (gitUrl, gitRef, profileEntered string) {
return "https://github.com/test/HelloWorld", "asd", "foo"
},
verify: func(configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
ok, reason = verifyAsdBranch(configDir)
if !ok {
return ok, reason, nil
}
if !isTemporary {
return false, "expected isTemporary to be true", nil
}
if profile != "foo" {
return false, fmt.Sprintf("expected profile to be: foo, but it was: %v", profile), nil
}
return true, "", nil
},
cleanup: func(configDir string) error {
tmpDir := os.TempDir()
if path.Clean(path.Dir(path.Dir(configDir))) == path.Clean(tmpDir) && path.Base(configDir) == "repo" {
tmpTmpDir := path.Dir(configDir)
if err := os.RemoveAll(tmpTmpDir); err != nil {
return err
}
}
return nil
},
},
{
name: "config downloaded from git from master branch and default profile used",
setup: func(t *testing.T) (gitUrl, gitRef, profileEntered string) {
return "https://github.com/test/HelloWorld", "", ""
},
verify: func(configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
ok, reason = verifyMasterBranch(configDir)
if !ok {
return ok, reason, nil
}
if !isTemporary {
return false, "expected isTemporary to be true", nil
}
if profile != "docker-desktop" {
return false, fmt.Sprintf("expected profile to be: docker-desktop, but it was: %v", profile), nil
}
return true, "", nil
},
cleanup: func(configDir string) error {
tmpDir := os.TempDir()
if path.Clean(path.Dir(path.Dir(configDir))) == path.Clean(tmpDir) && path.Base(configDir) == "repo" {
tmpTmpDir := path.Dir(configDir)
if err := os.RemoveAll(tmpTmpDir); err != nil {
return err
}
}
return nil
},
},
{
name: "config downloaded from git from master branch and profile specified",
setup: func(t *testing.T) (gitUrl, gitRef, profileEntered string) {
return "https://github.com/test/HelloWorld", "", "foo"
},
verify: func(configDir string, isTemporary bool, profile string) (ok bool, reason string, err error) {
ok, reason = verifyMasterBranch(configDir)
if !ok {
return ok, reason, nil
}
if !isTemporary {
return false, "expected isTemporary to be true", nil
}
if profile != "foo" {
return false, fmt.Sprintf("expected profile to be: foo, but it was: %v", profile), nil
}
return true, "", nil
},
cleanup: func(configDir string) error {
tmpDir := os.TempDir()
if path.Clean(path.Dir(path.Dir(configDir))) == path.Clean(tmpDir) && path.Base(configDir) == "repo" {
tmpTmpDir := path.Dir(configDir)
if err := os.RemoveAll(tmpTmpDir); err != nil {
return err
}
}
return nil
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
configDirectory, isTemporary, profile, err := getConfigDirectory(testCase.setup(t))
if err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
if ok, reason, err := testCase.verify(configDirectory, isTemporary, profile); err != nil {
t.Fatalf("unexpected verification error: %v\n", err)
} else if !ok {
t.Fatalf("verification failed: %v\n", reason)
} else if err := testCase.cleanup(configDirectory); err != nil {
t.Fatalf("unexpected cleanup error: %v\n", err)
}
})
}
}

View File

@@ -1,82 +0,0 @@
package qliksense
import (
"fmt"
"path/filepath"
"github.com/qlik-oss/k-apis/pkg/cr"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
"gopkg.in/yaml.v2"
)
const (
Q_INIT_CRD_PATH = "manifests/base/manifests/qliksense-init"
)
func (q *Qliksense) ConfigApplyQK8s() error {
//get the current context cr
qConfig := qapi.NewQConfig(q.QliksenseHome)
qcr, err := qConfig.GetCurrentCR()
if err != nil {
fmt.Println("cannot get the current-context cr", err)
return err
}
return applyConfigToK8s(qcr)
}
func applyConfigToK8s(qcr *qapi.QliksenseCR) error {
// apply qliksense-init crd first
mroot := qcr.Spec.GetManifestsRoot()
qInitMsPath := filepath.Join(mroot, Q_INIT_CRD_PATH)
qInitByte, err := executeKustomizeBuild(qInitMsPath)
if err != nil {
fmt.Println("cannot generate crds for qliksense-init", err)
return err
}
if err = qapi.KubectlApply(string(qInitByte)); err != nil {
return err
}
// generate patches
cr.GeneratePatches(qcr.Spec)
// apply generated manifests
profilePath := filepath.Join(qcr.Spec.ManifestsRoot, qcr.Spec.Profile)
mByte, err := executeKustomizeBuild(profilePath)
if err != nil {
fmt.Println("cannot generate manifests for "+profilePath, err)
return err
}
if err = qapi.KubectlApply(string(mByte)); err != nil {
return err
}
return nil
}
func (q *Qliksense) ConfigViewCR() error {
//get the current context cr
r, err := q.getCurrentCRString()
if err != nil {
return err
}
fmt.Println(r)
return nil
}
func (q *Qliksense) getCurrentCRString() (string, error) {
qConfig := qapi.NewQConfig(q.QliksenseHome)
qcr, err := qConfig.GetCurrentCR()
if err != nil {
fmt.Println("cannot get the current-context cr", err)
return "", err
}
out, err := yaml.Marshal(qcr)
if err != nil {
fmt.Println("cannot unmarshal cr ", err)
return "", err
}
return string(out), nil
}

View File

@@ -1,411 +1,253 @@
package qliksense
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
"github.com/containers/image/v5/copy"
"github.com/containers/image/v5/signature"
"github.com/containers/image/v5/transports/alltransports"
imageTypes "github.com/containers/image/v5/types"
"github.com/docker/cli/cli/command"
cliflags "github.com/docker/cli/cli/flags"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/registry"
homedir "github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"golang.org/x/net/context"
yaml "gopkg.in/yaml.v2"
)
// PullImages ...
func (p *Qliksense) PullImages(gitRef, profile string, engine bool) error {
var (
image, versionFile, imagesDir, homeDir string
err error
versionOut *VersionOutput
)
println("getting images list...")
// TODO: get getref and profile from config/cr for About function call
if versionOut, err = p.About(gitRef, profile); err != nil {
return err
}
if homeDir, err = homedir.Dir(); err != nil {
return err
}
imagesDir = filepath.Join(homeDir, ".qliksense", "images")
os.MkdirAll(imagesDir, os.ModePerm)
versionFile = filepath.Join(imagesDir, versionOut.QliksenseVersion)
if _, err = os.Stat(versionFile); err != nil {
if os.IsNotExist(err) {
if yamlVersion, err := yaml.Marshal(versionOut); err != nil {
return err
} else if err = ioutil.WriteFile(versionFile, yamlVersion, os.ModePerm); err != nil {
return err
}
} else {
return errors.Errorf("Unable to determine About file %v exists", versionFile)
}
}
for _, image = range versionOut.Images {
if _, err = p.PullImage(image, engine); err != nil {
fmt.Print(err)
}
println("---")
}
return nil
}
// PullImage ...
func (p *Qliksense) PullImage(imageName string, engine bool) (map[string]string, error) {
if engine {
return p.pullDockerImage(imageName)
}
return p.pullImage(imageName)
}
func (p *Qliksense) commandTimeoutContext(commandTimeout time.Duration) (context.Context, context.CancelFunc) {
ctx := context.Background()
var cancel context.CancelFunc = func() {}
if commandTimeout > 0 {
ctx, cancel = context.WithTimeout(ctx, commandTimeout)
}
return ctx, cancel
}
func (p *Qliksense) pullImage(imageName string) (map[string]string, error) {
var (
ctx context.Context
cancel context.CancelFunc
srcRef, destRef imageTypes.ImageReference
blobDir, targetDir, homeDir string
segments []string
nameTag []string
err error
policyContext *signature.PolicyContext
)
ctx, cancel = p.commandTimeoutContext(0)
defer cancel()
if srcRef, err = alltransports.ParseImageName("docker://" + imageName); err != nil {
return nil, err
}
segments = strings.Split(imageName, "/")
nameTag = strings.Split(segments[len(segments)-1], ":")
if len(nameTag) < 2 {
nameTag = append(nameTag, "latest")
}
if homeDir, err = homedir.Dir(); err != nil {
return nil, err
}
targetDir = filepath.Join(homeDir, ".qliksense", "images", nameTag[0], nameTag[1])
fmt.Printf("==> Pulling image %v:%v", nameTag[0], nameTag[1])
fmt.Println()
os.MkdirAll(targetDir, os.ModePerm)
blobDir = filepath.Join(homeDir, ".qliksense", "blobs")
os.MkdirAll(blobDir, os.ModePerm)
if destRef, err = alltransports.ParseImageName("oci:" + targetDir); err != nil {
return nil, err
}
if policyContext, err = signature.NewPolicyContext(&signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}}); err != nil {
return nil, err
}
defer policyContext.Destroy()
_, err = copy.Image(ctx, policyContext, destRef, srcRef, &copy.Options{
ReportWriter: os.Stdout,
SourceCtx: &imageTypes.SystemContext{
ArchitectureChoice: "amd64",
OSChoice: "linux",
},
DestinationCtx: &imageTypes.SystemContext{
OCISharedBlobDirPath: blobDir,
},
})
return nil, err
}
func (p *Qliksense) pullDockerImage(imageName string) (map[string]string, error) {
var (
cli *command.DockerCli
dockerOutput io.Writer
response io.ReadCloser
pullOptions types.ImagePullOptions
ctx context.Context
cancel context.CancelFunc
ref reference.Named
repoInfo *registry.RepositoryInfo
authConfig types.AuthConfig
encodedAuth string
termFd uintptr
err error
)
ctx, cancel = p.commandTimeoutContext(0)
defer cancel()
if cli, err = command.NewDockerCli(); err != nil {
return nil, err
}
if err = cli.Initialize(cliflags.NewClientOptions()); err != nil {
return nil, err
}
if ref, err = reference.ParseNormalizedNamed(imageName); err != nil {
return nil, err
}
if repoInfo, err = registry.ParseRepositoryInfo(ref); err != nil {
return nil, err
}
authConfig = command.ResolveAuthConfig(ctx, cli, repoInfo.Index)
if encodedAuth, err = command.EncodeAuthToBase64(authConfig); err != nil {
return nil, err
}
pullOptions = types.ImagePullOptions{
RegistryAuth: encodedAuth,
}
if response, err = cli.Client().ImagePull(ctx, imageName, pullOptions); err != nil {
return nil, err
}
defer response.Close()
dockerOutput = ioutil.Discard
// if b.IsVerbose() {
// dockerOutput = b.Out
// }
dockerOutput = os.Stdout
termFd, _ = term.GetFdInfo(dockerOutput)
// Setting this to false here because Moby os.Exit(1) all over the place and this fails on WSL (only)
// when Term is true.
isTerm := false
if err = jsonmessage.DisplayJSONMessagesStream(response, dockerOutput, termFd, isTerm, nil); err != nil {
return nil, err
}
inspectData, _, err := cli.Client().ImageInspectWithRaw(ctx, imageName)
if err != nil {
return nil, err
}
return inspectData.ContainerConfig.Labels, nil
}
//TagAndPushImages ...
func (p *Qliksense) TagAndPushImages(registry string, engine bool) error {
var (
image string
err error
yamlVersion string
images VersionOutput
)
if err = yaml.Unmarshal([]byte(yamlVersion), &images); err != nil {
return err
}
for _, image = range images.Images {
if err = p.TagAndPush(image, registry, engine); err != nil {
fmt.Print(err)
}
println("---")
}
return nil
}
func (p *Qliksense) directoryExists(path string) (exists bool, err error) {
if info, err := os.Stat(path); err != nil && os.IsNotExist(err) {
exists = false
err = nil
} else if err != nil && !os.IsNotExist(err) {
exists = false
} else if err == nil && info.IsDir() {
exists = true
} else if err == nil && !info.IsDir() {
exists = false
err = fmt.Errorf("path: %v is occupied by a file instead of a directory", path)
}
return exists, err
}
//TagAndPush ...
func (p *Qliksense) TagAndPush(image string, registryName string, engine bool) error {
if engine {
return p.tagAndDockerPush(image, registryName)
}
return p.tagAndPush(image, registryName)
}
func (p *Qliksense) tagAndPush(image string, registryName string) error {
var (
ctx context.Context
cancel context.CancelFunc
srcRef, destRef imageTypes.ImageReference
blobDir, srcDir, homeDir, newName string
segments []string
nameTag []string
err error
policyContext *signature.PolicyContext
srcExists bool
)
ctx, cancel = p.commandTimeoutContext(0)
defer cancel()
segments = strings.Split(image, "/")
nameTag = strings.Split(segments[len(segments)-1], ":")
if len(nameTag) < 2 {
nameTag = append(nameTag, "latest")
}
if homeDir, err = homedir.Dir(); err != nil {
return err
}
srcDir = filepath.Join(homeDir, ".qliksense", "images", nameTag[0], nameTag[1])
if srcExists, err = p.directoryExists(srcDir); err != nil {
return err
}
if !srcExists {
if _, err = p.PullImage(image, false); err != nil {
return err
}
}
if srcRef, err = alltransports.ParseImageName("oci:" + srcDir); err != nil {
return err
}
if segments[0] == "docker.io" {
image = strings.Join(segments[1:], "/")
}
newName = "//" + registryName + "/" + segments[len(segments)-1]
fmt.Printf("==> Tag and push image to %v", newName)
fmt.Println()
if destRef, err = alltransports.ParseImageName("docker:" + newName); err != nil {
return err
}
if policyContext, err = signature.NewPolicyContext(&signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}}); err != nil {
return err
}
defer policyContext.Destroy()
blobDir = filepath.Join(homeDir, ".qliksense", "blobs")
os.MkdirAll(blobDir, os.ModePerm)
_, err = copy.Image(ctx, policyContext, destRef, srcRef, &copy.Options{
ReportWriter: os.Stdout,
SourceCtx: &imageTypes.SystemContext{
OCISharedBlobDirPath: blobDir,
},
DestinationCtx: &imageTypes.SystemContext{
DockerDaemonInsecureSkipTLSVerify: true,
},
})
return err
}
// PullImage ...
func (p *Qliksense) tagAndDockerPush(image string, registryName string) error {
var (
cli *command.DockerCli
dockerOutput io.Writer
response io.ReadCloser
pushOptions types.ImagePushOptions
ctx context.Context
cancel context.CancelFunc
newName string
segments []string
imageList []types.ImageSummary
imageListOptions types.ImageListOptions
filterArgs filters.Args
ref reference.Named
repoInfo *registry.RepositoryInfo
authConfig types.AuthConfig
encodedAuth string
termFd uintptr
err error
)
// TODO: Create a real cli config context
ctx, cancel = p.commandTimeoutContext(0)
defer cancel()
if cli, err = command.NewDockerCli(); err != nil {
return err
}
if err = cli.Initialize(cliflags.NewClientOptions()); err != nil {
return err
}
segments = strings.Split(image, "/")
if segments[0] == "docker.io" {
image = strings.Join(segments[1:], "/")
}
newName = registryName + "/" + segments[len(segments)-1]
filterArgs = filters.NewArgs()
filterArgs.Add("reference", image)
imageListOptions = types.ImageListOptions{
Filters: filterArgs,
}
if imageList, err = cli.Client().ImageList(ctx, imageListOptions); err != nil {
return err
}
if imageList == nil || len(imageList) <= 0 {
fmt.Printf("Use `qliksense pull`, to pull %v for an air gap push", newName)
return nil
}
if err = cli.Client().ImageTag(ctx, image, newName); err != nil {
return err
}
if ref, err = reference.ParseNormalizedNamed(image); err != nil {
return err
}
if repoInfo, err = registry.ParseRepositoryInfo(ref); err != nil {
return err
}
authConfig = command.ResolveAuthConfig(ctx, cli, repoInfo.Index)
if encodedAuth, err = command.EncodeAuthToBase64(authConfig); err != nil {
return err
}
pushOptions = types.ImagePushOptions{
All: true,
RegistryAuth: encodedAuth,
}
if response, err = cli.Client().ImagePush(ctx, newName, pushOptions); err != nil {
return err
}
defer response.Close()
dockerOutput = ioutil.Discard
// if b.IsVerbose() {
// dockerOutput = b.Out
// }
dockerOutput = os.Stdout
termFd, _ = term.GetFdInfo(dockerOutput)
// Setting this to false here because Moby os.Exit(1) all over the place and this fails on WSL (only)
// when Term is true.
isTerm := false
if err = jsonmessage.DisplayJSONMessagesStream(response, dockerOutput, termFd, isTerm, nil); err != nil {
return err
}
return nil
}
package qliksense
import (
"fmt"
"io"
"io/ioutil"
"os"
"github.com/docker/cli/cli/command"
cliflags "github.com/docker/cli/cli/flags"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/registry"
"strings"
"golang.org/x/net/context"
yaml "gopkg.in/yaml.v2"
)
// Images ...
type Images struct {
Images []string `yaml:"images"`
}
// PullImages ...
func (p *Qliksense) PullImages(args []string) error {
var (
image string
err error
yamlVersion string
valid bool
images Images
)
println("getting images list...")
if yamlVersion, err = p.CallPorter(append([]string{"invoke", "--action", "about"}, args...),
func(x string) (out *string) {
if strings.HasPrefix(x, "qlikSenseVersion") {
valid = true
}
if strings.HasPrefix(x, "execution") {
valid = false
}
if valid {
return &x
}
return nil
}); err != nil {
return err
}
if err = yaml.Unmarshal([]byte(yamlVersion), &images); err != nil {
return err
}
for _, image = range images.Images {
if err = p.PullImage(image); err != nil {
fmt.Print(err)
}
println("---")
}
return nil
}
// PullImage ...
func (p *Qliksense) PullImage(imageName string) error {
var (
cli *command.DockerCli
dockerOutput io.Writer
response io.ReadCloser
pullOptions types.ImagePullOptions
ctx context.Context
ref reference.Named
repoInfo *registry.RepositoryInfo
authConfig types.AuthConfig
encodedAuth string
termFd uintptr
err error
)
// TODO: Create a real cli config context
ctx = context.Background()
if cli, err = command.NewDockerCli(); err != nil {
return err
}
if err = cli.Initialize(cliflags.NewClientOptions()); err != nil {
return err
}
if ref, err = reference.ParseNormalizedNamed(imageName); err != nil {
return err
}
if repoInfo, err = registry.ParseRepositoryInfo(ref); err != nil {
return err
}
authConfig = command.ResolveAuthConfig(ctx, cli, repoInfo.Index)
if encodedAuth, err = command.EncodeAuthToBase64(authConfig); err != nil {
return err
}
pullOptions = types.ImagePullOptions{
RegistryAuth: encodedAuth,
}
if response, err = cli.Client().ImagePull(ctx, imageName, pullOptions); err != nil {
return err
}
defer response.Close()
dockerOutput = ioutil.Discard
// if b.IsVerbose() {
// dockerOutput = b.Out
// }
dockerOutput = os.Stdout
termFd, _ = term.GetFdInfo(dockerOutput)
// Setting this to false here because Moby os.Exit(1) all over the place and this fails on WSL (only)
// when Term is true.
isTerm := false
if err = jsonmessage.DisplayJSONMessagesStream(response, dockerOutput, termFd, isTerm, nil); err != nil {
return err
}
return nil
}
// PullImage ...
func (p *Qliksense) TagAndPushImages(registry string) error {
var (
image string
err error
yamlVersion string
valid bool
images Images
)
if yamlVersion, err = p.CallPorter([]string{"invoke", "--action", "about"},
func(x string) (out *string) {
if strings.HasPrefix(x, "qlikSenseVersion") {
valid = true
}
if strings.HasPrefix(x, "execution") {
valid = false
}
if valid {
return &x
}
return nil
}); err != nil {
return err
}
if err = yaml.Unmarshal([]byte(yamlVersion), &images); err != nil {
return err
}
for _, image = range images.Images {
if err = p.TagAndPush(image, registry); err != nil {
fmt.Print(err)
}
println("---")
}
return nil
}
// PullImage ...
func (p *Qliksense) TagAndPush(image string, registryName string) error {
var (
cli *command.DockerCli
dockerOutput io.Writer
response io.ReadCloser
pushOptions types.ImagePushOptions
ctx context.Context
newName string
segments []string
imageList []types.ImageSummary
imageListOptions types.ImageListOptions
filterArgs filters.Args
ref reference.Named
repoInfo *registry.RepositoryInfo
authConfig types.AuthConfig
encodedAuth string
termFd uintptr
err error
)
// TODO: Create a real cli config context
ctx = context.Background()
if cli, err = command.NewDockerCli(); err != nil {
return err
}
if err = cli.Initialize(cliflags.NewClientOptions()); err != nil {
return err
}
segments = strings.Split(image, "/")
if segments[0] == "docker.io" {
image = strings.Join(segments[1:], "/")
}
newName = registryName + "/" + segments[len(segments)-1]
filterArgs = filters.NewArgs()
filterArgs.Add("reference", image)
imageListOptions = types.ImageListOptions{
Filters: filterArgs,
}
if imageList, err = cli.Client().ImageList(ctx, imageListOptions); err != nil {
return err
}
if imageList == nil || len(imageList) <= 0 {
fmt.Printf("Use `qliksense pull`, to pull %v for an air gap push", newName)
return nil
}
if err = cli.Client().ImageTag(ctx, image, newName); err != nil {
return err
}
if ref, err = reference.ParseNormalizedNamed(image); err != nil {
return err
}
if repoInfo, err = registry.ParseRepositoryInfo(ref); err != nil {
return err
}
authConfig = command.ResolveAuthConfig(ctx, cli, repoInfo.Index)
if encodedAuth, err = command.EncodeAuthToBase64(authConfig); err != nil {
return err
}
pushOptions = types.ImagePushOptions{
All: true,
RegistryAuth: encodedAuth,
}
if response, err = cli.Client().ImagePush(ctx, newName, pushOptions); err != nil {
return err
}
defer response.Close()
dockerOutput = ioutil.Discard
// if b.IsVerbose() {
// dockerOutput = b.Out
// }
dockerOutput = os.Stdout
termFd, _ = term.GetFdInfo(dockerOutput)
// Setting this to false here because Moby os.Exit(1) all over the place and this fails on WSL (only)
// when Term is true.
isTerm := false
if err = jsonmessage.DisplayJSONMessagesStream(response, dockerOutput, termFd, isTerm, nil); err != nil {
return err
}
return nil
}

View File

@@ -1,10 +0,0 @@
package qliksense
import (
"fmt"
)
func (q *Qliksense) FetchQK8s(version string) {
//io.WriteString(os.Stdout, q.GetCRDString())
fmt.Println(version)
}

View File

@@ -1,52 +0,0 @@
package qliksense
import (
"fmt"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func (q *Qliksense) InstallQK8s(version string) 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()
//TODO: may need to check if CRD already installed, but doing apply does not hurt for now
//install crd into cluster
fmt.Println("Installing operator CRD")
if err := qapi.KubectlApply(q.GetCRDString()); err != nil {
fmt.Println("cannot do kubectl apply on opeartor CRD", err)
return err
}
// install generated manifests into cluster
fmt.Println("Installing generated manifests into cluster")
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 {
fmt.Println("cannot do kubectl apply on manifests")
return err
}
// install operator cr into cluster
//get the current context cr
fmt.Println("Install operator CR into cluster")
r, err := q.getCurrentCRString()
if err != nil {
return err
}
if err := qapi.KubectlApply(r); err != nil {
fmt.Println("cannot do kubectl apply on operator CR")
}
return nil
}

View File

@@ -1,32 +0,0 @@
package qliksense
import (
"log"
"os"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/krusty"
"sigs.k8s.io/kustomize/api/types"
)
func executeKustomizeBuild(directory string) ([]byte, error) {
log.SetOutput(&nullWriter{})
defer func() {
log.SetOutput(os.Stderr)
}()
fSys := filesys.MakeFsOnDisk()
options := &krusty.Options{
DoLegacyResourceSort: false,
LoadRestrictions: types.LoadRestrictionsNone,
DoPrune: false,
PluginConfig: konfig.DisabledPluginConfig(),
}
k := krusty.MakeKustomizer(fSys, options)
resMap, err := k.Run(directory)
if err != nil {
return nil, err
}
return resMap.AsYaml()
}

View File

@@ -1,71 +0,0 @@
package qliksense
import (
"io/ioutil"
"os"
"path"
"testing"
kapis_git "github.com/qlik-oss/k-apis/pkg/git"
)
func Test_executeKustomizeBuild(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
defer os.RemoveAll(tmpDir)
kustomizationYamlFilePath := path.Join(tmpDir, "kustomization.yaml")
kustomizationYaml := `
generatorOptions:
disableNameSuffixHash: true
configMapGenerator:
- name: foo-config
literals:
- foo=bar
`
err = ioutil.WriteFile(kustomizationYamlFilePath, []byte(kustomizationYaml), os.ModePerm)
if err != nil {
t.Fatalf("error writing kustomization file to path: %v error: %v\n", kustomizationYamlFilePath, err)
}
result, err := executeKustomizeBuild(tmpDir)
if err != nil {
t.Fatalf("unexpected kustomize error: %v\n", err)
}
expectedK8sYaml := `apiVersion: v1
data:
foo: bar
kind: ConfigMap
metadata:
name: foo-config
`
if string(result) != expectedK8sYaml {
t.Fatalf("expected k8s yaml: [%v] but got: [%v]\n", expectedK8sYaml, string(result))
}
}
func Test_executeKustomizeBuild_onQlikConfig_DISABLED(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
}
tmpDir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
defer os.RemoveAll(tmpDir)
configPath := path.Join(tmpDir, "config")
if repo, err := kapis_git.CloneRepository(configPath, defaultGitUrl, nil); err != nil {
t.Fatalf("unexpected error: %v\n", err)
} else if err := kapis_git.Checkout(repo, "v1.21.23-edge", "", nil); err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
if _, err := executeKustomizeBuild(path.Join(configPath, "manifests", "base")); err != nil {
t.Fatalf("unexpected kustomize error: %v\n", err)
}
}

View File

@@ -1,40 +0,0 @@
package qliksense
import (
"fmt"
"io"
"os"
"strings"
)
func (q *Qliksense) ViewOperatorCrd() {
io.WriteString(os.Stdout, q.GetCRDString())
}
// this will return crd,deployment,role, rolebinding,serviceaccount for operator
func (q *Qliksense) GetCRDString() string {
result := ""
for _, v := range q.getFileList("crd") {
result = q.getYamlFromPackrFile(v)
}
for _, v := range q.getFileList("crd-deploy") {
result = result + q.getYamlFromPackrFile(v)
}
return result
}
func (q *Qliksense) getYamlFromPackrFile(packrFile string) string {
s, err := q.CrdBox.FindString(packrFile)
if err != nil {
fmt.Printf("Cannot read file %s", packrFile)
}
return fmt.Sprintln("#soruce: " + packrFile + "\n\n" + s + "\n---")
}
func (q *Qliksense) getFileList(resourceType string) []string {
var resList []string
for _, v := range q.CrdBox.List() {
if strings.Contains(v, resourceType+"/") {
resList = append(resList, []string{v}...)
}
}
return resList
}

60
pkg/qliksense/porter.go Normal file
View File

@@ -0,0 +1,60 @@
package qliksense
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
)
// ProcessLine ...
type ProcessLine func(string) *string
// CallPorter ...
func (p *Qliksense) CallPorter(args []string, processor ProcessLine) (string, error) {
var (
outText string
cmd *exec.Cmd
err error
output io.ReadCloser
scanner *bufio.Scanner
done chan struct{}
)
cmd = exec.Command(p.porterExe, args[:]...)
if output, err = cmd.StdoutPipe(); err != nil {
return "", err
}
cmd.Stderr = os.Stderr
done = make(chan struct{})
scanner = bufio.NewScanner(output)
go func() {
for scanner.Scan() {
var text string
var newText *string
text = scanner.Text()
if processor != nil {
newText = processor(text)
if newText != nil {
outText = outText + fmt.Sprintln(*newText)
}
} else {
outText = outText + fmt.Sprintln(text)
}
}
done <- struct{}{}
}()
if err = cmd.Start(); err != nil {
return "", err
}
<-done
if err = cmd.Wait(); err != nil {
return "", err
}
if err = scanner.Err(); err != nil {
return "", err
}
return outText, nil
}

View File

@@ -1,20 +1,13 @@
//go:generate packr2
package qliksense
import (
"github.com/gobuffalo/packr/v2"
)
// Qliksense is the logic behind the qliksense client
type Qliksense struct {
QliksenseHome string
CrdBox *packr.Box
}
// New qliksense client, initialized with useful defaults.
func New(qliksenseHome string) *Qliksense {
return &Qliksense{
QliksenseHome: qliksenseHome,
CrdBox: packr.New("crds", "./crds"),
}
}
package qliksense
// Qliksense is the logic behind the qliksense client
type Qliksense struct {
porterExe string
}
// New qliksense client, initialized with useful defaults.
func New(porterExe string) *Qliksense {
return &Qliksense{
porterExe,
}
}

View File

@@ -1,8 +1,8 @@
package pkg
// These are build-time values, set during an official release
var (
Commit string
Version string
CommitDate string
)
package pkg
// These are build-time values, set during an official release
var (
Commit string
Version string
CommitDate string
)

39
test/Dockerfile.tmpl Normal file
View File

@@ -0,0 +1,39 @@
FROM quay.io/deis/lightweight-docker-go:v0.2.0
FROM debian:stretch as base
# This is a template Dockerfile for the bundle's invocation image
# You can customize it to use different base images, install tools and copy configuration files.
#
# Porter will use it as a template and append lines to it for the mixins
# and to set the CMD appropriately for the CNAB specification.
#
# Add the following line to porter.yaml to instruct Porter to use this template
# dockerfile: Dockerfile.tmpl
# You can control where the mixin's Dockerfile lines are inserted into this file by moving "# PORTER_MIXINS" line
# another location in this file. If you remove that line, the mixins generated content is appended to this file.
# PORTER_MIXINS
COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
ARG BUNDLE_DIR
ARG HELM_VERSION=v2.16.0
RUN apt-get update && \
apt-get install -y curl && \
curl -o helm.tgz https://get.helm.sh/helm-$HELM_VERSION-linux-amd64.tar.gz && \
tar -xzf helm.tgz && \
mv linux-amd64/helm /usr/local/bin && \
rm helm.tgz
RUN helm init --client-only
# CI job will update this version and make a PR
ARG QSEOK_VERSION=1.12.89
# This is likely not needed
ARG QLIKSENSE_INIT_VERSION=1.1.0
RUN mkdir -p /tmp/.chartcache
RUN helm repo add qlik-edge https://qlik.bintray.com/edge
RUN helm fetch qlik-edge/qliksense-init --version $QLIKSENSE_INIT_VERSION --untar -d /tmp/.chartcache/
# This is now done by qliksense mixin
#RUN helm fetch qlik-edge/qliksense --version $QSEOK_VERSION --untar -d /tmp/.chartcache/
# Use the BUNDLE_DIR build argument to copy files into the bundle
COPY . $BUNDLE_DIR

42
test/porter.yaml Normal file
View File

@@ -0,0 +1,42 @@
name: test-version
version: 0.1.0
description: "An example Porter bundle demonstrating exec mixin outputs"
invocationImage: qlik/test-version:0.1.0
tag: qlik/test-version-bundle:0.1.0
dockerfile: Dockerfile.tmpl
mixins:
- exec
- qliksense
customActions:
about:
description: "About QLiksense"
stateless: true
modifies: true
install:
- exec:
description: "Get the qliksense version"
command: bash
arguments:
- -c
- "echo 1.2.13"
outputs:
- name: version
regex: '.*'
uninstall:
- exec:
description: "Get the qliksense version"
command: bash
arguments:
- -c
- "echo 1.2.13"
outputs:
- name: version
regex: '.*'
about:
- qliksense:
description: "Use bundled version by default"
version: "bundled"