Compare commits
70 Commits
0.1.1-alph
...
2.0.0-beta
Author | SHA1 | Date | |
---|---|---|---|
418a0a8029 | |||
165054ca53 | |||
a87391e726 | |||
f8dcd2026b | |||
f99f146af2 | |||
e72a8a2e9c | |||
0f73aa3125 | |||
7dc5d509f7 | |||
a9b75c48c4 | |||
6ea3a923cc | |||
6cd1af015b | |||
68a031d0be | |||
7ba68c2854 | |||
1dd8fef4b3 | |||
6a2417c361 | |||
ad316d1f49 | |||
ed22774a93 | |||
8dfca953dc | |||
7b774e84d8 | |||
d0576d4b81 | |||
c1fc388b26 | |||
86ca723aa8 | |||
35ecb0d4d9 | |||
89adc17857 | |||
16fddbc6aa | |||
8543bc5232 | |||
3d45401649 | |||
95c24be14a | |||
bf44d442e5 | |||
5d574015ce | |||
3619cc4b20 | |||
5a49c4f869 | |||
88fb12a3bf | |||
d387d9aec0 | |||
1343f99e39 | |||
90d04346d5 | |||
950a77aade | |||
6ef4f7ac42 | |||
513039e3c9 | |||
a60ab484d2 | |||
d9fcf5a1b9 | |||
0668b718ea | |||
0d1a6f8c82 | |||
a4834a0661 | |||
722c7424d0 | |||
b602aa5e39 | |||
d965e1d19b | |||
5a4d9e396d | |||
8164603b47 | |||
9aec646ab2 | |||
8d4ea90a9a | |||
4320519a2a | |||
b9e91d56aa | |||
93a06b52fb | |||
cb88f2879d | |||
1e79e954c5 | |||
69982e4514 | |||
691c1a3b78 | |||
332f7a8787 | |||
6273e5531a | |||
e0382a8b83 | |||
3385b61272 | |||
a0e02af06e | |||
7adac3662e | |||
ca9ab8a13b | |||
b16897b875 | |||
8ccdb2854b | |||
df60c2c866 | |||
fe2a655796 | |||
714ccf771d |
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,5 +1,10 @@
|
||||
dist/*
|
||||
.cache/*
|
||||
chart/*
|
||||
docker-compose.yaml
|
||||
katenary
|
||||
./katenary
|
||||
*.env
|
||||
docker-compose*
|
||||
!examples/**/docker-compose*
|
||||
.credentials
|
||||
release.id
|
||||
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Patrice Ferlet
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
127
Makefile
127
Makefile
@@ -4,6 +4,16 @@ VERSION=$(shell git describe --exact-match --tags $(CUR_SHA) 2>/dev/null || echo
|
||||
CTN:=$(shell which podman 2>&1 1>/dev/null && echo "podman" || echo "docker")
|
||||
PREFIX=~/.local
|
||||
|
||||
GO=container
|
||||
OUT=katenary
|
||||
BLD_CMD=go build -ldflags="-X 'main.Version=$(VERSION)'" -o $(OUT) ./cmd/katenary/*.go
|
||||
GOOS=linux
|
||||
GOARCH=amd64
|
||||
|
||||
BUILD_IMAGE=docker.io/golang:1.18-alpine
|
||||
|
||||
.PHONY: help clean build
|
||||
|
||||
.ONESHELL:
|
||||
help:
|
||||
@cat <<EOF
|
||||
@@ -19,19 +29,92 @@ help:
|
||||
$$ sudo make install PREFIX=/usr/local
|
||||
|
||||
Katenary is statically built (in Go), so there is no library to install.
|
||||
|
||||
To build for others OS:
|
||||
$$ make build GOOS=linux GOARCH=amd64
|
||||
This will build the binary for linux amd64.
|
||||
|
||||
$$ make build GOOS=linux GOARCH=arm
|
||||
This will build the binary for linux arm.
|
||||
|
||||
$$ make build GOOS=windows GOARCH=amd64
|
||||
This will build the binary for windows amd64.
|
||||
|
||||
$$ make build GOOS=darwin GOARCH=amd64
|
||||
This will build the binary for darwin amd64.
|
||||
|
||||
Or you can build all versions:
|
||||
$$ make build-all
|
||||
EOF
|
||||
|
||||
build: pull katenary
|
||||
|
||||
build: katenary
|
||||
build-all:
|
||||
rm -f dist/*
|
||||
$(MAKE) _build-all
|
||||
|
||||
katenary: *.go generator/*.go compose/*.go helm/*.go
|
||||
@echo Build using $(CTN)
|
||||
ifeq ($(CTN),podman)
|
||||
@podman run --rm -v $(PWD):/go/src/katenary -w /go/src/katenary --userns keep-id -it golang go build -o katenary -ldflags="-X 'main.Version=$(VERSION)'" .
|
||||
else
|
||||
@docker run --rm -v $(PWD):/go/src/katenary:z -w /go/src/katenary --user $(shell id -u):$(shell id -g) -e HOME=/tmp -it golang go build -o katenary -ldflags="-X 'main.Version=$(VERSION)'" .
|
||||
_build-all: pull dist dist/katenary-linux-amd64 dist/katenary-linux-arm64 dist/katenary.exe dist/katenary-darwin-amd64 dist/katenary-freebsd-amd64 dist/katenary-freebsd-arm64
|
||||
|
||||
pull:
|
||||
ifneq ($(GO),local)
|
||||
@echo -e "\033[1;32mPulling $(BUILD_IMAGE) docker image\033[0m"
|
||||
@$(CTN) pull $(BUILD_IMAGE)
|
||||
endif
|
||||
|
||||
dist:
|
||||
mkdir -p dist
|
||||
|
||||
dist/katenary-linux-amd64:
|
||||
@echo
|
||||
@echo -e "\033[1;32mBuilding katenary $(VERSION) for linux-amd64...\033[0m"
|
||||
$(MAKE) katenary GOOS=linux GOARCH=amd64 OUT=$@
|
||||
|
||||
|
||||
dist/katenary-linux-arm64:
|
||||
@echo
|
||||
@echo -e "\033[1;32mBuilding katenary $(VERSION) for linux-arm...\033[0m"
|
||||
$(MAKE) katenary GOOS=linux GOARCH=arm64 OUT=$@
|
||||
|
||||
dist/katenary.exe:
|
||||
@echo
|
||||
@echo -e "\033[1;32mBuilding katenary $(VERSION) for windows...\033[0m"
|
||||
$(MAKE) katenary GOOS=windows GOARCH=amd64 OUT=$@
|
||||
|
||||
dist/katenary-darwin-amd64:
|
||||
@echo
|
||||
@echo -e "\033[1;32mBuilding katenary $(VERSION) for darwin...\033[0m"
|
||||
$(MAKE) katenary GOOS=darwin GOARCH=amd64 OUT=$@
|
||||
|
||||
dist/katenary-freebsd-amd64:
|
||||
@echo
|
||||
@echo -e "\033[1;32mBuilding katenary $(VERSION) for freebsd...\033[0m"
|
||||
$(MAKE) katenary GOOS=freebsd GOARCH=amd64 OUT=$@
|
||||
|
||||
dist/katenary-freebsd-arm64:
|
||||
@echo
|
||||
@echo -e "\033[1;32mBuilding katenary $(VERSION) for freebsd-arm64...\033[0m"
|
||||
$(MAKE) katenary GOOS=freebsd GOARCH=arm64 OUT=$@
|
||||
|
||||
katenary: $(wildcard */*.go Makefile go.mod go.sum)
|
||||
ifeq ($(GO),local)
|
||||
@echo "=> Build in host using go"
|
||||
else
|
||||
@echo "=> Build in container using" $(CTN)
|
||||
endif
|
||||
echo $(BLD_CMD)
|
||||
ifeq ($(GO),local)
|
||||
$(BLD_CMD)
|
||||
else ifeq ($(CTN),podman)
|
||||
@podman run -e CGO_ENABLED=0 -e GOOS=$(GOOS) -e GOARCH=$(GOARCH) \
|
||||
--rm -v $(PWD):/go/src/katenary:z -w /go/src/katenary --userns keep-id -it $(BUILD_IMAGE) $(BLD_CMD)
|
||||
else
|
||||
@docker run -e CGO_ENABLED=0 -e GOOS=$(GOOS) -e GOARCH=$(GOARCH) \
|
||||
--rm -v $(PWD):/go/src/katenary:z -w /go/src/katenary --user $(shell id -u):$(shell id -g) -e HOME=/tmp -it $(BUILD_IMAGE) $(BLD_CMD)
|
||||
endif
|
||||
echo "=> Stripping if possible"
|
||||
strip $(OUT) 2>/dev/null || echo "=> No strip available"
|
||||
|
||||
|
||||
|
||||
install: build
|
||||
cp katenary $(PREFIX)/bin/katenary
|
||||
@@ -40,6 +123,34 @@ uninstall:
|
||||
rm -f $(PREFIX)/bin/katenary
|
||||
|
||||
clean:
|
||||
rm -f katenary
|
||||
rm -rf katenary dist/* release.id
|
||||
|
||||
|
||||
tests: test
|
||||
test:
|
||||
@echo -e "\033[1;33mTesting katenary $(VERSION)...\033[0m"
|
||||
go test -v ./...
|
||||
|
||||
|
||||
.ONESHELL:
|
||||
push-release: build-all
|
||||
@rm -f release.id
|
||||
# read personal access token from .git-credentials
|
||||
TOKEN=$(shell cat .credentials)
|
||||
# create a new release based on current tag and get the release id
|
||||
@curl -sSL -X POST \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "Authorization: token $$TOKEN" \
|
||||
-d "{\"tag_name\": \"$(VERSION)\", \"target_commitish\": \"\", \"name\": \"$(VERSION)\", \"draft\": true, \"prerelease\": true}" \
|
||||
https://api.github.com/repos/metal3d/katenary/releases | jq -r '.id' > release.id
|
||||
@echo "Release id: $$(cat release.id) created"
|
||||
@echo "Uploading assets..."
|
||||
# push all dist binary as assets to the release
|
||||
@for i in $$(find dist -type f -name "katenary*"); do
|
||||
curl -sSL -H "Authorization: token $$TOKEN" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary @$$i \
|
||||
https://uploads.github.com/repos/metal3d/katenary/releases/$$(cat release.id)/assets?name=$$(basename $$i)
|
||||
done
|
||||
@rm -f release.id
|
||||
|
147
README.md
147
README.md
@@ -1,10 +1,31 @@
|
||||
<div style="text-align:center">
|
||||
<img src="./misc/logo.png" alt="Katenary Logo" />
|
||||
</div>
|
||||
|
||||
Katenary is a tool to help transforming `docker-compose` files to a working Helm Chart for Kubernetes.
|
||||
|
||||
> **Important Note** Katenary is a tool to help building Helm Chart from a docker-compose file, but docker-compose doesn't propose as many features as what can do Kubernetes. So, we strongly recommend to use Katenary as a "bootstrap" tool and then to manually enhance the generated helm chart.
|
||||
> **Important Note:** Katenary is a tool to help building Helm Chart from a docker-compose file, but docker-compose doesn't propose as many features as what can do Kubernetes. So, we strongly recommend to use Katenary as a "bootstrap" tool and then to manually enhance the generated helm chart.
|
||||
|
||||
This project is partially made at [Smile](https://www.smile.eu)
|
||||
|
||||
<div style="text-align:center">
|
||||
<a href="https://www.smile.eu"><img src="./misc/Logo_Smile.png" alt="Smile Logo" width="250" /></a>
|
||||
</div>
|
||||
|
||||
# Install
|
||||
|
||||
You can download the binaries from the [Release](https://github.com/metal3d/katenary/releases) section. Copy the binary and rename it to `katenary`. Place the binary inside your `PATH`. You should now be able to call the `katenary` command.
|
||||
|
||||
You can of course get the binary with `go install -u github.com/metal3d/katenary/cmd/katenary/...` but the `main` branch is continuously updated. It's preferable to use releases.
|
||||
|
||||
You can use this commands on Linux:
|
||||
|
||||
```bash
|
||||
sh <(curl -sSL https://raw.githubusercontent.com/metal3d/katenary/master/install.sh)
|
||||
```
|
||||
|
||||
# Else... Build yourself
|
||||
|
||||
If you've got `podman` or `docker`, you can build `katenary` by using:
|
||||
|
||||
```bash
|
||||
@@ -22,26 +43,75 @@ It will use the default PREFIX (`~/.local/`) to install the binary in the `bin`
|
||||
sudo make install PREFIX=/usr/local
|
||||
```
|
||||
|
||||
If that goes wrong, you can use your local Go compiler:
|
||||
|
||||
```bash
|
||||
make build GO=local
|
||||
|
||||
# To force OS or architecture
|
||||
make build GO=local GOOS=linux GOARCH=arm64
|
||||
```
|
||||
|
||||
Then place the `katenary` binary file inside your PATH.
|
||||
|
||||
|
||||
# Tips
|
||||
|
||||
We strongly recommand to add the "completion" call to you SHELL using the common bashrc, or whatever the profile file you use.
|
||||
|
||||
E.g. :
|
||||
|
||||
```bash
|
||||
# bash in ~/.bashrc file
|
||||
source <(katenary completion bash)
|
||||
# if the documentation breaks a bit your completion:
|
||||
source <(katenary completion bash --no-description)
|
||||
|
||||
# zsh in ~/.zshrc
|
||||
source <(helm completion zsh)
|
||||
|
||||
# fish in ~/.config/fish/config.fish
|
||||
katenary completion fish | source
|
||||
|
||||
# powershell (as we don't provide any support on Windows yet, please avoid this...)
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
```bash
|
||||
Usage of katenary:
|
||||
-appname string
|
||||
sive the helm chart app name (default "MyApp")
|
||||
-appversion string
|
||||
set the chart appVersion (default "0.0.1")
|
||||
-chart-dir string
|
||||
set the chart directory (default "chart")
|
||||
-compose string
|
||||
set the compose file to parse (default "docker-compose.yaml")
|
||||
-force
|
||||
force the removal of the chart-dir
|
||||
-version
|
||||
Show version and exit
|
||||
```
|
||||
Katenary aims to be a tool to convert docker-compose files to Helm Charts.
|
||||
It will create deployments, services, volumes, secrets, and ingress resources.
|
||||
But it will also create initContainers based on depend_on, healthcheck, and other features.
|
||||
It's not magical, sometimes you'll need to fix the generated charts.
|
||||
The general way to use it is to call one of these commands:
|
||||
|
||||
katenary convert
|
||||
katenary convert -c docker-compose.yml
|
||||
katenary convert -c docker-compose.yml -o ./charts
|
||||
|
||||
In case of, check the help of each command using:
|
||||
katenary <command> --help
|
||||
or
|
||||
"katenary help <command>"
|
||||
|
||||
Usage:
|
||||
katenary [command]
|
||||
|
||||
Available Commands:
|
||||
completion Generate the autocompletion script for the specified shell
|
||||
convert Convert docker-compose to helm chart
|
||||
help Help about any command
|
||||
show-labels Show labels of a resource
|
||||
upgrade Upgrade katenary to the latest version if available
|
||||
version Display version
|
||||
|
||||
Flags:
|
||||
-h, --help help for katenary
|
||||
|
||||
Use "katenary [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
Katenary will try to find a `docker-compose.yaml` file inside the current directory. It will check *the existence of the `chart` directory to create a new Helm Chart inside a named subdirectory. Katenary will ask you if you want to delete it before recreating.
|
||||
Katenary will try to find a `docker-compose.yaml` or `docker-compose.yml` file inside the current directory. It will check *the existence of the `chart` directory to create a new Helm Chart inside a named subdirectory. Katenary will ask you if you want to delete it before recreating.
|
||||
|
||||
It creates a subdirectory inside `chart` that is named with the `appname` option (default is `MyApp`)
|
||||
|
||||
@@ -56,7 +126,7 @@ What can be interpreted by Katenary:
|
||||
- `env_file` list will create a configMap object per environemnt file (⚠ todo: the "to-service" label doesn't work with configMap for now)
|
||||
- some labels can help to bind values, for example:
|
||||
- `katenary.io/ingress: 80` will expose the port 80 in a ingress
|
||||
- `katenary.io/to-service: VARNAME` will convert the value to a variable `{{ .Release.Name }}-VARNAME` - it's usefull when you want to pass the name of a service as a variable (think about the service name for mysql to pass to a container that wants to connect to this)
|
||||
- `katenary.io/mapenv: |`: allow to map environment to something else than the given value in the compose file
|
||||
|
||||
Exemple of a possible `docker-compose.yaml` file:
|
||||
|
||||
@@ -75,27 +145,56 @@ services:
|
||||
# because it's the "exposed" port
|
||||
- database
|
||||
labels:
|
||||
# explain to katenary that "DB_HOST" value is variable (using release name)
|
||||
katenary.io/env-to-service: DB_HOST
|
||||
# expose the port 80 as an ingress
|
||||
katenary.io/ingress: 80
|
||||
# make adaptations, DB_HOST environment is actually the service name
|
||||
# to hit (note the yaml style, start with "|")
|
||||
katenary.io/mapenv: |
|
||||
DB_HOST: {{ .Release.Name }}-database
|
||||
database:
|
||||
image: mariadb:10
|
||||
env_file:
|
||||
# this will create a configMap
|
||||
- my_env.env
|
||||
environment:
|
||||
MARIADB_USER: foo
|
||||
MARIADB_ROOT_PASSWORD: foobar
|
||||
MARIADB_PASSWORD: bar
|
||||
labels:
|
||||
# no need to declare this port in docker-compose
|
||||
# but katenary will need it
|
||||
katenary.io/ports: 3306
|
||||
# these variables are secrets
|
||||
katenary.io/secret-vars: MARIADB_ROOT_PASSWORD, MARIADB_PASSWORD
|
||||
```
|
||||
|
||||
# Labels
|
||||
|
||||
- `katenary.io/env-to-service` binds the given (coma separated) variables names to {{ .Release.Name }}-value
|
||||
- `katenary.io/ingress`: create an ingress and bind it to the given port
|
||||
- `katenary.io/secret-envfiles`: force the creation of a secret for the given coma separated list of "env_file"
|
||||
- `katenary.io/ports` is a coma separated list of ports if you want to avoid the "ports" section in your docker-compose for any reason
|
||||
- `katenary.io/configma-volumes` is a coma separated list of directory (should be declared as volumes also) to transform to a configMap object
|
||||
These labels could be found by `katenary show-labels`, and can be placed as "labels" inside your docker-compose file:
|
||||
|
||||
```
|
||||
katenary.io/ignore : ignore the container, it will not yied any object in the helm chart
|
||||
katenary.io/secret-vars : secret variables to push on a secret file
|
||||
katenary.io/secret-envfiles : set the given file names as a secret instead of configmap
|
||||
katenary.io/mapenv : map environment variable to a template string (yaml style)
|
||||
katenary.io/ports : set the ports to expose as a service (coma separated)
|
||||
katenary.io/ingress : set the port to expose in an ingress (coma separated)
|
||||
katenary.io/configmap-volumes : specifies that the volumes points on a configmap (coma separated)
|
||||
katenary.io/same-pod : specifies that the pod should be deployed in the same pod than the given service name
|
||||
katenary.io/empty-dirs : specifies that the given volume names should be "emptyDir" instead of persistentVolumeClaim (coma separated)
|
||||
katenary.io/healthcheck : specifies that the container should be monitored by a healthcheck, **it overrides the docker-compose healthcheck**.
|
||||
You can use these form of label values:
|
||||
- "http://[not used address][:port][/path]" to specify an http healthcheck
|
||||
- "tcp://[not used address]:port" to specify a tcp healthcheck
|
||||
- other string is condidered as a "command" healthcheck
|
||||
```
|
||||
|
||||
# What a name...
|
||||
|
||||
Katenary is the stylized name of the project that comes from the "catenary" word.
|
||||
|
||||
A catenary is a curve formed by a wire, rope, or chain hanging freely from two points that are not in the same vertical line. For example, the anchor chain between a bot and the anchor.
|
||||
|
||||
This "curved link" represents what we try to do, the project is a "streched link from docker-compose to helm chart".
|
||||
|
||||
|
||||
|
154
cmd/katenary/main.go
Normal file
154
cmd/katenary/main.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"katenary/generator/writers"
|
||||
"katenary/helm"
|
||||
"katenary/update"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var Version = "master" // changed at compile time
|
||||
|
||||
var longHelp = `Katenary aims to be a tool to convert docker-compose files to Helm Charts.
|
||||
It will create deployments, services, volumes, secrets, and ingress resources.
|
||||
But it will also create initContainers based on depend_on, healthcheck, and other features.
|
||||
It's not magical, sometimes you'll need to fix the generated charts.
|
||||
The general way to use it is to call one of these commands:
|
||||
|
||||
katenary convert
|
||||
katenary convert -c docker-compose.yml
|
||||
katenary convert -c docker-compose.yml -o ./charts
|
||||
|
||||
In case of, check the help of each command using:
|
||||
katenary <command> --help
|
||||
or
|
||||
"katenary help <command>"
|
||||
`
|
||||
|
||||
func init() {
|
||||
// apply the version to the "update" package
|
||||
update.Version = Version
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
// The base command
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "katenary",
|
||||
Long: longHelp,
|
||||
Short: "Katenary is a tool to convert docker-compose files to Helm Charts",
|
||||
}
|
||||
|
||||
// to display the version
|
||||
versionCmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Display version",
|
||||
Run: func(c *cobra.Command, args []string) { c.Println(Version) },
|
||||
}
|
||||
|
||||
// convert command, need some flags
|
||||
convertCmd := &cobra.Command{
|
||||
Use: "convert",
|
||||
Short: "Convert docker-compose to helm chart",
|
||||
Long: "Convert docker-compose to helm chart. The resulting helm chart will be in the current directory/" +
|
||||
ChartsDir + "/" + AppName +
|
||||
".\nThe appversion will be generated that way:\n" +
|
||||
"- if it's in a git project, it takes git version or tag\n" +
|
||||
"- if it's not defined, so the version will be get from the --app-version flag \n" +
|
||||
"- if it's not defined, so the 0.0.1 version is used",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
force := c.Flag("force").Changed
|
||||
// TODO: is there a way to get typed values from cobra?
|
||||
appversion := c.Flag("app-version").Value.String()
|
||||
composeFile := c.Flag("compose-file").Value.String()
|
||||
appName := c.Flag("app-name").Value.String()
|
||||
chartVersion := c.Flag("chart-version").Value.String()
|
||||
chartDir := c.Flag("output-dir").Value.String()
|
||||
indentation, err := strconv.Atoi(c.Flag("indent-size").Value.String())
|
||||
if err != nil {
|
||||
writers.IndentSize = indentation
|
||||
}
|
||||
Convert(composeFile, appversion, appName, chartDir, chartVersion, force)
|
||||
},
|
||||
}
|
||||
convertCmd.Flags().BoolP(
|
||||
"force", "f", false, "force overwrite of existing output files")
|
||||
convertCmd.Flags().StringP(
|
||||
"app-version", "a", AppVersion, "app version")
|
||||
convertCmd.Flags().StringP(
|
||||
"chart-version", "v", ChartVersion, "chart version")
|
||||
convertCmd.Flags().StringP(
|
||||
"compose-file", "c", ComposeFile, "docker compose file")
|
||||
convertCmd.Flags().StringP(
|
||||
"app-name", "n", AppName, "application name")
|
||||
convertCmd.Flags().StringP(
|
||||
"output-dir", "o", ChartsDir, "chart directory")
|
||||
convertCmd.Flags().IntP(
|
||||
"indent-size", "i", 2, "set the indent size of the YAML files")
|
||||
|
||||
// show possible labels to set in docker-compose file
|
||||
showLabelsCmd := &cobra.Command{
|
||||
Use: "show-labels",
|
||||
Short: "Show labels of a resource",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
c.Println(helm.GetLabelsDocumentation())
|
||||
},
|
||||
}
|
||||
|
||||
// Update the binary to the latest version
|
||||
updateCmd := &cobra.Command{
|
||||
Use: "upgrade",
|
||||
Short: "Upgrade katenary to the latest version if available",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
version, assets, err := update.CheckLatestVersion()
|
||||
if err != nil {
|
||||
c.Println(err)
|
||||
return
|
||||
}
|
||||
c.Println("Updating to version: " + version)
|
||||
err = update.DownloadLatestVersion(assets)
|
||||
if err != nil {
|
||||
c.Println(err)
|
||||
return
|
||||
}
|
||||
c.Println("Update completed")
|
||||
},
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(
|
||||
versionCmd,
|
||||
convertCmd,
|
||||
showLabelsCmd,
|
||||
updateCmd,
|
||||
)
|
||||
|
||||
// in parallel, check if the current katenary version is the latest
|
||||
ch := make(chan string)
|
||||
go func() {
|
||||
version, _, err := update.CheckLatestVersion()
|
||||
if err != nil {
|
||||
ch <- ""
|
||||
return
|
||||
}
|
||||
if Version != version {
|
||||
ch <- fmt.Sprintf("\x1b[33mNew version available: " +
|
||||
version +
|
||||
" - to auto upgrade katenary, you can execute: katenary upgrade\x1b[0m\n")
|
||||
}
|
||||
}()
|
||||
|
||||
// Execute the command
|
||||
finalize := make(chan error)
|
||||
go func() {
|
||||
finalize <- rootCmd.Execute()
|
||||
}()
|
||||
|
||||
// Wait for both goroutines to finish
|
||||
if err := <-finalize; err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Print(<-ch)
|
||||
}
|
144
cmd/katenary/utils.go
Normal file
144
cmd/katenary/utils.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"katenary/compose"
|
||||
"katenary/generator"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
composeFiles = []string{"compose.yml", "compose.yaml", "docker-compose.yaml", "docker-compose.yml"}
|
||||
ComposeFile = ""
|
||||
AppName = "MyApp"
|
||||
ChartsDir = "chart"
|
||||
AppVersion = "0.0.1"
|
||||
ChartVersion = "0.1.0"
|
||||
)
|
||||
|
||||
func init() {
|
||||
FindComposeFile()
|
||||
SetAppName()
|
||||
SetAppVersion()
|
||||
}
|
||||
|
||||
func FindComposeFile() bool {
|
||||
for _, file := range composeFiles {
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
ComposeFile = file
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetAppName sets the application name from the current directory name.
|
||||
func SetAppName() {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
AppName = filepath.Base(wd)
|
||||
|
||||
if AppName == "" {
|
||||
AppName = "MyApp"
|
||||
}
|
||||
}
|
||||
|
||||
// SetAppVersion set the AppVersion variable to the git version/tag
|
||||
func SetAppVersion() {
|
||||
AppVersion, _ = detectGitVersion()
|
||||
}
|
||||
|
||||
// Try to detect the git version/tag.
|
||||
func detectGitVersion() (string, error) {
|
||||
defaulVersion := "0.0.1"
|
||||
// Check if .git directory exists
|
||||
if s, err := os.Stat(".git"); err != nil {
|
||||
// .git should be a directory
|
||||
return defaulVersion, errors.New("no git repository found")
|
||||
} else if !s.IsDir() {
|
||||
// .git should be a directory
|
||||
return defaulVersion, errors.New(".git is not a directory")
|
||||
}
|
||||
|
||||
// check if "git" executable is callable
|
||||
if _, err := exec.LookPath("git"); err != nil {
|
||||
return defaulVersion, errors.New("git executable not found")
|
||||
}
|
||||
|
||||
// get the latest commit hash
|
||||
if out, err := exec.Command("git", "log", "-n1", "--pretty=format:%h").Output(); err == nil {
|
||||
latestCommit := strings.TrimSpace(string(out))
|
||||
// then get the current branch/tag
|
||||
out, err := exec.Command("git", "branch", "--show-current").Output()
|
||||
if err != nil {
|
||||
return defaulVersion, errors.New("git branch --show-current failed")
|
||||
} else {
|
||||
currentBranch := strings.TrimSpace(string(out))
|
||||
// finally, check if the current tag (if exists) correspond to the current commit
|
||||
// git describe --exact-match --tags <latestCommit>
|
||||
out, err := exec.Command("git", "describe", "--exact-match", "--tags", latestCommit).Output()
|
||||
if err == nil {
|
||||
return strings.TrimSpace(string(out)), nil
|
||||
} else {
|
||||
return currentBranch + "-" + latestCommit, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaulVersion, errors.New("git log failed")
|
||||
}
|
||||
|
||||
func Convert(composeFile, appVersion, appName, chartDir, chartVersion string, force bool) {
|
||||
if len(composeFile) == 0 {
|
||||
fmt.Println("No compose file given")
|
||||
return
|
||||
}
|
||||
_, err := os.Stat(composeFile)
|
||||
if err != nil {
|
||||
fmt.Println("No compose file found")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Parse the compose file now
|
||||
p := compose.NewParser(composeFile)
|
||||
p.Parse(appName)
|
||||
|
||||
dirname := filepath.Join(chartDir, appName)
|
||||
if _, err := os.Stat(dirname); err == nil && !force {
|
||||
response := ""
|
||||
for response != "y" && response != "n" {
|
||||
response = "n"
|
||||
fmt.Printf(""+
|
||||
"The %s directory already exists, it will be \x1b[31;1mremoved\x1b[0m!\n"+
|
||||
"Do you really want to continue? [y/N]: ", dirname)
|
||||
fmt.Scanf("%s", &response)
|
||||
response = strings.ToLower(response)
|
||||
}
|
||||
if response == "n" {
|
||||
fmt.Println("Cancelled")
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup and create the chart directory (until "templates")
|
||||
if err := os.RemoveAll(dirname); err != nil {
|
||||
fmt.Printf("Error removing %s: %s\n", dirname, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// create the templates directory
|
||||
templatesDir := filepath.Join(dirname, "templates")
|
||||
if err := os.MkdirAll(templatesDir, 0755); err != nil {
|
||||
fmt.Printf("Error creating %s: %s\n", templatesDir, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// start generator
|
||||
generator.Generate(p, Version, appName, appVersion, chartVersion, ComposeFile, dirname)
|
||||
|
||||
}
|
@@ -1,80 +1,91 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"katenary/helm"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
)
|
||||
|
||||
const (
|
||||
ICON_EXCLAMATION = "❕"
|
||||
)
|
||||
|
||||
// Parser is a docker-compose parser.
|
||||
type Parser struct {
|
||||
Data *Compose
|
||||
Data *types.Project
|
||||
temporary *string
|
||||
}
|
||||
|
||||
var Appname = ""
|
||||
var (
|
||||
Appname = ""
|
||||
CURRENT_DIR, _ = os.Getwd()
|
||||
)
|
||||
|
||||
// NewParser create a Parser and parse the file given in filename.
|
||||
func NewParser(filename string) *Parser {
|
||||
// NewParser create a Parser and parse the file given in filename. If filename is empty, we try to parse the content[0] argument that should be a valid YAML content.
|
||||
func NewParser(filename string, content ...string) *Parser {
|
||||
|
||||
f, err := os.Open(filename)
|
||||
p := &Parser{}
|
||||
|
||||
if len(content) > 0 { // mainly for the tests...
|
||||
dir := filepath.Dir(filename)
|
||||
err := os.MkdirAll(dir, 0755)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
c := NewCompose()
|
||||
dec := yaml.NewDecoder(f)
|
||||
dec.Decode(c)
|
||||
|
||||
p := &Parser{Data: c}
|
||||
|
||||
services := make(map[string][]string)
|
||||
// get the service list, to be sure that everything is ok
|
||||
|
||||
for name, s := range c.Services {
|
||||
if portlabel, ok := s.Labels[helm.LABEL_PORT]; ok {
|
||||
services := strings.Split(portlabel, ",")
|
||||
for _, serviceport := range services {
|
||||
portexists := false
|
||||
for _, found := range s.Ports {
|
||||
if found == serviceport {
|
||||
portexists = true
|
||||
p.temporary = &dir
|
||||
ioutil.WriteFile(filename, []byte(content[0]), 0644)
|
||||
cli.DefaultFileNames = []string{filename}
|
||||
}
|
||||
// if filename is not in cli Default files, add it
|
||||
if len(filename) > 0 {
|
||||
found := false
|
||||
for _, f := range cli.DefaultFileNames {
|
||||
if f == filename {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !portexists {
|
||||
s.Ports = append(s.Ports, serviceport)
|
||||
// add the file at first position
|
||||
if !found {
|
||||
cli.DefaultFileNames = append([]string{filename}, cli.DefaultFileNames...)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(s.Ports) > 0 {
|
||||
services[name] = s.Ports
|
||||
}
|
||||
}
|
||||
|
||||
// check if dependencies are resolved
|
||||
missing := []string{}
|
||||
for name, s := range c.Services {
|
||||
for _, dep := range s.DependsOn {
|
||||
if _, ok := services[dep]; !ok {
|
||||
missing = append(missing, fmt.Sprintf(
|
||||
"The service \"%s\" hasn't got "+
|
||||
"declared port for dependency from \"%s\" - please "+
|
||||
"append a %s label or a \"ports\" section in the docker-compose file",
|
||||
dep, name, helm.LABEL_PORT),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(missing) > 0 {
|
||||
log.Fatal(strings.Join(missing, "\n"))
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// Parse using compose-go parser, adapt a bit the Project and set Appname.
|
||||
func (p *Parser) Parse(appname string) {
|
||||
Appname = appname
|
||||
|
||||
// Reminder:
|
||||
// - set Appname
|
||||
// - loas services
|
||||
|
||||
options, err := cli.NewProjectOptions(nil,
|
||||
cli.WithDefaultConfigPath,
|
||||
cli.WithNormalization(true),
|
||||
cli.WithInterpolation(true),
|
||||
cli.WithResolvedPaths(true),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
proj, err := cli.ProjectFromOptions(options)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to create project", err)
|
||||
}
|
||||
|
||||
Appname = proj.Name
|
||||
p.Data = proj
|
||||
CURRENT_DIR = p.Data.WorkingDir
|
||||
}
|
||||
|
||||
func GetCurrentDir() string {
|
||||
return CURRENT_DIR
|
||||
}
|
||||
|
@@ -1,28 +0,0 @@
|
||||
package compose
|
||||
|
||||
// Compose is a complete docker-compse representation.
|
||||
type Compose struct {
|
||||
Version string `yaml:"version"`
|
||||
Services map[string]*Service `yaml:"services"`
|
||||
Volumes map[string]interface{} `yaml:"volumes"`
|
||||
}
|
||||
|
||||
// NewCompose resturs a Compose object.
|
||||
func NewCompose() *Compose {
|
||||
c := &Compose{}
|
||||
c.Services = make(map[string]*Service)
|
||||
c.Volumes = make(map[string]interface{})
|
||||
return c
|
||||
}
|
||||
|
||||
// Service represent a "service" in a docker-compose file.
|
||||
type Service struct {
|
||||
Image string `yaml:"image"`
|
||||
Ports []string `yaml:"ports"`
|
||||
Environment map[string]string `yaml:"environment"`
|
||||
Labels map[string]string `yaml:"labels"`
|
||||
DependsOn []string `yaml:"depends_on"`
|
||||
Volumes []string `yaml:"volumes"`
|
||||
Expose []int `yaml:"expose"`
|
||||
EnvFiles []string `yaml:"env_file"`
|
||||
}
|
10
examples/basic/README.md
Normal file
10
examples/basic/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Basic example
|
||||
|
||||
This is a basic example of what can do Katenary with standard docker-compose file.
|
||||
|
||||
In this example:
|
||||
|
||||
- `depends_on` yield a `initContainer` in the webapp ddeployment to wait for database
|
||||
- so we need to declare the listened port inside `database` container as we don't use it with docker-compose- also, we needed to declare that `DB_HOST` is actually a service name using `mapenv` label
|
||||
|
||||
Take a look on [chart/basic](chart/basic) directory to see what `katenary convert` command has generated.
|
8
examples/basic/chart/basic/Chart.yaml
Normal file
8
examples/basic/chart/basic/Chart.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
# Create on 2022-02-17T10:27:30+01:00
|
||||
# Katenary command line: katenary convert
|
||||
apiVersion: v2
|
||||
appVersion: 0.0.1
|
||||
description: A helm chart for basic
|
||||
name: basic
|
||||
type: application
|
||||
version: 0.1.0
|
8
examples/basic/chart/basic/templates/NOTES.txt
Normal file
8
examples/basic/chart/basic/templates/NOTES.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
Congratulations,
|
||||
|
||||
Your application is now deployed. This may take a while to be up and responding.
|
||||
|
||||
{{ if .Values.webapp.ingress.enabled -}}
|
||||
- webapp is accessible on : http://{{ .Values.webapp.ingress.host }}
|
||||
{{- end }}
|
@@ -0,0 +1,39 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-database'
|
||||
labels:
|
||||
katenary.io/component: database
|
||||
katenary.io/project: basic
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: b9f12bb7d1e97901c1d7680394209525763f6640
|
||||
katenary.io/version: master-3619cc4
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
katenary.io/component: database
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
katenary.io/component: database
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
spec:
|
||||
containers:
|
||||
- name: database
|
||||
image: '{{ .Values.database.image }}'
|
||||
ports:
|
||||
- name: database
|
||||
containerPort: 3306
|
||||
env:
|
||||
- name: MARIADB_PASSWORD
|
||||
value: foo
|
||||
- name: MARIADB_DATABASE
|
||||
value: myapp
|
||||
- name: MARIADB_ROOT_PASSWORD
|
||||
value: foobar
|
||||
- name: MARIADB_USER
|
||||
value: foo
|
||||
|
19
examples/basic/chart/basic/templates/database.service.yaml
Normal file
19
examples/basic/chart/basic/templates/database.service.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-database'
|
||||
labels:
|
||||
katenary.io/component: database
|
||||
katenary.io/project: basic
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: b9f12bb7d1e97901c1d7680394209525763f6640
|
||||
katenary.io/version: master-3619cc4
|
||||
spec:
|
||||
selector:
|
||||
katenary.io/component: database
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 3306
|
||||
targetPort: 3306
|
48
examples/basic/chart/basic/templates/webapp.deployment.yaml
Normal file
48
examples/basic/chart/basic/templates/webapp.deployment.yaml
Normal file
@@ -0,0 +1,48 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-webapp'
|
||||
labels:
|
||||
katenary.io/component: webapp
|
||||
katenary.io/project: basic
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: b9f12bb7d1e97901c1d7680394209525763f6640
|
||||
katenary.io/version: master-3619cc4
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
katenary.io/component: webapp
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
katenary.io/component: webapp
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
spec:
|
||||
initContainers:
|
||||
- name: check-database
|
||||
image: busybox
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |-
|
||||
OK=0
|
||||
echo "Checking database port"
|
||||
while [ $OK != 1 ]; do
|
||||
echo -n "."
|
||||
nc -z {{ .Release.Name }}-database 3306 2>&1 >/dev/null && OK=1 || sleep 1
|
||||
done
|
||||
echo
|
||||
echo "Done"
|
||||
containers:
|
||||
- name: webapp
|
||||
image: '{{ .Values.webapp.image }}'
|
||||
ports:
|
||||
- name: webapp
|
||||
containerPort: 80
|
||||
env:
|
||||
- name: DB_HOST
|
||||
value: '{{ .Release.Name }}-database'
|
||||
|
34
examples/basic/chart/basic/templates/webapp.ingress.yaml
Normal file
34
examples/basic/chart/basic/templates/webapp.ingress.yaml
Normal file
@@ -0,0 +1,34 @@
|
||||
{{- if .Values.webapp.ingress.enabled -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-webapp'
|
||||
labels:
|
||||
katenary.io/component: webapp
|
||||
katenary.io/project: basic
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: b9f12bb7d1e97901c1d7680394209525763f6640
|
||||
katenary.io/version: master-3619cc4
|
||||
spec:
|
||||
{{- if and .Values.webapp.ingress.class (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: '{{ .Values.webapp.ingress.class }}'
|
||||
{{- end }}
|
||||
rules:
|
||||
- host: '{{ .Values.webapp.ingress.host }}'
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
{{- if semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: '{{ .Release.Name }}-webapp'
|
||||
port:
|
||||
number: 80
|
||||
{{- else }}
|
||||
serviceName: '{{ .Release.Name }}-webapp'
|
||||
servicePort: 80
|
||||
{{- end }}
|
||||
|
||||
{{- end -}}
|
19
examples/basic/chart/basic/templates/webapp.service.yaml
Normal file
19
examples/basic/chart/basic/templates/webapp.service.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-webapp'
|
||||
labels:
|
||||
katenary.io/component: webapp
|
||||
katenary.io/project: basic
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: b9f12bb7d1e97901c1d7680394209525763f6640
|
||||
katenary.io/version: master-3619cc4
|
||||
spec:
|
||||
selector:
|
||||
katenary.io/component: webapp
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 80
|
8
examples/basic/chart/basic/values.yaml
Normal file
8
examples/basic/chart/basic/values.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
database:
|
||||
image: mariadb:10
|
||||
webapp:
|
||||
image: php:7-apache
|
||||
ingress:
|
||||
class: nginx
|
||||
enabled: false
|
||||
host: webapp.basic.tld
|
31
examples/basic/docker-compose.yaml
Normal file
31
examples/basic/docker-compose.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
version: "3"
|
||||
|
||||
# this example is absolutely not working, it's an example to see how it is converted
|
||||
# by Katenary
|
||||
services:
|
||||
webapp:
|
||||
image: php:7-apache
|
||||
environment:
|
||||
DB_HOST: database
|
||||
ports:
|
||||
- "8080:80"
|
||||
labels:
|
||||
# expose an ingress
|
||||
katenary.io/ingress: 80
|
||||
# DB_HOST is actually a service name
|
||||
katenary.io/mapenv: |
|
||||
DB_HOST: {{ .Release.Name }}-database
|
||||
depends_on:
|
||||
- database
|
||||
|
||||
database:
|
||||
image: mariadb:10
|
||||
environment:
|
||||
MARIADB_ROOT_PASSWORD: foobar
|
||||
MARIADB_USER: foo
|
||||
MARIADB_PASSWORD: foo
|
||||
MARIADB_DATABASE: myapp
|
||||
labels:
|
||||
# because we don't provide "ports" or "expose", alert katenary
|
||||
# to use the mysql port for service declaration
|
||||
katenary.io/ports: 3306
|
9
examples/ghost/README.md
Normal file
9
examples/ghost/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Example with Ghost
|
||||
|
||||
[Ghost](https://ghost.org/) is a simple but powerfull blog engine. It is very nice to test some behaviors with Docker or Podman.
|
||||
|
||||
The given `docker-compose.yaml` file here declares a stand-alone blog service. To help using it, we use [Patwae](https://pathwae.net) reverse-proxy to listend http://ghost.example.localhost
|
||||
|
||||
The problem to solve is that the `url` environment variable correspond to the Ingress host when we will convert it to Helm Chart. So, we use the `mapenv` label to declare that `url` is actually `{{ .Values.blog.ingress.host }}` value.
|
||||
|
||||
Note that we also `ignore` pathwae because we don't need it in our Helm Chart.
|
8
examples/ghost/chart/ghost/Chart.yaml
Normal file
8
examples/ghost/chart/ghost/Chart.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
# Create on 2022-05-05T14:16:27+02:00
|
||||
# Katenary command line: /tmp/go-build669507924/b001/exe/main convert
|
||||
apiVersion: v2
|
||||
appVersion: 0.0.1
|
||||
description: A helm chart for ghost
|
||||
name: ghost
|
||||
type: application
|
||||
version: 0.1.0
|
8
examples/ghost/chart/ghost/templates/NOTES.txt
Normal file
8
examples/ghost/chart/ghost/templates/NOTES.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
Congratulations,
|
||||
|
||||
Your application is now deployed. This may take a while to be up and responding.
|
||||
|
||||
{{ if .Values.blog.ingress.enabled -}}
|
||||
- blog is accessible on : http://{{ .Values.blog.ingress.host }}
|
||||
{{- end }}
|
33
examples/ghost/chart/ghost/templates/blog.deployment.yaml
Normal file
33
examples/ghost/chart/ghost/templates/blog.deployment.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-blog'
|
||||
labels:
|
||||
katenary.io/component: blog
|
||||
katenary.io/project: ghost
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: 0c2bbf548ff569c3dc5d77dc158e98bbe86fb5d4
|
||||
katenary.io/version: master
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
katenary.io/component: blog
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
katenary.io/component: blog
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
spec:
|
||||
containers:
|
||||
- name: blog
|
||||
image: '{{ .Values.blog.image }}'
|
||||
ports:
|
||||
- name: blog
|
||||
containerPort: 2368
|
||||
env:
|
||||
- name: url
|
||||
value: http://{{ .Values.blog.ingress.host }}
|
||||
|
42
examples/ghost/chart/ghost/templates/blog.ingress.yaml
Normal file
42
examples/ghost/chart/ghost/templates/blog.ingress.yaml
Normal file
@@ -0,0 +1,42 @@
|
||||
{{- if .Values.blog.ingress.enabled -}}
|
||||
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-blog'
|
||||
labels:
|
||||
katenary.io/component: blog
|
||||
katenary.io/project: ghost
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: 0c2bbf548ff569c3dc5d77dc158e98bbe86fb5d4
|
||||
katenary.io/version: master
|
||||
spec:
|
||||
{{- if and .Values.blog.ingress.class (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: '{{ .Values.blog.ingress.class }}'
|
||||
{{- end }}
|
||||
rules:
|
||||
- host: '{{ .Values.blog.ingress.host }}'
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
{{- if semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion }}
|
||||
pathType: Prefix
|
||||
{{- end }}
|
||||
backend:
|
||||
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: '{{ .Release.Name }}-blog'
|
||||
port:
|
||||
number: 2368
|
||||
{{- else }}
|
||||
serviceName: '{{ .Release.Name }}-blog'
|
||||
servicePort: 2368
|
||||
{{- end }}
|
||||
|
||||
{{- end -}}
|
19
examples/ghost/chart/ghost/templates/blog.service.yaml
Normal file
19
examples/ghost/chart/ghost/templates/blog.service.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-blog'
|
||||
labels:
|
||||
katenary.io/component: blog
|
||||
katenary.io/project: ghost
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: 0c2bbf548ff569c3dc5d77dc158e98bbe86fb5d4
|
||||
katenary.io/version: master
|
||||
spec:
|
||||
selector:
|
||||
katenary.io/component: blog
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 2368
|
||||
targetPort: 2368
|
6
examples/ghost/chart/ghost/values.yaml
Normal file
6
examples/ghost/chart/ghost/values.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
blog:
|
||||
image: ghost
|
||||
ingress:
|
||||
class: nginx
|
||||
enabled: false
|
||||
host: blog.ghost.tld
|
30
examples/ghost/docker-compose.yaml
Normal file
30
examples/ghost/docker-compose.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
blog:
|
||||
image: ghost
|
||||
environment:
|
||||
# this is OK for local test, but not with Helm
|
||||
# because the URL depends on Ingress
|
||||
url: http://ghost.example.localhost
|
||||
labels:
|
||||
katenary.io/ports: 2368
|
||||
katenary.io/ingress: 2368
|
||||
# ... so we declare that "url" is actually
|
||||
# the ingress host
|
||||
katenary.io/mapenv: |
|
||||
url: http://{{ .Values.blog.ingress.host }}
|
||||
|
||||
proxy:
|
||||
# A simple proxy for localhost
|
||||
image: quay.io/pathwae/proxy
|
||||
environment:
|
||||
CONFIG: |
|
||||
ghost.example.localhost:
|
||||
to: http://blog:2368
|
||||
ports:
|
||||
- 80:80
|
||||
labels:
|
||||
# we don't want this in Helm because we will use
|
||||
# an ingress
|
||||
katenary.io/ignore: true
|
13
examples/same-pod/README.md
Normal file
13
examples/same-pod/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Make it possible to bind several containers in one pod
|
||||
|
||||
In this example, we need to make nginx and php-fpm to run inside the same "pod". The reason is that we configured FPM to listen an unix socket instead of the 9000 port.
|
||||
|
||||
Because NGinx will need to connect to the unix socket wich is a file, both containers should share the same node and work together.
|
||||
|
||||
So, in the docker-compose file, we need to declare:
|
||||
- `katenary.io/empty-dirs: socket` where `socket` is the "volume name", this will avoid the creation of a PVC
|
||||
- `katenary.io/same-pod: http` in `php` container to declare that this will be added in the `containers` section of the `http` deployment
|
||||
|
||||
You can note that we also use `configmap-volumes` to declare our configuration as `configMap`.
|
||||
|
||||
Take a look on [chart/same-pod](chart/same-pod) directory to see the result of the `katenary convert` command.
|
8
examples/same-pod/chart/same-pod/Chart.yaml
Normal file
8
examples/same-pod/chart/same-pod/Chart.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
# Create on 2022-02-17T11:36:02+01:00
|
||||
# Katenary command line: katenary convert --force
|
||||
apiVersion: v2
|
||||
appVersion: 0.0.1
|
||||
description: A helm chart for same-pod
|
||||
name: same-pod
|
||||
type: application
|
||||
version: 0.1.0
|
8
examples/same-pod/chart/same-pod/templates/NOTES.txt
Normal file
8
examples/same-pod/chart/same-pod/templates/NOTES.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
Congratulations,
|
||||
|
||||
Your application is now deployed. This may take a while to be up and responding.
|
||||
|
||||
{{ if .Values.http.ingress.enabled -}}
|
||||
- http is accessible on : http://{{ .Values.http.ingress.host }}
|
||||
{{- end }}
|
@@ -0,0 +1,23 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-config-nginx-http'
|
||||
labels:
|
||||
katenary.io/component: ""
|
||||
katenary.io/project: same-pod
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: 74e67695bfdbb829f15531321e158808018280e0
|
||||
katenary.io/version: master-bf44d44
|
||||
data:
|
||||
default.conf: |
|
||||
upstream _php {
|
||||
server unix:/sock/fpm.sock;
|
||||
}
|
||||
server {
|
||||
listen 80;
|
||||
location ~ ^/index\.php(/|$) {
|
||||
fastcgi_pass _php;
|
||||
include fastcgi_params;
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-config-php-php'
|
||||
labels:
|
||||
katenary.io/component: ""
|
||||
katenary.io/project: same-pod
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: 74e67695bfdbb829f15531321e158808018280e0
|
||||
katenary.io/version: master-bf44d44
|
||||
data:
|
||||
www.conf: |
|
||||
[www]
|
||||
user = www-data
|
||||
group = www-data
|
||||
|
||||
listen = /sock/fpm.sock
|
||||
|
||||
pm = dynamic
|
||||
pm.max_children = 5
|
||||
pm.start_servers = 2
|
||||
pm.min_spare_servers = 1
|
||||
pm.max_spare_servers = 3
|
||||
|
||||
access.log = /proc/self/fd/2
|
||||
log_limit = 8192
|
||||
clear_env = no
|
||||
catch_workers_output = yes
|
||||
decorate_workers_output = no
|
@@ -0,0 +1,52 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-http'
|
||||
labels:
|
||||
katenary.io/component: http
|
||||
katenary.io/project: same-pod
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: 74e67695bfdbb829f15531321e158808018280e0
|
||||
katenary.io/version: master-bf44d44
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
katenary.io/component: http
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
katenary.io/component: http
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
spec:
|
||||
containers:
|
||||
- name: http
|
||||
image: '{{ .Values.http.image }}'
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
volumeMounts:
|
||||
- mountPath: /sock
|
||||
name: sock
|
||||
- mountPath: /etc/nginx/conf.d
|
||||
name: config-nginx
|
||||
- name: php
|
||||
image: '{{ .Values.php.image }}'
|
||||
volumeMounts:
|
||||
- mountPath: /sock
|
||||
name: sock
|
||||
- mountPath: /usr/local/etc/php-fpm.d/www.conf
|
||||
name: config-php
|
||||
subPath: www.conf
|
||||
volumes:
|
||||
- emptyDir: {}
|
||||
name: sock
|
||||
- configMap:
|
||||
name: '{{ .Release.Name }}-config-nginx-http'
|
||||
name: config-nginx
|
||||
- configMap:
|
||||
name: '{{ .Release.Name }}-config-php-php'
|
||||
name: config-php
|
||||
|
34
examples/same-pod/chart/same-pod/templates/http.ingress.yaml
Normal file
34
examples/same-pod/chart/same-pod/templates/http.ingress.yaml
Normal file
@@ -0,0 +1,34 @@
|
||||
{{- if .Values.http.ingress.enabled -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-http'
|
||||
labels:
|
||||
katenary.io/component: http
|
||||
katenary.io/project: same-pod
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: 74e67695bfdbb829f15531321e158808018280e0
|
||||
katenary.io/version: master-bf44d44
|
||||
spec:
|
||||
{{- if and .Values.http.ingress.class (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: '{{ .Values.http.ingress.class }}'
|
||||
{{- end }}
|
||||
rules:
|
||||
- host: '{{ .Values.http.ingress.host }}'
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
{{- if semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: '{{ .Release.Name }}-http'
|
||||
port:
|
||||
number: 80
|
||||
{{- else }}
|
||||
serviceName: '{{ .Release.Name }}-http'
|
||||
servicePort: 80
|
||||
{{- end }}
|
||||
|
||||
{{- end -}}
|
19
examples/same-pod/chart/same-pod/templates/http.service.yaml
Normal file
19
examples/same-pod/chart/same-pod/templates/http.service.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-http'
|
||||
labels:
|
||||
katenary.io/component: http
|
||||
katenary.io/project: same-pod
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: 74e67695bfdbb829f15531321e158808018280e0
|
||||
katenary.io/version: master-bf44d44
|
||||
spec:
|
||||
selector:
|
||||
katenary.io/component: http
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 80
|
8
examples/same-pod/chart/same-pod/values.yaml
Normal file
8
examples/same-pod/chart/same-pod/values.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
http:
|
||||
image: nginx:alpine
|
||||
ingress:
|
||||
class: nginx
|
||||
enabled: false
|
||||
host: http.same-pod.tld
|
||||
php:
|
||||
image: php:fpm
|
10
examples/same-pod/config/nginx/default.conf
Normal file
10
examples/same-pod/config/nginx/default.conf
Normal file
@@ -0,0 +1,10 @@
|
||||
upstream _php {
|
||||
server unix:/sock/fpm.sock;
|
||||
}
|
||||
server {
|
||||
listen 80;
|
||||
location ~ ^/index\.php(/|$) {
|
||||
fastcgi_pass _php;
|
||||
include fastcgi_params;
|
||||
}
|
||||
}
|
17
examples/same-pod/config/php/www.conf
Normal file
17
examples/same-pod/config/php/www.conf
Normal file
@@ -0,0 +1,17 @@
|
||||
[www]
|
||||
user = www-data
|
||||
group = www-data
|
||||
|
||||
listen = /sock/fpm.sock
|
||||
|
||||
pm = dynamic
|
||||
pm.max_children = 5
|
||||
pm.start_servers = 2
|
||||
pm.min_spare_servers = 1
|
||||
pm.max_spare_servers = 3
|
||||
|
||||
access.log = /proc/self/fd/2
|
||||
log_limit = 8192
|
||||
clear_env = no
|
||||
catch_workers_output = yes
|
||||
decorate_workers_output = no
|
38
examples/same-pod/docker-compose.yaml
Normal file
38
examples/same-pod/docker-compose.yaml
Normal file
@@ -0,0 +1,38 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
|
||||
http:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "8080:80"
|
||||
volumes:
|
||||
- "sock:/sock"
|
||||
- "./config/nginx:/etc/nginx/conf.d:z"
|
||||
|
||||
labels:
|
||||
# the "sock" volume will need to be shared to the same pod, so let's
|
||||
# declare that this is not a PVC
|
||||
katenary.io/empty-dirs: sock
|
||||
|
||||
# use ./config/nginx as a configMap
|
||||
katenary.io/configmap-volumes: ./config/nginx
|
||||
|
||||
# declare an ingress
|
||||
katenary.io/ingress: 80
|
||||
|
||||
php:
|
||||
image: php:fpm
|
||||
volumes:
|
||||
- "sock:/sock"
|
||||
- "./config/php/www.conf:/usr/local/etc/php-fpm.d/www.conf:z"
|
||||
labels:
|
||||
# fpm will need to use a unix socket shared
|
||||
# with nginx (http service above), so we want here
|
||||
# make a single pod containing nginx and php
|
||||
katenary.io/same-pod: http
|
||||
# use the ./config/php files as a configMap
|
||||
katenary.io/configmap-volumes: ./config/php/www.conf
|
||||
|
||||
volumes:
|
||||
sock:
|
File diff suppressed because it is too large
Load Diff
397
generator/main_test.go
Normal file
397
generator/main_test.go
Normal file
@@ -0,0 +1,397 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"katenary/compose"
|
||||
"katenary/helm"
|
||||
"katenary/logger"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
)
|
||||
|
||||
const DOCKER_COMPOSE_YML = `version: '3'
|
||||
services:
|
||||
# first service, very simple
|
||||
http:
|
||||
image: nginx
|
||||
ports:
|
||||
- "80:80"
|
||||
|
||||
# second service, with environment variables
|
||||
http2:
|
||||
image: nginx
|
||||
environment:
|
||||
SOME_ENV_VAR: some_value
|
||||
ANOTHER_ENV_VAR: another_value
|
||||
|
||||
# third service with ingress label
|
||||
web:
|
||||
image: nginx
|
||||
ports:
|
||||
- "80:80"
|
||||
labels:
|
||||
katenary.io/ingress: 80
|
||||
|
||||
web2:
|
||||
image: nginx
|
||||
command: ["/bin/sh", "-c", "while true; do echo hello; sleep 1; done"]
|
||||
|
||||
# fourth service is a php service depending on database
|
||||
php:
|
||||
image: php:7.2-apache
|
||||
depends_on:
|
||||
- database
|
||||
environment:
|
||||
SOME_ENV_VAR: some_value
|
||||
ANOTHER_ENV_VAR: another_value
|
||||
DB_HOST: database
|
||||
labels:
|
||||
katenary.io/mapenv: |
|
||||
DB_HOST: {{ .Release.Name }}-database
|
||||
|
||||
database:
|
||||
image: mysql:5.7
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: database
|
||||
MYSQL_USER: user
|
||||
MYSQL_PASSWORD: password
|
||||
volumes:
|
||||
- data:/var/lib/mysql
|
||||
labels:
|
||||
katenary.io/ports: 3306
|
||||
|
||||
|
||||
# try to deploy 2 services but one is in the same pod than the other
|
||||
http3:
|
||||
image: nginx
|
||||
|
||||
http4:
|
||||
image: nginx
|
||||
labels:
|
||||
katenary.io/same-pod: http3
|
||||
|
||||
# unmapped volumes
|
||||
novol:
|
||||
image: nginx
|
||||
volumes:
|
||||
- /tmp/data
|
||||
labels:
|
||||
katenary.io/ports: 80
|
||||
|
||||
# use = sign for environment variables
|
||||
eqenv:
|
||||
image: nginx
|
||||
environment:
|
||||
- SOME_ENV_VAR=some_value
|
||||
- ANOTHER_ENV_VAR=another_value
|
||||
|
||||
# use environment file
|
||||
useenvfile:
|
||||
image: nginx
|
||||
env_file:
|
||||
- config/env
|
||||
|
||||
volumes:
|
||||
data:
|
||||
`
|
||||
|
||||
var defaultCliFiles = cli.DefaultFileNames
|
||||
var TMP_DIR = ""
|
||||
var TMPWORK_DIR = ""
|
||||
|
||||
func init() {
|
||||
logger.NOLOG = len(os.Getenv("NOLOG")) < 1
|
||||
}
|
||||
|
||||
func setUp(t *testing.T) (string, *compose.Parser) {
|
||||
|
||||
// cleanup "made" files
|
||||
helm.ResetMadePVC()
|
||||
|
||||
cli.DefaultFileNames = defaultCliFiles
|
||||
|
||||
// create a temporary directory
|
||||
tmp, err := os.MkdirTemp(os.TempDir(), "katenary-test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tmpwork, err := os.MkdirTemp(os.TempDir(), "katenary-test-work-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
composefile := filepath.Join(tmpwork, "docker-compose.yaml")
|
||||
p := compose.NewParser(composefile, DOCKER_COMPOSE_YML)
|
||||
|
||||
// create envfile for "useenvfile" service
|
||||
err = os.Mkdir(filepath.Join(tmpwork, "config"), 0777)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
envfile := filepath.Join(tmpwork, "config", "env")
|
||||
fp, err := os.Create(envfile)
|
||||
if err != nil {
|
||||
t.Fatal("MKFILE", err)
|
||||
}
|
||||
fp.WriteString("FILEENV1=some_value\n")
|
||||
fp.WriteString("FILEENV2=another_value\n")
|
||||
fp.Close()
|
||||
|
||||
TMP_DIR = tmp
|
||||
TMPWORK_DIR = tmpwork
|
||||
|
||||
p.Parse("testapp")
|
||||
|
||||
Generate(p, "test-0", "testapp", "1.2.3", "4.5.6", DOCKER_COMPOSE_YML, tmp)
|
||||
|
||||
return tmp, p
|
||||
}
|
||||
|
||||
func tearDown() {
|
||||
if len(TMP_DIR) > 0 {
|
||||
os.RemoveAll(TMP_DIR)
|
||||
}
|
||||
if len(TMPWORK_DIR) > 0 {
|
||||
os.RemoveAll(TMPWORK_DIR)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the web2 service has got a command.
|
||||
func TestCommand(t *testing.T) {
|
||||
tmp, p := setUp(t)
|
||||
defer tearDown()
|
||||
|
||||
for _, service := range p.Data.Services {
|
||||
name := service.Name
|
||||
if name == "web2" {
|
||||
// Ensure that the command is correctly set
|
||||
// The command should be a string array
|
||||
path := filepath.Join(tmp, "templates", name+".deployment.yaml")
|
||||
path = filepath.Join(tmp, "templates", name+".deployment.yaml")
|
||||
fp, _ := os.Open(path)
|
||||
defer fp.Close()
|
||||
lines, _ := ioutil.ReadAll(fp)
|
||||
next := false
|
||||
commands := make([]string, 0)
|
||||
for _, line := range strings.Split(string(lines), "\n") {
|
||||
if strings.Contains(line, "command") {
|
||||
next = true
|
||||
continue
|
||||
}
|
||||
if next {
|
||||
commands = append(commands, line)
|
||||
}
|
||||
}
|
||||
ok := 0
|
||||
for _, command := range commands {
|
||||
if strings.Contains(command, "- /bin/sh") {
|
||||
ok++
|
||||
}
|
||||
if strings.Contains(command, "- -c") {
|
||||
ok++
|
||||
}
|
||||
if strings.Contains(command, "while true; do") {
|
||||
ok++
|
||||
}
|
||||
}
|
||||
if ok != 3 {
|
||||
t.Error("Command is not correctly set")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if environment is correctly set.
|
||||
func TestEnvs(t *testing.T) {
|
||||
tmp, p := setUp(t)
|
||||
defer tearDown()
|
||||
|
||||
for _, service := range p.Data.Services {
|
||||
name := service.Name
|
||||
|
||||
if name == "php" {
|
||||
// the "DB_HOST" environment variable inside the template must be set to '{{ .Release.Name }}-database'
|
||||
path := filepath.Join(tmp, "templates", name+".deployment.yaml")
|
||||
// read the file and find the DB_HOST variable
|
||||
matched := false
|
||||
fp, _ := os.Open(path)
|
||||
defer fp.Close()
|
||||
lines, _ := ioutil.ReadAll(fp)
|
||||
next := false
|
||||
for _, line := range strings.Split(string(lines), "\n") {
|
||||
if !next && strings.Contains(line, "name: DB_HOST") {
|
||||
next = true
|
||||
continue
|
||||
} else if next && strings.Contains(line, "value:") {
|
||||
matched = true
|
||||
if !strings.Contains(line, "{{ tpl .Values.php.environment.DB_HOST . }}") {
|
||||
t.Error("DB_HOST variable should be set to {{ tpl .Values.php.environment.DB_HOST . }}", line, string(lines))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
t.Error("DB_HOST variable not found in ", path)
|
||||
t.Log(string(lines))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the same pod is not deployed twice.
|
||||
func TestSamePod(t *testing.T) {
|
||||
tmp, p := setUp(t)
|
||||
defer tearDown()
|
||||
|
||||
for _, service := range p.Data.Services {
|
||||
name := service.Name
|
||||
path := filepath.Join(tmp, "templates", name+".deployment.yaml")
|
||||
|
||||
if _, found := service.Labels[helm.LABEL_SAMEPOD]; found {
|
||||
// fail if the service has a deployment
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
t.Error("Service ", name, " should not have a deployment")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// others should have a deployment file
|
||||
t.Log("Checking ", name, " deployment file")
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the ports are correctly set.
|
||||
func TestPorts(t *testing.T) {
|
||||
tmp, p := setUp(t)
|
||||
defer tearDown()
|
||||
|
||||
for _, service := range p.Data.Services {
|
||||
name := service.Name
|
||||
path := ""
|
||||
|
||||
// if the service has a port found in helm.LABEL_PORT or ports, so the service file should exist
|
||||
hasPort := false
|
||||
if _, found := service.Labels[helm.LABEL_PORT]; found {
|
||||
hasPort = true
|
||||
}
|
||||
if service.Ports != nil {
|
||||
hasPort = true
|
||||
}
|
||||
if hasPort {
|
||||
path = filepath.Join(tmp, "templates", name+".service.yaml")
|
||||
t.Log("Checking ", name, " service file")
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the volumes are correctly set.
|
||||
func TestPVC(t *testing.T) {
|
||||
tmp, p := setUp(t)
|
||||
defer tearDown()
|
||||
|
||||
for _, service := range p.Data.Services {
|
||||
name := service.Name
|
||||
path := filepath.Join(tmp, "templates", name+"-data.pvc.yaml")
|
||||
|
||||
// the "database" service should have a pvc file in templates (name-data.pvc.yaml)
|
||||
if name == "database" {
|
||||
path = filepath.Join(tmp, "templates", name+"-data.pvc.yaml")
|
||||
t.Log("Checking ", name, " pvc file")
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
list, _ := filepath.Glob(tmp + "/templates/*")
|
||||
t.Log(list)
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Check if web service has got a ingress.
|
||||
func TestIngress(t *testing.T) {
|
||||
tmp, p := setUp(t)
|
||||
defer tearDown()
|
||||
|
||||
for _, service := range p.Data.Services {
|
||||
name := service.Name
|
||||
path := filepath.Join(tmp, "templates", name+".ingress.yaml")
|
||||
|
||||
// the "web" service should have a ingress file in templates (name.ingress.yaml)
|
||||
if name == "web" {
|
||||
path = filepath.Join(tmp, "templates", name+".ingress.yaml")
|
||||
t.Log("Checking ", name, " ingress file")
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check unmapped volumes
|
||||
func TestUnmappedVolumes(t *testing.T) {
|
||||
tmp, p := setUp(t)
|
||||
defer tearDown()
|
||||
|
||||
for _, service := range p.Data.Services {
|
||||
name := service.Name
|
||||
if name == "novol" {
|
||||
path := filepath.Join(tmp, "templates", name+".deployment.yaml")
|
||||
fp, _ := os.Open(path)
|
||||
defer fp.Close()
|
||||
lines, _ := ioutil.ReadAll(fp)
|
||||
for _, line := range strings.Split(string(lines), "\n") {
|
||||
if strings.Contains(line, "novol-data") {
|
||||
t.Error("novol service should not have a volume")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if service using equal sign for environment works
|
||||
func TestEqualSignOnEnv(t *testing.T) {
|
||||
tmp, p := setUp(t)
|
||||
defer tearDown()
|
||||
|
||||
// if the name is eqenv, the service should habe environment
|
||||
for _, service := range p.Data.Services {
|
||||
name := service.Name
|
||||
if name == "eqenv" {
|
||||
path := filepath.Join(tmp, "templates", name+".deployment.yaml")
|
||||
fp, _ := os.Open(path)
|
||||
defer fp.Close()
|
||||
lines, _ := ioutil.ReadAll(fp)
|
||||
match := 0
|
||||
for _, line := range strings.Split(string(lines), "\n") {
|
||||
// we must find the line with the environment variable name
|
||||
if strings.Contains(line, "SOME_ENV_VAR") {
|
||||
// we must find the line with the environment variable value
|
||||
match++
|
||||
}
|
||||
if strings.Contains(line, "ANOTHER_ENV_VAR") {
|
||||
// we must find the line with the environment variable value
|
||||
match++
|
||||
}
|
||||
}
|
||||
if match != 4 { // because the value points on .Values...
|
||||
t.Error("eqenv service should have 2 environment variables")
|
||||
t.Log(string(lines))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,89 +1,22 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"katenary/compose"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Color int
|
||||
// replaceChars replaces some chars in a string.
|
||||
const replaceChars = `[^a-zA-Z0-9._]`
|
||||
|
||||
var ActivateColors = false
|
||||
|
||||
const (
|
||||
GREY Color = 30 + iota
|
||||
RED
|
||||
GREEN
|
||||
YELLOW
|
||||
BLUE
|
||||
MAGENTA
|
||||
CYAN
|
||||
)
|
||||
|
||||
var waiter = sync.Mutex{}
|
||||
|
||||
func color(c Color, args ...interface{}) {
|
||||
if !ActivateColors {
|
||||
fmt.Println(args...)
|
||||
return
|
||||
}
|
||||
waiter.Lock()
|
||||
fmt.Fprintf(os.Stdout, "\x1b[%dm", c)
|
||||
fmt.Fprint(os.Stdout, args...)
|
||||
fmt.Fprintf(os.Stdout, "\x1b[0m\n")
|
||||
waiter.Unlock()
|
||||
// GetRelPath return the relative path from the root of the project.
|
||||
func GetRelPath(path string) string {
|
||||
return strings.Replace(path, compose.GetCurrentDir(), ".", 1)
|
||||
}
|
||||
|
||||
func colorf(c Color, format string, args ...interface{}) {
|
||||
if !ActivateColors {
|
||||
fmt.Printf(format, args...)
|
||||
return
|
||||
}
|
||||
waiter.Lock()
|
||||
fmt.Fprintf(os.Stdout, "\x1b[%dm", c)
|
||||
fmt.Fprintf(os.Stdout, format, args...)
|
||||
fmt.Fprintf(os.Stdout, "\x1b[0m")
|
||||
waiter.Unlock()
|
||||
}
|
||||
|
||||
func Grey(args ...interface{}) {
|
||||
color(GREY, args...)
|
||||
}
|
||||
|
||||
func Red(args ...interface{}) {
|
||||
color(RED, args...)
|
||||
}
|
||||
func Green(args ...interface{}) {
|
||||
color(GREEN, args...)
|
||||
}
|
||||
func Yellow(args ...interface{}) {
|
||||
color(YELLOW, args...)
|
||||
}
|
||||
func Blue(args ...interface{}) {
|
||||
color(BLUE, args...)
|
||||
}
|
||||
func Magenta(args ...interface{}) {
|
||||
color(MAGENTA, args...)
|
||||
}
|
||||
func Greyf(format string, args ...interface{}) {
|
||||
colorf(GREY, format, args...)
|
||||
}
|
||||
|
||||
func Redf(format string, args ...interface{}) {
|
||||
colorf(RED, format, args...)
|
||||
}
|
||||
func Greenf(format string, args ...interface{}) {
|
||||
colorf(GREEN, format, args...)
|
||||
}
|
||||
func Yellowf(format string, args ...interface{}) {
|
||||
colorf(YELLOW, format, args...)
|
||||
}
|
||||
func Bluef(format string, args ...interface{}) {
|
||||
colorf(BLUE, format, args...)
|
||||
}
|
||||
func Magentaf(format string, args ...interface{}) {
|
||||
colorf(MAGENTA, format, args...)
|
||||
}
|
||||
func Cyanf(format string, args ...interface{}) {
|
||||
colorf(CYAN, format, args...)
|
||||
// PathToName transform a path to a yaml name.
|
||||
func PathToName(path string) string {
|
||||
path = strings.TrimPrefix(GetRelPath(path), "./")
|
||||
path = regexp.MustCompile(replaceChars).ReplaceAllString(path, "-")
|
||||
return path
|
||||
}
|
||||
|
231
generator/writer.go
Normal file
231
generator/writer.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"katenary/compose"
|
||||
"katenary/generator/writers"
|
||||
"katenary/helm"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// HelmFile represents a helm file from helm package that has got some necessary methods
|
||||
// to generate a helm file.
|
||||
type HelmFile interface {
|
||||
GetType() string
|
||||
GetPathRessource() string
|
||||
}
|
||||
|
||||
// HelmFileGenerator is a chanel of HelmFile.
|
||||
type HelmFileGenerator chan HelmFile
|
||||
|
||||
var PrefixRE = regexp.MustCompile(`\{\{.*\}\}-?`)
|
||||
|
||||
func portExists(port int, ports []types.ServicePortConfig) bool {
|
||||
for _, p := range ports {
|
||||
if p.Target == uint32(port) {
|
||||
log.Println("portExists:", port, p.Target)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Generate get a parsed compose file, and generate the helm files.
|
||||
func Generate(p *compose.Parser, katernayVersion, appName, appVersion, chartVersion, composeFile, dirName string) {
|
||||
|
||||
// make the appname global (yes... ugly but easy)
|
||||
helm.Appname = appName
|
||||
helm.Version = katernayVersion
|
||||
templatesDir := filepath.Join(dirName, "templates")
|
||||
|
||||
// try to create the directory
|
||||
err := os.MkdirAll(templatesDir, 0755)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
generators := make(map[string]HelmFileGenerator)
|
||||
|
||||
// remove skipped services from the parsed data
|
||||
for i, service := range p.Data.Services {
|
||||
if v, ok := service.Labels[helm.LABEL_IGNORE]; !ok || v != "true" {
|
||||
continue
|
||||
}
|
||||
p.Data.Services = append(p.Data.Services[:i], p.Data.Services[i+1:]...)
|
||||
i--
|
||||
|
||||
// find this service in others as "depends_on" and remove it
|
||||
for _, service2 := range p.Data.Services {
|
||||
delete(service2.DependsOn, service.Name)
|
||||
}
|
||||
}
|
||||
|
||||
for i, service := range p.Data.Services {
|
||||
n := service.Name
|
||||
|
||||
// if the service port is declared in labels, add it to the service.
|
||||
if ports, ok := service.Labels[helm.LABEL_PORT]; ok {
|
||||
if service.Ports == nil {
|
||||
service.Ports = make([]types.ServicePortConfig, 0)
|
||||
}
|
||||
for _, port := range strings.Split(ports, ",") {
|
||||
target, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if portExists(target, service.Ports) {
|
||||
continue
|
||||
}
|
||||
service.Ports = append(service.Ports, types.ServicePortConfig{
|
||||
Target: uint32(target),
|
||||
})
|
||||
}
|
||||
}
|
||||
// find port and store it in servicesMap
|
||||
for _, port := range service.Ports {
|
||||
target := int(port.Target)
|
||||
if target != 0 {
|
||||
servicesMap[n] = target
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// manage emptyDir volumes
|
||||
if empty, ok := service.Labels[helm.LABEL_EMPTYDIRS]; ok {
|
||||
//split empty list by coma
|
||||
emptyDirs := strings.Split(empty, ",")
|
||||
for i, emptyDir := range emptyDirs {
|
||||
emptyDirs[i] = strings.TrimSpace(emptyDir)
|
||||
}
|
||||
//append them in EmptyDirs
|
||||
EmptyDirs = append(EmptyDirs, emptyDirs...)
|
||||
}
|
||||
p.Data.Services[i] = service
|
||||
|
||||
}
|
||||
|
||||
// for all services in linked map, and not in samePods map, generate the service
|
||||
for _, s := range p.Data.Services {
|
||||
name := s.Name
|
||||
|
||||
// do not make a deployment for services declared to be in the same pod than another
|
||||
if _, ok := s.Labels[helm.LABEL_SAMEPOD]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// find services that is in the same pod
|
||||
linked := make(map[string]types.ServiceConfig, 0)
|
||||
for _, service := range p.Data.Services {
|
||||
n := service.Name
|
||||
if linkname, ok := service.Labels[helm.LABEL_SAMEPOD]; ok && linkname == name {
|
||||
linked[n] = service
|
||||
}
|
||||
}
|
||||
|
||||
generators[name] = CreateReplicaObject(name, s, linked)
|
||||
}
|
||||
|
||||
// to generate notes, we need to keep an Ingresses list
|
||||
ingresses := make(map[string]*helm.Ingress)
|
||||
|
||||
for n, generator := range generators { // generators is a map : name -> generator
|
||||
for helmFile := range generator { // generator is a chan
|
||||
if helmFile == nil { // generator finished
|
||||
break
|
||||
}
|
||||
kind := helmFile.(helm.Kinded).Get()
|
||||
kind = strings.ToLower(kind)
|
||||
|
||||
// Add a SHA inside the generated file, it's only
|
||||
// to make it easy to check it the compose file corresponds to the
|
||||
// generated helm chart
|
||||
helmFile.(helm.Signable).BuildSHA(composeFile)
|
||||
|
||||
// Some types need special fixes in yaml generation
|
||||
switch c := helmFile.(type) {
|
||||
case *helm.Storage:
|
||||
// For storage, we need to add a "condition" to activate it
|
||||
writers.BuildStorage(c, n, templatesDir)
|
||||
|
||||
case *helm.Deployment:
|
||||
// for the deployment, we need to fix persitence volumes
|
||||
// to be activated only when the storage is "enabled",
|
||||
// either we use an "emptyDir"
|
||||
writers.BuildDeployment(c, n, templatesDir)
|
||||
|
||||
case *helm.Service:
|
||||
// Change the type for service if it's an "exposed" port
|
||||
writers.BuildService(c, n, templatesDir)
|
||||
|
||||
case *helm.Ingress:
|
||||
// we need to make ingresses "activable" from values
|
||||
ingresses[n] = c // keep it to generate notes
|
||||
writers.BuildIngress(c, n, templatesDir)
|
||||
|
||||
case *helm.ConfigMap, *helm.Secret:
|
||||
// there could be several files, so let's force the filename
|
||||
name := c.(helm.Named).Name() + "-" + c.GetType()
|
||||
suffix := c.GetPathRessource()
|
||||
suffix = PathToName(suffix)
|
||||
name += suffix
|
||||
name = PrefixRE.ReplaceAllString(name, "")
|
||||
writers.BuildConfigMap(c, kind, n, name, templatesDir)
|
||||
|
||||
default:
|
||||
fname := filepath.Join(templatesDir, n+"."+kind+".yaml")
|
||||
fp, err := os.Create(fname)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer fp.Close()
|
||||
enc := yaml.NewEncoder(fp)
|
||||
enc.SetIndent(writers.IndentSize)
|
||||
enc.Encode(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create the values.yaml file
|
||||
valueFile, err := os.Create(filepath.Join(dirName, "values.yaml"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer valueFile.Close()
|
||||
enc := yaml.NewEncoder(valueFile)
|
||||
enc.SetIndent(writers.IndentSize)
|
||||
enc.Encode(Values)
|
||||
|
||||
// Create tht Chart.yaml file
|
||||
chartFile, err := os.Create(filepath.Join(dirName, "Chart.yaml"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer chartFile.Close()
|
||||
chartFile.WriteString(`# Create on ` + time.Now().Format(time.RFC3339) + "\n")
|
||||
chartFile.WriteString(`# Katenary command line: ` + strings.Join(os.Args, " ") + "\n")
|
||||
enc = yaml.NewEncoder(chartFile)
|
||||
enc.SetIndent(writers.IndentSize)
|
||||
enc.Encode(map[string]interface{}{
|
||||
"apiVersion": "v2",
|
||||
"name": appName,
|
||||
"description": "A helm chart for " + appName,
|
||||
"type": "application",
|
||||
"version": chartVersion,
|
||||
"appVersion": appVersion,
|
||||
})
|
||||
|
||||
// And finally, create a NOTE.txt file
|
||||
noteFile, err := os.Create(filepath.Join(templatesDir, "NOTES.txt"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer noteFile.Close()
|
||||
noteFile.WriteString(helm.GenerateNotesFile(ingresses))
|
||||
}
|
18
generator/writers/configmap.go
Normal file
18
generator/writers/configmap.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package writers
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// BuildConfigMap writes the configMap.
|
||||
func BuildConfigMap(c interface{}, kind, servicename, name, templatesDir string) {
|
||||
fname := filepath.Join(templatesDir, name+"."+kind+".yaml")
|
||||
fp, _ := os.Create(fname)
|
||||
enc := yaml.NewEncoder(fp)
|
||||
enc.SetIndent(IndentSize)
|
||||
enc.Encode(c)
|
||||
fp.Close()
|
||||
}
|
44
generator/writers/deployment.go
Normal file
44
generator/writers/deployment.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package writers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"katenary/helm"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// BuildDeployment builds a deployment.
|
||||
func BuildDeployment(deployment *helm.Deployment, name, templatesDir string) {
|
||||
kind := "deployment"
|
||||
fname := filepath.Join(templatesDir, name+"."+kind+".yaml")
|
||||
fp, _ := os.Create(fname)
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
enc := yaml.NewEncoder(buffer)
|
||||
enc.SetIndent(IndentSize)
|
||||
enc.Encode(deployment)
|
||||
_content := string(buffer.Bytes())
|
||||
content := strings.Split(string(_content), "\n")
|
||||
dataname := ""
|
||||
component := deployment.Spec.Selector["matchLabels"].(map[string]string)[helm.K+"/component"]
|
||||
n := 0 // will be count of lines only on "persistentVolumeClaim" line, to indent "else" and "end" at the right place
|
||||
for _, line := range content {
|
||||
if strings.Contains(line, "name:") {
|
||||
dataname = strings.Split(line, ":")[1]
|
||||
dataname = strings.TrimSpace(dataname)
|
||||
} else if strings.Contains(line, "persistentVolumeClaim") {
|
||||
n = CountSpaces(line)
|
||||
line = strings.Repeat(" ", n) + "{{- if .Values." + component + ".persistence." + dataname + ".enabled }}\n" + line
|
||||
} else if strings.Contains(line, "claimName") {
|
||||
spaces := strings.Repeat(" ", n)
|
||||
line += "\n" + spaces + "{{ else }}"
|
||||
line += "\n" + spaces + "emptyDir: {}"
|
||||
line += "\n" + spaces + "{{- end }}"
|
||||
}
|
||||
fp.WriteString(line + "\n")
|
||||
}
|
||||
fp.Close()
|
||||
|
||||
}
|
101
generator/writers/ingress.go
Normal file
101
generator/writers/ingress.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package writers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"katenary/helm"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
classAndVersionCondition = `{{- if and .Values.__name__.ingress.class (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}` + "\n"
|
||||
versionCondition118 = `{{- if semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion }}` + "\n"
|
||||
versionCondition119 = `{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }}` + "\n"
|
||||
apiVersion = `{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}`
|
||||
)
|
||||
|
||||
// BuildIngress generates the ingress yaml file with conditions.
|
||||
func BuildIngress(ingress *helm.Ingress, name, templatesDir string) {
|
||||
// Set the backend for 1.18
|
||||
for _, b := range ingress.Spec.Rules {
|
||||
for _, p := range b.Http.Paths {
|
||||
p.Backend.ServiceName = p.Backend.Service.Name
|
||||
if n, ok := p.Backend.Service.Port["number"]; ok {
|
||||
p.Backend.ServicePort = n
|
||||
}
|
||||
}
|
||||
}
|
||||
kind := "ingress"
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
fname := filepath.Join(templatesDir, name+"."+kind+".yaml")
|
||||
enc := yaml.NewEncoder(buffer)
|
||||
enc.SetIndent(IndentSize)
|
||||
buffer.WriteString("{{- if .Values." + name + ".ingress.enabled -}}\n")
|
||||
enc.Encode(ingress)
|
||||
buffer.WriteString("{{- end -}}")
|
||||
|
||||
fp, err := os.Create(fname)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
content := string(buffer.Bytes())
|
||||
lines := strings.Split(content, "\n")
|
||||
|
||||
backendHit := false
|
||||
for _, l := range lines {
|
||||
// apiVersion is a pain...
|
||||
if strings.Contains(l, "apiVersion:") {
|
||||
l = apiVersion
|
||||
}
|
||||
|
||||
// add annotations linked to the Values
|
||||
if strings.Contains(l, "annotations:") {
|
||||
n := CountSpaces(l) + IndentSize
|
||||
l += "\n" + strings.Repeat(" ", n) + "{{- range $k, $v := .Values.__name__.ingress.annotations }}\n"
|
||||
l += strings.Repeat(" ", n) + "{{ $k }}: {{ $v }}\n"
|
||||
l += strings.Repeat(" ", n) + "{{- end }}"
|
||||
l = strings.ReplaceAll(l, "__name__", name)
|
||||
}
|
||||
|
||||
// pathTyype is ony for 1.19+
|
||||
if strings.Contains(l, "pathType:") {
|
||||
n := CountSpaces(l)
|
||||
l = strings.Repeat(" ", n) + versionCondition118 +
|
||||
l + "\n" +
|
||||
strings.Repeat(" ", n) + "{{- end }}"
|
||||
}
|
||||
|
||||
if strings.Contains(l, "ingressClassName") {
|
||||
// should be set only if the version of Kubernetes is 1.18-0 or higher
|
||||
cond := strings.ReplaceAll(classAndVersionCondition, "__name__", name)
|
||||
l = ` ` + cond + l + "\n" + ` {{- end }}`
|
||||
}
|
||||
|
||||
// manage the backend format following the Kubernetes 1.19-0 version or higher
|
||||
if strings.Contains(l, "service:") {
|
||||
n := CountSpaces(l)
|
||||
l = strings.Repeat(" ", n) + versionCondition119 + l
|
||||
}
|
||||
if strings.Contains(l, "serviceName:") || strings.Contains(l, "servicePort:") {
|
||||
n := CountSpaces(l)
|
||||
if !backendHit {
|
||||
l = strings.Repeat(" ", n) + "{{- else }}\n" + l
|
||||
} else {
|
||||
l = l + "\n" + strings.Repeat(" ", n) + "{{- end }}\n"
|
||||
}
|
||||
backendHit = true
|
||||
}
|
||||
fp.WriteString(l + "\n")
|
||||
}
|
||||
}
|
24
generator/writers/service.go
Normal file
24
generator/writers/service.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package writers
|
||||
|
||||
import (
|
||||
"katenary/helm"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// BuildService writes the service (external or not).
|
||||
func BuildService(service *helm.Service, name, templatesDir string) {
|
||||
kind := "service"
|
||||
suffix := ""
|
||||
if service.Spec.Type == "NodePort" {
|
||||
suffix = "-external"
|
||||
}
|
||||
fname := filepath.Join(templatesDir, name+suffix+"."+kind+".yaml")
|
||||
fp, _ := os.Create(fname)
|
||||
enc := yaml.NewEncoder(fp)
|
||||
enc.SetIndent(IndentSize)
|
||||
enc.Encode(service)
|
||||
fp.Close()
|
||||
}
|
32
generator/writers/storage.go
Normal file
32
generator/writers/storage.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package writers
|
||||
|
||||
import (
|
||||
"katenary/helm"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// BuildStorage writes the persistentVolumeClaim.
|
||||
func BuildStorage(storage *helm.Storage, name, templatesDir string) {
|
||||
kind := "pvc"
|
||||
name = storage.Metadata.Labels[helm.K+"/component"]
|
||||
pvcname := storage.Metadata.Labels[helm.K+"/pvc-name"]
|
||||
fname := filepath.Join(templatesDir, name+"-"+pvcname+"."+kind+".yaml")
|
||||
fp, err := os.Create(fname)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer fp.Close()
|
||||
volname := storage.K8sBase.Metadata.Labels[helm.K+"/pvc-name"]
|
||||
|
||||
fp.WriteString("{{ if .Values." + name + ".persistence." + volname + ".enabled }}\n")
|
||||
enc := yaml.NewEncoder(fp)
|
||||
enc.SetIndent(IndentSize)
|
||||
if err := enc.Encode(storage); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fp.WriteString("{{- end -}}")
|
||||
}
|
17
generator/writers/utils.go
Normal file
17
generator/writers/utils.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package writers
|
||||
|
||||
// IndentSize set the indentation size for yaml output. Could ba changed by command line argument.
|
||||
var IndentSize = 2
|
||||
|
||||
// CountSpaces returns the number of spaces from the begining of the line.
|
||||
func CountSpaces(line string) int {
|
||||
var spaces int
|
||||
for _, char := range line {
|
||||
if char == ' ' {
|
||||
spaces++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return spaces
|
||||
}
|
13
go.mod
13
go.mod
@@ -2,4 +2,15 @@ module katenary
|
||||
|
||||
go 1.16
|
||||
|
||||
require gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
require (
|
||||
github.com/compose-spec/compose-go v1.2.4
|
||||
github.com/distribution/distribution/v3 v3.0.0-20220505155552-985711c1f414 // indirect
|
||||
github.com/kr/pretty v0.2.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
golang.org/x/mod v0.5.1
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
)
|
||||
|
302
go.sum
302
go.sum
@@ -1,4 +1,304 @@
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v56.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
|
||||
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.43.16/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||
github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
|
||||
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
|
||||
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
|
||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/compose-spec/compose-go v1.2.4 h1:nzTFqM8+2J7Veao5Pq5U451thinv3U1wChIvcjX59/A=
|
||||
github.com/compose-spec/compose-go v1.2.4/go.mod h1:pAy7Mikpeft4pxkFU565/DRHEbDfR84G6AQuiL+Hdg8=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
|
||||
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e/go.mod h1:xpWTC2KnJMiDLkoawhsPQcXjvwATEBcbq0xevG2YR9M=
|
||||
github.com/distribution/distribution/v3 v3.0.0-20220504180456-7a6b9e3042bd h1:KRoLSsR7wZ4H2dueR/O6BGBIXDxfOxUVmaMiu1QiQPw=
|
||||
github.com/distribution/distribution/v3 v3.0.0-20220504180456-7a6b9e3042bd/go.mod h1:qLi7jGj1b5TUaYTB3ekkHiocxHJi8+3CFhXM54MGKBs=
|
||||
github.com/distribution/distribution/v3 v3.0.0-20220505155552-985711c1f414 h1:KfVB1Z5fm10trO24Rn5Zzocd8sTm5k/gS24ijxQ1aJU=
|
||||
github.com/distribution/distribution/v3 v3.0.0-20220505155552-985711c1f414/go.mod h1:2oyLKljQFnsI1tzJxjUg4GI+HEpDfzFP3LrGM04rKg0=
|
||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
|
||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
|
||||
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
|
||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk=
|
||||
gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
@@ -10,30 +10,38 @@ import (
|
||||
// InlineConfig is made to represent a configMap or a secret
|
||||
type InlineConfig interface {
|
||||
AddEnvFile(filename string) error
|
||||
AddEnv(key, val string) error
|
||||
Metadata() *Metadata
|
||||
}
|
||||
|
||||
// ConfigMap is made to represent a configMap with data.
|
||||
type ConfigMap struct {
|
||||
*K8sBase `yaml:",inline"`
|
||||
Data map[string]string `yaml:"data"`
|
||||
}
|
||||
|
||||
func NewConfigMap(name string) *ConfigMap {
|
||||
// NewConfigMap returns a new initialzed ConfigMap.
|
||||
func NewConfigMap(name, path string) *ConfigMap {
|
||||
base := NewBase()
|
||||
base.ApiVersion = "v1"
|
||||
base.Kind = "ConfigMap"
|
||||
base.Metadata.Name = "{{ .Release.Name }}-" + name
|
||||
base.Metadata.Name = ReleaseNameTpl + "-" + name
|
||||
base.Metadata.Labels[K+"/component"] = name
|
||||
if path != "" {
|
||||
base.Metadata.Labels[K+"/path"] = path
|
||||
}
|
||||
return &ConfigMap{
|
||||
K8sBase: base,
|
||||
Data: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata returns the metadata of the configMap.
|
||||
func (c *ConfigMap) Metadata() *Metadata {
|
||||
return c.K8sBase.Metadata
|
||||
}
|
||||
|
||||
// AddEnvFile adds an environment file to the configMap.
|
||||
func (c *ConfigMap) AddEnvFile(file string) error {
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
@@ -52,28 +60,37 @@ func (c *ConfigMap) AddEnvFile(file string) error {
|
||||
}
|
||||
c.Data[parts[0]] = parts[1]
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (c *ConfigMap) AddEnv(key, val string) error {
|
||||
c.Data[key] = val
|
||||
return nil
|
||||
}
|
||||
|
||||
// Secret is made to represent a secret with data.
|
||||
type Secret struct {
|
||||
*K8sBase `yaml:",inline"`
|
||||
Data map[string]string `yaml:"data"`
|
||||
}
|
||||
|
||||
func NewSecret(name string) *Secret {
|
||||
// NewSecret returns a new initialzed Secret.
|
||||
func NewSecret(name, path string) *Secret {
|
||||
base := NewBase()
|
||||
base.ApiVersion = "v1"
|
||||
base.Kind = "Secret"
|
||||
base.Metadata.Name = "{{ .Release.Name }}-" + name
|
||||
base.Metadata.Name = ReleaseNameTpl + "-" + name
|
||||
base.Metadata.Labels[K+"/component"] = name
|
||||
if path != "" {
|
||||
base.Metadata.Labels[K+"/path"] = path
|
||||
}
|
||||
return &Secret{
|
||||
K8sBase: base,
|
||||
Data: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// AddEnvFile adds an environment file to the secret.
|
||||
func (s *Secret) AddEnvFile(file string) error {
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
@@ -96,6 +113,14 @@ func (s *Secret) AddEnvFile(file string) error {
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// Metadata returns the metadata of the secret.
|
||||
func (s *Secret) Metadata() *Metadata {
|
||||
return s.K8sBase.Metadata
|
||||
}
|
||||
|
||||
// AddEnv adds an environment variable to the secret.
|
||||
func (s *Secret) AddEnv(key, val string) error {
|
||||
s.Data[key] = fmt.Sprintf(`{{ %s | b64enc }}`, val)
|
||||
return nil
|
||||
}
|
65
helm/container.go
Normal file
65
helm/container.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package helm
|
||||
|
||||
import (
|
||||
"katenary/logger"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
)
|
||||
|
||||
type EnvValue interface{}
|
||||
|
||||
// ContainerPort represent a port mapping.
|
||||
type ContainerPort struct {
|
||||
Name string
|
||||
ContainerPort int `yaml:"containerPort"`
|
||||
}
|
||||
|
||||
// Value represent a environment variable with name and value.
|
||||
type Value struct {
|
||||
Name string `yaml:"name"`
|
||||
Value EnvValue `yaml:"value"`
|
||||
}
|
||||
|
||||
// Container represent a container with name, image, and environment variables. It is used in Deployment.
|
||||
type Container struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Image string `yaml:"image"`
|
||||
Ports []*ContainerPort `yaml:"ports,omitempty"`
|
||||
Env []*Value `yaml:"env,omitempty"`
|
||||
EnvFrom []map[string]map[string]string `yaml:"envFrom,omitempty"`
|
||||
Command []string `yaml:"command,omitempty"`
|
||||
VolumeMounts []interface{} `yaml:"volumeMounts,omitempty"`
|
||||
LivenessProbe *Probe `yaml:"livenessProbe,omitempty"`
|
||||
}
|
||||
|
||||
// NewContainer creates a new container with name, image, labels and environment variables.
|
||||
func NewContainer(name, image string, environment types.MappingWithEquals, labels map[string]string) *Container {
|
||||
container := &Container{
|
||||
Image: image,
|
||||
Name: name,
|
||||
EnvFrom: make([]map[string]map[string]string, 0),
|
||||
}
|
||||
|
||||
// find bound environment variable to a service
|
||||
toServices := make([]string, 0)
|
||||
if bound, ok := labels[LABEL_ENV_SERVICE]; ok {
|
||||
toServices = strings.Split(bound, ",")
|
||||
}
|
||||
if len(toServices) > 0 {
|
||||
// warn, it's deprecated now
|
||||
logger.ActivateColors = true
|
||||
logger.Yellowf(
|
||||
"[deprecated] in \"%s\" service: label %s is deprecated and **ignored**, please use %s instead\n"+
|
||||
"e.g.\n"+
|
||||
" labels:\n"+
|
||||
" FOO: {{ .Release.Name }}-fooservice\n",
|
||||
name,
|
||||
LABEL_ENV_SERVICE,
|
||||
LABEL_MAP_ENV,
|
||||
)
|
||||
logger.ActivateColors = false
|
||||
}
|
||||
|
||||
return container
|
||||
}
|
@@ -1,7 +1,5 @@
|
||||
package helm
|
||||
|
||||
import "strings"
|
||||
|
||||
// Deployment is a k8s deployment.
|
||||
type Deployment struct {
|
||||
*K8sBase `yaml:",inline"`
|
||||
@@ -10,7 +8,7 @@ type Deployment struct {
|
||||
|
||||
func NewDeployment(name string) *Deployment {
|
||||
d := &Deployment{K8sBase: NewBase(), Spec: NewDepSpec()}
|
||||
d.K8sBase.Metadata.Name = "{{ .Release.Name }}-" + name
|
||||
d.K8sBase.Metadata.Name = ReleaseNameTpl + "-" + name
|
||||
d.K8sBase.ApiVersion = "apps/v1"
|
||||
d.K8sBase.Kind = "Deployment"
|
||||
d.K8sBase.Metadata.Labels[K+"/component"] = name
|
||||
@@ -29,53 +27,6 @@ func NewDepSpec() *DepSpec {
|
||||
}
|
||||
}
|
||||
|
||||
type Value struct {
|
||||
Name string `yaml:"name"`
|
||||
Value interface{} `yaml:"value"`
|
||||
}
|
||||
|
||||
type ContainerPort struct {
|
||||
Name string
|
||||
ContainerPort int `yaml:"containerPort"`
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Image string `yaml:"image"`
|
||||
Ports []*ContainerPort `yaml:"ports,omitempty"`
|
||||
Env []Value `yaml:"env,omitempty"`
|
||||
EnvFrom []map[string]map[string]string `yaml:"envFrom,omitempty"`
|
||||
Command []string `yaml:"command,omitempty"`
|
||||
VolumeMounts []interface{} `yaml:"volumeMounts,omitempty"`
|
||||
}
|
||||
|
||||
func NewContainer(name, image string, environment, labels map[string]string) *Container {
|
||||
container := &Container{
|
||||
Image: image,
|
||||
Name: name,
|
||||
Env: make([]Value, len(environment)),
|
||||
EnvFrom: make([]map[string]map[string]string, 0),
|
||||
}
|
||||
|
||||
// find bound environment variable to a service
|
||||
toServices := make([]string, 0)
|
||||
if bound, ok := labels[LABEL_ENV_SERVICE]; ok {
|
||||
toServices = strings.Split(bound, ",")
|
||||
}
|
||||
|
||||
idx := 0
|
||||
for n, v := range environment {
|
||||
for _, name := range toServices {
|
||||
if name == n {
|
||||
v = "{{ .Release.Name }}-" + v
|
||||
}
|
||||
}
|
||||
container.Env[idx] = Value{Name: n, Value: v}
|
||||
idx++
|
||||
}
|
||||
return container
|
||||
}
|
||||
|
||||
type PodSpec struct {
|
||||
InitContainers []*Container `yaml:"initContainers,omitempty"`
|
||||
Containers []*Container `yaml:"containers"`
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package helm
|
||||
|
||||
// Ingress is the kubernetes ingress object.
|
||||
type Ingress struct {
|
||||
*K8sBase `yaml:",inline"`
|
||||
Spec IngressSpec
|
||||
@@ -8,7 +9,7 @@ type Ingress struct {
|
||||
func NewIngress(name string) *Ingress {
|
||||
i := &Ingress{}
|
||||
i.K8sBase = NewBase()
|
||||
i.K8sBase.Metadata.Name = "{{ .Release.Name }}-" + name
|
||||
i.K8sBase.Metadata.Name = ReleaseNameTpl + "-" + name
|
||||
i.K8sBase.Kind = "Ingress"
|
||||
i.ApiVersion = "networking.k8s.io/v1"
|
||||
i.K8sBase.Metadata.Labels[K+"/component"] = name
|
||||
@@ -18,7 +19,6 @@ func NewIngress(name string) *Ingress {
|
||||
|
||||
func (i *Ingress) SetIngressClass(name string) {
|
||||
class := "{{ .Values." + name + ".ingress.class }}"
|
||||
i.Metadata.Annotations["kubernetes.io/ingress.class"] = class
|
||||
i.Spec.IngressClassName = class
|
||||
}
|
||||
|
||||
@@ -39,14 +39,16 @@ type IngressHttp struct {
|
||||
type IngressPath struct {
|
||||
Path string
|
||||
PathType string `yaml:"pathType"`
|
||||
Backend IngressBackend
|
||||
Backend *IngressBackend
|
||||
}
|
||||
|
||||
type IngressBackend struct {
|
||||
Service IngressService
|
||||
ServiceName string `yaml:"serviceName"` // for kubernetes version < 1.18
|
||||
ServicePort interface{} `yaml:"servicePort"` // for kubernetes version < 1.18
|
||||
}
|
||||
|
||||
type IngressService struct {
|
||||
Name string
|
||||
Port map[string]interface{}
|
||||
Name string `yaml:"name"`
|
||||
Port map[string]interface{} `yaml:"port"`
|
||||
}
|
||||
|
73
helm/k8sbase.go
Normal file
73
helm/k8sbase.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package helm
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Metadata is the metadata for a kubernetes object.
|
||||
type Metadata struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Labels map[string]string `yaml:"labels"`
|
||||
Annotations map[string]string `yaml:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
func NewMetadata() *Metadata {
|
||||
return &Metadata{
|
||||
Name: "",
|
||||
Labels: make(map[string]string),
|
||||
Annotations: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// K8sBase is the base for all kubernetes objects.
|
||||
type K8sBase struct {
|
||||
ApiVersion string `yaml:"apiVersion"`
|
||||
Kind string `yaml:"kind"`
|
||||
Metadata *Metadata `yaml:"metadata"`
|
||||
}
|
||||
|
||||
// NewBase is a factory for creating a new base object with metadata, labels and annotations set to the default.
|
||||
func NewBase() *K8sBase {
|
||||
b := &K8sBase{
|
||||
Metadata: NewMetadata(),
|
||||
}
|
||||
// add some information of the build
|
||||
b.Metadata.Labels[K+"/project"] = "{{ .Chart.Name }}"
|
||||
b.Metadata.Labels[K+"/release"] = ReleaseNameTpl
|
||||
b.Metadata.Annotations[K+"/version"] = Version
|
||||
return b
|
||||
}
|
||||
|
||||
func (k *K8sBase) BuildSHA(filename string) {
|
||||
c, _ := ioutil.ReadFile(filename)
|
||||
//sum := sha256.Sum256(c)
|
||||
sum := sha1.Sum(c)
|
||||
k.Metadata.Annotations[K+"/docker-compose-sha1"] = fmt.Sprintf("%x", string(sum[:]))
|
||||
}
|
||||
|
||||
// Get returns the Kind.
|
||||
func (k *K8sBase) Get() string {
|
||||
return k.Kind
|
||||
}
|
||||
|
||||
// Name returns the name of the object from Metadata.
|
||||
func (k *K8sBase) Name() string {
|
||||
return k.Metadata.Name
|
||||
}
|
||||
|
||||
func (k *K8sBase) GetType() string {
|
||||
if n, ok := k.Metadata.Labels[K+"/type"]; ok {
|
||||
return n
|
||||
}
|
||||
return strings.ToLower(k.Kind)
|
||||
}
|
||||
|
||||
func (k *K8sBase) GetPathRessource() string {
|
||||
if p, ok := k.Metadata.Labels[K+"/path"]; ok {
|
||||
return p
|
||||
}
|
||||
return ""
|
||||
}
|
61
helm/labels.go
Normal file
61
helm/labels.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package helm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
const ReleaseNameTpl = "{{ .Release.Name }}"
|
||||
const (
|
||||
LABEL_MAP_ENV = K + "/mapenv"
|
||||
LABEL_ENV_SECRET = K + "/secret-envfiles"
|
||||
LABEL_PORT = K + "/ports"
|
||||
LABEL_INGRESS = K + "/ingress"
|
||||
LABEL_VOL_CM = K + "/configmap-volumes"
|
||||
LABEL_HEALTHCHECK = K + "/healthcheck"
|
||||
LABEL_SAMEPOD = K + "/same-pod"
|
||||
LABEL_VOLUMEFROM = K + "/volume-from"
|
||||
LABEL_EMPTYDIRS = K + "/empty-dirs"
|
||||
LABEL_IGNORE = K + "/ignore"
|
||||
LABEL_SECRETVARS = K + "/secret-vars"
|
||||
|
||||
//deprecated: use LABEL_MAP_ENV instead
|
||||
LABEL_ENV_SERVICE = K + "/env-to-service"
|
||||
)
|
||||
|
||||
// GetLabelsDocumentation returns the documentation for the labels.
|
||||
func GetLabelsDocumentation() string {
|
||||
t, _ := template.New("labels").Parse(`
|
||||
# Labels
|
||||
{{.LABEL_IGNORE | printf "%-33s"}}: ignore the container, it will not yied any object in the helm chart
|
||||
{{.LABEL_SECRETVARS | printf "%-33s"}}: secret variables to push on a secret file
|
||||
{{.LABEL_ENV_SECRET | printf "%-33s"}}: set the given file names as a secret instead of configmap
|
||||
{{.LABEL_MAP_ENV | printf "%-33s"}}: map environment variable to a template string (yaml style)
|
||||
{{.LABEL_PORT | printf "%-33s"}}: set the ports to expose as a service (coma separated)
|
||||
{{.LABEL_INGRESS | printf "%-33s"}}: set the port to expose in an ingress (coma separated)
|
||||
{{.LABEL_VOL_CM | printf "%-33s"}}: specifies that the volumes points on a configmap (coma separated)
|
||||
{{.LABEL_SAMEPOD | printf "%-33s"}}: specifies that the pod should be deployed in the same pod than the given service name
|
||||
{{.LABEL_VOLUMEFROM | printf "%-33s"}}: specifies that the volumes to be mounted from the given service (yaml style)
|
||||
{{.LABEL_EMPTYDIRS | printf "%-33s"}}: specifies that the given volume names should be "emptyDir" instead of persistentVolumeClaim (coma separated)
|
||||
{{.LABEL_HEALTHCHECK | printf "%-33s"}}: specifies that the container should be monitored by a healthcheck, **it overrides the docker-compose healthcheck**.
|
||||
{{ printf "%-34s" ""}} You can use these form of label values:
|
||||
{{ printf "%-35s" ""}}- "http://[not used address][:port][/path]" to specify an http healthcheck
|
||||
{{ printf "%-35s" ""}}- "tcp://[not used address]:port" to specify a tcp healthcheck
|
||||
{{ printf "%-35s" ""}}- other string is condidered as a "command" healthcheck
|
||||
`)
|
||||
buff := bytes.NewBuffer(nil)
|
||||
t.Execute(buff, map[string]string{
|
||||
"LABEL_ENV_SECRET": LABEL_ENV_SECRET,
|
||||
"LABEL_PORT": LABEL_PORT,
|
||||
"LABEL_INGRESS": LABEL_INGRESS,
|
||||
"LABEL_VOL_CM": LABEL_VOL_CM,
|
||||
"LABEL_HEALTHCHECK": LABEL_HEALTHCHECK,
|
||||
"LABEL_SAMEPOD": LABEL_SAMEPOD,
|
||||
"LABEL_VOLUMEFROM": LABEL_VOLUMEFROM,
|
||||
"LABEL_EMPTYDIRS": LABEL_EMPTYDIRS,
|
||||
"LABEL_IGNORE": LABEL_IGNORE,
|
||||
"LABEL_MAP_ENV": LABEL_MAP_ENV,
|
||||
"LABEL_SECRETVARS": LABEL_SECRETVARS,
|
||||
})
|
||||
return buff.String()
|
||||
}
|
@@ -10,7 +10,8 @@ Your application is now deployed. This may take a while to be up and responding.
|
||||
__list__
|
||||
`
|
||||
|
||||
func GenNotes(ingressess map[string]*Ingress) string {
|
||||
// GenerateNotesFile generates the notes file for the helm chart.
|
||||
func GenerateNotesFile(ingressess map[string]*Ingress) string {
|
||||
|
||||
list := make([]string, 0)
|
||||
|
||||
|
104
helm/probe.go
Normal file
104
helm/probe.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package helm
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
)
|
||||
|
||||
// Probe is a struct that can be used to create a Liveness or Readiness probe.
|
||||
type Probe struct {
|
||||
HttpGet *HttpGet `yaml:"httpGet,omitempty"`
|
||||
Exec *Exec `yaml:"exec,omitempty"`
|
||||
TCP *TCP `yaml:"tcp,omitempty"`
|
||||
Period float64 `yaml:"periodSeconds"`
|
||||
InitialDelay float64 `yaml:"initialDelaySeconds"`
|
||||
Success uint64 `yaml:"successThreshold"`
|
||||
Failure uint64 `yaml:"failureThreshold"`
|
||||
}
|
||||
|
||||
// Create a new Probe object that can be apply to HttpProbe or TCPProbe.
|
||||
func NewProbe(period, initialDelaySeconds float64, success, failure uint64) *Probe {
|
||||
probe := &Probe{
|
||||
Period: period,
|
||||
Success: success,
|
||||
Failure: failure,
|
||||
InitialDelay: initialDelaySeconds,
|
||||
}
|
||||
|
||||
// fix default values from
|
||||
// https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
|
||||
if period == 0 {
|
||||
probe.Period = 10
|
||||
}
|
||||
if success == 0 {
|
||||
probe.Success = 1
|
||||
}
|
||||
if failure == 0 {
|
||||
probe.Failure = 3
|
||||
}
|
||||
return probe
|
||||
}
|
||||
|
||||
// NewProbeWithDuration creates a new Probe object with the given duration from types.
|
||||
func NewProbeWithDuration(period, initialDelaySeconds *types.Duration, success, failure *uint64) *Probe {
|
||||
|
||||
if period == nil {
|
||||
d := types.Duration(0 * time.Second)
|
||||
period = &d
|
||||
}
|
||||
|
||||
if initialDelaySeconds == nil {
|
||||
d := types.Duration(0 * time.Second)
|
||||
initialDelaySeconds = &d
|
||||
}
|
||||
|
||||
if success == nil {
|
||||
s := uint64(0)
|
||||
success = &s
|
||||
}
|
||||
|
||||
if failure == nil {
|
||||
f := uint64(0)
|
||||
failure = &f
|
||||
}
|
||||
|
||||
p, err := time.ParseDuration(period.String())
|
||||
if err != nil {
|
||||
p = time.Second * 10
|
||||
}
|
||||
|
||||
i, err := time.ParseDuration(initialDelaySeconds.String())
|
||||
if err != nil {
|
||||
i = time.Second * 0
|
||||
}
|
||||
|
||||
return NewProbe(p.Seconds(), i.Seconds(), *success, *failure)
|
||||
|
||||
}
|
||||
|
||||
// NewProbeFromService creates a new Probe object from a ServiceConfig.
|
||||
func NewProbeFromService(s *types.ServiceConfig) *Probe {
|
||||
if s == nil || s.HealthCheck == nil {
|
||||
return NewProbe(0, 0, 0, 0)
|
||||
}
|
||||
|
||||
return NewProbeWithDuration(s.HealthCheck.Interval, s.HealthCheck.StartPeriod, nil, s.HealthCheck.Retries)
|
||||
|
||||
}
|
||||
|
||||
// HttpGet is a Probe configuration to check http health.
|
||||
type HttpGet struct {
|
||||
Path string `yaml:"path"`
|
||||
Port int `yaml:"port"`
|
||||
}
|
||||
|
||||
// Execis a Probe configuration to check exec health.
|
||||
type Exec struct {
|
||||
Command []string `yaml:"command"`
|
||||
}
|
||||
|
||||
// TCP is a Probe configuration to check tcp health.
|
||||
type TCP struct {
|
||||
Port int `yaml:"port"`
|
||||
}
|
@@ -1,28 +1,32 @@
|
||||
package helm
|
||||
|
||||
// Service is a Kubernetes service.
|
||||
type Service struct {
|
||||
*K8sBase `yaml:",inline"`
|
||||
Spec *ServiceSpec `yaml:"spec"`
|
||||
}
|
||||
|
||||
// NewService creates a new initialized service.
|
||||
func NewService(name string) *Service {
|
||||
s := &Service{
|
||||
K8sBase: NewBase(),
|
||||
Spec: NewServiceSpec(),
|
||||
}
|
||||
s.K8sBase.Metadata.Name = "{{ .Release.Name }}-" + name
|
||||
s.K8sBase.Metadata.Name = ReleaseNameTpl + "-" + name
|
||||
s.K8sBase.Kind = "Service"
|
||||
s.K8sBase.ApiVersion = "v1"
|
||||
s.K8sBase.Metadata.Labels[K+"/component"] = name
|
||||
return s
|
||||
}
|
||||
|
||||
// ServicePort is a port on a service.
|
||||
type ServicePort struct {
|
||||
Protocol string `yaml:"protocol"`
|
||||
Port int `yaml:"port"`
|
||||
TargetPort int `yaml:"targetPort"`
|
||||
}
|
||||
|
||||
// NewServicePort creates a new initialized service port.
|
||||
func NewServicePort(port, target int) *ServicePort {
|
||||
return &ServicePort{
|
||||
Protocol: "TCP",
|
||||
@@ -31,12 +35,14 @@ func NewServicePort(port, target int) *ServicePort {
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceSpec is the spec for a service.
|
||||
type ServiceSpec struct {
|
||||
Selector map[string]string
|
||||
Ports []*ServicePort
|
||||
Type string `yaml:"type,omitempty"`
|
||||
}
|
||||
|
||||
// NewServiceSpec creates a new initialized service spec.
|
||||
func NewServiceSpec() *ServiceSpec {
|
||||
return &ServiceSpec{
|
||||
Selector: make(map[string]string),
|
||||
|
@@ -1,17 +1,40 @@
|
||||
package helm
|
||||
|
||||
import "sync"
|
||||
|
||||
var (
|
||||
made = make(map[string]bool)
|
||||
locker = sync.Mutex{}
|
||||
)
|
||||
|
||||
// ResetMadePVC resets the cache of made PVCs.
|
||||
// Useful in tests only.
|
||||
func ResetMadePVC() {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
made = make(map[string]bool)
|
||||
}
|
||||
|
||||
// Storage is a struct for a PersistentVolumeClaim.
|
||||
type Storage struct {
|
||||
*K8sBase `yaml:",inline"`
|
||||
Spec *PVCSpec
|
||||
}
|
||||
|
||||
// NewPVC creates a new PersistentVolumeClaim object.
|
||||
func NewPVC(name, storageName string) *Storage {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
if _, ok := made[name+storageName]; ok {
|
||||
return nil
|
||||
}
|
||||
made[name+storageName] = true
|
||||
pvc := &Storage{}
|
||||
pvc.K8sBase = NewBase()
|
||||
pvc.K8sBase.Kind = "PersistentVolumeClaim"
|
||||
pvc.K8sBase.Metadata.Labels[K+"/pvc-name"] = storageName
|
||||
pvc.K8sBase.ApiVersion = "v1"
|
||||
pvc.K8sBase.Metadata.Name = "{{ .Release.Name }}-" + storageName
|
||||
pvc.K8sBase.Metadata.Name = ReleaseNameTpl + "-" + storageName
|
||||
pvc.K8sBase.Metadata.Labels[K+"/component"] = name
|
||||
pvc.Spec = &PVCSpec{
|
||||
Resouces: map[string]interface{}{
|
||||
@@ -24,6 +47,7 @@ func NewPVC(name, storageName string) *Storage {
|
||||
return pvc
|
||||
}
|
||||
|
||||
// PVCSpec is a struct for a PersistentVolumeClaim spec.
|
||||
type PVCSpec struct {
|
||||
Resouces map[string]interface{} `yaml:"resources"`
|
||||
AccessModes []string `yaml:"accessModes"`
|
||||
|
@@ -1,83 +1,36 @@
|
||||
package helm
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const K = "katenary.io"
|
||||
const (
|
||||
LABEL_ENV_SECRET = K + "/secret-envfiles"
|
||||
LABEL_PORT = K + "/ports"
|
||||
LABEL_INGRESS = K + "/ingress"
|
||||
LABEL_ENV_SERVICE = K + "/env-to-service"
|
||||
LABEL_VOL_CM = K + "/configmap-volumes"
|
||||
|
||||
var (
|
||||
Appname = "" // set at runtime
|
||||
Version = "1.0" // should be set from main.Version
|
||||
)
|
||||
|
||||
var Appname = ""
|
||||
|
||||
var Version = "1.0" // should be set from main.Version
|
||||
|
||||
// Kinded represent an object with a kind.
|
||||
type Kinded interface {
|
||||
// Get must resturn the kind name.
|
||||
Get() string
|
||||
}
|
||||
|
||||
// Signable represents an object with a signature.
|
||||
type Signable interface {
|
||||
// BuildSHA must return the signature.
|
||||
BuildSHA(filename string)
|
||||
}
|
||||
|
||||
// Named represents an object with a name.
|
||||
type Named interface {
|
||||
// Name must return the name of the object (from metadata).
|
||||
Name() string
|
||||
}
|
||||
|
||||
type Metadata struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Labels map[string]string `yaml:"labels"`
|
||||
Annotations map[string]string `yaml:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
func NewMetadata() *Metadata {
|
||||
return &Metadata{
|
||||
Name: "",
|
||||
Labels: make(map[string]string),
|
||||
Annotations: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
type K8sBase struct {
|
||||
ApiVersion string `yaml:"apiVersion"`
|
||||
Kind string `yaml:"kind"`
|
||||
Metadata *Metadata `yaml:"metadata"`
|
||||
}
|
||||
|
||||
func NewBase() *K8sBase {
|
||||
|
||||
b := &K8sBase{
|
||||
Metadata: NewMetadata(),
|
||||
}
|
||||
b.Metadata.Labels[K+"/project"] = GetProjectName()
|
||||
b.Metadata.Labels[K+"/release"] = "{{ .Release.Name }}"
|
||||
b.Metadata.Annotations[K+"/version"] = Version
|
||||
return b
|
||||
}
|
||||
|
||||
func (k *K8sBase) BuildSHA(filename string) {
|
||||
c, _ := ioutil.ReadFile(filename)
|
||||
sum := sha256.Sum256(c)
|
||||
k.Metadata.Annotations[K+"/docker-compose-sha256"] = fmt.Sprintf("%x", string(sum[:]))
|
||||
}
|
||||
|
||||
func (k *K8sBase) Get() string {
|
||||
return k.Kind
|
||||
}
|
||||
|
||||
func (k *K8sBase) Name() string {
|
||||
return k.Metadata.Name
|
||||
}
|
||||
|
||||
// GetProjectName returns the name of the project.
|
||||
func GetProjectName() string {
|
||||
if len(Appname) > 0 {
|
||||
return Appname
|
||||
|
57
install.sh
Normal file
57
install.sh
Normal file
@@ -0,0 +1,57 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Install the latest version of the Katenary detecting the right OS and architecture.
|
||||
# Can be launched with the following command:
|
||||
# sh <(curl -sSL https://raw.githubusercontent.com/metal3d/katenary/master/install.sh)
|
||||
|
||||
set -e
|
||||
|
||||
# Detect the OS and architecture
|
||||
OS=$(uname)
|
||||
ARCH=$(uname -m)
|
||||
|
||||
# Detect where to install the binary, local path is the prefered method
|
||||
INSTALL_TYPE=$(echo $PATH | grep "$HOME/.local/bin" 2>&1 >/dev/null && echo "local" || echo "global")
|
||||
|
||||
# Where to download the binary
|
||||
BASE="https://github.com/metal3d/katenary/releases/latest/download/"
|
||||
|
||||
|
||||
if [ $ARCH = "x86_64" ]; then
|
||||
ARCH="amd64"
|
||||
fi
|
||||
|
||||
BIN_URL="$BASE/katenary-$OS-$ARCH"
|
||||
|
||||
if [ "$INSTALL_TYPE" = "local" ]; then
|
||||
echo "Installing to local directory, installing in $HOME/.local/bin"
|
||||
BIN_PATH="$HOME/.local/bin"
|
||||
else
|
||||
echo "Installing to global directory, installing in /usr/local/bin - we need to use sudo..."
|
||||
answer=""
|
||||
while [ "$answer" != "y" ] && [ "$answer" != "n" ]; do
|
||||
echo -n "Are you OK? [y/N] "
|
||||
read answer
|
||||
# lower case answer
|
||||
answer=$(echo $answer | tr '[:upper:]' '[:lower:]')
|
||||
if [ "$answer" == "n" ] || [ -z "$answer" ]; then
|
||||
echo "--> To install locally, please ensure that \$HOME/.local/bin is in your PATH"
|
||||
echo "Cancelling installation"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
BIN_PATH="/usr/local/bin"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Downloading $BIN_URL"
|
||||
USE_SUDO=$([ "$INSTALL_TYPE" = "local" ] && echo "" || echo "sudo")
|
||||
|
||||
T=$(mktemp -u)
|
||||
$USE_SUDO curl -SL -# $BIN_URL -o $T || (echo "Failed to download katenary" && rm -f $T && exit 1)
|
||||
|
||||
$USE_SUDO mv $T $BIN_PATH/katenary
|
||||
$USE_SUDO chmod +x $BIN_PATH/katenary
|
||||
echo
|
||||
echo "Installed to $BIN_PATH/katenary"
|
||||
echo "Installation complete! Run 'katenary --help' to get started."
|
@@ -1,8 +1,9 @@
|
||||
package generator
|
||||
package logger
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestColor(t *testing.T) {
|
||||
NOLOG = false
|
||||
Red("Red text")
|
||||
Grey("Grey text")
|
||||
}
|
96
logger/utils.go
Normal file
96
logger/utils.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Color int
|
||||
|
||||
var ActivateColors = false
|
||||
var NOLOG = false
|
||||
|
||||
const (
|
||||
GREY Color = 30 + iota
|
||||
RED
|
||||
GREEN
|
||||
YELLOW
|
||||
BLUE
|
||||
MAGENTA
|
||||
CYAN
|
||||
)
|
||||
|
||||
var waiter = sync.Mutex{}
|
||||
|
||||
func color(c Color, args ...interface{}) {
|
||||
if NOLOG {
|
||||
return
|
||||
}
|
||||
if !ActivateColors {
|
||||
fmt.Println(args...)
|
||||
return
|
||||
}
|
||||
waiter.Lock()
|
||||
fmt.Fprintf(os.Stdout, "\x1b[%dm", c)
|
||||
fmt.Fprint(os.Stdout, args...)
|
||||
fmt.Fprintf(os.Stdout, "\x1b[0m\n")
|
||||
waiter.Unlock()
|
||||
}
|
||||
|
||||
func colorf(c Color, format string, args ...interface{}) {
|
||||
if NOLOG {
|
||||
return
|
||||
}
|
||||
if !ActivateColors {
|
||||
fmt.Printf(format, args...)
|
||||
return
|
||||
}
|
||||
waiter.Lock()
|
||||
fmt.Fprintf(os.Stdout, "\x1b[%dm", c)
|
||||
fmt.Fprintf(os.Stdout, format, args...)
|
||||
fmt.Fprintf(os.Stdout, "\x1b[0m")
|
||||
waiter.Unlock()
|
||||
}
|
||||
|
||||
func Grey(args ...interface{}) {
|
||||
color(GREY, args...)
|
||||
}
|
||||
|
||||
func Red(args ...interface{}) {
|
||||
color(RED, args...)
|
||||
}
|
||||
func Green(args ...interface{}) {
|
||||
color(GREEN, args...)
|
||||
}
|
||||
func Yellow(args ...interface{}) {
|
||||
color(YELLOW, args...)
|
||||
}
|
||||
func Blue(args ...interface{}) {
|
||||
color(BLUE, args...)
|
||||
}
|
||||
func Magenta(args ...interface{}) {
|
||||
color(MAGENTA, args...)
|
||||
}
|
||||
func Greyf(format string, args ...interface{}) {
|
||||
colorf(GREY, format, args...)
|
||||
}
|
||||
|
||||
func Redf(format string, args ...interface{}) {
|
||||
colorf(RED, format, args...)
|
||||
}
|
||||
func Greenf(format string, args ...interface{}) {
|
||||
colorf(GREEN, format, args...)
|
||||
}
|
||||
func Yellowf(format string, args ...interface{}) {
|
||||
colorf(YELLOW, format, args...)
|
||||
}
|
||||
func Bluef(format string, args ...interface{}) {
|
||||
colorf(BLUE, format, args...)
|
||||
}
|
||||
func Magentaf(format string, args ...interface{}) {
|
||||
colorf(MAGENTA, format, args...)
|
||||
}
|
||||
func Cyanf(format string, args ...interface{}) {
|
||||
colorf(CYAN, format, args...)
|
||||
}
|
213
main.go
213
main.go
@@ -1,213 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"katenary/compose"
|
||||
"katenary/generator"
|
||||
"katenary/helm"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var ComposeFile = "docker-compose.yaml"
|
||||
var AppName = "MyApp"
|
||||
var AppVersion = "0.0.1"
|
||||
var Version = "master"
|
||||
var ChartsDir = "chart"
|
||||
|
||||
var PrefixRE = regexp.MustCompile(`\{\{.*\}\}-?`)
|
||||
|
||||
func main() {
|
||||
flag.StringVar(&ChartsDir, "chart-dir", ChartsDir, "set the chart directory")
|
||||
flag.StringVar(&ComposeFile, "compose", ComposeFile, "set the compose file to parse")
|
||||
flag.StringVar(&AppName, "appname", helm.GetProjectName(), "set the helm chart app name")
|
||||
flag.StringVar(&AppVersion, "appversion", AppVersion, "set the chart appVersion")
|
||||
version := flag.Bool("version", false, "Show version and exit")
|
||||
force := flag.Bool("force", false, "force the removal of the chart-dir")
|
||||
flag.Parse()
|
||||
|
||||
if *version {
|
||||
fmt.Println(Version)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// make the appname global (yes...)
|
||||
helm.Appname = AppName
|
||||
dirname := filepath.Join(ChartsDir, AppName)
|
||||
|
||||
if _, err := os.Stat(dirname); err == nil && !*force {
|
||||
response := ""
|
||||
for response != "y" && response != "n" {
|
||||
response = "n"
|
||||
fmt.Printf("The %s directory already exists, it will be \x1b[31;1mremoved\x1b[0m!\nDo you really want to continue ? [y/N]: ", dirname)
|
||||
fmt.Scanf("%s", &response)
|
||||
response = strings.ToLower(response)
|
||||
}
|
||||
if response == "n" {
|
||||
fmt.Println("Cancelled")
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
os.RemoveAll(dirname)
|
||||
templatesDir := filepath.Join(dirname, "templates")
|
||||
os.MkdirAll(templatesDir, 0755)
|
||||
|
||||
helm.Version = Version
|
||||
p := compose.NewParser(ComposeFile)
|
||||
p.Parse(AppName)
|
||||
|
||||
files := make(map[string]chan interface{})
|
||||
|
||||
//wait := sync.WaitGroup{}
|
||||
for name, s := range p.Data.Services {
|
||||
//wait.Add(1)
|
||||
// it's mandatory to build in goroutines because some dependencies can
|
||||
// wait for a port number discovery.
|
||||
// So the entire services are built in parallel.
|
||||
//go func(name string, s compose.Service) {
|
||||
// defer wait.Done()
|
||||
o := generator.CreateReplicaObject(name, s)
|
||||
files[name] = o
|
||||
//}(name, s)
|
||||
}
|
||||
//wait.Wait()
|
||||
|
||||
// to generate notes, we need to keep an Ingresses list
|
||||
ingresses := make(map[string]*helm.Ingress)
|
||||
|
||||
for n, f := range files {
|
||||
for c := range f {
|
||||
if c == nil {
|
||||
break
|
||||
}
|
||||
kind := c.(helm.Kinded).Get()
|
||||
kind = strings.ToLower(kind)
|
||||
c.(helm.Signable).BuildSHA(ComposeFile)
|
||||
switch c := c.(type) {
|
||||
case *helm.Storage:
|
||||
fname := filepath.Join(templatesDir, n+"."+kind+".yaml")
|
||||
fp, _ := os.Create(fname)
|
||||
volname := c.K8sBase.Metadata.Labels[helm.K+"/pvc-name"]
|
||||
fp.WriteString("{{ if .Values." + n + ".persistence." + volname + ".enabled }}\n")
|
||||
enc := yaml.NewEncoder(fp)
|
||||
enc.SetIndent(2)
|
||||
enc.Encode(c)
|
||||
fp.WriteString("{{- end -}}")
|
||||
case *helm.Deployment:
|
||||
fname := filepath.Join(templatesDir, n+"."+kind+".yaml")
|
||||
fp, _ := os.Create(fname)
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
enc := yaml.NewEncoder(buffer)
|
||||
enc.SetIndent(2)
|
||||
enc.Encode(c)
|
||||
_content := string(buffer.Bytes())
|
||||
content := strings.Split(string(_content), "\n")
|
||||
dataname := ""
|
||||
component := c.Spec.Selector["matchLabels"].(map[string]string)[helm.K+"/component"]
|
||||
for _, line := range content {
|
||||
if strings.Contains(line, "name:") {
|
||||
dataname = strings.Split(line, ":")[1]
|
||||
dataname = strings.TrimSpace(dataname)
|
||||
} else if strings.Contains(line, "persistentVolumeClaim") {
|
||||
line = " {{- if .Values." + component + ".persistence." + dataname + ".enabled }}\n" + line
|
||||
} else if strings.Contains(line, "claimName") {
|
||||
line += "\n {{ else }}"
|
||||
line += "\n emptyDir: {}"
|
||||
line += "\n {{- end }}"
|
||||
}
|
||||
fp.WriteString(line + "\n")
|
||||
}
|
||||
fp.Close()
|
||||
|
||||
case *helm.Service:
|
||||
suffix := ""
|
||||
if c.Spec.Type == "NodePort" {
|
||||
suffix = "-external"
|
||||
}
|
||||
fname := filepath.Join(templatesDir, n+suffix+"."+kind+".yaml")
|
||||
fp, _ := os.Create(fname)
|
||||
enc := yaml.NewEncoder(fp)
|
||||
enc.SetIndent(2)
|
||||
enc.Encode(c)
|
||||
fp.Close()
|
||||
|
||||
case *helm.Ingress:
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
fname := filepath.Join(templatesDir, n+"."+kind+".yaml")
|
||||
ingresses[n] = c // keep it to generate notes
|
||||
enc := yaml.NewEncoder(buffer)
|
||||
enc.SetIndent(2)
|
||||
buffer.WriteString("{{- if .Values." + n + ".ingress.enabled -}}\n")
|
||||
enc.Encode(c)
|
||||
buffer.WriteString("{{- end -}}")
|
||||
|
||||
fp, _ := os.Create(fname)
|
||||
content := string(buffer.Bytes())
|
||||
lines := strings.Split(content, "\n")
|
||||
for _, l := range lines {
|
||||
if strings.Contains(l, "ingressClassName") {
|
||||
p := strings.Split(l, ":")
|
||||
condition := p[1]
|
||||
condition = strings.ReplaceAll(condition, "'", "")
|
||||
condition = strings.ReplaceAll(condition, "{{", "")
|
||||
condition = strings.ReplaceAll(condition, "}}", "")
|
||||
condition = strings.TrimSpace(condition)
|
||||
condition = "{{- if " + condition + " }}"
|
||||
l = " " + condition + "\n" + l + "\n {{- end }}"
|
||||
}
|
||||
fp.WriteString(l + "\n")
|
||||
}
|
||||
fp.Close()
|
||||
|
||||
case *helm.ConfigMap, *helm.Secret:
|
||||
// there could be several files, so let's force the filename
|
||||
name := c.(helm.Named).Name()
|
||||
name = PrefixRE.ReplaceAllString(name, "")
|
||||
fname := filepath.Join(templatesDir, n+"."+name+"."+kind+".yaml")
|
||||
fp, _ := os.Create(fname)
|
||||
enc := yaml.NewEncoder(fp)
|
||||
enc.SetIndent(2)
|
||||
enc.Encode(c)
|
||||
fp.Close()
|
||||
|
||||
default:
|
||||
fname := filepath.Join(templatesDir, n+"."+kind+".yaml")
|
||||
fp, _ := os.Create(fname)
|
||||
enc := yaml.NewEncoder(fp)
|
||||
enc.SetIndent(2)
|
||||
enc.Encode(c)
|
||||
fp.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fp, _ := os.Create(filepath.Join(dirname, "values.yaml"))
|
||||
enc := yaml.NewEncoder(fp)
|
||||
enc.SetIndent(2)
|
||||
enc.Encode(generator.Values)
|
||||
fp.Close()
|
||||
|
||||
fp, _ = os.Create(filepath.Join(dirname, "Chart.yaml"))
|
||||
enc = yaml.NewEncoder(fp)
|
||||
enc.SetIndent(2)
|
||||
enc.Encode(map[string]interface{}{
|
||||
"apiVersion": "v2",
|
||||
"name": AppName,
|
||||
"description": "A helm chart for " + AppName,
|
||||
"type": "application",
|
||||
"version": "0.1.0",
|
||||
"appVersion": AppVersion,
|
||||
})
|
||||
fp.Close()
|
||||
|
||||
fp, _ = os.Create(filepath.Join(templatesDir, "NOTES.txt"))
|
||||
fp.WriteString(helm.GenNotes(ingresses))
|
||||
fp.Close()
|
||||
}
|
BIN
misc/LogoMakr-1TEtSp.png
Normal file
BIN
misc/LogoMakr-1TEtSp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.0 KiB |
BIN
misc/Logo_Smile.png
Normal file
BIN
misc/Logo_Smile.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
misc/logo.png
Normal file
BIN
misc/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 63 KiB |
140
update/main.go
Normal file
140
update/main.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
var exe, _ = os.Executable()
|
||||
var Version = "master" // reset by cmd/main.go
|
||||
|
||||
// Asset is a github asset from release url.
|
||||
type Asset struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"browser_download_url"`
|
||||
}
|
||||
|
||||
// CheckLatestVersion check katenary latest version from release and propose to download it
|
||||
func CheckLatestVersion() (string, []Asset, error) {
|
||||
|
||||
githuburl := "https://api.github.com/repos/metal3d/katenary/releases/latest"
|
||||
// Create a HTTP client with 1s timeout
|
||||
client := &http.Client{
|
||||
Timeout: time.Second * 1,
|
||||
}
|
||||
// Create a request
|
||||
req, err := http.NewRequest("GET", githuburl, nil)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
// Send the request via a client
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Get tag_name from the json response
|
||||
var release = struct {
|
||||
TagName string `json:"tag_name"`
|
||||
Assets []Asset `json:"assets"`
|
||||
PreRelease bool `json:"prerelease"`
|
||||
}{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&release)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
// if it's a prerelease, don't update
|
||||
if release.PreRelease {
|
||||
return "", nil, errors.New("Prerelease detected, not updating")
|
||||
}
|
||||
|
||||
// no tag, don't update
|
||||
if release.TagName == "" {
|
||||
return "", nil, errors.New("No release found")
|
||||
}
|
||||
|
||||
// compare the current version, if the current version is the same or lower than the latest version, don't update
|
||||
versions := []string{Version, release.TagName}
|
||||
semver.Sort(versions)
|
||||
if versions[1] == Version {
|
||||
return "", nil, errors.New("Current version is the latest version")
|
||||
}
|
||||
|
||||
return release.TagName, release.Assets, nil
|
||||
}
|
||||
|
||||
// DownloadLatestVersion will download the latest version of katenary.
|
||||
func DownloadLatestVersion(assets []Asset) error {
|
||||
// Download the latest version
|
||||
fmt.Println("Downloading the latest version...")
|
||||
|
||||
// ok, replace this from the current version to the latest version
|
||||
err := os.Rename(exe, exe+".old")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Download the latest version for the current OS
|
||||
for _, asset := range assets {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
if asset.Name == "katenary.exe" {
|
||||
err = DownloadFile(asset.URL, exe)
|
||||
}
|
||||
case "linux":
|
||||
switch runtime.GOARCH {
|
||||
case "amd64":
|
||||
if asset.Name == "katenary-linux-amd64" {
|
||||
err = DownloadFile(asset.URL, exe)
|
||||
}
|
||||
case "arm64":
|
||||
if asset.Name == "katenary-linux-arm64" {
|
||||
err = DownloadFile(asset.URL, exe)
|
||||
}
|
||||
}
|
||||
case "darwin":
|
||||
if asset.Name == "katenary-darwin" {
|
||||
err = DownloadFile(asset.URL, exe)
|
||||
}
|
||||
default:
|
||||
fmt.Println("Unsupported OS")
|
||||
err = errors.New("Unsupported OS")
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
// remove the old version
|
||||
os.Remove(exe + ".old")
|
||||
} else {
|
||||
// restore the old version
|
||||
os.Rename(exe+".old", exe)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// DownloadFile will download a url to a local file. It also ensure that the file is executable.
|
||||
func DownloadFile(url, exe string) error {
|
||||
// Download the url binary to exe path
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
fp, err := os.OpenFile(exe, os.O_WRONLY|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fp.Close()
|
||||
_, err = io.Copy(fp, resp.Body)
|
||||
return err
|
||||
}
|
52
update/update_test.go
Normal file
52
update/update_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDownloadLatestRelease(t *testing.T) {
|
||||
|
||||
// Reset the version to test the latest release
|
||||
Version = "0.0.0"
|
||||
|
||||
// change "exe" to /tmp/test-katenary
|
||||
exe = "/tmp/test-katenary"
|
||||
defer os.Remove(exe)
|
||||
|
||||
// Now call the CheckLatestVersion function
|
||||
version, assets, err := CheckLatestVersion()
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Error: %s", err)
|
||||
}
|
||||
|
||||
fmt.Println("Version found", version)
|
||||
|
||||
// Touch exe binary
|
||||
f, _ := os.OpenFile(exe, os.O_RDONLY|os.O_CREATE, 0755)
|
||||
f.Write(nil)
|
||||
f.Close()
|
||||
|
||||
err = DownloadLatestVersion(assets)
|
||||
if err != nil {
|
||||
t.Errorf("Error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlreadyUpToDate(t *testing.T) {
|
||||
Version = "99999.999.99"
|
||||
exe = "/tmp/test-katenary"
|
||||
defer os.Remove(exe)
|
||||
|
||||
// Call the version check
|
||||
version, _, err := CheckLatestVersion()
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Error: %s", err)
|
||||
}
|
||||
|
||||
t.Log("Version is already the most recent", version)
|
||||
|
||||
}
|
Reference in New Issue
Block a user