Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e26ca77b05 | ||
|
|
644498ddb8 | ||
|
|
bcf2b1ab4b | ||
|
|
0545fd7d16 | ||
|
|
ea240ce3f1 | ||
|
|
d6a16cea8b | ||
|
|
ee557c2068 | ||
|
|
fb14d30328 | ||
|
|
ca83942fbe | ||
|
|
7f68dad586 | ||
|
|
fdc2877174 | ||
|
|
b9b7068689 | ||
|
|
cada3690e1 | ||
|
|
947486d347 | ||
|
|
793f6e9f36 | ||
|
|
a569bc1ddd | ||
|
|
f5165fbeea | ||
|
|
4437b31592 | ||
|
|
a943efe5df | ||
|
|
70aea58d3a | ||
|
|
f5dadd522a | ||
|
|
deb103c592 | ||
|
|
dd25d07fcb | ||
|
|
0328607a77 | ||
|
|
550aea24d6 | ||
|
|
edca43ca4f | ||
|
|
513125a9ca | ||
|
|
87ebd74daf | ||
|
|
39d02db187 | ||
|
|
0393a431fb | ||
|
|
d1088e2635 | ||
|
|
dabaed4c07 | ||
|
|
54d0972e85 | ||
|
|
2839e5b77b | ||
|
|
3608f9693c | ||
|
|
60a328d69f | ||
|
|
ee02016f16 | ||
|
|
e5024b7f0a | ||
|
|
d408d33971 | ||
|
|
cff29ed862 | ||
|
|
e5108e0040 | ||
|
|
0372b82b49 | ||
|
|
48ca84f5f8 | ||
|
|
97a137ff23 | ||
|
|
776c91d300 | ||
|
|
c37f5338dc | ||
|
|
0cf8a9b4c9 | ||
|
|
75fcb93c33 | ||
|
|
465095e4ee |
@@ -9,6 +9,7 @@ jobs:
|
||||
working_directory: /go/src/github.com/qlik-oss/sense-installer
|
||||
steps:
|
||||
- checkout
|
||||
- run: make test
|
||||
- run: make build
|
||||
build_release:
|
||||
docker:
|
||||
@@ -16,34 +17,27 @@ jobs:
|
||||
working_directory: /go/src/github.com/qlik-oss/sense-installer
|
||||
steps:
|
||||
- checkout
|
||||
- run: make test
|
||||
- run: make xbuild-all
|
||||
- run:
|
||||
name: "Publish Release on GitHub"
|
||||
command: |
|
||||
go get github.com/tcnksm/ghr
|
||||
# VERSION=v$(./artifacts/qliksense-linux-amd64 version | sed -nre 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*/\1/p')
|
||||
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}/
|
||||
ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${CIRCLE_TAG} /go/src/github.com/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/bin/${CIRCLE_TAG}/
|
||||
workflows:
|
||||
version: 2
|
||||
commit:
|
||||
jobs:
|
||||
- build:
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
tags:
|
||||
ignore: /^v.*/
|
||||
build_release:
|
||||
jobs:
|
||||
- build:
|
||||
- build_release:
|
||||
filters:
|
||||
tags:
|
||||
only: /^v.*/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /v.*/
|
||||
- build_release:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /v.*/
|
||||
|
||||
|
||||
47
Makefile
47
Makefile
@@ -1,15 +1,17 @@
|
||||
PKG = qlik-oss/sense-installer
|
||||
PKG = github.com/qlik-oss/sense-installer
|
||||
|
||||
# --no-print-directory avoids verbose logging when invoking targets that utilize sub-makes
|
||||
MAKE_OPTS ?= --no-print-directory
|
||||
|
||||
LDFLAGS = -w -X $(PKG)/pkg.Version=$(VERSION) -X $(PKG)/pkg.Commit=$(COMMIT)
|
||||
XBUILD = CGO_ENABLED=0 go build -a -tags netgo -ldflags '$(LDFLAGS)'
|
||||
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)'
|
||||
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)
|
||||
@@ -30,17 +32,48 @@ FILE_EXT=
|
||||
endif
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
build: clean generate
|
||||
mkdir -p $(BINDIR)
|
||||
go build -ldflags '$(LDFLAGS)' -o $(BINDIR)/$(MIXIN)$(FILE_EXT) ./cmd/$(MIXIN)
|
||||
go build -ldflags '$(LDFLAGS)' -tags "$(BUILDTAGS)" -o $(BINDIR)/$(MIXIN)$(FILE_EXT) ./cmd/$(MIXIN)
|
||||
$(MAKE) clean
|
||||
|
||||
xbuild-all:
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -short -count=1 -tags "$(BUILDTAGS)" -v ./...
|
||||
|
||||
xbuild-all: clean generate
|
||||
$(foreach OS, $(SUPPORTED_PLATFORMS), \
|
||||
$(foreach ARCH, $(SUPPORTED_ARCHES), \
|
||||
$(MAKE) $(MAKE_OPTS) CLIENT_PLATFORM=$(OS) CLIENT_ARCH=$(ARCH) MIXIN=$(MIXIN) xbuild; \
|
||||
))
|
||||
|
||||
$(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
|
||||
|
||||
169
README.md
169
README.md
@@ -1,15 +1,166 @@
|
||||
# Qlik Sense installation and operations CLI
|
||||
|
||||
The Qlik Sense installations and operations CLI provides capabilities for installing the Qlik Sense on Kubernetes packaging and performing operations on qliksense.
|
||||
- [Qlik Sense installation and operations CLI](#qlik-sense-installation-and-operations-cli)
|
||||
- [About](#about)
|
||||
- [Future Direction](#future-direction)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Requirements](#requirements)
|
||||
- [Download](#download)
|
||||
- [Porter CLI](#porter-cli)
|
||||
- [Generate Credentials from published bundle](#generate-credentials-from-published-bundle)
|
||||
- [Qlik Sense version and image list](#qliksense-version-and-image-list)
|
||||
- [Optional: Pulling images in manifest locally, "air gap"](#optional-pulling-images-in-manifest-locally-%22air-gap%22)
|
||||
- [Running Preflight checks](#running-preflight-checks)
|
||||
- [Installation](#installation)
|
||||
- [Supported Parameters during install](#supported-parameters-during-install)
|
||||
- [How To Add Identity Provider Config](#how-to-add-identity-provider-config)
|
||||
- [Packaging a Custom bundle](#packaging-a-custom-bundle)
|
||||
|
||||
## About
|
||||
|
||||
## Getting started
|
||||
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).
|
||||
|
||||
Download the appropriate executable for your platform from the [releases page](https://github.com/qlik-oss/sense-installer/releases). When used, the CLI will check to see if porter is installed, if not, will download and install it. Once done, you can find porter through `echo $HOME/.porter` on Linux and MacOS and in `$Env:USERPROFILE\.porter` on Windows. You can also install it in advance, release > 0.22.1-beta.1 is required.
|
||||
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.
|
||||
|
||||
To make sure everything is order, you can fetch the Qlik Sense bundle version and corresponding image list from:
|
||||
- `qliksense about -tag qlik/qliksense-cnab-bundle:latest `
|
||||
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).
|
||||
|
||||
## Qliksense Packaging
|
||||
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 publisked on DockerHub as a [Cloud Natvie Application Bundle](https://cnab.io/) called [qliksense-cnab-bundle](https://hub.docker.com/r/qlik/qliksense-cnab-bundle).
|
||||
### Versioning
|
||||
A version of [qliksense-cnab-bundle](https://hub.docker.com/r/qlik/qliksense-cnab-bundle) is published corresponding to an edge release. To get the latest edge release simply specify `qliksense-cnab-bundle:latest`
|
||||
For each version of a qliksense sense edge build there should be a corresponding release current posted on docker hub. ex. `qlik/qliksense-cnab-bundle:v1.21.23-edge` for `v1.21.23` edge release of qliksense. The latest version posted will also be labelled as `latest`
|
||||
|
||||
### Future Direction
|
||||
|
||||
- Porter is currently used as a core technology for the CLI. In the future Porter will be moved "up the stack" to allow the CLI to perform the current and expanded operations independently and encapsulate core functionality currently provided by Porter and other dependent tooling.
|
||||
- More operations:
|
||||
- Expanded preflight checks
|
||||
- backup/restore operations
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Requirements
|
||||
|
||||
- Docker Client connected to a docker engine into which images can built, pulled and pushed.
|
||||
- (Docker Desktop setup tested for these instructions)
|
||||
|
||||
### Download
|
||||
|
||||
- Download the appropriate executable for your platform from the [releases page](https://github.com/qlik-oss/sense-installer/releases).
|
||||
- To allow the CLI to download and initialize dependencies (including porter and it's associated mixins), simply execute `qliksense` with no arguments
|
||||
- `qliksense`
|
||||
|
||||
#### Porter CLI
|
||||
- *Optional*: If wanting to use porter CLI directly, two environment variables will need to be set so as not to conflict with an existing porter installation:
|
||||
- _Bash_
|
||||
```shell
|
||||
bash# export PORTER_HOME="$HOME\.qliksense"
|
||||
bash# export PATH="$HOME\.qliksense;$PATH"
|
||||
```
|
||||
|
||||
- _PowerShell_
|
||||
```shell
|
||||
PS> $Env:PORTER_HOME="$Env:USERPROFILE\.qliksense"
|
||||
PS> $Env:PATH="$Env:USERPROFILE\.qliksense;$Env:PATH"
|
||||
```
|
||||
|
||||
### Generate Credentials from published bundle
|
||||
|
||||
- Ensure connectivity to the target cluster create a kubeconfig credential for a target bundle.
|
||||
- generating a file as follows, replace `<credential_name>` with a name of your choosing.
|
||||
- _Bash_
|
||||
```shell
|
||||
bash# CREDENTIAL_NAME=<credential_name>
|
||||
bash# cat <<EOF > $HOME/.qliksense/credentials/$CREDENTIAL_NAME.yaml
|
||||
name: $CREDENTIAL_NAME
|
||||
credentials:
|
||||
- name: kubeconfig
|
||||
source:
|
||||
path: $HOME/.kube/config
|
||||
EOF
|
||||
```
|
||||
- _PowerShell_
|
||||
```shell
|
||||
PS> $CREDENTIAL_NAME="<credential_name>"
|
||||
PS> Add-Content -Value @"
|
||||
name: $CREDENTIAL_NAME
|
||||
credentials:
|
||||
- name: kubeconfig
|
||||
source:
|
||||
path: $Env:USERPROFILE\.kube\config
|
||||
"@ -Path $Env:USERPROFILE\.qliksense\credentials\$CREDENTIAL_NAME".yaml"
|
||||
```
|
||||
- credentials can also be created using the [porter](https://porter.sh) CLI *(the correct environmental variable need to have been set up as shown in [Porter CLI](#porter-cli) above)*
|
||||
- `porter cred generate <credential_name> --tag qlik/qliksense-cnab-bundle:v1.21.23-edge`, replace `<credential_name>` with a name of your choosing.
|
||||
- Select `file path` and specify full path to a kube config file ex. _Bash_: `/home/user/.kube/config` or _PowerShell_ `C:\Users\user\.kube\config`
|
||||
|
||||
### Qlik Sense version and image list
|
||||
|
||||
It is possible verify the version of the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository bundled into the `qlik/qliksense-cnab-bundle` image and retreive the list of images included in that release. (This operation can take a minute or so)<https://github.com/qlik-oss/kustomize/issues/13> as the entire manifests needs to be rendered:
|
||||
|
||||
- `qliksense about --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
|
||||
|
||||
### Optional: Pulling images in manifest locally, "air gap"
|
||||
|
||||
If the `dockerRegistry` parameter is specified as the private docker registry to be used by the kubernetes cluster hosting qliksense, it is possible to pull images to the local docker engine for an eventual push during a `qliksense install` or `qliksense upgrade`
|
||||
|
||||
- `qliksense pull --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
|
||||
|
||||
### Running Preflight checks
|
||||
|
||||
You can run preflight checks to ensure that the cluster is in a healthy state before installing Qliksense.
|
||||
|
||||
- `qliksense preflight -c <credential_name> --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
|
||||
|
||||
The above command runs the checks in the default namespace. If you want to specify the namespace to run preflight checks on:
|
||||
|
||||
- `qliksense preflight --param namespace=<value> -c <credential_name> --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
|
||||
|
||||
### Installation
|
||||
|
||||
- Install the bundle : `qliksense install --param acceptEULA=yes -c <credential_name> --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
|
||||
|
||||
#### Supported Parameters during install
|
||||
|
||||
| Name | Descriptions | Default |
|
||||
| ------------- |:-------------:| -----:|
|
||||
| profile | select a profile i.e docker-desktop, aws-eks, gke | docker-desktop |
|
||||
| acceptEULA | yes | has to be yes |
|
||||
| namespace | any kubernetes namespace | default |
|
||||
| dockerRegistry | A private docker image regitry for pods | not specified (public) |
|
||||
| rotateKeys | regenerate application PKI keys on upgrade (yes/no) | no |
|
||||
| mongoDbUri | the mongodb URI to use | URI of development mongodb |
|
||||
| scName | storage class name | none |
|
||||
|
||||
#### How To Add Identity Provider Config
|
||||
|
||||
Since idp configs are usually multiline configs it is not conventional to pass to porter during install as a `param`. Rather put the configs in a file and refer to that file during `porter install` command. For example to add `keycloak` IDP create file named `idpconfigs.txt` and put
|
||||
|
||||
```shell
|
||||
idpConfigs=[{"discoveryUrl":"http://keycloak-insecure:8089/keycloak/realms/master22/.well-known/openid-configuration","clientId":"edge-auth","clientSecret":"e15b5075-9399-4b20-a95e-023022aa4aed","realm":"master","hostname":"elastic.example","claimsMapping":{"sub":["sub","client_id"],"name":["name","given_name","family_name","preferred_username"]}}]
|
||||
|
||||
```
|
||||
|
||||
Then pass that file during install command like this
|
||||
|
||||
```shell
|
||||
qliksense install --param acceptEULA=yes -c <credential_name> --param-file idpconfigs.txt --tag qlik/qliksense-cnab-bundle:<qliksense_version>`
|
||||
```
|
||||
|
||||
## Packaging a Custom bundle
|
||||
|
||||
If files need to be added to the [qliksense-k8s repository](https://github.com/qlik-oss/qliksense-k8s) in order to perform advanced configuration outside the scope of the what the operator provides, a custom bundle needs to be built.
|
||||
|
||||
Packaging of Qlik Sense on Kubernetes is done through a [Porter](https://porter.sh/) definition in the [Qlik Sense on Kubernetes configuration repository](https://github.com/qlik-oss/qliksense-k8s/blob/master/porter.yaml), the resulting bundle published on DockerHub as a [Cloud Natvie Application Bundle](https://cnab.io/) called [qliksense-cnab-bundle](https://hub.docker.com/r/qlik/qliksense-cnab-bundle)
|
||||
|
||||
To start, clone [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and modify the repo as desired, once finished make sure to be in the `qliksense-k8s` directory from which the porter bundle can be built:
|
||||
|
||||
```shell
|
||||
git clone git@github.com:qlik-oss/qliksense-k8s.git
|
||||
|
||||
cd qliksense-k8s
|
||||
|
||||
qliksense build
|
||||
```
|
||||
|
||||
Once built, all of the `porter` command that were used with `--tag` can be now be used without this flag provided that porter is executed with the `qliksense-k8s` directory. `porter` will automatically use the qliksense-k8s (and the porter.yaml) in the current directory.
|
||||
|
||||
## List of Commands
|
||||
|
||||
- [qliksense about](action_about.md)
|
||||
|
||||
41
action_about.md
Normal file
41
action_about.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# qliksense about
|
||||
|
||||
About action will display inside information regarding [qliksense-k8](https://github.com/qlik-oss/qliksense-k8s) release.
|
||||
|
||||
it will support following flags
|
||||
|
||||
- `qliksense about 1.0.0` display default profile for tag `1.0.0`.
|
||||
- `qliksense about 1.0.0 --profile=docker-desktop`
|
||||
- `qliksense about`
|
||||
- assuming current directory has `manifests/docker-desktop`
|
||||
- or get version information from pull of `qliksense-k8s` `master`
|
||||
|
||||
using other supported commands user might have built the CR into the location `~/.qliksense/myqliksense.yaml`
|
||||
|
||||
```yaml
|
||||
apiVersion: qlik.com/v1
|
||||
kind: QlikSense
|
||||
metadata:
|
||||
name: myqliksense
|
||||
spec:
|
||||
profile: docker-desktop
|
||||
manifestsRoot: /Usr/ddd/my-k8-repo/manifests
|
||||
namespace: myqliksense
|
||||
storageClassName: efs
|
||||
configs:
|
||||
qliksense:
|
||||
- name: acceptEULA
|
||||
value: "yes"
|
||||
secrets:
|
||||
qliksense:
|
||||
- name: mongoDbUri
|
||||
value: "mongo://mongo:3307"
|
||||
- name: messagingPassword
|
||||
valueFromKey: messagingPassword
|
||||
```
|
||||
|
||||
In that case the command would be
|
||||
|
||||
- `qliksense about`
|
||||
- display from `/Usr/ddd/my-k8-repo/manifests/docker-desktop` location
|
||||
- pull from `master` if directory invalid/empty
|
||||
65
cmd/qliksense/about.go
Normal file
65
cmd/qliksense/about.go
Normal file
@@ -0,0 +1,65 @@
|
||||
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
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
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),
|
||||
buildAboutAlias(porterCmd),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 = ¶mOptions{}
|
||||
|
||||
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 = opts.getTagDefaults(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 (o *aboutOptions) getTagDefaults(args []string) []string {
|
||||
var err error
|
||||
if len(o.Tag) <= 0 && len(o.File) <= 0 && len(o.CNABFile) <= 0 {
|
||||
if _, err = os.Stat("porter.yaml"); err != nil {
|
||||
args = append(args, []string{"--tag", "qlik/qliksense-cnab-bundle:" + o.Version}...)
|
||||
}
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
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 (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
|
||||
}
|
||||
42
cmd/qliksense/config.go
Normal file
42
cmd/qliksense/config.go
Normal file
@@ -0,0 +1,42 @@
|
||||
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
|
||||
}
|
||||
30
cmd/qliksense/fetch.go
Normal file
30
cmd/qliksense/fetch.go
Normal file
@@ -0,0 +1,30 @@
|
||||
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
|
||||
}
|
||||
30
cmd/qliksense/install.go
Normal file
30
cmd/qliksense/install.go
Normal file
@@ -0,0 +1,30 @@
|
||||
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
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package main
|
||||
|
||||
|
||||
func main() {
|
||||
initAndExecute()
|
||||
}
|
||||
|
||||
|
||||
28
cmd/qliksense/operator.go
Normal file
28
cmd/qliksense/operator.go
Normal file
@@ -0,0 +1,28 @@
|
||||
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
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,27 @@
|
||||
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
|
||||
)
|
||||
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()
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -3,141 +3,77 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/qlik-oss/sense-installer/pkg"
|
||||
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// To run this project in ddebug mode, run:
|
||||
// export QLIKSENSE_DEBUG=true
|
||||
// qliksense <command>
|
||||
|
||||
const (
|
||||
porterURLBase = "https://deislabs.blob.core.windows.net/porter"
|
||||
porterHomeVar = "PORTER_HOME"
|
||||
porterDirVar = ".porter"
|
||||
mixinDirVar = "mixins"
|
||||
porterPermaLink = "latest"
|
||||
porterRuntime = "porter-runtime"
|
||||
qlikSenseHomeVar = "QLIKSENSE_HOME"
|
||||
qlikSenseDirVar = ".qliksense"
|
||||
)
|
||||
|
||||
func initAndExecute() error {
|
||||
var (
|
||||
porterExe string
|
||||
err error
|
||||
qlikSenseHome string
|
||||
err error
|
||||
)
|
||||
if porterExe, err = installPorter(); err != nil {
|
||||
return err
|
||||
|
||||
qlikSenseHome, err = setUpPaths()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := rootCmd(qliksense.New(porterExe)).Execute(); err != nil {
|
||||
|
||||
if err = rootCmd(qliksense.New(qlikSenseHome)).Execute(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func installPorter() (string, error) {
|
||||
func setUpPaths() (string, error) {
|
||||
var (
|
||||
homeDir, mixin, mixinOpts, porterHome, porterExe string
|
||||
mixinsVar = map[string]string{
|
||||
"kustomize": "-v 0.2-beta-3-0e19ca4 --url https://github.com/donmstewart/porter-kustomize/releases/download",
|
||||
"qliksense": "-v v0.11.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
|
||||
homeDir, qlikSenseHome string
|
||||
err error
|
||||
)
|
||||
porterExe = "porter"
|
||||
if runtime.GOOS == "windows" {
|
||||
porterExe = porterExe + ".exe"
|
||||
}
|
||||
if porterHome = os.Getenv(porterHomeVar); porterHome == "" {
|
||||
|
||||
if qlikSenseHome = os.Getenv(qlikSenseHomeVar); qlikSenseHome == "" {
|
||||
if homeDir, err = homedir.Dir(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if homeDir, err = homedir.Expand(homeDir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
porterHome = filepath.Join(homeDir, porterDirVar)
|
||||
}
|
||||
porterExe = filepath.Join(porterHome, porterExe)
|
||||
if _, err = os.Stat(porterHome); 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(porterHome, os.ModePerm)
|
||||
if err = downloadFile(porterURLBase+"/"+porterPermaLink+"/porter-linux-amd64", filepath.Join(porterHome, porterRuntime)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if runtime.GOOS == "linux" && runtime.GOARCH == "amd64" {
|
||||
if _, err = copy(filepath.Join(porterHome, porterRuntime), porterExe); err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
if err = downloadFile(porterURLBase+"/"+porterPermaLink+"/"+"porter-"+runtime.GOOS+"-"+runtime.GOARCH, porterExe); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
qlikSenseHome = filepath.Join(homeDir, qlikSenseDirVar)
|
||||
}
|
||||
os.Mkdir(qlikSenseHome, os.ModePerm)
|
||||
return qlikSenseHome, nil
|
||||
}
|
||||
|
||||
if _, err = os.Stat(filepath.Join(porterHome, 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(porterHome, mixinDirVar, mixin)); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
downloadMixins[mixin] = mixinOpts
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for mixin, mixinOpts = range downloadMixins {
|
||||
cmd = exec.Command(porterExe, "mixin", "install", mixin, mixinOpts)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err = cmd.Run(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return porterExe, nil
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the version number of qliksense cli",
|
||||
Long: `All software has versions. This is Hugo's`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("%s (%s, %s)\n", pkg.Version, pkg.Commit, pkg.CommitDate)
|
||||
},
|
||||
}
|
||||
|
||||
func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||
var (
|
||||
cmd, porterCmd, alias *cobra.Command
|
||||
cmd *cobra.Command
|
||||
)
|
||||
|
||||
cmd = &cobra.Command{
|
||||
@@ -156,11 +92,23 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
||||
// For qliksense overrides/commands
|
||||
|
||||
cmd.AddCommand(pullQliksenseImages(p))
|
||||
porterCmd = porter(p)
|
||||
cmd.AddCommand(porterCmd)
|
||||
for _, alias = range buildAliasCommands(porterCmd, p) {
|
||||
cmd.AddCommand(alias)
|
||||
}
|
||||
cmd.AddCommand(about(p))
|
||||
// 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("-", "_"))
|
||||
|
||||
|
||||
61
go.mod
61
go.mod
@@ -12,65 +12,62 @@ 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 (
|
||||
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
|
||||
cloud.google.com/go v0.52.0 // indirect
|
||||
cloud.google.com/go/storage v1.5.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.0.3
|
||||
github.com/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/deislabs/cnab-go v0.7.1-beta1 // indirect
|
||||
github.com/containers/image/v5 v5.1.0
|
||||
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/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/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/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
|
||||
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/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
||||
github.com/jinzhu/gorm v1.9.11 // indirect
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||
github.com/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/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/morikuni/aec v1.0.0 // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/qlik-oss/k-apis v0.0.2
|
||||
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/net v0.0.0-20191209160850-c0dbc17a3553
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.7 // 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
|
||||
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.7
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
sigs.k8s.io/kustomize/api v0.3.2
|
||||
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 // indirect
|
||||
)
|
||||
|
||||
exclude github.com/Azure/go-autorest v12.0.0+incompatible
|
||||
|
||||
83
pkg/api/apis.go
Normal file
83
pkg/api/apis.go
Normal file
@@ -0,0 +1,83 @@
|
||||
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
|
||||
}
|
||||
85
pkg/api/apis_test.go
Normal file
85
pkg/api/apis_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
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()
|
||||
}
|
||||
29
pkg/api/kubectl.go
Normal file
29
pkg/api/kubectl.go
Normal file
@@ -0,0 +1,29 @@
|
||||
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
|
||||
}
|
||||
40
pkg/api/types.go
Normal file
40
pkg/api/types.go
Normal file
@@ -0,0 +1,40 @@
|
||||
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"`
|
||||
}
|
||||
238
pkg/qliksense/about.go
Normal file
238
pkg/qliksense/about.go
Normal file
@@ -0,0 +1,238 @@
|
||||
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
|
||||
}
|
||||
505
pkg/qliksense/about_test.go
Normal file
505
pkg/qliksense/about_test.go
Normal file
@@ -0,0 +1,505 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
82
pkg/qliksense/config.go
Normal file
82
pkg/qliksense/config.go
Normal file
@@ -0,0 +1,82 @@
|
||||
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
|
||||
}
|
||||
@@ -1,250 +1,411 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
cliflags "github.com/docker/cli/cli/flags"
|
||||
"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"
|
||||
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Images ...
|
||||
type Images struct {
|
||||
Images []string `yaml:"images"`
|
||||
}
|
||||
|
||||
// PullImages ...
|
||||
func (p *Qliksense) PullImages() 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.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 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 err = cli.Initialize(cliflags.NewClientOptions()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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, registry 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
|
||||
// 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 = registry + "/" + 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(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
|
||||
// }
|
||||
pushOptions = types.ImagePushOptions{
|
||||
All: true,
|
||||
RegistryAuth: "temp",
|
||||
// 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"
|
||||
"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, ©.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, ©.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
|
||||
}
|
||||
|
||||
10
pkg/qliksense/fetch.go
Normal file
10
pkg/qliksense/fetch.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (q *Qliksense) FetchQK8s(version string) {
|
||||
//io.WriteString(os.Stdout, q.GetCRDString())
|
||||
fmt.Println(version)
|
||||
}
|
||||
52
pkg/qliksense/install.go
Normal file
52
pkg/qliksense/install.go
Normal file
@@ -0,0 +1,52 @@
|
||||
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
|
||||
}
|
||||
32
pkg/qliksense/kuz.go
Normal file
32
pkg/qliksense/kuz.go
Normal file
@@ -0,0 +1,32 @@
|
||||
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()
|
||||
}
|
||||
71
pkg/qliksense/kuz_test.go
Normal file
71
pkg/qliksense/kuz_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
40
pkg/qliksense/operator.go
Normal file
40
pkg/qliksense/operator.go
Normal file
@@ -0,0 +1,40 @@
|
||||
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
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package qliksense
|
||||
|
||||
import (
|
||||
"io"
|
||||
"fmt"
|
||||
"bufio"
|
||||
"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
|
||||
}
|
||||
@@ -1,13 +1,20 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
//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"),
|
||||
}
|
||||
}
|
||||
|
||||
8
pkg/version.go
Normal file
8
pkg/version.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package pkg
|
||||
|
||||
// These are build-time values, set during an official release
|
||||
var (
|
||||
Commit string
|
||||
Version string
|
||||
CommitDate string
|
||||
)
|
||||
5
renovate.json
Normal file
5
renovate.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
]
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
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
|
||||
@@ -1,42 +0,0 @@
|
||||
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"
|
||||
Reference in New Issue
Block a user