11 Commits

Author SHA1 Message Date
8cf3ff9f73 Add local test directory 2022-05-24 11:17:46 +02:00
114fab4870 Fix the problem with environment as secret
We needed to filter the environment coming from a env file, but declared
as secet in `secret-vars` label

fix #17
2022-05-23 12:11:23 +02:00
5fc8c06d8b Layout again 2022-05-23 11:24:06 +02:00
51ca4e81d1 Layout... 2022-05-23 11:23:27 +02:00
a6f6b91ab9 Layout 2022-05-23 11:20:52 +02:00
63afc3066b Fix problems with local volumes to configmap
- make it possible to mount only one file as configmap
- remove "dots" from path to convert it to volume name
- see #11 that can be resolved
2022-05-23 11:13:42 +02:00
adrian-salas
1e8fd44857 ISSUE-12 - Trim space on port label (#14)
Co-authored-by: Adrian SALAS <adrian.salas@smile.fr>

fix #12
2022-05-22 22:53:05 +02:00
adrian-salas
1403f9b198 ISSUE-13 - Ignore comment prefix in envfiles (#15)
Co-authored-by: Adrian SALAS <adrian.salas@smile.fr>

fix #13
2022-05-22 22:52:07 +02:00
dependabot[bot]
22ef9211ec Bump github.com/compose-spec/compose-go from 1.2.4 to 1.2.5 (#10)
Bumps [github.com/compose-spec/compose-go](https://github.com/compose-spec/compose-go) from 1.2.4 to 1.2.5.
- [Release notes](https://github.com/compose-spec/compose-go/releases)
- [Commits](https://github.com/compose-spec/compose-go/compare/v1.2.4...v1.2.5)

---
updated-dependencies:
- dependency-name: github.com/compose-spec/compose-go
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-18 14:20:03 +02:00
418a0a8029 Use compose-go + improvements (#9)
Use compose-go https://github.com/compose-spec/compose-go  to make Katenary parsing compose file the official way.
Add labels:
- `volume-from` (with `same-pod`) to avoid volume repetition
- `ignore` to ignore a service
- `mapenv` (replaces the `env-to-service`) to map environment to helm variable (as a template string)
- `secret-vars` declares variables as secret values

More:
- Now, environment (as secret vars) are set in values.yaml
- Ingress has got annotations in values.yaml
- Probes (liveness probe) are improved
- fixed code to optimize
- many others fixes about path, bad volume check, refactorisation, tests...
2022-05-08 09:55:25 +02:00
165054ca53 For single env_file, it's a string...
see #8
2022-04-01 17:49:10 +02:00
43 changed files with 1847 additions and 1128 deletions

3
.gitignore vendored
View File

@@ -2,9 +2,10 @@ dist/*
.cache/*
chart/*
docker-compose.yaml
katenary
./katenary
*.env
docker-compose*
!examples/**/docker-compose*
.credentials
release.id
configs/

View File

@@ -6,7 +6,7 @@ PREFIX=~/.local
GO=container
OUT=katenary
BLD_CMD=go build -ldflags="-X 'main.Version=$(VERSION)'" -o $(OUT) ./cmd/*.go
BLD_CMD=go build -ldflags="-X 'main.Version=$(VERSION)'" -o $(OUT) ./cmd/katenary/*.go
GOOS=linux
GOARCH=amd64
@@ -106,10 +106,10 @@ 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 docker.io/golang $(BLD_CMD)
--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 docker.io/golang $(BLD_CMD)
--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"

View File

@@ -1,5 +1,5 @@
<div style="text-align:center">
<img src="./misc/logo.png" alt="Katenary Logo" />
<div style="text-align:center; margin: auto" align="center">
<img src="./misc/logo.png" alt="Katenary Logo" style="max-width: 90%" align="center"/>
</div>
Katenary is a tool to help transforming `docker-compose` files to a working Helm Chart for Kubernetes.
@@ -8,7 +8,7 @@ Katenary is a tool to help transforming `docker-compose` files to a working Helm
This project is partially made at [Smile](https://www.smile.eu)
<div style="text-align:center">
<div style="text-align:center" align="center">
<a href="https://www.smile.eu"><img src="./misc/Logo_Smile.png" alt="Smile Logo" width="250" /></a>
</div>
@@ -16,6 +16,7 @@ This project is partially made at [Smile](https://www.smile.eu)
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:
@@ -125,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/env-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:
@@ -144,21 +145,27 @@ 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
@@ -166,10 +173,12 @@ services:
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/env-to-service : specifies that the environment variable points on a service name (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)

View File

@@ -65,18 +65,21 @@ func main() {
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, force)
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(

View File

@@ -12,11 +12,12 @@ import (
)
var (
composeFiles = []string{"docker-compose.yaml", "docker-compose.yml"}
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() {
@@ -92,12 +93,12 @@ func detectGitVersion() (string, error) {
return defaulVersion, errors.New("git log failed")
}
func Convert(composeFile, appVersion, appName, chartDir string, force bool) {
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)
_, err := os.Stat(composeFile)
if err != nil {
fmt.Println("No compose file found")
os.Exit(1)
@@ -138,6 +139,6 @@ func Convert(composeFile, appVersion, appName, chartDir string, force bool) {
}
// start generator
generator.Generate(p, Version, appName, appVersion, ComposeFile, dirname)
generator.Generate(p, Version, appName, appVersion, chartVersion, ComposeFile, dirname)
}

View File

@@ -1,14 +1,13 @@
package compose
import (
"fmt"
"katenary/helm"
"io/ioutil"
"log"
"os"
"strings"
"path/filepath"
"github.com/google/shlex"
"gopkg.in/yaml.v3"
"github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/types"
)
const (
@@ -17,230 +16,76 @@ const (
// 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. 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 {
c := NewCompose()
if filename != "" {
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)
}
dec := yaml.NewDecoder(f)
err = dec.Decode(c)
if err != nil {
log.Fatal(err)
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
}
}
} else {
dec := yaml.NewDecoder(strings.NewReader(content[0]))
err := dec.Decode(c)
if err != nil {
log.Fatal(err)
// add the file at first position
if !found {
cli.DefaultFileNames = append([]string{filename}, cli.DefaultFileNames...)
}
}
p := &Parser{Data: c}
return p
}
// Parse using compose-go parser, adapt a bit the Project and set Appname.
func (p *Parser) Parse(appname string) {
Appname = appname
services := make(map[string][]string)
// get the service list, to be sure that everything is ok
// Reminder:
// - set Appname
// - loas services
// fix ugly types
for _, s := range p.Data.Services {
parseEnv(s)
parseCommand(s)
parseEnvFiles(s)
parseHealthCheck(s)
options, err := cli.NewProjectOptions(nil,
cli.WithDefaultConfigPath,
cli.WithNormalization(true),
cli.WithInterpolation(true),
cli.WithResolvedPaths(true),
)
if err != nil {
log.Fatal(err)
}
c := p.Data
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
}
}
if !portexists {
s.Ports = append(s.Ports, serviceport)
}
}
}
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"))
}
// check if all "image" properties are set
missing = []string{}
for name, s := range c.Services {
if s.Image == "" {
missing = append(missing, fmt.Sprintf(
"The service \"%s\" hasn't got "+
"an image property - please "+
"append an image property in the docker-compose file",
name,
))
}
}
if len(missing) > 0 {
log.Fatal(strings.Join(missing, "\n"))
}
// check the build element
for name, s := range c.Services {
if s.RawBuild == nil {
continue
}
fmt.Println(ICON_EXCLAMATION +
" \x1b[33myou will need to build and push your image named \"" + s.Image + "\"" +
" for the \"" + name + "\" service \x1b[0m")
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
}
// manage environment variables, if the type is map[string]string so we can use it, else we need to split "=" sign
// and apply this in env variable
func parseEnv(s *Service) {
env := make(map[string]string)
if s.RawEnvironment == nil {
return
}
switch s.RawEnvironment.(type) {
case map[string]string:
env = s.RawEnvironment.(map[string]string)
case map[string]interface{}:
for k, v := range s.RawEnvironment.(map[string]interface{}) {
// force to string
env[k] = fmt.Sprintf("%v", v)
}
case []interface{}:
for _, v := range s.RawEnvironment.([]interface{}) {
// Splot the value of the env variable with "="
parts := strings.Split(v.(string), "=")
env[parts[0]] = parts[1]
}
case string:
parts := strings.Split(s.RawEnvironment.(string), "=")
env[parts[0]] = parts[1]
default:
log.Printf("%+v, %T", s.RawEnvironment, s.RawEnvironment)
log.Fatal("Environment type not supported")
}
s.Environment = env
}
func parseCommand(s *Service) {
if s.RawCommand == nil {
return
}
// following the command type, it can be a "slice" or a simple sting, so we need to check it
switch v := s.RawCommand.(type) {
case string:
// use shlex to parse the command
command, err := shlex.Split(v)
if err != nil {
log.Fatal(err)
}
s.Command = command
case []string:
s.Command = v
case []interface{}:
for _, v := range v {
s.Command = append(s.Command, v.(string))
}
default:
log.Printf("%+v %T", s.RawCommand, s.RawCommand)
log.Fatal("Command type not supported")
}
}
func parseEnvFiles(s *Service) {
// Same than parseEnv, but for env files
if s.RawEnvFiles == nil {
return
}
envfiles := make([]string, 0)
switch v := s.RawEnvFiles.(type) {
case []string:
envfiles = v
case []interface{}:
for _, v := range v {
envfiles = append(envfiles, v.(string))
}
default:
log.Printf("%+v %T", s.RawEnvFiles, s.RawEnvFiles)
log.Fatal("EnvFile type not supported")
}
s.EnvFiles = envfiles
}
func parseHealthCheck(s *Service) {
// HealthCheck command can be a string or slice of strings
if s.HealthCheck == nil {
return
}
if s.HealthCheck.RawTest == nil {
return
}
switch v := s.HealthCheck.RawTest.(type) {
case string:
c, err := shlex.Split(v)
if err != nil {
log.Fatal(err)
}
s.HealthCheck = &HealthCheck{
Test: c,
}
case []string:
s.HealthCheck = &HealthCheck{
Test: v,
}
case []interface{}:
for _, v := range v {
s.HealthCheck.Test = append(s.HealthCheck.Test, v.(string))
}
default:
log.Printf("%+v %T", s.HealthCheck.RawTest, s.HealthCheck.RawTest)
log.Fatal("HealthCheck type not supported")
}
func GetCurrentDir() string {
return CURRENT_DIR
}

View File

@@ -1,199 +0,0 @@
package compose
import (
"katenary/logger"
"testing"
)
const DOCKER_COMPOSE_YML1 = `
version: "3"
services:
# first service, very basic
web:
image: nginx
ports:
- "80:80"
environment:
FOO: bar
BAZ: qux
networks:
- frontend
database:
image: postgres
networks:
- frontend
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: mysecretpassword
POSTGRES_DB: mydb
labels:
katenary.io/ports: "5432"
commander1:
image: foo
command: ["/bin/sh", "-c", "echo 'hello world'"]
commander2:
image: foo
command: echo "hello world"
hc1:
image: foo
healthcheck:
test: ["CMD-SHELL", "echo 'hello world1'"]
hc2:
image: foo
healthcheck:
test: echo "hello world2"
hc3:
image: foo
healthcheck:
test: ["CMD", "echo 'hello world3'"]
`
func init() {
logger.NOLOG = true
}
func TestParser(t *testing.T) {
p := NewParser("", DOCKER_COMPOSE_YML1)
p.Parse("test")
// check if the "web" and "database" service is parsed correctly
// by checking if the "ports" and "environment"
for name, service := range p.Data.Services {
if name == "web" {
if len(service.Ports) != 1 {
t.Errorf("Expected 1 port, got %d", len(service.Ports))
}
if service.Ports[0] != "80:80" {
t.Errorf("Expected port 80:80, got %s", service.Ports[0])
}
if len(service.Environment) != 2 {
t.Errorf("Expected 2 environment variables, got %d", len(service.Environment))
}
if service.Environment["FOO"] != "bar" {
t.Errorf("Expected FOO=bar, got %s", service.Environment["FOO"])
}
if service.Environment["BAZ"] != "qux" {
t.Errorf("Expected BAZ=qux, got %s", service.Environment["BAZ"])
}
}
// same for the "database" service
if name == "database" {
if len(service.Ports) != 1 {
t.Errorf("Expected 1 port, got %d", len(service.Ports))
}
if service.Ports[0] != "5432" {
t.Errorf("Expected port 5432, got %s", service.Ports[0])
}
if len(service.Environment) != 3 {
t.Errorf("Expected 3 environment variables, got %d", len(service.Environment))
}
if service.Environment["POSTGRES_USER"] != "postgres" {
t.Errorf("Expected POSTGRES_USER=postgres, got %s", service.Environment["POSTGRES_USER"])
}
if service.Environment["POSTGRES_PASSWORD"] != "mysecretpassword" {
t.Errorf("Expected POSTGRES_PASSWORD=mysecretpassword, got %s", service.Environment["POSTGRES_PASSWORD"])
}
if service.Environment["POSTGRES_DB"] != "mydb" {
t.Errorf("Expected POSTGRES_DB=mydb, got %s", service.Environment["POSTGRES_DB"])
}
// check labels
if len(service.Labels) != 1 {
t.Errorf("Expected 1 label, got %d", len(service.Labels))
}
// is label katenary.io/ports correct?
if service.Labels["katenary.io/ports"] != "5432" {
t.Errorf("Expected katenary.io/ports=5432, got %s", service.Labels["katenary.io/ports"])
}
}
}
}
func TestParseCommand(t *testing.T) {
p := NewParser("", DOCKER_COMPOSE_YML1)
p.Parse("test")
for name, s := range p.Data.Services {
if name == "commander1" {
t.Log(s.Command)
if len(s.Command) != 3 {
t.Errorf("Expected 3 command, got %d", len(s.Command))
}
if s.Command[0] != "/bin/sh" {
t.Errorf("Expected /bin/sh, got %s", s.Command[0])
}
if s.Command[1] != "-c" {
t.Errorf("Expected -c, got %s", s.Command[1])
}
if s.Command[2] != "echo 'hello world'" {
t.Errorf("Expected echo 'hello world', got %s", s.Command[2])
}
}
if name == "commander2" {
t.Log(s.Command)
if len(s.Command) != 2 {
t.Errorf("Expected 1 command, got %d", len(s.Command))
}
if s.Command[0] != "echo" {
t.Errorf("Expected echo, got %s", s.Command[0])
}
if s.Command[1] != "hello world" {
t.Errorf("Expected hello world, got %s", s.Command[1])
}
}
}
}
func TestHealthChecks(t *testing.T) {
p := NewParser("", DOCKER_COMPOSE_YML1)
p.Parse("test")
for name, s := range p.Data.Services {
if name != "hc1" && name != "hc2" && name != "hc3" {
continue
}
if name == "hc1" {
if len(s.HealthCheck.Test) != 2 {
t.Errorf("Expected 2 healthcheck tests, got %d", len(s.HealthCheck.Test))
}
if s.HealthCheck.Test[0] != "CMD-SHELL" {
t.Errorf("Expected CMD-SHELL, got %s", s.HealthCheck.Test[0])
}
if s.HealthCheck.Test[1] != "echo 'hello world1'" {
t.Errorf("Expected echo 'hello world1', got %s", s.HealthCheck.Test[1])
}
}
if name == "hc2" {
if len(s.HealthCheck.Test) != 2 {
t.Errorf("Expected 2 healthcheck tests, got %d", len(s.HealthCheck.Test))
}
if s.HealthCheck.Test[0] != "echo" {
t.Errorf("Expected echo, got %s", s.HealthCheck.Test[1])
}
if s.HealthCheck.Test[1] != "hello world2" {
t.Errorf("Expected echo 'hello world2', got %s", s.HealthCheck.Test[1])
}
}
if name == "hc3" {
if len(s.HealthCheck.Test) != 2 {
t.Errorf("Expected 2 healthcheck tests, got %d", len(s.HealthCheck.Test))
}
if s.HealthCheck.Test[0] != "CMD" {
t.Errorf("Expected CMD, got %s", s.HealthCheck.Test[0])
}
if s.HealthCheck.Test[1] != "echo 'hello world3'" {
t.Errorf("Expected echo 'hello world3', got %s", s.HealthCheck.Test[1])
}
}
}
}

View File

@@ -1,44 +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
}
// HealthCheck manage generic type to handle TCP, HTTP and TCP health check.
type HealthCheck struct {
Test []string `yaml:"-"`
RawTest interface{} `yaml:"test"`
Interval string `yaml:"interval"`
Timeout string `yaml:"timeout"`
Retries int `yaml:"retries"`
StartPeriod string `yaml:"start_period"`
}
// 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:"-"`
RawEnvironment interface{} `yaml:"environment"`
Labels map[string]string `yaml:"labels"`
DependsOn []string `yaml:"depends_on"`
Volumes []string `yaml:"volumes"`
Expose []int `yaml:"expose"`
EnvFiles []string `yaml:"-"`
RawEnvFiles interface{} `yaml:"env_file"`
RawBuild interface{} `yaml:"build"`
HealthCheck *HealthCheck `yaml:"healthcheck"`
Command []string `yaml:"-"`
RawCommand interface{} `yaml:"command"`
}

View File

@@ -5,6 +5,6 @@ This is a basic example of what can do Katenary with standard docker-compose fil
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
- 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.

View File

@@ -1,7 +1,8 @@
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:
@@ -12,7 +13,8 @@ services:
# expose an ingress
katenary.io/ingress: 80
# DB_HOST is actually a service name
katenary.io/env-to-service: DB_HOST
katenary.io/mapenv: |
DB_HOST: {{ .Release.Name }}-database
depends_on:
- database

9
examples/ghost/README.md Normal file
View 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.

View 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

View 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 }}

View 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 }}

View 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 -}}

View 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

View File

@@ -0,0 +1,6 @@
blog:
image: ghost
ingress:
class: nginx
enabled: false
host: blog.ghost.tld

View 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

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,8 @@ import (
"path/filepath"
"strings"
"testing"
"github.com/compose-spec/compose-go/cli"
)
const DOCKER_COMPOSE_YML = `version: '3'
@@ -48,7 +50,8 @@ services:
ANOTHER_ENV_VAR: another_value
DB_HOST: database
labels:
katenary.io/env-to-service: DB_HOST
katenary.io/mapenv: |
DB_HOST: {{ .Release.Name }}-database
database:
image: mysql:5.7
@@ -87,37 +90,85 @@ services:
- SOME_ENV_VAR=some_value
- ANOTHER_ENV_VAR=another_value
# use environment file
useenvfile:
image: nginx
env_file:
- config/env
volumes:
data:
driver: local
`
var defaultCliFiles = cli.DefaultFileNames
var TMP_DIR = ""
var TMPWORK_DIR = ""
func init() {
logger.NOLOG = true
logger.NOLOG = len(os.Getenv("NOLOG")) < 1
}
func setUp(t *testing.T) (string, *compose.Parser) {
p := compose.NewParser("", DOCKER_COMPOSE_YML)
p.Parse("testapp")
// cleanup "made" files
helm.ResetMadePVC()
cli.DefaultFileNames = defaultCliFiles
// create a temporary directory
tmp, err := os.MkdirTemp(os.TempDir(), "katenary-test")
t.Log("Generated ", tmp, "directory")
tmp, err := os.MkdirTemp(os.TempDir(), "katenary-test-")
if err != nil {
t.Fatal(err)
}
Generate(p, "test-0", "testapp", "1.2.3", DOCKER_COMPOSE_YML, tmp)
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 os.RemoveAll(tmp)
defer tearDown()
for name := range p.Data.Services {
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
@@ -159,9 +210,10 @@ func TestCommand(t *testing.T) {
// Check if environment is correctly set.
func TestEnvs(t *testing.T) {
tmp, p := setUp(t)
defer os.RemoveAll(tmp)
defer tearDown()
for name := range p.Data.Services {
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'
@@ -173,20 +225,20 @@ func TestEnvs(t *testing.T) {
lines, _ := ioutil.ReadAll(fp)
next := false
for _, line := range strings.Split(string(lines), "\n") {
if strings.Contains(line, "DB_HOST") {
if !next && strings.Contains(line, "name: DB_HOST") {
next = true
continue
}
if next {
} else if next && strings.Contains(line, "value:") {
matched = true
if !strings.Contains(line, helm.RELEASE_NAME+"-database") {
t.Error("DB_HOST variable should be set to " + helm.RELEASE_NAME + "-database")
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))
}
}
}
@@ -195,9 +247,10 @@ func TestEnvs(t *testing.T) {
// Check if the same pod is not deployed twice.
func TestSamePod(t *testing.T) {
tmp, p := setUp(t)
defer os.RemoveAll(tmp)
defer tearDown()
for name, service := range p.Data.Services {
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 {
@@ -220,9 +273,10 @@ func TestSamePod(t *testing.T) {
// Check if the ports are correctly set.
func TestPorts(t *testing.T) {
tmp, p := setUp(t)
defer os.RemoveAll(tmp)
defer tearDown()
for name, service := range p.Data.Services {
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
@@ -238,7 +292,7 @@ func TestPorts(t *testing.T) {
t.Log("Checking ", name, " service file")
_, err := os.Stat(path)
if err != nil {
t.Fatal(err)
t.Error(err)
}
}
}
@@ -247,9 +301,10 @@ func TestPorts(t *testing.T) {
// Check if the volumes are correctly set.
func TestPVC(t *testing.T) {
tmp, p := setUp(t)
defer os.RemoveAll(tmp)
defer tearDown()
for name := range p.Data.Services {
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)
@@ -258,6 +313,8 @@ func TestPVC(t *testing.T) {
t.Log("Checking ", name, " pvc file")
_, err := os.Stat(path)
if err != nil {
list, _ := filepath.Glob(tmp + "/templates/*")
t.Log(list)
t.Fatal(err)
}
}
@@ -267,9 +324,10 @@ func TestPVC(t *testing.T) {
//Check if web service has got a ingress.
func TestIngress(t *testing.T) {
tmp, p := setUp(t)
defer os.RemoveAll(tmp)
defer tearDown()
for name := range p.Data.Services {
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)
@@ -287,9 +345,10 @@ func TestIngress(t *testing.T) {
// Check unmapped volumes
func TestUnmappedVolumes(t *testing.T) {
tmp, p := setUp(t)
defer os.RemoveAll(tmp)
defer tearDown()
for name := range p.Data.Services {
for _, service := range p.Data.Services {
name := service.Name
if name == "novol" {
path := filepath.Join(tmp, "templates", name+".deployment.yaml")
fp, _ := os.Open(path)
@@ -307,10 +366,11 @@ func TestUnmappedVolumes(t *testing.T) {
// Check if service using equal sign for environment works
func TestEqualSignOnEnv(t *testing.T) {
tmp, p := setUp(t)
defer os.RemoveAll(tmp)
defer tearDown()
// if the name is eqenv, the service should habe environment
for name, _ := range p.Data.Services {
for _, service := range p.Data.Services {
name := service.Name
if name == "eqenv" {
path := filepath.Join(tmp, "templates", name+".deployment.yaml")
fp, _ := os.Open(path)
@@ -328,8 +388,9 @@ func TestEqualSignOnEnv(t *testing.T) {
match++
}
}
if match != 2 {
if match != 4 { // because the value points on .Values...
t.Error("eqenv service should have 2 environment variables")
t.Log(string(lines))
}
}
}

22
generator/utils.go Normal file
View File

@@ -0,0 +1,22 @@
package generator
import (
"katenary/compose"
"regexp"
"strings"
)
// replaceChars replaces some chars in a string.
const replaceChars = `[^a-zA-Z0-9_]+`
// GetRelPath return the relative path from the root of the project.
func GetRelPath(path string) string {
return strings.Replace(path, compose.GetCurrentDir(), ".", 1)
}
// 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
}

14
generator/utils_test.go Normal file
View File

@@ -0,0 +1,14 @@
package generator
import (
"katenary/compose"
"testing"
)
func Test_PathToName(t *testing.T) {
path := compose.GetCurrentDir() + "/envéfoo.file"
name := PathToName(path)
if name != "env-foo-file" {
t.Error("Expected env-foo-file, got ", name)
}
}

View File

@@ -4,18 +4,42 @@ 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 Generate(p *compose.Parser, katernayVersion, appName, appVersion, composeFile, dirName string) {
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
@@ -25,64 +49,109 @@ func Generate(p *compose.Parser, katernayVersion, appName, appVersion, composeFi
// try to create the directory
err := os.MkdirAll(templatesDir, 0755)
if err != nil {
panic(err)
log.Fatal(err)
}
files := make(map[string]chan interface{})
generators := make(map[string]HelmFileGenerator)
// list avoided services
avoids := make(map[string]bool)
for n, service := range p.Data.Services {
if _, ok := service.Labels[helm.LABEL_SAMEPOD]; ok {
avoids[n] = true
// 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 name, s := range p.Data.Services {
for i, service := range p.Data.Services {
n := service.Name
// Manage emptyDir volumes
if empty, ok := s.Labels[helm.LABEL_EMPTYDIRS]; ok {
//split empty list by coma
emptyDirs := strings.Split(empty, ",")
//append them in EmptyDirs
EmptyDirs = append(EmptyDirs, emptyDirs...)
}
// fetch corresponding service in "links"
linked := make(map[string]*compose.Service, 0)
// find service linked to this one
for n, service := range p.Data.Services {
if _, ok := service.Labels[helm.LABEL_SAMEPOD]; ok {
if service.Labels[helm.LABEL_SAMEPOD] == name {
linked[n] = service
// 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, ",") {
port = strings.TrimSpace(port)
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
}
}
if _, found := avoids[name]; found {
// 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
}
files[name] = CreateReplicaObject(name, s, linked)
// 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, f := range files {
for c := range f {
if c == nil {
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 := c.(helm.Kinded).Get()
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
c.(helm.Signable).BuildSHA(composeFile)
helmFile.(helm.Signable).BuildSHA(composeFile)
// Some types need special fixes in yaml generation
switch c := c.(type) {
switch c := helmFile.(type) {
case *helm.Storage:
// For storage, we need to add a "condition" to activate it
writers.BuildStorage(c, n, templatesDir)
@@ -104,45 +173,60 @@ func Generate(p *compose.Parser, katernayVersion, appName, appVersion, composeFi
case *helm.ConfigMap, *helm.Secret:
// there could be several files, so let's force the filename
name := c.(helm.Named).Name()
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, _ := os.Create(fname)
fp, err := os.Create(fname)
if err != nil {
log.Fatal(err)
}
defer fp.Close()
enc := yaml.NewEncoder(fp)
enc.SetIndent(2)
enc.SetIndent(writers.IndentSize)
enc.Encode(c)
fp.Close()
}
}
}
// Create the values.yaml file
fp, _ := os.Create(filepath.Join(dirName, "values.yaml"))
enc := yaml.NewEncoder(fp)
enc.SetIndent(2)
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)
fp.Close()
// Create tht Chart.yaml file
fp, _ = os.Create(filepath.Join(dirName, "Chart.yaml"))
fp.WriteString(`# Create on ` + time.Now().Format(time.RFC3339) + "\n")
fp.WriteString(`# Katenary command line: ` + strings.Join(os.Args, " ") + "\n")
enc = yaml.NewEncoder(fp)
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": "0.1.0",
"version": chartVersion,
"appVersion": appVersion,
})
fp.Close()
// And finally, create a NOTE.txt file
fp, _ = os.Create(filepath.Join(templatesDir, "NOTES.txt"))
fp.WriteString(helm.GenerateNotesFile(ingresses))
fp.Close()
noteFile, err := os.Create(filepath.Join(templatesDir, "NOTES.txt"))
if err != nil {
log.Fatal(err)
}
defer noteFile.Close()
noteFile.WriteString(helm.GenerateNotesFile(ingresses))
}

View File

@@ -7,8 +7,9 @@ import (
"gopkg.in/yaml.v3"
)
// BuildConfigMap writes the configMap.
func BuildConfigMap(c interface{}, kind, servicename, name, templatesDir string) {
fname := filepath.Join(templatesDir, servicename+"."+name+"."+kind+".yaml")
fname := filepath.Join(templatesDir, name+"."+kind+".yaml")
fp, _ := os.Create(fname)
enc := yaml.NewEncoder(fp)
enc.SetIndent(IndentSize)

View File

@@ -10,6 +10,7 @@ import (
"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")

View File

@@ -10,9 +10,20 @@ import (
"gopkg.in/yaml.v3"
)
const classAndVersionCondition = `{{- if and .Values.__name__.ingress.class (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}` + "\n"
const versionCondition = `{{- if semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion }}` + "\n"
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 {
@@ -32,22 +43,49 @@ func BuildIngress(ingress *helm.Ingress, name, templatesDir string) {
enc.Encode(ingress)
buffer.WriteString("{{- end -}}")
fp, _ := os.Create(fname)
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.18-0 version or higher
// 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) + versionCondition + l
l = strings.Repeat(" ", n) + versionCondition119 + l
}
if strings.Contains(l, "serviceName:") || strings.Contains(l, "servicePort:") {
n := CountSpaces(l)
@@ -58,9 +96,6 @@ func BuildIngress(ingress *helm.Ingress, name, templatesDir string) {
}
backendHit = true
}
fp.WriteString(l + "\n")
}
fp.Close()
}

View File

@@ -8,6 +8,7 @@ import (
"gopkg.in/yaml.v3"
)
// BuildService writes the service (external or not).
func BuildService(service *helm.Service, name, templatesDir string) {
kind := "service"
suffix := ""

View File

@@ -2,23 +2,31 @@ 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, _ := os.Create(fname)
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)
enc.Encode(storage)
if err := enc.Encode(storage); err != nil {
log.Fatal(err)
}
fp.WriteString("{{- end -}}")
}

View File

@@ -1,6 +1,6 @@
package writers
// IndentSize set the indentation size for yaml output.
// 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.

5
go.mod
View File

@@ -3,10 +3,13 @@ module katenary
go 1.16
require (
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/compose-spec/compose-go v1.2.5
github.com/distribution/distribution/v3 v3.0.0-20220505155552-985711c1f414 // indirect
github.com/kr/pretty v0.2.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
)

271
go.sum
View File

@@ -1,34 +1,301 @@
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.5 h1:yGaACRJrWTQMFW89/ck2o1qd3gIxAji4EQkgewH3hqc=
github.com/compose-spec/compose-go v1.2.5/go.mod h1:a/H2RY5UejNRhzbNXHygSAxBARTpLmLlaCkh58xfStM=
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/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
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-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.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.2.0 h1:I0DwBVMGAx26dttAj1BtJLAkVGncrkkUXfJLC4Flt/I=
gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

150
helm/configAndSecretMap.go Normal file
View File

@@ -0,0 +1,150 @@
package helm
import (
"errors"
"fmt"
"io/ioutil"
"strings"
)
// InlineConfig is made to represent a configMap or a secret
type InlineConfig interface {
AddEnvFile(filename string, filter []string) error
AddEnv(key, val string) error
Metadata() *Metadata
}
var _ InlineConfig = (*ConfigMap)(nil)
var _ InlineConfig = (*Secret)(nil)
// ConfigMap is made to represent a configMap with data.
type ConfigMap struct {
*K8sBase `yaml:",inline"`
Data map[string]string `yaml:"data"`
}
// NewConfigMap returns a new initialzed ConfigMap.
func NewConfigMap(name, path string) *ConfigMap {
base := NewBase()
base.ApiVersion = "v1"
base.Kind = "ConfigMap"
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, filter []string) error {
content, err := ioutil.ReadFile(file)
if err != nil {
return err
}
lines := strings.Split(string(content), "\n")
for _, l := range lines {
//Check if the line is a comment
l = strings.TrimSpace(l)
isComment := strings.HasPrefix(l, "#")
if len(l) == 0 || isComment {
continue
}
parts := strings.SplitN(l, "=", 2)
if len(parts) < 2 {
return errors.New("The environment file " + file + " is not valid")
}
var skip bool
for _, filterEnv := range filter {
if parts[0] == filterEnv {
skip = true
}
}
if !skip {
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"`
}
// NewSecret returns a new initialzed Secret.
func NewSecret(name, path string) *Secret {
base := NewBase()
base.ApiVersion = "v1"
base.Kind = "Secret"
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, filter []string) error {
content, err := ioutil.ReadFile(file)
if err != nil {
return err
}
lines := strings.Split(string(content), "\n")
for _, l := range lines {
l = strings.TrimSpace(l)
isComment := strings.HasPrefix(l, "#")
if len(l) == 0 || isComment {
continue
}
parts := strings.SplitN(l, "=", 2)
if len(parts) < 2 {
return errors.New("The environment file " + file + " is not valid")
}
var skip bool
for _, filterEnv := range filter {
if parts[0] == filterEnv {
skip = true
}
}
if !skip {
s.Data[parts[0]] = fmt.Sprintf(`{{ "%s" | b64enc }}`, parts[1])
}
}
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
}

View File

@@ -1,101 +0,0 @@
package helm
import (
"errors"
"fmt"
"io/ioutil"
"strings"
)
// InlineConfig is made to represent a configMap or a secret
type InlineConfig interface {
AddEnvFile(filename string) error
Metadata() *Metadata
}
type ConfigMap struct {
*K8sBase `yaml:",inline"`
Data map[string]string `yaml:"data"`
}
func NewConfigMap(name string) *ConfigMap {
base := NewBase()
base.ApiVersion = "v1"
base.Kind = "ConfigMap"
base.Metadata.Name = RELEASE_NAME + "-" + name
base.Metadata.Labels[K+"/component"] = name
return &ConfigMap{
K8sBase: base,
Data: make(map[string]string),
}
}
func (c *ConfigMap) Metadata() *Metadata {
return c.K8sBase.Metadata
}
func (c *ConfigMap) AddEnvFile(file string) error {
content, err := ioutil.ReadFile(file)
if err != nil {
return err
}
lines := strings.Split(string(content), "\n")
for _, l := range lines {
l = strings.TrimSpace(l)
if len(l) == 0 {
continue
}
parts := strings.SplitN(l, "=", 2)
if len(parts) < 2 {
return errors.New("The environment file " + file + " is not valid")
}
c.Data[parts[0]] = parts[1]
}
return nil
}
type Secret struct {
*K8sBase `yaml:",inline"`
Data map[string]string `yaml:"data"`
}
func NewSecret(name string) *Secret {
base := NewBase()
base.ApiVersion = "v1"
base.Kind = "Secret"
base.Metadata.Name = RELEASE_NAME + "-" + name
base.Metadata.Labels[K+"/component"] = name
return &Secret{
K8sBase: base,
Data: make(map[string]string),
}
}
func (s *Secret) AddEnvFile(file string) error {
content, err := ioutil.ReadFile(file)
if err != nil {
return err
}
lines := strings.Split(string(content), "\n")
for _, l := range lines {
l = strings.TrimSpace(l)
if len(l) == 0 {
continue
}
parts := strings.SplitN(l, "=", 2)
if len(parts) < 2 {
return errors.New("The environment file " + file + " is not valid")
}
s.Data[parts[0]] = fmt.Sprintf(`{{ "%s" | b64enc }}`, parts[1])
}
return nil
}
func (s *Secret) Metadata() *Metadata {
return s.K8sBase.Metadata
}

65
helm/container.go Normal file
View 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
}

View File

@@ -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,86 +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"`
LivenessProbe *Probe `yaml:"livenessProbe,omitempty"`
}
type HttpGet struct {
Path string `yaml:"path"`
Port int `yaml:"port"`
}
type Exec struct {
Command []string `yaml:"command"`
}
type TCP struct {
Port int `yaml:"port"`
}
type Probe struct {
HttpGet *HttpGet `yaml:"httpGet,omitempty"`
Exec *Exec `yaml:"exec,omitempty"`
TCP *TCP `yaml:"tcp,omitempty"`
Period int `yaml:"periodSeconds"`
Success int `yaml:"successThreshold"`
Failure int `yaml:"failureThreshold"`
InitialDelay int `yaml:"initialDelaySeconds"`
}
func NewProbe(period, initialDelaySeconds, success, failure int) *Probe {
return &Probe{
Period: period,
Success: success,
Failure: failure,
InitialDelay: initialDelaySeconds,
}
}
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"`

View File

@@ -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

73
helm/k8sbase.go Normal file
View 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
View 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()
}

View File

@@ -10,6 +10,7 @@ Your application is now deployed. This may take a while to be up and responding.
__list__
`
// 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
View 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"`
}

View File

@@ -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),

View File

@@ -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"`

View File

@@ -1,122 +1,36 @@
package helm
import (
"bytes"
"crypto/sha1"
"fmt"
"io/ioutil"
"os"
"strings"
"text/template"
)
const K = "katenary.io"
const RELEASE_NAME = "{{ .Release.Name }}"
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"
LABEL_HEALTHCHECK = K + "/healthcheck"
LABEL_SAMEPOD = K + "/same-pod"
LABEL_EMPTYDIRS = K + "/empty-dirs"
)
func GetLabelsDocumentation() string {
t, _ := template.New("labels").Parse(`
# Labels
{{.LABEL_ENV_SECRET | printf "%-33s"}}: set the given file names as a secret instead of configmap
{{.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_ENV_SERVICE | printf "%-33s"}}: specifies that the environment variable points on a service name (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_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_ENV_SERVICE": LABEL_ENV_SERVICE,
"LABEL_PORT": LABEL_PORT,
"LABEL_INGRESS": LABEL_INGRESS,
"LABEL_VOL_CM": LABEL_VOL_CM,
"LABEL_HEALTHCHECK": LABEL_HEALTHCHECK,
"LABEL_SAMEPOD": LABEL_SAMEPOD,
"LABEL_EMPTYDIRS": LABEL_EMPTYDIRS,
})
return buff.String()
}
var (
Appname = ""
Appname = "" // set at runtime
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(),
}
// add some information of the build
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)
sum := sha1.Sum(c)
k.Metadata.Annotations[K+"/docker-compose-sha1"] = 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