Compare commits
42 Commits
gitops_spe
...
load-file
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
763f59e042 | ||
|
|
ddcaba4fff | ||
|
|
19c4d37b42 | ||
|
|
dcd90ed81a | ||
|
|
05e90c057c | ||
|
|
2ddfab9440 | ||
|
|
1eccc50e66 | ||
|
|
1a2de669ba | ||
|
|
3638994b91 | ||
|
|
86e8805bc7 | ||
|
|
7e9dea4e5f | ||
|
|
c2430c3817 | ||
|
|
436162f173 | ||
|
|
2f039f2d2e | ||
|
|
48ee673ddc | ||
|
|
57a80a9533 | ||
|
|
4fe04d6142 | ||
|
|
1fd3310e05 | ||
|
|
b85269d908 | ||
|
|
cbdafadbaf | ||
|
|
c0e2128d5d | ||
|
|
df19cadcb6 | ||
|
|
d9cbbf54cc | ||
|
|
c4f0ddcea3 | ||
|
|
f57457029d | ||
|
|
69aca05a86 | ||
|
|
aa737b0594 | ||
|
|
e4d69f059a | ||
|
|
b7c0fd48b7 | ||
|
|
4530d1d9e2 | ||
|
|
ca20f8c992 | ||
|
|
b2c16a490b | ||
|
|
7f70cc661e | ||
|
|
2c054cd54e | ||
|
|
cfc8fbb1f1 | ||
|
|
30f00461ec | ||
|
|
613b918dde | ||
|
|
bdcadebeca | ||
|
|
626a2ebe68 | ||
|
|
1f64641ab1 | ||
|
|
b764fd179d | ||
|
|
e8d1899a41 |
@@ -1,43 +0,0 @@
|
|||||||
# Golang CircleCI 2.0 configuration file
|
|
||||||
#
|
|
||||||
# Check https://circleci.com/docs/2.0/language-go/ for more details
|
|
||||||
version: 2
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
docker:
|
|
||||||
- image: circleci/golang:stretch
|
|
||||||
working_directory: /go/src/github.com/qlik-oss/sense-installer
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- run: make test
|
|
||||||
- run: make build
|
|
||||||
build_release:
|
|
||||||
docker:
|
|
||||||
- image: circleci/golang:stretch
|
|
||||||
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
|
|
||||||
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:
|
|
||||||
tags:
|
|
||||||
ignore: /^v.*/
|
|
||||||
build_release:
|
|
||||||
jobs:
|
|
||||||
- build_release:
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /^v.*/
|
|
||||||
branches:
|
|
||||||
ignore: /.*/
|
|
||||||
|
|
||||||
27
.github/workflows/build.yml
vendored
Normal file
27
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: Build Sense installer
|
||||||
|
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up Go 1.13
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.13
|
||||||
|
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
|
||||||
|
- name: Set GOPATH
|
||||||
|
# temporary fix
|
||||||
|
# see https://github.com/actions/setup-go/issues/14
|
||||||
|
run: |
|
||||||
|
echo "##[set-env name=GOPATH;]$(dirname $GITHUB_WORKSPACE)"
|
||||||
|
echo "##[add-path]$(dirname $GITHUB_WORKSPACE)/bin"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- run: make test
|
||||||
|
- run: make build
|
||||||
37
.github/workflows/release.yml
vendored
Normal file
37
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: Release Sense installer binaries
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*.*.*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up Go 1.13
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.13
|
||||||
|
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* # Needed in makefile for versioning
|
||||||
|
- name: Set GOPATH
|
||||||
|
# temporary fix
|
||||||
|
# see https://github.com/actions/setup-go/issues/14
|
||||||
|
run: |
|
||||||
|
echo "##[set-env name=GOPATH;]$(dirname $GITHUB_WORKSPACE)"
|
||||||
|
echo "##[add-path]$(dirname $GITHUB_WORKSPACE)/bin"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- run: make test
|
||||||
|
- run: make xbuild-all
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
files: bin/**/*
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ pkg/qliksense/packrd
|
|||||||
pkg/qliksense/qliksense-packr.go
|
pkg/qliksense/qliksense-packr.go
|
||||||
pkg/qliksense/docker-registry
|
pkg/qliksense/docker-registry
|
||||||
/pkg/qliksense/tests
|
/pkg/qliksense/tests
|
||||||
|
.DS_Store
|
||||||
|
|||||||
19
Makefile
19
Makefile
@@ -57,17 +57,24 @@ endif
|
|||||||
|
|
||||||
xbuild-all: clean generate
|
xbuild-all: clean generate
|
||||||
$(foreach OS, $(SUPPORTED_PLATFORMS), \
|
$(foreach OS, $(SUPPORTED_PLATFORMS), \
|
||||||
$(foreach ARCH, $(SUPPORTED_ARCHES), \
|
$(foreach ARCH, $(SUPPORTED_ARCHES), \
|
||||||
$(MAKE) $(MAKE_OPTS) CLIENT_PLATFORM=$(OS) CLIENT_ARCH=$(ARCH) MIXIN=$(MIXIN) xbuild; \
|
$(MAKE) $(MAKE_OPTS) CLIENT_PLATFORM=$(OS) CLIENT_ARCH=$(ARCH) MIXIN=$(MIXIN) xbuild; \
|
||||||
))
|
))
|
||||||
|
|
||||||
$(MAKE) clean
|
$(MAKE) clean
|
||||||
|
|
||||||
xbuild: $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
xbuild: $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
||||||
|
|
||||||
$(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT):
|
$(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT):
|
||||||
mkdir -p $(dir $@)
|
mkdir -p $(dir $@)
|
||||||
GOOS=$(CLIENT_PLATFORM) GOARCH=$(CLIENT_ARCH) $(XBUILD) -o $@ ./cmd/$(MIXIN)
|
GOOS=$(CLIENT_PLATFORM) GOARCH=$(CLIENT_ARCH) $(XBUILD) -o $@ ./cmd/$(MIXIN)
|
||||||
tar -czvf $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).tar.gz -C $(BINDIR)/$(VERSION)/ $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
|
||||||
#tar -C $(BINDIR)/$(VERSION)/ -cvf $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).tar.gz $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
|
||||||
|
|
||||||
|
ifeq ($(CLIENT_PLATFORM),windows)
|
||||||
|
zip $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).zip $(BINDIR)/$(VERSION)/ $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
||||||
|
else
|
||||||
|
tar -czvf $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH).tar.gz -C $(BINDIR)/$(VERSION)/ $(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
||||||
|
endif
|
||||||
|
upx $(BINDIR)/$(VERSION)/$(MIXIN)-$(CLIENT_PLATFORM)-$(CLIENT_ARCH)$(FILE_EXT)
|
||||||
|
|
||||||
generate: get-crds packr2
|
generate: get-crds packr2
|
||||||
go generate ./...
|
go generate ./...
|
||||||
@@ -85,7 +92,7 @@ clean-packr: packr2
|
|||||||
|
|
||||||
get-crds:
|
get-crds:
|
||||||
$(eval TMP := $(shell mktemp -d))
|
$(eval TMP := $(shell mktemp -d))
|
||||||
git clone git@github.com:qlik-oss/qliksense-operator.git -b ms-3 $(TMP)/operator
|
git clone https://github.com/qlik-oss/qliksense-operator.git -b master $(TMP)/operator
|
||||||
mkdir -p pkg/qliksense/crds/cr
|
mkdir -p pkg/qliksense/crds/cr
|
||||||
mkdir -p pkg/qliksense/crds/crd
|
mkdir -p pkg/qliksense/crds/crd
|
||||||
mkdir -p pkg/qliksense/crds/crd-deploy
|
mkdir -p pkg/qliksense/crds/crd-deploy
|
||||||
|
|||||||
131
README.md
131
README.md
@@ -1,16 +1,8 @@
|
|||||||
# (WIP) Qlik Sense installation and operations CLI
|
# (WIP) Qlik Sense installation and operations CLI
|
||||||
|
|
||||||
- [Qlik Sense installation and operations CLI](#qlik-sense-installation-and-operations-cli)
|
## Documentation
|
||||||
- [About](#about)
|
|
||||||
- [Future Direction](#future-direction)
|
To learn more about Qlik Sense installer go to https://qlik-oss.github.io/sense-installer/
|
||||||
- [Getting Started](#getting-started)
|
|
||||||
- [Requirements](#requirements)
|
|
||||||
- [Download](#download)
|
|
||||||
- [TL;DR](#TL;DR)
|
|
||||||
- [How qliksense CLI works](#how-qliksense-cli-works)
|
|
||||||
- [Witout Git Repo](#Without-git-repo)
|
|
||||||
- [With Git Repo](#With-a-git-repo)
|
|
||||||
- [Air Gapped](#air-gaped)
|
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
@@ -31,120 +23,3 @@ For each version of a qliksense edge build there should be a corresponding relea
|
|||||||
- backup/restore operations
|
- backup/restore operations
|
||||||
- fully support airgap installation of QSEoK
|
- fully support airgap installation of QSEoK
|
||||||
- restore unwanted deletion of kubernetes resources
|
- restore unwanted deletion of kubernetes resources
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
### Requirements
|
|
||||||
|
|
||||||
- `kubectl` need to be installed and configured properly so that `kubectl` can connect to the kubernetes cluser. The `qliksense` CLI uses `kubectl` under the hood to perform operations on cluster
|
|
||||||
- (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) and rename it to `qliksense`. All the examplease down below uses `qliksense`.
|
|
||||||
|
|
||||||
### TL;DR
|
|
||||||
|
|
||||||
- To download the version `v0.0.2` from qliksense-k8s [releases](https://github.com/qlik-oss/qliksense-k8s/releases).
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$qliksense fetch v0.0.2
|
|
||||||
```
|
|
||||||
|
|
||||||
- To install CRDs for QSEoK and qliksense operator into the kubernetes cluster.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$qliksense crds install --all
|
|
||||||
```
|
|
||||||
|
|
||||||
- To install QSEoK into a namespace in the kubernetes cluster where `kubectl` is pointing to.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$qliksense install --acceptEULA="yes"
|
|
||||||
```
|
|
||||||
|
|
||||||
## How qliksense cli works
|
|
||||||
|
|
||||||
At the initialization `qliksense` cli create few files in the director `~/.qliksene` and it contains following files
|
|
||||||
|
|
||||||
```console
|
|
||||||
.qliksense
|
|
||||||
├── config.yaml
|
|
||||||
├── contexts
|
|
||||||
│ └── qlik-default
|
|
||||||
│ └── qlik-default.yaml
|
|
||||||
└── ejson
|
|
||||||
└── keys
|
|
||||||
```
|
|
||||||
|
|
||||||
`qlik-default.yaml` is a default CR has been created with some default values like this
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
apiVersion: qlik.com/v1
|
|
||||||
kind: Qliksense
|
|
||||||
metadata:
|
|
||||||
name: qlik-default
|
|
||||||
spec:
|
|
||||||
profile: docker-desktop
|
|
||||||
secrets:
|
|
||||||
qliksense:
|
|
||||||
- name: mongoDbUri
|
|
||||||
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
|
|
||||||
rotateKeys: "yes"
|
|
||||||
releaseName: qlik-default
|
|
||||||
```
|
|
||||||
|
|
||||||
The `qliksense` cli creates a default qliksense context (different from kubectl context) named `qlik-default` which will be the prefix for all kubernetes resources created by the cli under this context latter on. New context and configuration can be created by the cli.
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ qliksense config -h
|
|
||||||
do operations on/around CR
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
qliksense config [command]
|
|
||||||
|
|
||||||
Available Commands:
|
|
||||||
apply generate the patchs and apply manifests to k8s
|
|
||||||
list-contexts retrieves the contexts and lists them
|
|
||||||
set configure a key value pair into the current context
|
|
||||||
set-configs set configurations into the qliksense context as key-value pairs
|
|
||||||
set-context Sets the context in which the Kubernetes cluster and resources live in
|
|
||||||
set-secrets set secrets configurations into the qliksense context as key-value pairs
|
|
||||||
view view the qliksense operator CR
|
|
||||||
|
|
||||||
Flags:
|
|
||||||
-h, --help help for config
|
|
||||||
|
|
||||||
Use "qliksense config [command] --help" for more information about a command.
|
|
||||||
```
|
|
||||||
|
|
||||||
`qliksense` cli works in two modes
|
|
||||||
|
|
||||||
- with a git repo fork/clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s)
|
|
||||||
- without git repo
|
|
||||||
|
|
||||||
### Without git repo
|
|
||||||
|
|
||||||
In this mode `qliksense` CLI download the specified version from [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and put it into folder `~/.qliksense/contexts/<context-name>/qlik-k8s`.
|
|
||||||
|
|
||||||
The qliksense cli create a CR for the qliksense operator and all the config operations are peformed to edit the CR. So when `qliksense install` or `qliksense config apply` both generate patches in local file system (i.e `~/.qliksense/contexts/<context-name>/qlik-k8s`) and install those manifests into the cluster and create a custom resoruce (CR) for the `qliksene operator` then the operator make association to the isntalled resoruces so that when `qliksenes uninstall` is performed the operator can delete all those kubernetes resources related to QSEoK for the current context.
|
|
||||||
|
|
||||||
### With a git repo
|
|
||||||
|
|
||||||
User has to create fork or clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and push it to their own git server. When user perform `qliksense install` or `qliksene config apply` the qliksense operator do these tasks
|
|
||||||
|
|
||||||
- downloads the corresponding version of manifests from the user's git repo.
|
|
||||||
- generate kustomize patches
|
|
||||||
- install kubernetes resoruces
|
|
||||||
- push those generated patches into a new branch in the provided git repo. so that user user can merge those patches into their master branch.
|
|
||||||
- spinup a cornjob to monitor master branch. If user modifies anything in the master branch those changes will be applied into the cluster. This is a light weight `git-ops` model
|
|
||||||
|
|
||||||
This is how repo info is provided into the CR
|
|
||||||
|
|
||||||
```console
|
|
||||||
qliksense config set git.repository="https://github.com/my-org/qliksense-k8s"
|
|
||||||
|
|
||||||
qliksense config set git.accessToken=blablalaala
|
|
||||||
```
|
|
||||||
|
|
||||||
## Air gaped
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func configCmd(q *qliksense.Qliksense) *cobra.Command {
|
|||||||
func configApplyCmd(q *qliksense.Qliksense) *cobra.Command {
|
func configApplyCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||||
c := &cobra.Command{
|
c := &cobra.Command{
|
||||||
Use: "apply",
|
Use: "apply",
|
||||||
Short: "generate the patchs and apply manifests to k8s",
|
Short: "generate the patches and apply manifests to k8s",
|
||||||
Long: `generate patches based on CR and apply manifests to k8s`,
|
Long: `generate patches based on CR and apply manifests to k8s`,
|
||||||
Example: `qliksense config apply`,
|
Example: `qliksense config apply`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|||||||
29
cmd/qliksense/get_installable_versions.go
Normal file
29
cmd/qliksense/get_installable_versions.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultVersionsLimit = 10
|
||||||
|
|
||||||
|
func getInstallableVersionsCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
opts := &qliksense.LsRemoteCmdOptions{
|
||||||
|
IncludeBranches: false,
|
||||||
|
Limit: defaultVersionsLimit,
|
||||||
|
}
|
||||||
|
c := &cobra.Command{
|
||||||
|
Use: "get-versions",
|
||||||
|
Short: "list remote/installable versions",
|
||||||
|
Long: `list remote/installable versions`,
|
||||||
|
Example: `qliksense get-versions`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return q.GetInstallableVersions(opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
f := c.Flags()
|
||||||
|
f.BoolVarP(&opts.IncludeBranches, "include-branches", "", opts.IncludeBranches, "Include branches")
|
||||||
|
f.IntVarP(&opts.Limit, "limit", "", opts.Limit, "Maximum versions to list (starting with the highest)")
|
||||||
|
return c
|
||||||
|
}
|
||||||
38
cmd/qliksense/load.go
Normal file
38
cmd/qliksense/load.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadCrFile(q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
filePath := ""
|
||||||
|
c := &cobra.Command{
|
||||||
|
Use: "load",
|
||||||
|
Short: "load a CR a file and create necessary structure for future use",
|
||||||
|
Long: `load a CR a file and create necessary structure for future use`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if filePath == "-" {
|
||||||
|
return q.LoadCr(os.Stdin)
|
||||||
|
}
|
||||||
|
file, e := os.Open(filePath)
|
||||||
|
if e != nil {
|
||||||
|
return errors.Wrapf(e,
|
||||||
|
"unable to read the file %s", filePath)
|
||||||
|
}
|
||||||
|
return q.LoadCr(file)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
f := c.Flags()
|
||||||
|
f.StringVarP(&filePath, "file", "f", "", "File to laod CR from")
|
||||||
|
c.MarkFlagRequired("file")
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func isInputFromPipe() bool {
|
||||||
|
fileInfo, _ := os.Stdin.Stat()
|
||||||
|
return fileInfo.Mode()&os.ModeCharDevice == 0
|
||||||
|
}
|
||||||
41
cmd/qliksense/preflight.go
Normal file
41
cmd/qliksense/preflight.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/qlik-oss/sense-installer/pkg/qliksense"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func preflightCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
var configCmd = &cobra.Command{
|
||||||
|
Use: "preflight",
|
||||||
|
Short: "perform preflight checks on the cluster",
|
||||||
|
Long: `perform preflight checks on the cluster`,
|
||||||
|
Example: `qliksense preflight <preflight_check_to_run>
|
||||||
|
Usage:
|
||||||
|
qliksense preflight dns
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
return configCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func preflightCheckDnsCmd(q *qliksense.Qliksense) *cobra.Command {
|
||||||
|
var preflightDnsCmd = &cobra.Command{
|
||||||
|
Use: "dns",
|
||||||
|
Short: "perform preflight dns check",
|
||||||
|
Long: `perform preflight dns check to check DNS connectivity status in the cluster`,
|
||||||
|
Example: `qliksense preflight dns`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
err := q.DownloadPreflight()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("There has been an error downloading preflight: %+v", err)
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return q.CheckDns()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return preflightDnsCmd
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -19,7 +18,7 @@ import (
|
|||||||
"github.com/ttacon/chalk"
|
"github.com/ttacon/chalk"
|
||||||
)
|
)
|
||||||
|
|
||||||
// To run this project in ddebug mode, run:
|
// To run this project in debug mode, run:
|
||||||
// export QLIKSENSE_DEBUG=true
|
// export QLIKSENSE_DEBUG=true
|
||||||
// qliksense <command>
|
// qliksense <command>
|
||||||
|
|
||||||
@@ -45,12 +44,12 @@ func initAndExecute() error {
|
|||||||
qliksenseClient := qliksense.New(qlikSenseHome)
|
qliksenseClient := qliksense.New(qlikSenseHome)
|
||||||
qliksenseClient.SetUpQliksenseDefaultContext()
|
qliksenseClient.SetUpQliksenseDefaultContext()
|
||||||
cmd := rootCmd(qliksenseClient)
|
cmd := rootCmd(qliksenseClient)
|
||||||
//levenstein checks
|
if err := cmd.Execute(); err != nil {
|
||||||
if levenstein(cmd) == false {
|
//levenstein checks (auto-suggestions)
|
||||||
if err := cmd.Execute(); err != nil {
|
levenstein(cmd)
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +103,7 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
|||||||
|
|
||||||
// For qliksense overrides/commands
|
// For qliksense overrides/commands
|
||||||
|
|
||||||
|
cmd.AddCommand(getInstallableVersionsCmd(p))
|
||||||
cmd.AddCommand(pullQliksenseImages(p))
|
cmd.AddCommand(pullQliksenseImages(p))
|
||||||
cmd.AddCommand(pushQliksenseImages(p))
|
cmd.AddCommand(pushQliksenseImages(p))
|
||||||
cmd.AddCommand(about(p))
|
cmd.AddCommand(about(p))
|
||||||
@@ -164,6 +164,15 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
|
|||||||
cmd.AddCommand(crdsCmd)
|
cmd.AddCommand(crdsCmd)
|
||||||
crdsCmd.AddCommand(crdsViewCmd(p))
|
crdsCmd.AddCommand(crdsViewCmd(p))
|
||||||
crdsCmd.AddCommand(crdsInstallCmd(p))
|
crdsCmd.AddCommand(crdsInstallCmd(p))
|
||||||
|
|
||||||
|
// add preflight command
|
||||||
|
preflightCmd := preflightCmd(p)
|
||||||
|
preflightCmd.AddCommand(preflightCheckDnsCmd(p))
|
||||||
|
//preflightCmd.AddCommand(preflightCheckMongoCmd(p))
|
||||||
|
//preflightCmd.AddCommand(preflightCheckAllCmd(p))
|
||||||
|
|
||||||
|
cmd.AddCommand(preflightCmd)
|
||||||
|
cmd.AddCommand(loadCrFile(p))
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,32 +181,6 @@ func initConfig() {
|
|||||||
viper.AutomaticEnv()
|
viper.AutomaticEnv()
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadFile(url string, filepath string) error {
|
|
||||||
var (
|
|
||||||
out *os.File
|
|
||||||
err error
|
|
||||||
resp *http.Response
|
|
||||||
)
|
|
||||||
// Create the file
|
|
||||||
if out, err = os.Create(filepath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer out.Close()
|
|
||||||
|
|
||||||
// Get the data
|
|
||||||
if resp, err = http.Get(url); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// Write the body to file
|
|
||||||
if _, err = io.Copy(out, resp.Body); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func copy(src, dst string) (int64, error) {
|
func copy(src, dst string) (int64, error) {
|
||||||
var (
|
var (
|
||||||
source, destination *os.File
|
source, destination *os.File
|
||||||
@@ -226,28 +209,21 @@ func copy(src, dst string) (int64, error) {
|
|||||||
return nBytes, err
|
return nBytes, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func levenstein(cmd *cobra.Command) bool {
|
func levenstein(cmd *cobra.Command) {
|
||||||
cmd.SuggestionsMinimumDistance = 4
|
cmd.SuggestionsMinimumDistance = 2
|
||||||
if len(os.Args) > 1 {
|
if len(os.Args) > 1 {
|
||||||
args := os.Args[1]
|
args := os.Args[1]
|
||||||
for _, ctx := range cmd.Commands() {
|
suggest := cmd.SuggestionsFor(args)
|
||||||
val := *ctx
|
|
||||||
if args == val.Name() {
|
|
||||||
//found command
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
suggest := cmd.SuggestionsFor(os.Args[1])
|
|
||||||
if len(suggest) > 0 {
|
if len(suggest) > 0 {
|
||||||
arg := []string{}
|
arg := []string{}
|
||||||
for _, cm := range os.Args {
|
for _, cm := range os.Args {
|
||||||
arg = append(arg, cm)
|
arg = append(arg, cm)
|
||||||
}
|
}
|
||||||
arg[1] = suggest[0]
|
if !strings.EqualFold(arg[1], suggest[0]) {
|
||||||
out := ansi.NewColorableStdout()
|
arg[1] = suggest[0]
|
||||||
fmt.Fprintln(out, chalk.Green.Color("Did you mean: "), chalk.Bold.TextStyle(strings.Join(arg, " ")), "?")
|
out := ansi.NewColorableStdout()
|
||||||
return true
|
fmt.Fprintln(out, chalk.Green.Color("Did you mean: "), chalk.Bold.TextStyle(strings.Join(arg, " ")), "?")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# How qliksense cli works
|
# How qliksense cli works
|
||||||
|
|
||||||
At the initialization `qliksense` cli create few files in the director `~/.qliksene` and it contains following files
|
At the initialization, `qliksense` cli creates few files in the director `~/.qliksene` and it contains following files:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
.qliksense
|
.qliksense
|
||||||
@@ -12,7 +12,7 @@ At the initialization `qliksense` cli create few files in the director `~/.qliks
|
|||||||
└── keys
|
└── keys
|
||||||
```
|
```
|
||||||
|
|
||||||
`qlik-default.yaml` is a default CR has been created with some default values like this
|
`qlik-default.yaml` is a default CR created with some default values like:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
apiVersion: qlik.com/v1
|
apiVersion: qlik.com/v1
|
||||||
@@ -29,55 +29,68 @@ spec:
|
|||||||
releaseName: qlik-default
|
releaseName: qlik-default
|
||||||
```
|
```
|
||||||
|
|
||||||
The `qliksense` cli creates a default qliksense context (different from kubectl context) named `qlik-default` which will be the prefix for all kubernetes resources created by the cli under this context latter on. New context and configuration can be created by the cli.
|
The `qliksense` cli creates a default qliksense context (different from kubectl context) named `qlik-default` which will be the prefix for all kubernetes resources created by the cli under this context later on.
|
||||||
|
|
||||||
|
New context and configuration can be created by the cli, get available commands using:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ qliksense config -h
|
qliksense config -h
|
||||||
do operations on/around CR
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
qliksense config [command]
|
|
||||||
|
|
||||||
Available Commands:
|
|
||||||
apply generate the patchs and apply manifests to k8s
|
|
||||||
list-contexts retrieves the contexts and lists them
|
|
||||||
set configure a key value pair into the current context
|
|
||||||
set-configs set configurations into the qliksense context as key-value pairs
|
|
||||||
set-context Sets the context in which the Kubernetes cluster and resources live in
|
|
||||||
set-secrets set secrets configurations into the qliksense context as key-value pairs
|
|
||||||
view view the qliksense operator CR
|
|
||||||
|
|
||||||
Flags:
|
|
||||||
-h, --help help for config
|
|
||||||
|
|
||||||
Use "qliksense config [command] --help" for more information about a command.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
`qliksense` cli works in two modes
|
`qliksense` cli works in two modes
|
||||||
|
|
||||||
- with a git repo fork/clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s)
|
- With a git repo fork/clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s)
|
||||||
- without git repo
|
- Without git repo
|
||||||
|
|
||||||
## Without git repo
|
## Without git repo
|
||||||
|
|
||||||
In this mode `qliksense` CLI download the specified version from [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and put it into folder `~/.qliksense/contexts/<context-name>/qlik-k8s`.
|
In this mode `qliksense` CLI downloads the specified version from [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and places it in `~/.qliksense/contexts/<context-name>/qlik-k8s` folder.
|
||||||
|
|
||||||
The qliksense cli create a CR for the qliksense operator and all the config operations are peformed to edit the CR. So when `qliksense install` or `qliksense config apply` both generate patches in local file system (i.e `~/.qliksense/contexts/<context-name>/qlik-k8s`) and install those manifests into the cluster and create a custom resoruce (CR) for the `qliksene operator` then the operator make association to the isntalled resoruces so that when `qliksenes uninstall` is performed the operator can delete all those kubernetes resources related to QSEoK for the current context.
|
The qliksense cli creates a CR for the QlikSense operator and all config operations are peformed to edit the CR.
|
||||||
|
|
||||||
|
`qliksense install` or `qliksense config apply` will generate patches in local file system (i.e `~/.qliksense/contexts/<context-name>/qlik-k8s`) and
|
||||||
|
|
||||||
|
- Install those manifests into the cluster
|
||||||
|
- Create a custom resoruce (CR) for the `qliksene operator`.
|
||||||
|
|
||||||
|
The operator makes the association to the installed resoruces so that when `qliksense uninstall` is performed the operator can delete all kubernetes resources related to QSEoK for the current context.
|
||||||
|
|
||||||
## With a git repo
|
## With a git repo
|
||||||
|
|
||||||
User has to create fork or clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and push it to their own git server. When user perform `qliksense install` or `qliksene config apply` the qliksense operator do these tasks
|
Create a fork or clone of [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) and push it to your git repo/server
|
||||||
|
|
||||||
- downloads the corresponding version of manifests from the user's git repo.
|
To add your repo into CR, perform the following:
|
||||||
- generate kustomize patches
|
|
||||||
- install kubernetes resoruces
|
|
||||||
- push those generated patches into a new branch in the provided git repo. so that user user can merge those patches into their master branch.
|
|
||||||
- spinup a cornjob to monitor master branch. If user modifies anything in the master branch those changes will be applied into the cluster. This is a light weight `git-ops` model
|
|
||||||
|
|
||||||
This is how repo info is provided into the CR
|
```bash
|
||||||
|
|
||||||
```console
|
|
||||||
qliksense config set git.repository="https://github.com/my-org/qliksense-k8s"
|
qliksense config set git.repository="https://github.com/my-org/qliksense-k8s"
|
||||||
|
qliksense config set git.accessToken="<mySecretToken>"
|
||||||
qliksense config set git.accessToken=blablalaala
|
```
|
||||||
|
|
||||||
|
When you perform `qliksense install` or `qliksene config apply`, qliksense operator performs these tasks:
|
||||||
|
|
||||||
|
- Download corresponding version of manifests from the your git repo
|
||||||
|
- Generate kustomize patches
|
||||||
|
- Install kubernetes resources
|
||||||
|
- Push generated patches into a new branch in the provided git repo. _Gives you ability to merge patches into your master branch_
|
||||||
|
- Create a CronJob to monitor master branch. Any changes pushed to master branch will be applied into the cluster. _This is a light weight `git-ops` model_
|
||||||
|
|
||||||
|
## Enable GitOps
|
||||||
|
|
||||||
|
to enable gitops the following section should be in the CR
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
....
|
||||||
|
spec:
|
||||||
|
git:
|
||||||
|
repository: https://github.com/ffoysal/qliksense-k8s
|
||||||
|
accessToken: git-token
|
||||||
|
userName: git-username
|
||||||
|
gitOps:
|
||||||
|
enabled: "yes"
|
||||||
|
schedule: "*/5 * * * *"
|
||||||
|
watchBranch: pr-branch-24868a33
|
||||||
|
image: qlik-docker-oss.bintray.io/qliksense-repo-watcher
|
||||||
|
....
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -2,29 +2,52 @@
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- `kubectl` need to be installed and configured properly so that `kubectl` can connect to the kubernetes cluser. The `qliksense` CLI uses `kubectl` under the hood to perform operations on cluster
|
- Kubernetes cluster (Docker Desktop with enabled Kubernetes)
|
||||||
- (Docker Desktop setup tested for these instructions)
|
- `kubectl` installed, configured and able to communicate with kubernetes cluster. _`qliksense` CLI uses `kubectl` under the hood to perform operations on cluster_
|
||||||
|
|
||||||
|
## Installing Sense installer
|
||||||
|
|
||||||
|
Download the executable for your platform from [releases page](https://github.com/qlik-oss/sense-installer/releases) and rename it to `qliksense`
|
||||||
|
|
||||||
|
??? tldr "Linux"
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
curl -Lo qliksense https://github.com/qlik-oss/sense-installer/releases/download/v0.7.0/qliksense-linux-amd64
|
||||||
|
chmod +x qliksense
|
||||||
|
sudo mv qliksense /usr/local/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
??? tldr "MacOS"
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
curl -Lo qliksense https://github.com/qlik-oss/sense-installer/releases/download/v0.7.0/qliksense-darwin-amd64
|
||||||
|
chmod +x qliksense
|
||||||
|
sudo mv qliksense /usr/local/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
??? tldr "Windows"
|
||||||
|
Download Windows executable and add it in your `PATH` as `qliksense.exe`
|
||||||
|
|
||||||
|
[https://github.com/qlik-oss/sense-installer/releases/download/v0.7.0/qliksense-windows-amd64.exe](https://github.com/qlik-oss/sense-installer/releases/download/v0.7.0/qliksense-windows-amd64.exe)
|
||||||
|
|
||||||
## Download
|
|
||||||
|
|
||||||
- Download the appropriate executable for your platform from the [releases page](https://github.com/qlik-oss/sense-installer/releases) and rename it to `qliksense`. All the examplease down below uses `qliksense`.
|
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
- To download the version `v0.0.2` from qliksense-k8s [releases](https://github.com/qlik-oss/qliksense-k8s/releases).
|
- To download the version `v0.0.2` from qliksense-k8s [releases](https://github.com/qlik-oss/qliksense-k8s/releases).
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$qliksense fetch v0.0.2
|
qliksense fetch v0.0.2
|
||||||
```
|
```
|
||||||
|
|
||||||
- To install CRDs for QSEoK and qliksense operator into the kubernetes cluster.
|
- To install CRDs for QSEoK and qliksense operator into the kubernetes cluster.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$qliksense crds install --all
|
qliksense crds install --all
|
||||||
```
|
```
|
||||||
|
|
||||||
- To install QSEoK into a namespace in the kubernetes cluster where `kubectl` is pointing to.
|
- To install QSEoK into a namespace in the kubernetes cluster where `kubectl` is pointing to.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$qliksense install --acceptEULA="yes"
|
qliksense install --acceptEULA="yes"
|
||||||
```
|
```
|
||||||
@@ -1,19 +1,22 @@
|
|||||||
# Overview
|
# Overview
|
||||||
|
|
||||||
The Qlik Sense installer CLI (qliksense) provides an imperative interface to many of the configurations that need to be applied against the declarative structure described in [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s). This cli facilitates:
|
The Qlik Sense installer CLI (`qliksense`) provides an imperative interface to many of the configurations that need to be applied against the declarative structure described in [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s). This cli facilitates:
|
||||||
|
|
||||||
- installation of QSEoK
|
- Installation of QSEoK
|
||||||
- installation of qliksense operator to manage QSEoK
|
- Installation of qliksense operator to manage QSEoK
|
||||||
- air gapped installation of QSEoK
|
- Air gapped installation of QSEoK
|
||||||
|
|
||||||
This is a technology preview that uses Qlik modified [kustomize](https://github.com/qlik-oss/kustomize) to kubernetes manifests of the versions of the [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository.
|
!!! info ""
|
||||||
|
This is a technology preview that uses Qlik modified [kustomize](https://github.com/qlik-oss/kustomize) for kubernetes manifests on [qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s) repository
|
||||||
|
|
||||||
For each version of a qliksense edge build there should be a corresponding release in [qliksense-k8s] repository under [releases](https://github.com/qlik-oss/qliksense-k8s/releases)
|
!!! info ""
|
||||||
|
See QlikSense [edge releases on qliksense-k8s](https://github.com/qlik-oss/qliksense-k8s/releases) repository
|
||||||
|
|
||||||
## Future Direction
|
## Future Direction
|
||||||
|
|
||||||
- More operations:
|
Operations:
|
||||||
- Expand preflight checks
|
|
||||||
- backup/restore operations
|
- Expand preflight checks
|
||||||
- fully support airgap installation of QSEoK
|
- Backup/restore operations
|
||||||
- restore unwanted deletion of kubernetes resources
|
- Fully support airgap installation of QSEoK
|
||||||
|
- Restore unwanted deletion of kubernetes resources
|
||||||
|
|||||||
9
go.mod
9
go.mod
@@ -39,22 +39,25 @@ require (
|
|||||||
github.com/mattn/go-colorable v0.1.4
|
github.com/mattn/go-colorable v0.1.4
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/qlik-oss/k-apis v0.0.19
|
github.com/pkg/errors v0.8.1
|
||||||
|
github.com/qlik-oss/k-apis v0.0.22
|
||||||
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/rogpeppe/go-internal v1.5.2 // indirect
|
github.com/rogpeppe/go-internal v1.5.2 // indirect
|
||||||
github.com/spf13/cobra v0.0.6
|
github.com/spf13/cobra v0.0.6
|
||||||
github.com/spf13/viper v1.6.1
|
github.com/spf13/viper v1.6.1
|
||||||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31
|
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31
|
||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 // indirect
|
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 // indirect
|
||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a // indirect
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a // indirect
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b
|
||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect
|
||||||
golang.org/x/tools v0.0.0-20200309202150-20ab64c0d93f // indirect
|
golang.org/x/tools v0.0.0-20200312194400-c312e98713c2 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b // indirect
|
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b // indirect
|
||||||
google.golang.org/grpc v1.27.0 // indirect
|
google.golang.org/grpc v1.27.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.2.8
|
gopkg.in/yaml.v2 v2.2.8
|
||||||
gopkg.in/yaml.v3 v3.0.0-20190924164351-c8b7dadae555
|
gopkg.in/yaml.v3 v3.0.0-20190924164351-c8b7dadae555
|
||||||
k8s.io/api v0.17.0
|
k8s.io/api v0.17.0
|
||||||
k8s.io/apimachinery v0.17.0
|
k8s.io/apimachinery v0.17.0
|
||||||
|
k8s.io/client-go v11.0.0+incompatible
|
||||||
sigs.k8s.io/kustomize/api v0.3.2
|
sigs.k8s.io/kustomize/api v0.3.2
|
||||||
sigs.k8s.io/yaml v1.1.0
|
sigs.k8s.io/yaml v1.1.0
|
||||||
)
|
)
|
||||||
|
|||||||
19
go.sum
19
go.sum
@@ -843,17 +843,16 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
|
|||||||
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
|
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
|
||||||
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
github.com/qlik-oss/k-apis v0.0.16/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE=
|
github.com/qlik-oss/k-apis v0.0.22 h1:tntQEeRqDYkBi2Ku5+xt7ABGMeFPck7+DOKrHUnpzwI=
|
||||||
github.com/qlik-oss/k-apis v0.0.17 h1:tOdrEe9gfb9CXq0+uowFnXIsI781qz/zgeN8xqupXYw=
|
github.com/qlik-oss/k-apis v0.0.22/go.mod h1:DNiWYqCqPIN216l7+1rccArNIYPb1Le7kYDcPSyNp+Q=
|
||||||
github.com/qlik-oss/k-apis v0.0.17/go.mod h1:KOFzKVIdRqp47ytnHg3+9zb8fTlnrQjO6aKiwcrCJUE=
|
|
||||||
github.com/qlik-oss/k-apis v0.0.19 h1:yrMgALQ08vMDi5hN6fnvIfyNsEaXA5fZjB1YhyIdTfg=
|
|
||||||
github.com/qlik-oss/k-apis v0.0.19/go.mod h1:DNiWYqCqPIN216l7+1rccArNIYPb1Le7kYDcPSyNp+Q=
|
|
||||||
github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9 h1:iqeqTS4zjp6rPEaxmFB7pemA2CMjOEN5dYSXZaQ82uw=
|
github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9 h1:iqeqTS4zjp6rPEaxmFB7pemA2CMjOEN5dYSXZaQ82uw=
|
||||||
github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9/go.mod h1:OCt7FTrRVHj4kmR2xLJJUIqu00BTr6GeF09hSmM17Kw=
|
github.com/qlik-oss/kustomize/api v0.3.3-0.20200206224201-2e697eccbad9/go.mod h1:OCt7FTrRVHj4kmR2xLJJUIqu00BTr6GeF09hSmM17Kw=
|
||||||
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
|
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
|
||||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
|
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
|
||||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
|
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
@@ -1043,10 +1042,8 @@ golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
|
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
|
||||||
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA=
|
||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@@ -1208,8 +1205,8 @@ golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapK
|
|||||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c h1:2EA2K0k9bcvvEDlqD8xdlOhCOqq+O/p9Voqi4x9W1YU=
|
golang.org/x/tools v0.0.0-20200117161641-43d50277825c h1:2EA2K0k9bcvvEDlqD8xdlOhCOqq+O/p9Voqi4x9W1YU=
|
||||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.0.0-20200309202150-20ab64c0d93f h1:NbrfHxef+IfdI86qCgO/1Siq1BuMH2xG0NqgvCguRhQ=
|
golang.org/x/tools v0.0.0-20200312194400-c312e98713c2 h1:6TB4+MaZlkcSsJDu+BS5yxSEuZIYhjWz+jhbSLEZylI=
|
||||||
golang.org/x/tools v0.0.0-20200309202150-20ab64c0d93f/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
golang.org/x/tools v0.0.0-20200312194400-c312e98713c2/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ markdown_extensions:
|
|||||||
- toc:
|
- toc:
|
||||||
permalink: true
|
permalink: true
|
||||||
- admonition
|
- admonition
|
||||||
|
- codehilite
|
||||||
|
- pymdownx.inlinehilite
|
||||||
|
- pymdownx.superfences
|
||||||
|
- pymdownx.details
|
||||||
nav:
|
nav:
|
||||||
- Overview: index.md
|
- Overview: index.md
|
||||||
- getting_started.md
|
- getting_started.md
|
||||||
|
|||||||
BIN
pkg/.DS_Store
vendored
Normal file
BIN
pkg/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -46,7 +46,7 @@ func (qc *QliksenseConfig) GetCR(contextName string) (*QliksenseCR, error) {
|
|||||||
if crFilePath == "" {
|
if crFilePath == "" {
|
||||||
return nil, errors.New("context name " + contextName + " not found")
|
return nil, errors.New("context name " + contextName + " not found")
|
||||||
}
|
}
|
||||||
return getCRObject(crFilePath)
|
return GetCRObject(crFilePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUnencryptedCR() {
|
func getUnencryptedCR() {
|
||||||
@@ -77,7 +77,8 @@ func (qc *QliksenseConfig) SetCrLocation(contextName, filepath string) (*Qliksen
|
|||||||
return nil, errors.New("cannot find the context")
|
return nil, errors.New("cannot find the context")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCRObject(crfile string) (*QliksenseCR, error) {
|
// GetCRObject create a qliksense CR object from file
|
||||||
|
func GetCRObject(crfile string) (*QliksenseCR, error) {
|
||||||
cr := &QliksenseCR{}
|
cr := &QliksenseCR{}
|
||||||
err := ReadFromFile(cr, crfile)
|
err := ReadFromFile(cr, crfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -88,6 +89,20 @@ func getCRObject(crfile string) (*QliksenseCR, error) {
|
|||||||
return cr, nil
|
return cr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//CreateCRObjectFromString create a QliksenseCR from string content
|
||||||
|
func CreateCRObjectFromString(crContent string) (*QliksenseCR, error) {
|
||||||
|
if crContent == "" {
|
||||||
|
return nil, errors.New("empty string cannot qliksensecr")
|
||||||
|
}
|
||||||
|
cr := &QliksenseCR{}
|
||||||
|
err := ReadFromStream(cr, strings.NewReader(crContent))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("cannot unmarshal cr ", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cr, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (qc *QliksenseConfig) getCRFilePath(contextName string) string {
|
func (qc *QliksenseConfig) getCRFilePath(contextName string) string {
|
||||||
crFilePath := ""
|
crFilePath := ""
|
||||||
for _, ctx := range qc.Spec.Contexts {
|
for _, ctx := range qc.Spec.Contexts {
|
||||||
@@ -374,3 +389,36 @@ func (qc *QliksenseConfig) GetDecryptedCr(cr *QliksenseCR) (*QliksenseCR, error)
|
|||||||
newCr.Spec.Secrets = finalSecrets
|
newCr.Spec.Secrets = finalSecrets
|
||||||
return newCr, nil
|
return newCr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Validate validate CR
|
||||||
|
func (cr *QliksenseCR) Validate() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
//CreateContextDirs create context dir structure ~/.qliksense/contexts/contextName
|
||||||
|
func (qc *QliksenseConfig) CreateContextDirs(contextName string) {
|
||||||
|
contexPath := filepath.Join(qc.QliksenseHomePath, qliksenseContextsDirName, contextName)
|
||||||
|
os.MkdirAll(contexPath, os.ModePerm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qc *QliksenseConfig) BuildCrFilePath(contextName string) string {
|
||||||
|
return filepath.Join(qc.QliksenseHomePath, qliksenseContextsDirName, contextName, contextName+".yaml")
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddToContexts add the context into qc.Spec.Contexts
|
||||||
|
func (qc *QliksenseConfig) AddToContexts(crName, crFile string) {
|
||||||
|
qc.Spec.Contexts = append(qc.Spec.Contexts, []Context{
|
||||||
|
{CrFile: crFile,
|
||||||
|
Name: crName},
|
||||||
|
}...)
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetCurrentContextName set the qc.Spec.CurrentContext
|
||||||
|
func (qc *QliksenseConfig) SetCurrentContextName(name string) {
|
||||||
|
qc.Spec.CurrentContext = name
|
||||||
|
}
|
||||||
|
|
||||||
|
//Write write QliksenseConfig into config.yaml
|
||||||
|
func (qc *QliksenseConfig) Write() error {
|
||||||
|
return WriteToFile(qc, filepath.Join(qc.QliksenseHomePath, "config.yaml"))
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package api
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/qlik-oss/k-apis/pkg/config"
|
"github.com/qlik-oss/k-apis/pkg/config"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
@@ -103,14 +105,22 @@ func ReadFromFile(content interface{}, sourceFile string) error {
|
|||||||
if content == nil || sourceFile == "" {
|
if content == nil || sourceFile == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
contents, err := ioutil.ReadFile(sourceFile)
|
file, e := os.Open(sourceFile)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return ReadFromStream(content, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFromStream reads from input stream and creat yaml struct of type content
|
||||||
|
func ReadFromStream(content interface{}, reader io.Reader) error {
|
||||||
|
contents, err := ioutil.ReadAll(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("There was an error reading from file: %s, %v", sourceFile, err)
|
err = fmt.Errorf("There was an error reading from reader: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// reading k8s style object
|
// reading k8s style object
|
||||||
// https://stackoverflow.com/questions/44306554/how-to-deserialize-kubernetes-yaml-file
|
// https://stackoverflow.com/questions/44306554/how-to-deserialize-kubernetes-yaml-file
|
||||||
dec := machine_yaml.NewYAMLOrJSONDecoder(bytes.NewReader(contents), 10000)
|
dec := machine_yaml.NewYAMLOrJSONDecoder(bytes.NewReader(contents), 10000)
|
||||||
dec.Decode(content)
|
return dec.Decode(content)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,3 +97,20 @@ func kubectlOperation(manifests string, oprName string, namespace string) error
|
|||||||
os.Remove(tempYaml.Name())
|
os.Remove(tempYaml.Name())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func KubectlDirectOps(opr []string, namespace string) error {
|
||||||
|
arguments := []string{}
|
||||||
|
if namespace != "" {
|
||||||
|
arguments = append(arguments, "-n", namespace)
|
||||||
|
}
|
||||||
|
arguments = append(arguments, opr...)
|
||||||
|
|
||||||
|
cmd := exec.Command("kubectl", arguments...)
|
||||||
|
LogDebugMessage("Kubectl command: %s %v\n", "kubectl", arguments)
|
||||||
|
sterrBuffer := &bytes.Buffer{}
|
||||||
|
cmd.Stderr = sterrBuffer
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("kubectl %v failed with: %v, %v\n", opr, err, sterrBuffer.String())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,3 +17,16 @@ func TestGetKubectlNamespace(t *testing.T) {
|
|||||||
}
|
}
|
||||||
SetKubectlNamespace(ns)
|
SetKubectlNamespace(ns)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestKubectlDirectOps(t *testing.T) {
|
||||||
|
t.Skip()
|
||||||
|
SetKubectlNamespace("test")
|
||||||
|
ns := GetKubectlNamespace()
|
||||||
|
opr := fmt.Sprintf("version")
|
||||||
|
opr1 := strings.Fields(opr)
|
||||||
|
err := KubectlDirectOps(opr1, ns)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
157
pkg/api/utils.go
157
pkg/api/utils.go
@@ -1,13 +1,21 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"archive/zip"
|
||||||
|
"compress/gzip"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkExists(filename string) os.FileInfo {
|
func checkExists(filename string) os.FileInfo {
|
||||||
@@ -109,3 +117,152 @@ func ExecuteTaskWithBlinkingStdoutFeedback(task func() (interface{}, error), fee
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DownloadFile(url, baseFolder, installerName string) error {
|
||||||
|
var (
|
||||||
|
out *os.File
|
||||||
|
err error
|
||||||
|
resp *http.Response
|
||||||
|
)
|
||||||
|
// Create the file
|
||||||
|
fileName := filepath.Join(baseFolder, installerName)
|
||||||
|
LogDebugMessage("Installer Filename: %s\n", fileName)
|
||||||
|
if out, err = os.Create(fileName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
// Get the data
|
||||||
|
if resp, err = http.Get(url); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
err = fmt.Errorf("unable to download the file from URL: %s, status: %s", url, resp.Status)
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the body to file
|
||||||
|
if _, err = io.Copy(out, resp.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.Chmod(fileName, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExplodePackage(destination, fileToUntar string) error {
|
||||||
|
LogDebugMessage("Destination: %s\n", destination)
|
||||||
|
LogDebugMessage("fileToUntar: %s\n", fileToUntar)
|
||||||
|
|
||||||
|
if strings.HasSuffix(fileToUntar, "zip") {
|
||||||
|
LogDebugMessage("This is a windows file : %s", fileToUntar)
|
||||||
|
err := UnZipFile(destination, fileToUntar)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else if strings.HasSuffix(fileToUntar, "tar.gz") {
|
||||||
|
LogDebugMessage("This is a mac/linux file: %s", fileToUntar)
|
||||||
|
err := UntarGzFile(destination, fileToUntar)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UntarGzFile(destination, fileToUntar string) error {
|
||||||
|
lFile, err := os.Open(fileToUntar)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrapf(err, "unable to read the local file %s", fileToUntar)
|
||||||
|
log.Fatal(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gzReader, err := gzip.NewReader(lFile)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "unable to load the file into a gz reader")
|
||||||
|
log.Fatal(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer gzReader.Close()
|
||||||
|
|
||||||
|
tarReader := tar.NewReader(gzReader)
|
||||||
|
for {
|
||||||
|
header, err := tarReader.Next()
|
||||||
|
switch {
|
||||||
|
case err == io.EOF:
|
||||||
|
return nil
|
||||||
|
case err != nil:
|
||||||
|
err = errors.Wrap(err, "error during untar")
|
||||||
|
log.Fatal(err)
|
||||||
|
return err
|
||||||
|
case header == nil:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInLoop := filepath.Join(destination, header.Name)
|
||||||
|
switch header.Typeflag {
|
||||||
|
case tar.TypeDir:
|
||||||
|
if _, err := os.Stat(fileInLoop); err != nil {
|
||||||
|
if err := os.MkdirAll(fileInLoop, 0755); err != nil {
|
||||||
|
err = errors.Wrapf(err, "error creating directory %s", fileInLoop)
|
||||||
|
log.Fatal(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case tar.TypeReg:
|
||||||
|
fileAtLoc, err := os.OpenFile(fileInLoop, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrapf(err, "error opening file %s", fileInLoop)
|
||||||
|
log.Fatal(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.Copy(fileAtLoc, tarReader); err != nil {
|
||||||
|
err = errors.Wrapf(err, "error writing file %s", fileInLoop)
|
||||||
|
log.Fatal(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fileAtLoc.Close()
|
||||||
|
fileAtLoc.Chmod(os.ModePerm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnZipFile(destination, fileToUnzip string) error {
|
||||||
|
zipReader, _ := zip.OpenReader(fileToUnzip)
|
||||||
|
for _, file := range zipReader.Reader.File {
|
||||||
|
|
||||||
|
zippedFile, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer zippedFile.Close()
|
||||||
|
extractedFilePath := filepath.Join(
|
||||||
|
destination,
|
||||||
|
file.Name,
|
||||||
|
)
|
||||||
|
outputFile, err := os.OpenFile(
|
||||||
|
extractedFilePath,
|
||||||
|
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
|
||||||
|
file.Mode(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer outputFile.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(outputFile, zippedFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
LogDebugMessage("File extracted: %s, Extracted file path: %s\n", file.Name, extractedFilePath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
BIN
pkg/qliksense/.DS_Store
vendored
Normal file
BIN
pkg/qliksense/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -106,7 +106,6 @@ func (q *Qliksense) ConfigViewCR() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println(r)
|
|
||||||
oth, err := q.getCurrentCrDependentResourceAsString()
|
oth, err := q.getCurrentCrDependentResourceAsString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package qliksense
|
|||||||
import (
|
import (
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@@ -13,6 +14,7 @@ import (
|
|||||||
"github.com/qlik-oss/k-apis/pkg/config"
|
"github.com/qlik-oss/k-apis/pkg/config"
|
||||||
|
|
||||||
b64 "encoding/base64"
|
b64 "encoding/base64"
|
||||||
|
|
||||||
ansi "github.com/mattn/go-colorable"
|
ansi "github.com/mattn/go-colorable"
|
||||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
"github.com/ttacon/chalk"
|
"github.com/ttacon/chalk"
|
||||||
@@ -149,7 +151,6 @@ func (q *Qliksense) SetConfigs(args []string) error {
|
|||||||
func retrieveCurrentContextInfo(q *Qliksense) (*api.QliksenseCR, string, error) {
|
func retrieveCurrentContextInfo(q *Qliksense) (*api.QliksenseCR, string, error) {
|
||||||
var qliksenseConfig api.QliksenseConfig
|
var qliksenseConfig api.QliksenseConfig
|
||||||
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
|
qliksenseConfigFile := filepath.Join(q.QliksenseHome, QliksenseConfigFile)
|
||||||
|
|
||||||
if err := api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile); err != nil {
|
if err := api.ReadFromFile(&qliksenseConfig, qliksenseConfigFile); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
@@ -225,8 +226,42 @@ func (q *Qliksense) SetOtherConfigs(args []string) error {
|
|||||||
}
|
}
|
||||||
qliksenseCR.Spec.RotateKeys = rotateKeys
|
qliksenseCR.Spec.RotateKeys = rotateKeys
|
||||||
api.LogDebugMessage("Current rotateKeys after modification: %s ", qliksenseCR.Spec.RotateKeys)
|
api.LogDebugMessage("Current rotateKeys after modification: %s ", qliksenseCR.Spec.RotateKeys)
|
||||||
|
case "gitops.enabled":
|
||||||
|
if qliksenseCR.Spec.GitOps == nil {
|
||||||
|
qliksenseCR.Spec.GitOps = &config.GitOps{}
|
||||||
|
}
|
||||||
|
if strings.EqualFold(argsString[1], "yes") || strings.EqualFold(argsString[1], "no") {
|
||||||
|
qliksenseCR.Spec.GitOps.Enabled = argsString[1]
|
||||||
|
api.LogDebugMessage("Current gitOps enabled status : %s ", qliksenseCR.Spec.GitOps.Enabled)
|
||||||
|
} else {
|
||||||
|
err := fmt.Errorf("Please use yes or no")
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "gitops.schedule":
|
||||||
|
if qliksenseCR.Spec.GitOps == nil {
|
||||||
|
qliksenseCR.Spec.GitOps = &config.GitOps{}
|
||||||
|
}
|
||||||
|
if _, err := cron.ParseStandard(argsString[1]); err != nil {
|
||||||
|
err := fmt.Errorf("Please enter string with standard cron scheduling syntax ")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
qliksenseCR.Spec.GitOps.Schedule = argsString[1]
|
||||||
|
api.LogDebugMessage("Current gitOps schedule is : %s ", qliksenseCR.Spec.GitOps.Schedule)
|
||||||
|
case "gitops.watchbranch":
|
||||||
|
if qliksenseCR.Spec.GitOps == nil {
|
||||||
|
qliksenseCR.Spec.GitOps = &config.GitOps{}
|
||||||
|
}
|
||||||
|
qliksenseCR.Spec.GitOps.WatchBranch = argsString[1]
|
||||||
|
api.LogDebugMessage("Current gitOps watchbranch is : %s ", qliksenseCR.Spec.GitOps.WatchBranch)
|
||||||
|
case "gitops.image":
|
||||||
|
if qliksenseCR.Spec.GitOps == nil {
|
||||||
|
qliksenseCR.Spec.GitOps = &config.GitOps{}
|
||||||
|
}
|
||||||
|
qliksenseCR.Spec.GitOps.Image = argsString[1]
|
||||||
|
api.LogDebugMessage("Current gitOps image is : %s ", qliksenseCR.Spec.GitOps.Image)
|
||||||
default:
|
default:
|
||||||
err := fmt.Errorf("Please enter one of: profile, storageClassName,rotateKeys, manifestRoot or git.repository arguments to configure the current context")
|
err := fmt.Errorf("Please enter one of: profile, storageClassName,rotateKeys, manifestRoot, git.repository or gitops arguments to configure the current context")
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ func removePrivateKey() {
|
|||||||
|
|
||||||
func setup() func() {
|
func setup() func() {
|
||||||
// create tests dir
|
// create tests dir
|
||||||
|
os.RemoveAll(testDir)
|
||||||
if err := os.Mkdir(testDir, 0777); err != nil {
|
if err := os.Mkdir(testDir, 0777); err != nil {
|
||||||
log.Printf("\nError occurred: %v", err)
|
log.Printf("\nError occurred: %v", err)
|
||||||
}
|
}
|
||||||
@@ -326,7 +327,7 @@ func TestSetOtherConfigs(t *testing.T) {
|
|||||||
q: &Qliksense{
|
q: &Qliksense{
|
||||||
QliksenseHome: testDir,
|
QliksenseHome: testDir,
|
||||||
},
|
},
|
||||||
args: []string{"profile=minikube", "rotateKeys=yes", "storageClassName=efs"},
|
args: []string{"profile=minikube", "rotateKeys=yes", "storageClassName=efs", "gitops.enabled=yes", "gitops.schedule=30 * * * *"},
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
@@ -336,7 +337,7 @@ func TestSetOtherConfigs(t *testing.T) {
|
|||||||
q: &Qliksense{
|
q: &Qliksense{
|
||||||
QliksenseHome: testDir,
|
QliksenseHome: testDir,
|
||||||
},
|
},
|
||||||
args: []string{"someconfig=somevalue"},
|
args: []string{"someconfig=somevalue, gitops.schedule=bar", "gitops.enabled=bar"},
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
|
|||||||
88
pkg/qliksense/get_installable_versions.go
Normal file
88
pkg/qliksense/get_installable_versions.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package qliksense
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qlik-oss/k-apis/pkg/git"
|
||||||
|
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LsRemoteCmdOptions struct {
|
||||||
|
IncludeBranches bool
|
||||||
|
Limit int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Qliksense) GetInstallableVersions(opts *LsRemoteCmdOptions) error {
|
||||||
|
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||||
|
qcr, err := qConfig.GetCurrentCR()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var repoPath string
|
||||||
|
if qcr.Spec.GetManifestsRoot() != "" {
|
||||||
|
repoPath = qcr.Spec.GetManifestsRoot()
|
||||||
|
} else {
|
||||||
|
repoPath, err = downloadFromGitRepoToTmpDir(defaultConfigRepoGitUrl, "master")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := git.OpenRepository(repoPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteRefsList, err := git.GetRemoteRefs(r, nil,
|
||||||
|
&git.RemoteRefConstraints{
|
||||||
|
Include: true,
|
||||||
|
Sort: true,
|
||||||
|
SortOrder: git.RefSortOrderDescending,
|
||||||
|
},
|
||||||
|
&git.RemoteRefConstraints{
|
||||||
|
Include: opts.IncludeBranches,
|
||||||
|
Sort: true,
|
||||||
|
SortOrder: git.RefSortOrderAscending,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(remoteRefsList) < 1 {
|
||||||
|
return errors.New("cannot find git remote information in the config repository")
|
||||||
|
}
|
||||||
|
|
||||||
|
var originRemoteRefs *git.RemoteRefs
|
||||||
|
for _, remoteRefs := range remoteRefsList {
|
||||||
|
if remoteRefs.Name == "origin" {
|
||||||
|
originRemoteRefs = remoteRefs
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if originRemoteRefs == nil {
|
||||||
|
return errors.New(`cannot find git remote called "origin" in the config repository`)
|
||||||
|
}
|
||||||
|
|
||||||
|
tags := originRemoteRefs.Tags
|
||||||
|
if len(tags) > opts.Limit {
|
||||||
|
tags = tags[:opts.Limit]
|
||||||
|
}
|
||||||
|
fmt.Print("Versions:\n")
|
||||||
|
for _, tag := range tags {
|
||||||
|
fmt.Printf(" %s\n", tag)
|
||||||
|
}
|
||||||
|
if opts.IncludeBranches {
|
||||||
|
branches := originRemoteRefs.Branches
|
||||||
|
if len(branches) > opts.Limit {
|
||||||
|
branches = branches[:opts.Limit]
|
||||||
|
}
|
||||||
|
fmt.Print("Branches:\n")
|
||||||
|
for _, branch := range branches {
|
||||||
|
fmt.Printf(" %s\n", branch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/qlik-oss/sense-installer/pkg/api"
|
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
|
||||||
|
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // don't delete this line ref: https://github.com/kubernetes/client-go/issues/242
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
"sigs.k8s.io/kustomize/api/konfig"
|
||||||
"sigs.k8s.io/kustomize/api/krusty"
|
"sigs.k8s.io/kustomize/api/krusty"
|
||||||
|
|||||||
60
pkg/qliksense/load_cr.go
Normal file
60
pkg/qliksense/load_cr.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package qliksense
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
//
|
||||||
|
func (q *Qliksense) LoadCr(reader io.Reader) error {
|
||||||
|
for _, doc := range readMultipleYamlFromReader(reader) {
|
||||||
|
if crName, err := q.loadCrStringIntoFileSystem(doc); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
fmt.Println("cr name: [ " + crName + " ] has been loaded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Qliksense) loadCrStringIntoFileSystem(crstr string) (string, error) {
|
||||||
|
cr, err := qapi.CreateCRObjectFromString(crstr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
qConfig := qapi.NewQConfig(q.QliksenseHome)
|
||||||
|
if qConfig.IsContextExist(cr.GetName()) {
|
||||||
|
return "", errors.New("Context Name: " + cr.GetName() + " already exist. please delete the existing context first using delete-context command")
|
||||||
|
}
|
||||||
|
qConfig.CreateContextDirs(cr.GetName())
|
||||||
|
|
||||||
|
if err = qapi.WriteToFile(cr, qConfig.BuildCrFilePath(cr.GetName())); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
qConfig.AddToContexts(cr.GetName(), qConfig.BuildCrFilePath(cr.GetName()))
|
||||||
|
qConfig.SetCurrentContextName(cr.GetName())
|
||||||
|
qConfig.Write()
|
||||||
|
return cr.GetName(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readMultipleYamlFromReader(reader io.Reader) []string {
|
||||||
|
docs := make([]string, 0)
|
||||||
|
scanner := bufio.NewScanner(bufio.NewReader(reader))
|
||||||
|
adoc := ""
|
||||||
|
for scanner.Scan() {
|
||||||
|
s := scanner.Text()
|
||||||
|
if s == "---" {
|
||||||
|
docs = append(docs, adoc)
|
||||||
|
adoc = ""
|
||||||
|
}
|
||||||
|
adoc = adoc + "\n" + s
|
||||||
|
}
|
||||||
|
if adoc != "" {
|
||||||
|
docs = append(docs, adoc)
|
||||||
|
}
|
||||||
|
return docs
|
||||||
|
}
|
||||||
91
pkg/qliksense/load_cr_test.go
Normal file
91
pkg/qliksense/load_cr_test.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package qliksense
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
qapi "github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadCrFile(t *testing.T) {
|
||||||
|
td := setup()
|
||||||
|
sampleCr := `
|
||||||
|
apiVersion: qlik.com/v1
|
||||||
|
kind: Qliksense
|
||||||
|
metadata:
|
||||||
|
name: qlik-test
|
||||||
|
labels:
|
||||||
|
version: v0.0.2
|
||||||
|
spec:
|
||||||
|
git:
|
||||||
|
repository: https://github.com/ffoysal/qliksense-k8s
|
||||||
|
accessToken: abababababababaab
|
||||||
|
userName: "blblbl"
|
||||||
|
gitOps:
|
||||||
|
enabled: "no"
|
||||||
|
schedule: "*/1 * * * *"
|
||||||
|
watchBranch: pr-branch-db1d26d6
|
||||||
|
image: qlik-docker-oss.bintray.io/qliksense-repo-watcher
|
||||||
|
configs:
|
||||||
|
qliksense:
|
||||||
|
- name: acceptEULA
|
||||||
|
value: "yes"
|
||||||
|
secrets:
|
||||||
|
qliksense:
|
||||||
|
- name: mongoDbUri
|
||||||
|
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
|
||||||
|
profile: docker-desktop
|
||||||
|
rotateKeys: "yes"`
|
||||||
|
|
||||||
|
duplicateCr := `
|
||||||
|
apiVersion: qlik.com/v1
|
||||||
|
kind: Qliksense
|
||||||
|
metadata:
|
||||||
|
name: qlik-default
|
||||||
|
labels:
|
||||||
|
version: v0.0.2
|
||||||
|
spec:
|
||||||
|
git:
|
||||||
|
repository: https://github.com/ffoysal/qliksense-k8s
|
||||||
|
accessToken: abababababababaab
|
||||||
|
userName: "blblbl"`
|
||||||
|
crFile := filepath.Join(testDir, "testcr.yaml")
|
||||||
|
ioutil.WriteFile(crFile, []byte(sampleCr), 0644)
|
||||||
|
|
||||||
|
dupCrFile := filepath.Join(testDir, "dupcr.yaml")
|
||||||
|
ioutil.WriteFile(dupCrFile, []byte(duplicateCr), 0644)
|
||||||
|
|
||||||
|
q := New(testDir)
|
||||||
|
file, e := os.Open(crFile)
|
||||||
|
if e != nil {
|
||||||
|
t.Log(e)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if err := q.LoadCr(file); err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
qConfig := qapi.NewQConfig(testDir)
|
||||||
|
cr, err := qConfig.GetCR("qlik-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if cr.GetName() != "qlik-test" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if qConfig.Spec.CurrentContext != "qlik-test" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
file, e = os.Open(dupCrFile)
|
||||||
|
if e != nil {
|
||||||
|
t.Log(e)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if err := q.LoadCr(file); err == nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
td()
|
||||||
|
}
|
||||||
302
pkg/qliksense/preflight_checks.go
Normal file
302
pkg/qliksense/preflight_checks.go
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
package qliksense
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/qlik-oss/sense-installer/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// preflight releases have the same version
|
||||||
|
preflightRelease = "v0.9.26"
|
||||||
|
preflightLinuxFile = "preflight_linux_amd64.tar.gz"
|
||||||
|
preflightMacFile = "preflight_darwin_amd64.tar.gz"
|
||||||
|
preflightWindowsFile = "preflight_windows_amd64.zip"
|
||||||
|
PreflightChecksDirName = "preflight_checks"
|
||||||
|
)
|
||||||
|
|
||||||
|
var preflightBaseURL = fmt.Sprintf("https://github.com/replicatedhq/troubleshoot/releases/download/%s/", preflightRelease)
|
||||||
|
|
||||||
|
const dnsCheckYAML = `
|
||||||
|
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||||
|
kind: Preflight
|
||||||
|
metadata:
|
||||||
|
name: cluster-preflight-checks
|
||||||
|
namespace: {{ . }}
|
||||||
|
spec:
|
||||||
|
collectors:
|
||||||
|
- run:
|
||||||
|
collectorName: spin-up-pod
|
||||||
|
args: ["-z", "-v", "-w 1", "qnginx001", "80"]
|
||||||
|
command: ["nc"]
|
||||||
|
image: subfuzion/netcat:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
name: spin-up-pod-check-dns
|
||||||
|
namespace: {{ . }}
|
||||||
|
timeout: 30s
|
||||||
|
|
||||||
|
analyzers:
|
||||||
|
- textAnalyze:
|
||||||
|
checkName: DNS check
|
||||||
|
collectorName: spin-up-pod-check-dns
|
||||||
|
fileName: spin-up-pod.txt
|
||||||
|
regex: succeeded
|
||||||
|
outcomes:
|
||||||
|
- fail:
|
||||||
|
message: DNS check failed
|
||||||
|
- pass:
|
||||||
|
message: DNS check passed
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Qliksense) DownloadPreflight() error {
|
||||||
|
const preflightExecutable = "preflight"
|
||||||
|
|
||||||
|
preflightInstallDir := filepath.Join(q.QliksenseHome, PreflightChecksDirName)
|
||||||
|
platform := runtime.GOOS
|
||||||
|
|
||||||
|
exists, err := checkInstalled(preflightInstallDir, preflightExecutable)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("There has been an error when trying to determine if preflight installer exists")
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
// preflight exist, no need to download again.
|
||||||
|
api.LogDebugMessage("Preflight already exist, proceeding to perform checks")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the Preflight-check directory, download and install preflight
|
||||||
|
if !api.DirExists(preflightInstallDir) {
|
||||||
|
api.LogDebugMessage("%s does not exist, creating now\n", preflightInstallDir)
|
||||||
|
if err := os.Mkdir(preflightInstallDir, os.ModePerm); err != nil {
|
||||||
|
err = fmt.Errorf("Not able to create %s dir: %v", preflightInstallDir, err)
|
||||||
|
log.Println(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
api.LogDebugMessage("Preflight-checks install Dir: %s exists", preflightInstallDir)
|
||||||
|
|
||||||
|
preflightUrl, preflightFile, err := determinePlatformSpecificUrls(platform)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("There was an error when trying to determine platform specific paths")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download Preflight
|
||||||
|
err = downloadAndExplode(preflightUrl, preflightInstallDir, preflightFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("Downloaded Preflight")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkInstalled(preflightInstallDir, preflightExecutable string) (bool, error) {
|
||||||
|
installerExists := true
|
||||||
|
preflightInstaller := filepath.Join(preflightInstallDir, preflightExecutable)
|
||||||
|
if api.DirExists(preflightInstallDir) {
|
||||||
|
if !api.FileExists(preflightInstaller) {
|
||||||
|
installerExists = false
|
||||||
|
api.LogDebugMessage("Preflight install directory exists, but preflight installer does not exist")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
installerExists = false
|
||||||
|
}
|
||||||
|
return installerExists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadAndExplode(url, installDir, file string) error {
|
||||||
|
err := api.DownloadFile(url, installDir, file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
api.LogDebugMessage("Downloaded File: %s", file)
|
||||||
|
|
||||||
|
fileToUntar := filepath.Join(installDir, file)
|
||||||
|
api.LogDebugMessage("File to explode: %s", file)
|
||||||
|
|
||||||
|
err = api.ExplodePackage(installDir, fileToUntar)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func determinePlatformSpecificUrls(platform string) (string, string, error) {
|
||||||
|
|
||||||
|
var preflightUrl, preflightFile string
|
||||||
|
|
||||||
|
if runtime.GOARCH != `amd64` {
|
||||||
|
err := fmt.Errorf("%s architecture is not supported", runtime.GOARCH)
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch platform {
|
||||||
|
case "windows":
|
||||||
|
preflightFile = preflightWindowsFile
|
||||||
|
case "darwin":
|
||||||
|
preflightFile = preflightMacFile
|
||||||
|
case "linux":
|
||||||
|
preflightFile = preflightLinuxFile
|
||||||
|
default:
|
||||||
|
err := fmt.Errorf("Unable to download the preflight executable for the underlying platform\n")
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
preflightUrl = fmt.Sprintf("%s%s", preflightBaseURL, preflightFile)
|
||||||
|
|
||||||
|
return preflightUrl, preflightFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Qliksense) CheckDns() error {
|
||||||
|
// retrieve namespace
|
||||||
|
namespace := api.GetKubectlNamespace()
|
||||||
|
api.LogDebugMessage("Namespace here: %s", namespace)
|
||||||
|
|
||||||
|
tmpl, err := template.New("test").Parse(dnsCheckYAML)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("cannot parse template: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tempYaml, err := ioutil.TempFile("", "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("cannot create file: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
api.LogDebugMessage("Temp Yaml file: %s\n", tempYaml.Name())
|
||||||
|
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
err = tmpl.Execute(&b, namespace)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tempYaml.WriteString(b.String())
|
||||||
|
|
||||||
|
// creating Kubectl resources
|
||||||
|
appName := "qnginx001"
|
||||||
|
const PreflightChecksDirName = "preflight_checks"
|
||||||
|
const preflightFileName = "preflight"
|
||||||
|
|
||||||
|
// kubectl create deployment
|
||||||
|
opr := fmt.Sprintf("create deployment %s --image=nginx", appName)
|
||||||
|
err = initiateK8sOps(opr, namespace)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
api.LogDebugMessage("create deployment executed")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// Deleting deployment..
|
||||||
|
opr = fmt.Sprintf("delete deployment %s", appName)
|
||||||
|
// we want to delete the k8s resource here, we dont care a lot about an error here
|
||||||
|
_ = initiateK8sOps(opr, namespace)
|
||||||
|
api.LogDebugMessage("delete deployment executed")
|
||||||
|
}()
|
||||||
|
|
||||||
|
// create service
|
||||||
|
opr = fmt.Sprintf("create service clusterip %s --tcp=80:80", appName)
|
||||||
|
err = initiateK8sOps(opr, namespace)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
api.LogDebugMessage("create service executed")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// delete service
|
||||||
|
opr = fmt.Sprintf("delete service %s", appName)
|
||||||
|
// we want to delete the k8s resource here, we dont care a lot about an error here
|
||||||
|
_ = initiateK8sOps(opr, namespace)
|
||||||
|
api.LogDebugMessage("delete service executed")
|
||||||
|
}()
|
||||||
|
|
||||||
|
//kubectl -n $namespace wait --for=condition=ready pod -l app=$appName --timeout=120s
|
||||||
|
opr = fmt.Sprintf("wait --for=condition=ready pod -l app=%s --timeout=120s", appName)
|
||||||
|
err = initiateK8sOps(opr, namespace)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
api.LogDebugMessage("kubectl wait executed")
|
||||||
|
|
||||||
|
// call preflight
|
||||||
|
preflightCommand := filepath.Join(q.QliksenseHome, PreflightChecksDirName, preflightFileName)
|
||||||
|
trackSuccess, err := invokePreflight(preflightCommand, tempYaml)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if trackSuccess {
|
||||||
|
fmt.Println("PREFLIGHT DNS CHECK PASSED")
|
||||||
|
} else {
|
||||||
|
fmt.Println("PREFLIGHT DNS CHECK FAILED")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initiateK8sOps(opr, namespace string) error {
|
||||||
|
opr1 := strings.Fields(opr)
|
||||||
|
err := api.KubectlDirectOps(opr1, namespace)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokePreflight(preflightCommand string, yamlFile *os.File) (bool, error) {
|
||||||
|
arguments := []string{}
|
||||||
|
arguments = append(arguments, yamlFile.Name(), "--interactive=false")
|
||||||
|
cmd := exec.Command(preflightCommand, arguments...)
|
||||||
|
|
||||||
|
sterrBuffer := &bytes.Buffer{}
|
||||||
|
cmd.Stdout = sterrBuffer
|
||||||
|
cmd.Stderr = sterrBuffer
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return false, fmt.Errorf("Error when running preflight command: %v\n", err)
|
||||||
|
}
|
||||||
|
ind := strings.Index(sterrBuffer.String(), "---")
|
||||||
|
output := sterrBuffer.String()
|
||||||
|
if ind > -1 {
|
||||||
|
output = fmt.Sprintf("%s\n%s", output[:ind], output[ind:])
|
||||||
|
}
|
||||||
|
fmt.Printf("%v\n", output)
|
||||||
|
outputArr := strings.Fields(strings.TrimSpace(output))
|
||||||
|
trackSuccess := false
|
||||||
|
trackPrg := false
|
||||||
|
|
||||||
|
// We are only checking the overall "PASS" or "FAIL"
|
||||||
|
// We are going to look for the first occurance of PASS or FAIL from the end
|
||||||
|
// there are also some space-like deceiving characters
|
||||||
|
for i := len(outputArr) - 1; i >= 0; i-- {
|
||||||
|
if strings.TrimSpace(outputArr[i]) != "" {
|
||||||
|
if outputArr[i] == "PASS" {
|
||||||
|
trackSuccess = true
|
||||||
|
trackPrg = true
|
||||||
|
} else if outputArr[i] == "FAIL" {
|
||||||
|
trackPrg = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if trackPrg {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return trackSuccess, nil
|
||||||
|
}
|
||||||
90
pkg/qliksense/preflight_checks_test.go
Normal file
90
pkg/qliksense/preflight_checks_test.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package qliksense
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_initiateK8sOps(t *testing.T) {
|
||||||
|
t.Skip()
|
||||||
|
type args struct {
|
||||||
|
opr string
|
||||||
|
namespace string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid case",
|
||||||
|
args: args{
|
||||||
|
opr: fmt.Sprintf("version"),
|
||||||
|
namespace: "ash-ns",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid case",
|
||||||
|
args: args{
|
||||||
|
opr: fmt.Sprintf("versions"),
|
||||||
|
namespace: "ash-ns",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := initiateK8sOps(tt.args.opr, tt.args.namespace); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("initiateK8sOps() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_determinePlatformSpecificUrls(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
platform string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
want1 string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid platform",
|
||||||
|
args: args{
|
||||||
|
platform: "windows",
|
||||||
|
},
|
||||||
|
want: fmt.Sprintf("%s%s", preflightBaseURL, preflightWindowsFile),
|
||||||
|
want1: preflightWindowsFile,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid platform",
|
||||||
|
args: args{
|
||||||
|
platform: "unix",
|
||||||
|
},
|
||||||
|
want: "",
|
||||||
|
want1: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, got1, err := determinePlatformSpecificUrls(tt.args.platform)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("determinePlatformSpecificUrls() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("determinePlatformSpecificUrls() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
if got1 != tt.want1 {
|
||||||
|
t.Errorf("determinePlatformSpecificUrls() got1 = %v, want %v", got1, tt.want1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user