Compare commits
395 Commits
0.2.0-alph
...
d1f2957512
Author | SHA1 | Date | |
---|---|---|---|
d1f2957512 | |||
95609a3092 | |||
8e54d611eb | |||
ef1ff2737f | |||
d34f97fa5d | |||
c85e61f5d0 | |||
f1c31b0eeb | |||
677b0d7b3c | |||
f539be8f52 | |||
4336490a72 | |||
9f75f874d1 | |||
90eda75219 | |||
b13e922966 | |||
0d36a48f93 | |||
60016b8c3e | |||
07263520b6 | |||
0f3812b666 | |||
50a9d0ddde | |||
f5d3005c3b | |||
76b3b83d07 | |||
14ca5bf0ea | |||
d1768e5742 | |||
0fcf0f60e8 | |||
e9ad85a0ac | |||
b4b122fe7f | |||
6aaeda7a3c | |||
0b3f7b2b5c | |||
e9e7c5f7b5 | |||
0df53ed5ae | |||
136478aff7 | |||
5b812b30f7 | |||
f2b5c16e71 | |||
df1f29c0ff | |||
afbd6fc1ac | |||
327e0aa799 | |||
b7e948954b | |||
93c0658c19 | |||
bdf74f2deb | |||
2435bb9f8a | |||
5ee38dcf7f | |||
a754dc9275 | |||
732e080da3 | |||
22e1ebdce1 | |||
e4280f2c4e | |||
a99609d21d | |||
e55658d382 | |||
9dbe9e1114 | |||
eea198786a | |||
da9c0a7b58 | |||
d582b884bf | |||
aa3e4c64ee | |||
ce479629f6 | |||
89fd516b20 | |||
8bbb991afb | |||
4c5c9ba553 | |||
ef1511d4f7 | |||
a290219c81 | |||
c4e406f855 | |||
2beac5f4dd | |||
5f5cd0268a | |||
796cbb6d30 | |||
39ebf7eb78 | |||
a1c5161ff7 | |||
2307ad667e | |||
a26e832fed | |||
8582010d90 | |||
b7186eb8b4 | |||
0f3528818f | |||
9fadc77979 | |||
13d231a62c | |||
6cbaa06bec | |||
1552e9c877 | |||
3b2cd37ea4 | |||
4c566cbc1a | |||
69c528e5e0 | |||
42bf35593f | |||
4e739c5f08 | |||
f75623b4ff | |||
d82b5e4a98 | |||
42c738211c | |||
c54bc35c9f | |||
1f406dc558 | |||
0c0b50b0df | |||
6efee2fbae | |||
47e149e20b | |||
d163700147 | |||
fa3befaa88 | |||
e5b7c0d550 | |||
9e4663cc6e | |||
1a5c95d240 | |||
21845c5de1 | |||
4a91d458fd | |||
a52b20efab | |||
02c4c5168b | |||
7b875454cb | |||
2da5d9df08 | |||
a0eb3c0bb6 | |||
7e76543c48 | |||
09c2c86d59 | |||
e58948bb44 | |||
a00b03b2aa | |||
b47b956798 | |||
a409a1347e | |||
8999aabc21 | |||
14e8907437 | |||
740c400b9a | |||
7cd38bbd23 | |||
8c509b5bff | |||
28b22a0b30 | |||
748d0bf1ea | |||
130e6d4e24 | |||
a66fec07e1 | |||
a3d1e9342f | |||
72bc88661a | |||
e2b897eb9d | |||
9fcce059e5 | |||
36f6413917 | |||
8c729f3c57 | |||
063cc9d439 | |||
ac5317e600 | |||
bc0b65006d | |||
933f04bf5e | |||
7d46435ba2 | |||
d94bb8ac32 | |||
b143f743ef | |||
a8341a9b44 | |||
def5d097a4 | |||
ba0ae1bc60 | |||
d77029b597 | |||
b5f62d43af | |||
9220dc3278 | |||
36c72fb665 | |||
|
c90504f4f1 | ||
9ef961ae7c | |||
fe6663f9f4 | |||
1190b316bb | |||
7068dc229c | |||
|
61ce6fb25b | ||
116ef658d8 | |||
d7b354de8c | |||
|
d06c0574fb | ||
|
bcd7894e3b | ||
80aba05f66 | |||
41a4292939 | |||
131ea5d569 | |||
e36bbf41f4 | |||
c97b398914 | |||
d27ed76cf4 | |||
17b6ea51af | |||
0986f73f06 | |||
dcd282779f | |||
b93a3df98c | |||
4768330c1a | |||
23d0afd85f | |||
5c939383be | |||
72ddb8aa74 | |||
4d0e5b2e6a | |||
58d1e8e450 | |||
d0790f37a8 | |||
62b0576c2d | |||
e574a2e2a8 | |||
eb760d4299 | |||
3833037862 | |||
6dc92df4b5 | |||
d458cdbd73 | |||
628b35d471 | |||
84363be0e8 | |||
5284cdf5cc | |||
632ffc2b66 | |||
2ff705164e | |||
73ab867509 | |||
45e44dee14 | |||
689c2a4803 | |||
e023544c8a | |||
10b7a49bbf | |||
bb1354e228 | |||
957cc4bcf6 | |||
9f1f6c7e78 | |||
a80ddcc054 | |||
921eaff367 | |||
b63d8e4210 | |||
9766dac763 | |||
441be30720 | |||
e13653fba1 | |||
6b301f3171 | |||
9181c389d4 | |||
3b4dade699 | |||
4f0298c0a9 | |||
5f20585fb2 | |||
0714eac615 | |||
456e7f41f2 | |||
fc335247f8 | |||
ad16005091 | |||
a676372bbe | |||
7fadc45e9e | |||
41a0dc58a5 | |||
dc34d32c5c | |||
36984e3825 | |||
046410a5ec | |||
827b5bc830 | |||
8aee6d9983 | |||
7b890df1c5 | |||
e925f58e82 | |||
91fc0fd9f0 | |||
1a1d2b5ee8 | |||
95f3abfa74 | |||
3b51f41716 | |||
3f63375b60 | |||
8c97937b44 | |||
96f843630a | |||
48f6045cd3 | |||
49045a2ccd | |||
af8dabba85 | |||
cc1019b5a8 | |||
14877fbfa3 | |||
7b5e45131c | |||
b09316b416 | |||
315f5da970 | |||
9b392a1f64 | |||
9358076a36 | |||
427f2909d5 | |||
8ab1763902 | |||
e409be235e | |||
1ca71f264c | |||
5f464253c4 | |||
75869975f6 | |||
ac43256511 | |||
5b51ba6d98 | |||
dd63bb6343 | |||
817ebe0e53 | |||
6023ca4508 | |||
e0c829c2ce | |||
2d33367422 | |||
d72f371c59 | |||
db168c91c9 | |||
d31993953b | |||
63c6d5d0ef | |||
164a617869 | |||
865473b41b
|
|||
533e1422d0
|
|||
d2c8d08b7f
|
|||
4703aa7df5
|
|||
918f1b845b
|
|||
78dfb15cf5
|
|||
adc44a5e8b
|
|||
4367a01769
|
|||
d98268f45b
|
|||
ccfebd1a70
|
|||
e4f67dbd31
|
|||
d01a35e2d4
|
|||
0aa7023947
|
|||
451a1341bd
|
|||
da7d92bbfa | |||
15a2f25e51 | |||
f73d598bb4 | |||
98c7c6ddc1 | |||
39d63c11b1 | |||
8f4d69d6e2 | |||
531756d8ea | |||
e0c18ec2ad
|
|||
a3e7435544 | |||
c01cdf50c8
|
|||
2e3dd5032f
|
|||
8b01807568
|
|||
46c878b56e
|
|||
10b9342607 | |||
77de53c999
|
|||
6a7fedee7e
|
|||
c31299197f
|
|||
49c1fa5fb0
|
|||
d1186ee1e1
|
|||
50975ae94a
|
|||
7e8cb57979 | |||
12814f4732 | |||
dc41826691 | |||
6770f8176a | |||
cd946b2df6 | |||
734b0ed39d | |||
78d37c4405 | |||
fd3ba6d577 | |||
8ae9350d31 | |||
9621493343 | |||
f291d17aa3
|
|||
9826a54187
|
|||
d48fd2f911
|
|||
ec62a79d82
|
|||
58d19cce52
|
|||
3bb635a627 | |||
35f464a1cb | |||
85f1b2d43c | |||
77e8be4e63 | |||
57b274e345 | |||
3c743fb135 | |||
ab15614076
|
|||
c41fa22c59
|
|||
81ea0fbb6f
|
|||
821c038206
|
|||
19a37ace18
|
|||
d8bd66e66f
|
|||
c780e6c2a2
|
|||
3317459b0b | |||
564b939464 | |||
c7c18f01cd | |||
2f53638f82 | |||
5070101706 | |||
cc3c42b8fd | |||
45de7ab543
|
|||
984b50356a
|
|||
441b30a570
|
|||
2aad5d4b9d
|
|||
50169c8fbc
|
|||
fc67cb668d
|
|||
8e4b3be108
|
|||
3ae5ec99ff
|
|||
ef7fcb6133
|
|||
50c2f9d1dc
|
|||
eeb044bab0
|
|||
6ce52cc037
|
|||
9a3fc6a2b4
|
|||
4ded4d4e09
|
|||
dc47e73b4b | |||
76aa332dc2 | |||
5d4f72e984 | |||
5a358f0a6a | |||
ed9b4681ad | |||
d135f5bc0a
|
|||
f9cf3972d5
|
|||
6e33fa474a
|
|||
3a0cf1a7db
|
|||
475a025d9e
|
|||
c37bde487b | |||
|
982917c25b | ||
|
7feb7427f2 | ||
a2bf9c005b | |||
119b5d699c
|
|||
|
eca5154d75 | ||
16196aae34
|
|||
9726cc4a18
|
|||
7eb75015a0
|
|||
66dec780ae
|
|||
6cd361705d
|
|||
9c449eefab
|
|||
|
16c8e3dd20 | ||
b21c821086
|
|||
890d7b5017
|
|||
|
1266545919 | ||
874e12f35d
|
|||
b25b2fda2c
|
|||
e2a4d296f6
|
|||
7d5b4d9306
|
|||
a1d963c15b
|
|||
bb83384d62
|
|||
bc3c5dc932
|
|||
203449aa9e
|
|||
a3dbdfd8c6
|
|||
1b1397b45f
|
|||
1122c02e07
|
|||
01e045e4b7
|
|||
d28cbab0fe
|
|||
b1dc94e0e9
|
|||
f9fd6332d6 | |||
|
7203928d95 | ||
dc9b198208
|
|||
0f9a46f52d
|
|||
8cf3ff9f73
|
|||
114fab4870
|
|||
5fc8c06d8b
|
|||
51ca4e81d1
|
|||
a6f6b91ab9
|
|||
63afc3066b
|
|||
|
1e8fd44857 | ||
|
1403f9b198 | ||
|
22ef9211ec | ||
418a0a8029 | |||
165054ca53 | |||
a87391e726 | |||
f8dcd2026b | |||
f99f146af2 | |||
e72a8a2e9c | |||
0f73aa3125 | |||
7dc5d509f7 | |||
a9b75c48c4 | |||
6ea3a923cc | |||
6cd1af015b | |||
68a031d0be | |||
7ba68c2854 | |||
1dd8fef4b3 | |||
6a2417c361 | |||
ad316d1f49 | |||
ed22774a93 | |||
8dfca953dc | |||
7b774e84d8 | |||
d0576d4b81 | |||
c1fc388b26 | |||
86ca723aa8 |
@@ -4,3 +4,8 @@ root = true
|
||||
indent_style=tab
|
||||
indent_size=4
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
|
47
.gitea/workflows/build-oci.yaml
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Build and push OCI image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "**"
|
||||
|
||||
env:
|
||||
REGISTRY: repo.katenary.io
|
||||
IMAGE_NAME: ${{ gitea.repository }}
|
||||
VERSION: ${{ gitea.ref_name }}
|
||||
STORAGE_DRIVER: vfs
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Install Buildah
|
||||
run: |-
|
||||
env
|
||||
if ! command -v buildah 2>/dev/null; then
|
||||
echo "Install Buildah"
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y buildah
|
||||
else
|
||||
echo "Buildah already installed"
|
||||
fi
|
||||
- name: Login to repository
|
||||
run: |
|
||||
buildah login $REGISTRY -u ${{ gitea.actor }} -p ${{ secrets.ACCESS_TOKEN }}
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build and tag
|
||||
run: |-
|
||||
buildah build --isolation=chroot --build-arg VERSION=$VERSION -t katenary -f ./oci/katenary/Containerfile .
|
||||
buildah tag katenary $REGISTRY/${IMAGE_NAME,,}:$VERSION
|
||||
buildah tag katenary $REGISTRY/${IMAGE_NAME,,}:latest
|
||||
|
||||
- name: Push image
|
||||
run: |-
|
||||
buildah push $REGISTRY/${IMAGE_NAME,,}:$VERSION
|
||||
buildah push $REGISTRY/${IMAGE_NAME,,}:latest
|
68
.gitea/workflows/go-test.yaml
Normal file
@@ -0,0 +1,68 @@
|
||||
name: Go-Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
- "main"
|
||||
env:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.25
|
||||
- name: Install helm
|
||||
run: |
|
||||
command -v helm || curl -sSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
|
||||
|
||||
- name: Tidy
|
||||
run: |
|
||||
go mod tidy
|
||||
- name: Launch Test
|
||||
run: |
|
||||
go vet ./...
|
||||
go test -coverprofile=coverprofile.out -json -v ./... > gotest.json
|
||||
# - uses: actions/upload-artifact@v4
|
||||
- name: Upload artifact
|
||||
uses: christopherhx/gitea-upload-artifact@v4
|
||||
with:
|
||||
name: tests-results
|
||||
path: |
|
||||
coverprofile.out
|
||||
gotest.json
|
||||
sonar:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
runs-on: ubuntu-latest
|
||||
needs: tests
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
#- uses: actions/download-artifact@v4
|
||||
- name: Download artifact
|
||||
uses: christopherhx/gitea-download-artifact@v4
|
||||
with:
|
||||
name: tests-results
|
||||
- name: Sonar
|
||||
run: |
|
||||
export SONAR_SCANNER_VERSION=7.2.0.5079
|
||||
export SONAR_SCANNER_HOME=$HOME/.sonar/sonar-scanner-$SONAR_SCANNER_VERSION-linux-x64
|
||||
curl --create-dirs -sSLo $HOME/.sonar/sonar-scanner.zip https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_SCANNER_VERSION-linux-x64.zip
|
||||
unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/
|
||||
export PATH=$SONAR_SCANNER_HOME/bin:$PATH
|
||||
sonar-scanner \
|
||||
-Dsonar.organization=katenary \
|
||||
-Dsonar.projectKey=katenary_katenary
|
37
.gitignore
vendored
@@ -1,8 +1,33 @@
|
||||
dist/*
|
||||
.cache/*
|
||||
chart/*
|
||||
docker-compose.yaml
|
||||
katenary
|
||||
*.env
|
||||
docker-compose*
|
||||
!examples/**/docker-compose*
|
||||
.credentials
|
||||
release.id
|
||||
cover*
|
||||
.sq
|
||||
.aider*
|
||||
.python_history
|
||||
.bash_history
|
||||
|
||||
.cache/
|
||||
.aider/
|
||||
.config/
|
||||
*/venv
|
||||
|
||||
|
||||
# will be treated later
|
||||
/examples/*
|
||||
|
||||
# nsis
|
||||
nsis/*.dll
|
||||
nsis/*.exe
|
||||
|
||||
doc/share
|
||||
__pycache__
|
||||
|
||||
.rpmmacros
|
||||
*.gpg
|
||||
|
||||
# local binaries
|
||||
katenary
|
||||
!cmd/katenary
|
||||
!oci/katenary
|
||||
|
0
.gitmodules
vendored
Normal file
26
.golangci.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
version: "2"
|
||||
run:
|
||||
issues-exit-code: 1
|
||||
linters:
|
||||
enabled:
|
||||
- unused
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
- "(.+)_test.go"
|
||||
formatters:
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
- "(.+)_test.go"
|
21
.markdownlint.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
# markdownlint configuration file
|
||||
default: true
|
||||
|
||||
MD013: # Line length
|
||||
line_length: 120
|
||||
|
||||
MD010: # Hard tabs
|
||||
code_blocks: false
|
||||
|
||||
# no inline HTML
|
||||
MD033: false
|
||||
|
||||
# heading as first line element...
|
||||
MD041: false
|
||||
|
||||
# list indentation
|
||||
MD007:
|
||||
indent: 4
|
||||
|
||||
# no problem using several code blocks styles
|
||||
MD046: false
|
20
.readthedocs.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
# .readthedocs.yaml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Set the version of Python and other tools you might need
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
tools:
|
||||
python: "3.9"
|
||||
|
||||
mkdocs:
|
||||
configuration: doc/mkdocs.yml
|
||||
|
||||
# Optionally declare the Python requirements required to build your docs
|
||||
python:
|
||||
install:
|
||||
- requirements: doc/requirements.txt
|
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Patrice Ferlet
|
||||
Copyright (c) 2022-2025 The Katenary authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
161
Makefile
@@ -1,114 +1,121 @@
|
||||
# Strict mode
|
||||
SHELL := bash
|
||||
.SHELLFLAGS := -eu -o pipefail -c
|
||||
.ONESHELL:
|
||||
.DELETE_ON_ERROR:
|
||||
.PHONY: all binaries build check-dist-all check-dist-archlinux check-dist-debian check-dist-fedora check-dist-rocky check-dist-ubuntu check-sign clean-all clean-dist clean-go-cache clean-package-signer cover deb dist dist-full doc freebsd gpg-sign help install install-gomarkdoc katenary manpage packager-oci-image packages pacman prepare pull rpm rpm-sign sast serve-doc show-cover tar test uninstall upx warn-docker
|
||||
MAKEFLAGS += --warn-undefined-variables
|
||||
MAKEFLAGS += --no-builtin-rules
|
||||
|
||||
# Get a version string from git
|
||||
CUR_SHA=$(shell git log -n1 --pretty='%h')
|
||||
CUR_BRANCH=$(shell git branch --show-current)
|
||||
VERSION=$(shell git describe --exact-match --tags $(CUR_SHA) 2>/dev/null || echo $(CUR_BRANCH)-$(CUR_SHA))
|
||||
CTN:=$(shell which podman 2>&1 1>/dev/null && echo "podman" || echo "docker")
|
||||
PREFIX=~/.local
|
||||
VERSION=$(shell git describe --exact-match --tags $(CUR_SHA) 2>/dev/null || echo $(CUR_BRANCH)-$(CUR_SHA))# use by golang flags
|
||||
|
||||
GO=container
|
||||
BLD_CMD=go build -o katenary -ldflags="-X 'main.Version=$(VERSION)'" .
|
||||
# Go build command and environment variables for target OS and architecture
|
||||
GOVERSION=1.25
|
||||
GO=container# container, local
|
||||
OUTPUT=katenary
|
||||
GOOS=linux
|
||||
GOARCH=amd64
|
||||
CGO_ENABLED=0
|
||||
PREFIX=~/.local
|
||||
|
||||
BUILD_IMAGE=docker.io/golang:1.17
|
||||
# UPX compression
|
||||
UPX_OPTS =
|
||||
UPX ?= upx $(UPX_OPTS)
|
||||
|
||||
.PHONY: help clean build
|
||||
# List of source files
|
||||
SOURCES=$(shell find -name "*.go" -or -name "*.tpl" -type f | grep -v -P "^./example|^./vendor")
|
||||
# List of binaries to build and sign
|
||||
BINARIES=\
|
||||
dist/katenary-linux-amd64\
|
||||
dist/katenary-linux-arm64\
|
||||
dist/katenary-darwin-amd64\
|
||||
dist/katenary-freebsd-amd64\
|
||||
dist/katenary-freebsd-arm64\
|
||||
dist/katenary.exe\
|
||||
dist/katenary-windows-setup.exe
|
||||
|
||||
## GPG
|
||||
# List of signatures to build
|
||||
ASC_BINARIES=$(patsubst %,%.asc,$(BINARIES))
|
||||
# GPG signer
|
||||
SIGNER=metal3d@gmail.com
|
||||
|
||||
# Browser command to see coverage report after tests
|
||||
BROWSER=$(shell command -v epiphany || echo xdg-open)
|
||||
|
||||
include makefiles/build.mk
|
||||
include makefiles/containers.mk
|
||||
include makefiles/doc.mk
|
||||
include makefiles/gpg.mk
|
||||
include makefiles/packager.mk
|
||||
include makefiles/test.mk
|
||||
|
||||
all: build
|
||||
|
||||
# if docker is used instead of podman, we warn the user
|
||||
warn-docker:
|
||||
@echo -e "\033[1;31mWarning: Docker is not recommended, use Podman instead.\033[0m"
|
||||
sleep 5
|
||||
|
||||
.ONESHELL:
|
||||
help:
|
||||
@cat <<EOF
|
||||
@cat <<EOF | fold -s -w 80
|
||||
=== HELP ===
|
||||
To avoid you to install Go, the build is made by podman or docker.
|
||||
|
||||
You can use:
|
||||
Installinf (you can use local Go by setting GO=local)):
|
||||
# use podman or docker to build
|
||||
$$ make install
|
||||
# or use local Go
|
||||
$$ make install GO=local
|
||||
This will build and install katenary inside the PREFIX(/bin) value (default is $(PREFIX))
|
||||
|
||||
To change the PREFIX to somewhere where only root or sudo users can save the binary, it is recommended to build before install:
|
||||
|
||||
To change the PREFIX to somewhere where only root or sudo users can save the binary, it is recommended to build before install, one more time you can use local Go by setting GO=local:
|
||||
$$ make build
|
||||
$$ sudo make install PREFIX=/usr/local
|
||||
|
||||
Katenary is statically built (in Go), so there is no library to install.
|
||||
|
||||
To build for others OS:
|
||||
$$ make GOOS=linux GOARCH=amd64
|
||||
$$ make build GOOS=linux GOARCH=amd64
|
||||
This will build the binary for linux amd64.
|
||||
$$ make GOOS=linux GOARCH=arm
|
||||
|
||||
$$ make build GOOS=linux GOARCH=arm
|
||||
This will build the binary for linux arm.
|
||||
$$ make GOOS=windows GOARCH=amd64
|
||||
|
||||
$$ make build GOOS=windows GOARCH=amd64
|
||||
This will build the binary for windows amd64.
|
||||
$$ make GOOS=darwin GOARCH=amd64
|
||||
|
||||
$$ make build GOOS=darwin GOARCH=amd64
|
||||
This will build the binary for darwin amd64.
|
||||
|
||||
|
||||
Or you can build all versions:
|
||||
$$ make build-all
|
||||
|
||||
$$ make binaries
|
||||
EOF
|
||||
|
||||
|
||||
|
||||
build: pull katenary
|
||||
|
||||
build-all: pull dist dist/katenary-linux-amd64 dist/katenary-linux-arm64 dist/katenary.exe dist/katenary-darwin
|
||||
|
||||
pull:
|
||||
@echo -e "\033[1;32mPulling $(BUILD_IMAGE) docker image\033[0m"
|
||||
@$(CTN) pull $(BUILD_IMAGE)
|
||||
|
||||
dist:
|
||||
mkdir -p dist
|
||||
|
||||
dist/katenary-linux-amd64:
|
||||
@echo -e "\033[1;32mBuilding katenary for linux-amd64...\033[0m"
|
||||
$(MAKE) katenary GOOS=linux GOARCH=amd64
|
||||
strip katenary
|
||||
mv katenary dist/katenary-linux-amd64
|
||||
|
||||
|
||||
dist/katenary-linux-arm64:
|
||||
@echo -e "\033[1;32mBuilding katenary for linux-arm...\033[0m"
|
||||
$(MAKE) katenary GOOS=linux GOARCH=arm64
|
||||
strip katenary
|
||||
mv katenary dist/katenary-linux-arm64
|
||||
|
||||
dist/katenary.exe:
|
||||
@echo -e "\033[1;32mBuilding katenary for windows...\033[0m"
|
||||
$(MAKE) katenary GOOS=windows GOARCH=amd64
|
||||
strip katenary
|
||||
mv katenary dist/katenary-windows.exe
|
||||
|
||||
dist/katenary-darwin:
|
||||
@echo -e "\033[1;32mBuilding katenary for darwin...\033[0m"
|
||||
$(MAKE) katenary GOOS=darwin GOARCH=amd64
|
||||
strip katenary
|
||||
mv katenary dist/katenary-darwin
|
||||
|
||||
katenary: $(wildcard */*.go Makefile go.mod go.sum)
|
||||
@echo -e "\033[1;33mBuilding katenary $(VERSION)...\033[0m"
|
||||
ifeq ($(GO),local)
|
||||
@echo "=> Build in host using go"
|
||||
@echo
|
||||
else
|
||||
@echo "=> Build in container using" $(CTN)
|
||||
@echo
|
||||
endif
|
||||
echo $(BLD_CMD)
|
||||
ifeq ($(GO),local)
|
||||
$(BLD_CMD)
|
||||
else ifeq ($(CTN),podman)
|
||||
@podman run -e CGO_ENABLED=0 -e GOOS=$(GOOS) -e GOARCH=$(GOARCH) --rm -v $(PWD):/go/src/katenary:z -w /go/src/katenary --userns keep-id -it docker.io/golang $(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)
|
||||
endif
|
||||
|
||||
|
||||
## installation and uninstallation
|
||||
|
||||
install: build
|
||||
cp katenary $(PREFIX)/bin/katenary
|
||||
install -Dm755 katenary $(PREFIX)/bin/katenary
|
||||
|
||||
uninstall:
|
||||
rm -f $(PREFIX)/bin/katenary
|
||||
|
||||
clean:
|
||||
## Miscellaneous
|
||||
|
||||
clean-all: clean-dist clean-package-signer clean-go-cache
|
||||
|
||||
clean-dist:
|
||||
rm -rf dist
|
||||
rm -f katenary
|
||||
|
||||
|
||||
clean-package-signer:
|
||||
rm -f .secret.gpg .rpmmacros
|
||||
|
||||
clean-go-cache:
|
||||
$(CTN) volume rm -f go-cache
|
||||
|
||||
|
285
README.md
@@ -1,17 +1,60 @@
|
||||

|
||||
<div style="text-align:center; margin: auto 0 4em 0" align="center">
|
||||
<img src="./doc/docs/statics/logo-vertical.svg" 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.
|
||||
<div style="text-align:center; margin: auto 0 4em 0" align="center">
|
||||
|
||||
> **Important Note** Katenary is a tool to help building Helm Chart from a docker-compose file, but docker-compose doesn't propose as many features as what can do Kubernetes. So, we strongly recommend to use Katenary as a "bootstrap" tool and then to manually enhance the generated helm chart.
|
||||
[](https://katenary.readthedocs.io/latest/?badge=latest)
|
||||
[](https://sonarcloud.io/summary/new_code?id=katenary_katenary)
|
||||
[](https://sonarcloud.io/summary/new_code?id=katenary_katenary)
|
||||
[](https://sonarcloud.io/summary/new_code?id=katenary_katenary)
|
||||
|
||||
This project is partially made at [Smile](https://smile.eu)
|
||||
</div>
|
||||
|
||||

|
||||
<div style="text-align:center; margin: auto 0 4em 0" align="center">
|
||||
<h3>🚀 Unleash Productivity with Katenary! 🚀</h3>
|
||||
</div>
|
||||
|
||||
Tired of manual conversions? Katenary harnesses the labels from your "`compose`" file to craft complete Helm Charts
|
||||
effortlessly, saving you time and energy.
|
||||
|
||||
# Install
|
||||
🛠️ Simple automated CLI: Katenary handles the grunt work, generating everything needed for seamless service binding
|
||||
and Helm Chart creation.
|
||||
|
||||
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.
|
||||
💡 Effortless Efficiency: You only need to add labels when it's necessary to precise things.
|
||||
Then call `katenary convert` and let the magic happen.
|
||||
|
||||
## What ?
|
||||
|
||||
Katenary is a tool to help to transform `compose` (`docker compose`, `podman compose`, `nerdctl compose`, ...) files
|
||||
to a working Helm Chart for Kubernetes.
|
||||
|
||||
Today, it's partially developed in collaboration with [Klee Group](https://www.kleegroup.com). Note that Katenary is
|
||||
and **will stay an open source and free (as freedom) project**. We are convinced that the best way to make it better is to
|
||||
share it with the community.
|
||||
|
||||
The main developer is [Patrice FERLET](https://repo.katenary.io/metal3d).
|
||||
|
||||
## Install
|
||||
|
||||
You can download the binaries from the [Release](https://repo.katenary.io/Katenary/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.
|
||||
|
||||
On Linux, you can use the `install.sh` from the repository to install it in your `$HOME/.local/bin` directory:
|
||||
|
||||
```bash
|
||||
sh <(curl -sSL https://repo.katenary.io/Katenary/katenary/raw/branch/master/install.sh)
|
||||
```
|
||||
|
||||
All OS, if you've installed Go on your computer, you can install using:
|
||||
|
||||
```bash
|
||||
go install -u katenary.io/cmd@latest
|
||||
# or use a release
|
||||
go install -u katenary.io/cmd@v3.0.0
|
||||
```
|
||||
|
||||
## Or, build yourself
|
||||
|
||||
If you've got `podman` or `docker`, you can build `katenary` by using:
|
||||
|
||||
@@ -20,22 +63,35 @@ make build
|
||||
```
|
||||
|
||||
You can then install it with:
|
||||
|
||||
```bash
|
||||
make install
|
||||
```
|
||||
|
||||
It will use the default PREFIX (`~/.local/`) to install the binary in the `bin` subdirectory. You can force the PREFIX value at install time, but maybe you need to use "sudo":
|
||||
It will use the default PREFIX (`~/.local/`) to install the binary in the `bin` subdirectory. You can force the PREFIX
|
||||
value at install time, but maybe you need to use "sudo":
|
||||
|
||||
```bash
|
||||
sudo make install PREFIX=/usr/local
|
||||
```
|
||||
|
||||
If that goes wrong, you can use your local Go compiler:
|
||||
|
||||
# Tips
|
||||
```bash
|
||||
make build GO=local
|
||||
|
||||
We strongly recommand to add the "completion" call to you SHELL using the common bashrc, or whatever the profile file you use.
|
||||
# To force OS or architecture
|
||||
make build GO=local GOOS=linux GOARCH=arm64
|
||||
```
|
||||
|
||||
E.g. :
|
||||
Then place the `katenary` binary file inside your PATH.
|
||||
|
||||
## Tips
|
||||
|
||||
We strongly recommend adding the completion call to you SHELL using the common `bashrc`, or whatever the profile file
|
||||
you use.
|
||||
|
||||
E.g.,
|
||||
|
||||
```bash
|
||||
# bash in ~/.bashrc file
|
||||
@@ -44,75 +100,69 @@ source <(katenary completion bash)
|
||||
source <(katenary completion bash --no-description)
|
||||
|
||||
# zsh in ~/.zshrc
|
||||
source <(helm completion zsh)
|
||||
source <(katenary completion zsh)
|
||||
|
||||
# fish in ~/.config/fish/config.fish
|
||||
katenary completion fish | source
|
||||
|
||||
# experimental
|
||||
# powershell (as we don't provide any support on Windows yet, please avoid this...)
|
||||
```
|
||||
|
||||
# Usage
|
||||
## Usage
|
||||
|
||||
```
|
||||
Katenary aims to be a tool to convert docker-compose files to Helm Charts.
|
||||
It will create deployments, services, volumes, secrets, and ingress resources.
|
||||
But it will also create initContainers based on depend_on, healthcheck, and other features.
|
||||
It's not magical, sometimes you'll need to fix the generated charts.
|
||||
The general way to use it is to call one of these commands:
|
||||
```text
|
||||
|
||||
katenary convert
|
||||
katenary convert -f docker-compose.yml
|
||||
katenary convert -f docker-compose.yml -o ./charts
|
||||
Katenary is a tool to convert compose files to Helm Charts.
|
||||
|
||||
In case of, check the help of each command using:
|
||||
katenary <command> --help
|
||||
or
|
||||
"katenary help <command>"
|
||||
Each [command] and subcommand has got an "help" and "--help" flag to show more information.
|
||||
|
||||
Usage:
|
||||
katenary [command]
|
||||
|
||||
Examples:
|
||||
katenary convert -c docker-compose.yml -o ./charts
|
||||
|
||||
Available Commands:
|
||||
completion Generate the autocompletion script for the specified shell
|
||||
convert Convert docker-compose to helm chart
|
||||
help Help about any command
|
||||
show-labels Show labels of a resource
|
||||
version Display version
|
||||
completion Generates completion scripts
|
||||
convert Converts a docker-compose file to a Helm Chart
|
||||
hash-composefiles Print the hash of the composefiles
|
||||
help Help about any command
|
||||
help-labels Print the labels help for all or a specific label
|
||||
schema Print the schema of the katenary file
|
||||
version Print the version number of Katenary
|
||||
|
||||
Flags:
|
||||
-h, --help help for katenary
|
||||
-h, --help help for katenary
|
||||
-v, --version version for katenary
|
||||
|
||||
Use "katenary [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
Katenary will try to find a `docker-compose.yaml` or `docker-compose.yml` file inside the current directory. It will check *the existence of the `chart` directory to create a new Helm Chart inside a named subdirectory. Katenary will ask you if you want to delete it before recreating.
|
||||
Katenary will try to find a `docker-compose.yaml` or `docker-compose.yml` file inside the current directory. It will
|
||||
check \*the existence of the `chart` directory to create a new Helm Chart inside a named subdirectory. Katenary will ask
|
||||
you if you want to delete it before recreating.
|
||||
|
||||
It creates a subdirectory inside `chart` that is named with the `appname` option (default is `MyApp`)
|
||||
|
||||
> To respect the ability to install the same application in the same namespace, Katenary will create "variable" names like `{{ .Release.Name }}-servicename`. So, you will need to use some labels inside your docker-compose file to help katenary to build a correct helm chart.
|
||||
> To respect the ability to install the same application in the same namespace, Katenary will create variable names
|
||||
> like `{{ .Release.Name }}-servicename`. So, you will need to use some labels inside your docker-compose file to help
|
||||
> Katenary to build a correct helm chart.
|
||||
|
||||
What can be interpreted by Katenary:
|
||||
|
||||
- Services with "image" section (cannot work with "build" section)
|
||||
- **Named Volumes** are transformed to persistent volume claims - note that local volume will break the transformation to Helm Chart because there is (for now) no way to make it working (see below for resolution)
|
||||
- if `ports` and/or `expose` section, katenary will create Services and bind the port to the corresponding container port
|
||||
- `depends_on` will add init containers to wait for the depending service (using the first port)
|
||||
- `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)
|
||||
|
||||
Exemple of a possible `docker-compose.yaml` file:
|
||||
Example of a possible `docker-compose.yaml` file:
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
webapp:
|
||||
image: php:7-apache
|
||||
environment:
|
||||
# note that "database" is a service name
|
||||
# note that "database" is a "compose" service name
|
||||
# so we need to adapt it with the map-env label
|
||||
DB_HOST: database
|
||||
# a pitty to repeat this values, isn't it?
|
||||
# so, let's change them with "values-from" label
|
||||
DB_USER: foo
|
||||
DB_PASSWORD: bar
|
||||
expose:
|
||||
- 80
|
||||
depends_on:
|
||||
@@ -120,39 +170,138 @@ 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
|
||||
katenary.v3/ingress: |-
|
||||
hostname: myapp.example.com
|
||||
port: 80
|
||||
# make adaptations, DB_HOST environment is actually the service name
|
||||
katenary.v3/map-env: |-
|
||||
DB_HOST: '{{ .Release.Name }}-database'
|
||||
# get the values from the "database" service
|
||||
# this will use the database secrets and environment,
|
||||
# see the "database" service to see the values
|
||||
katenary.v3/values-from: |-
|
||||
DB_USER: database.MARIADB_USER
|
||||
DB_PASSWORD: database.MARIADB_PASSWORD
|
||||
|
||||
database:
|
||||
image: mariadb:10
|
||||
env_file:
|
||||
# this will create a configMap
|
||||
# this values will be added in 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
|
||||
katenary.v3/ports: |-
|
||||
- 3306
|
||||
# these variables are secrets
|
||||
katenary.v3/secrets: |-
|
||||
- MARIADB_ROOT_PASSWORD
|
||||
- MARIADB_PASSWORD
|
||||
```
|
||||
|
||||
# Labels
|
||||
## Labels
|
||||
|
||||
These labels could be found by `katenary show-labels`, and can be placed as "labels" inside your docker-compose file:
|
||||
These labels could be found by `katenary help-labels`, and can be placed as labels inside your docker-compose file:
|
||||
|
||||
```
|
||||
katenary.io/secret-envfiles : set the given file names as a secret instead of configmap
|
||||
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)
|
||||
katenary.io/healthcheck : specifies that the container should be monitored by a healthcheck, **it overrides the docker-compose healthcheck**.
|
||||
You can use these form of label values:
|
||||
- "http://[not used address][:port][/path]" to specify an http healthcheck
|
||||
- "tcp://[not used address]:port" to specify a tcp healthcheck
|
||||
- other string is condidered as a "command" healthcheck
|
||||
```text
|
||||
To get more information about a label, use `katenary help-label <name_without_prefix>
|
||||
e.g. katenary help-label dependencies
|
||||
|
||||
katenary.v3/configmap-files: []string Inject files as Configmap.
|
||||
katenary.v3/cronjob: object Create a cronjob from the service.
|
||||
katenary.v3/dependencies: []object Add Helm dependencies to the service.
|
||||
katenary.v3/description: string Description of the service
|
||||
katenary.v3/env-from: []string Add environment variables from another service.
|
||||
katenary.v3/exchange-volumes: []object Add exchange volumes (empty directory on the node) to share data
|
||||
katenary.v3/health-check: object Health check to be added to the deployment.
|
||||
katenary.v3/ignore: bool Ignore the service
|
||||
katenary.v3/ingress: object Ingress rules to be added to the service.
|
||||
katenary.v3/main-app: bool Mark the service as the main app.
|
||||
katenary.v3/map-env: map[string]string Map env vars from the service to the deployment.
|
||||
katenary.v3/ports: []uint32 Ports to be added to the service.
|
||||
katenary.v3/same-pod: string Move the same-pod deployment to the target deployment.
|
||||
katenary.v3/secrets: []string Env vars to be set as secrets.
|
||||
katenary.v3/values: []string or map[string]string Environment variables to be added to the values.yaml
|
||||
katenary.v3/values-from: map[string]string Add values from another service.
|
||||
```
|
||||
|
||||
## Katenary.yaml file and schema validation
|
||||
|
||||
Instead of using labels inside the docker-compose file, you can use a `katenary.yaml` file to define the labels. This
|
||||
file is simpler to read and maintain, but you need to keep it up-to-date with the docker-compose file.
|
||||
|
||||
For example, instead of using this:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
katenary.v3/ingress: |-
|
||||
hostname: myapp.example.com
|
||||
port: 80
|
||||
```
|
||||
|
||||
You can remove the labels, and use a `katenary.yaml` file:
|
||||
|
||||
```yaml
|
||||
web:
|
||||
ingress:
|
||||
hostname: myapp.example.com
|
||||
port: 80
|
||||
```
|
||||
|
||||
To validate the `katenary.yaml` file, you can use the JSON schema using the "master" raw content:
|
||||
|
||||
`https://repo.katenary.io/Katenary/katenary/raw/branch/master/katenary.json`
|
||||
|
||||
It's easy to configure in [LazyVim](https://www.lazyvim.org/), using `nvim-lspconfig`,
|
||||
create a Lua file in your `plugins` directory, or apply the settings as the example below:
|
||||
|
||||
```lua
|
||||
-- yaml.lua
|
||||
|
||||
return {
|
||||
{
|
||||
"neovim/nvim-lspconfig",
|
||||
opts = {
|
||||
servers = {
|
||||
yamlls = {
|
||||
settings = {
|
||||
yaml = {
|
||||
schemas = {
|
||||
["https://repo.katenary.io/Katenary/katenary/raw/branch/master/katenary.json"] = "katenary.yaml",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Use this address to validate the `katenary.yaml` file in VSCode:
|
||||
|
||||
```json
|
||||
{
|
||||
"yaml.schemas": {
|
||||
"https://repo.katenary.io/Katenary/katenary/raw/branch/master/katenary.json": "katenary.yaml"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> You can, of course, replace the `master` with a specific tag or branch.
|
||||
|
||||
## What a name…
|
||||
|
||||
Katenary is the stylized name of the project that comes from the "catenary" word.
|
||||
|
||||
A catenary is a curve formed by a wire, rope, or chain hanging freely from two points that are not in the same vertical
|
||||
line. For example, the anchor chain between a boat and the anchor.
|
||||
|
||||
This curved link represents what we try to do, the project is a stretched link from docker-compose to helm chart.
|
||||
|
320
cmd/katenary/main.go
Normal file
@@ -0,0 +1,320 @@
|
||||
// Katenary CLI, main package.
|
||||
//
|
||||
// This package is not intended to be imported. It contains the
|
||||
// main function that build the command line with `cobra` package.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"katenary.io/internal/generator"
|
||||
"katenary.io/internal/generator/katenaryfile"
|
||||
"katenary.io/internal/generator/labels"
|
||||
"katenary.io/internal/utils"
|
||||
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const longHelp = `Katenary is a tool to convert compose files to Helm Charts.
|
||||
|
||||
Each [command] and subcommand has got an "help" and "--help" flag to show more information.
|
||||
`
|
||||
|
||||
func main() {
|
||||
rootCmd := buildRootCmd()
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func buildRootCmd() *cobra.Command {
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "katenary",
|
||||
Long: longHelp,
|
||||
Short: "Katenary is a tool to convert docker-compose files to Helm Charts",
|
||||
}
|
||||
rootCmd.Example = ` katenary convert -c docker-compose.yml -o ./charts`
|
||||
|
||||
rootCmd.Version = generator.GetVersion()
|
||||
rootCmd.CompletionOptions.DisableDescriptions = false
|
||||
rootCmd.CompletionOptions.DisableNoDescFlag = false
|
||||
|
||||
rootCmd.AddCommand(
|
||||
generateCompletionCommand(rootCmd.Name()),
|
||||
generateVersionCommand(),
|
||||
generateConvertCommand(),
|
||||
generateHashComposefilesCommand(),
|
||||
generateLabelHelpCommand(),
|
||||
generateSchemaCommand(),
|
||||
)
|
||||
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
const completionHelp = `To load completions:
|
||||
|
||||
Bash:
|
||||
# Add this line in your ~/.bashrc or ~/.bash_profile file
|
||||
$ source <(%[1]s completion bash)
|
||||
|
||||
# Or, you can load completions for each users session. Execute once:
|
||||
# Linux:
|
||||
$ %[1]s completion bash > /etc/bash_completion.d/%[1]s
|
||||
# macOS:
|
||||
$ %[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s
|
||||
|
||||
Zsh:
|
||||
# If shell completion is not already enabled in your environment,
|
||||
# you will need to enable it. You can execute the following once:
|
||||
|
||||
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
|
||||
|
||||
# To load completions for each session, execute once:
|
||||
$ %[1]s completion zsh > "${fpath[1]}/_%[1]s"
|
||||
|
||||
# You will need to start a new shell for this setup to take effect.
|
||||
|
||||
fish:
|
||||
$ %[1]s completion fish | source
|
||||
|
||||
# To load completions for each session, execute once:
|
||||
$ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish
|
||||
|
||||
PowerShell:
|
||||
PS> %[1]s completion powershell | Out-String | Invoke-Expression
|
||||
|
||||
# To load completions for every new session, run:
|
||||
PS> %[1]s completion powershell > %[1]s.ps1
|
||||
# and source this file from your PowerShell profile.
|
||||
`
|
||||
|
||||
func generateCompletionCommand(name string) *cobra.Command {
|
||||
bashV1 := false
|
||||
cmd := &cobra.Command{
|
||||
Use: "completion",
|
||||
DisableFlagsInUseLine: true,
|
||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||
Short: "Generates completion scripts",
|
||||
Long: fmt.Sprintf(completionHelp, name),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return cmd.Help()
|
||||
}
|
||||
switch args[0] {
|
||||
case "bash":
|
||||
// get the bash version
|
||||
if cmd.Flags().Changed("bash-v1") {
|
||||
return cmd.Root().GenBashCompletion(os.Stdout)
|
||||
}
|
||||
return cmd.Root().GenBashCompletionV2(os.Stdout, true)
|
||||
case "zsh":
|
||||
return cmd.Root().GenZshCompletion(os.Stdout)
|
||||
case "fish":
|
||||
return cmd.Root().GenFishCompletion(os.Stdout, true)
|
||||
case "powershell":
|
||||
return cmd.Root().GenPowerShellCompletion(os.Stdout)
|
||||
}
|
||||
|
||||
return fmt.Errorf("unknown completion type: %s", args[0])
|
||||
},
|
||||
}
|
||||
|
||||
// add a flag to force bash completion v1
|
||||
cmd.Flags().Bool("bash-v1", bashV1, "Force bash completion v1")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func generateConvertCommand() *cobra.Command {
|
||||
force := false
|
||||
outputDir := "./chart"
|
||||
dockerComposeFile := make([]string, 0)
|
||||
profiles := make([]string, 0)
|
||||
helmdepUpdate := false
|
||||
var appVersion *string
|
||||
givenAppVersion := ""
|
||||
chartVersion := "0.1.0"
|
||||
icon := ""
|
||||
envFiles := []string{}
|
||||
|
||||
convertCmd := &cobra.Command{
|
||||
Use: "convert",
|
||||
Short: "Converts a docker-compose file to a Helm Chart",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(strings.TrimSpace(givenAppVersion)) > 0 {
|
||||
appVersion = &givenAppVersion
|
||||
}
|
||||
return generator.Convert(generator.ConvertOptions{
|
||||
Force: force,
|
||||
OutputDir: outputDir,
|
||||
Profiles: profiles,
|
||||
HelmUpdate: helmdepUpdate,
|
||||
AppVersion: appVersion,
|
||||
ChartVersion: chartVersion,
|
||||
Icon: icon,
|
||||
EnvFiles: envFiles,
|
||||
}, dockerComposeFile...)
|
||||
},
|
||||
}
|
||||
|
||||
convertCmd.Flags().BoolVarP(
|
||||
&force,
|
||||
"force",
|
||||
"f",
|
||||
force,
|
||||
"Force the overwrite of the chart directory",
|
||||
)
|
||||
convertCmd.Flags().BoolVarP(
|
||||
&helmdepUpdate,
|
||||
"helm-update",
|
||||
"u",
|
||||
helmdepUpdate,
|
||||
"Update helm dependencies if helm is installed",
|
||||
)
|
||||
convertCmd.Flags().StringSliceVarP(
|
||||
&profiles,
|
||||
"profile",
|
||||
"p",
|
||||
profiles,
|
||||
"Specify the profiles to use",
|
||||
)
|
||||
convertCmd.Flags().StringVarP(
|
||||
&outputDir,
|
||||
"output-dir",
|
||||
"o",
|
||||
outputDir,
|
||||
"Specify the output directory",
|
||||
)
|
||||
convertCmd.Flags().StringSliceVarP(
|
||||
&dockerComposeFile,
|
||||
"compose-file",
|
||||
"c",
|
||||
cli.DefaultFileNames,
|
||||
"Specify an alternate compose files - can be specified multiple times or use coma to separate them.\n"+
|
||||
"Note that overides files are also used whatever the files you specify here.\nThe overides files are:\n"+
|
||||
strings.Join(cli.DefaultOverrideFileNames, ", \n")+
|
||||
"\n",
|
||||
)
|
||||
convertCmd.Flags().StringVarP(
|
||||
&givenAppVersion,
|
||||
"app-version",
|
||||
"a",
|
||||
"",
|
||||
"Specify the app version (in Chart.yaml)",
|
||||
)
|
||||
convertCmd.Flags().StringVarP(
|
||||
&chartVersion,
|
||||
"chart-version",
|
||||
"v",
|
||||
chartVersion,
|
||||
"Specify the chart version (in Chart.yaml)",
|
||||
)
|
||||
convertCmd.Flags().StringVarP(
|
||||
&icon,
|
||||
"icon",
|
||||
"i",
|
||||
"",
|
||||
"Specify the icon (in Chart.yaml), use a valid URL, Helm does not support local files at this time.",
|
||||
)
|
||||
convertCmd.Flags().StringSliceVarP(
|
||||
&envFiles,
|
||||
"env-file",
|
||||
"e",
|
||||
envFiles,
|
||||
"Specify the env file to use additonnaly to the .env file. Can be specified multiple times.",
|
||||
)
|
||||
|
||||
return convertCmd
|
||||
}
|
||||
|
||||
func generateVersionCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the version number of Katenary",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println(generator.GetVersion())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func generateLabelHelpCommand() *cobra.Command {
|
||||
markdown := false
|
||||
all := false
|
||||
cmd := &cobra.Command{
|
||||
Use: "help-labels [label]",
|
||||
Short: "Print the labels help for all or a specific label",
|
||||
Long: `Print the labels help for all or a specific label
|
||||
If no label is specified, the help for all labels is printed.
|
||||
If a label is specified, the help for this label is printed.
|
||||
|
||||
The name of the label must be specified without the prefix ` + labels.Prefix() + `.
|
||||
|
||||
e.g.
|
||||
kanetary help-labels
|
||||
katenary help-labels ingress
|
||||
katenary help-labels map-env
|
||||
`,
|
||||
ValidArgs: labels.GetLabelNames(),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) > 0 {
|
||||
fmt.Println(labels.GetLabelHelpFor(args[0], markdown))
|
||||
return
|
||||
}
|
||||
if all {
|
||||
// show the help for all labels
|
||||
l := len(labels.GetLabelNames())
|
||||
for i, label := range labels.GetLabelNames() {
|
||||
fmt.Println(labels.GetLabelHelpFor(label, markdown))
|
||||
if !markdown && i < l-1 {
|
||||
fmt.Println(strings.Repeat("-", 80))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
fmt.Println(labels.GetLabelHelp(markdown))
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVarP(&markdown, "markdown", "m", markdown, "Use the markdown format")
|
||||
cmd.Flags().BoolVarP(&all, "all", "a", all, "Print the full help for all labels")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func generateHashComposefilesCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "hash-composefiles [composefile]",
|
||||
Short: "Print the hash of the composefiles",
|
||||
Long: `Print the hash of the composefiles
|
||||
If no composefile is specified, the hash of all composefiles is printed.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) > 0 {
|
||||
if hash, err := utils.HashComposefiles(args); err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
fmt.Println(hash)
|
||||
}
|
||||
return
|
||||
}
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func generateSchemaCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "schema",
|
||||
Short: "Print the schema of the katenary file",
|
||||
Long: "Generate a schama for katenary.yaml file that can be used to validate the file or to use with yaml LSP to complete and check your configuration.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println(katenaryfile.GenerateSchema())
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
78
cmd/katenary/main_test.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildCommand(t *testing.T) {
|
||||
rootCmd := buildRootCmd()
|
||||
if rootCmd == nil {
|
||||
t.Errorf("Expected rootCmd to be defined")
|
||||
} else if rootCmd.Use != "katenary" {
|
||||
t.Errorf("Expected rootCmd.Use to be katenary, got %s", rootCmd.Use)
|
||||
}
|
||||
numCommands := 6
|
||||
if len(rootCmd.Commands()) != numCommands {
|
||||
t.Errorf("Expected %d command, got %d", numCommands, len(rootCmd.Commands()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVersion(t *testing.T) {
|
||||
cmd := buildRootCmd()
|
||||
if cmd == nil {
|
||||
t.Errorf("Expected cmd to be defined")
|
||||
}
|
||||
version := generateVersionCommand()
|
||||
old := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
version.Run(cmd, nil)
|
||||
|
||||
w.Close()
|
||||
os.Stdout = old
|
||||
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, "(devel)") {
|
||||
t.Errorf("Expected output to contain '(devel)', got %s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSchemaCommand(t *testing.T) {
|
||||
cmd := buildRootCmd()
|
||||
if cmd == nil {
|
||||
t.Errorf("Expected cmd to be defined")
|
||||
}
|
||||
schema := generateSchemaCommand()
|
||||
old := os.Stdout
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create pipe: %v", err)
|
||||
}
|
||||
os.Stdout = w
|
||||
var buf bytes.Buffer
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
schema.Run(cmd, nil)
|
||||
w.Close()
|
||||
close(done)
|
||||
}()
|
||||
io.Copy(&buf, r)
|
||||
<-done
|
||||
|
||||
os.Stdout = old
|
||||
|
||||
// try to parse json
|
||||
schemaContent := make(map[string]any)
|
||||
if err := json.Unmarshal(buf.Bytes(), &schemaContent); err != nil {
|
||||
t.Errorf("Expected valid json")
|
||||
}
|
||||
}
|
141
cmd/main.go
@@ -1,141 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"katenary/compose"
|
||||
"katenary/generator"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
composeFiles = []string{"docker-compose.yaml", "docker-compose.yml"}
|
||||
ComposeFile = ""
|
||||
AppName = "MyApp"
|
||||
ChartsDir = "chart"
|
||||
Version = "master"
|
||||
AppVersion = "0.0.1"
|
||||
)
|
||||
|
||||
func init() {
|
||||
FindComposeFile()
|
||||
SetAppName()
|
||||
SetAppVersion()
|
||||
}
|
||||
|
||||
func FindComposeFile() bool {
|
||||
for _, file := range composeFiles {
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
ComposeFile = file
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetAppName sets the application name from the current directory name.
|
||||
func SetAppName() {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
AppName = filepath.Base(wd)
|
||||
|
||||
if AppName == "" {
|
||||
AppName = "MyApp"
|
||||
}
|
||||
}
|
||||
|
||||
// SetAppVersion set the AppVersion variable to the git version/tag
|
||||
func SetAppVersion() {
|
||||
AppVersion, _ = detectGitVersion()
|
||||
}
|
||||
|
||||
// Try to detect the git version/tag.
|
||||
func detectGitVersion() (string, error) {
|
||||
defaulVersion := "0.0.1"
|
||||
// Check if .git directory exists
|
||||
if s, err := os.Stat(".git"); err != nil {
|
||||
// .git should be a directory
|
||||
return defaulVersion, errors.New("no git repository found")
|
||||
} else if !s.IsDir() {
|
||||
// .git should be a directory
|
||||
return defaulVersion, errors.New(".git is not a directory")
|
||||
}
|
||||
|
||||
// check if "git" executable is callable
|
||||
if _, err := exec.LookPath("git"); err != nil {
|
||||
return defaulVersion, errors.New("git executable not found")
|
||||
}
|
||||
|
||||
// get the latest commit hash
|
||||
if out, err := exec.Command("git", "log", "-n1", "--pretty=format:%h").Output(); err == nil {
|
||||
latestCommit := strings.TrimSpace(string(out))
|
||||
// then get the current branch/tag
|
||||
out, err := exec.Command("git", "branch", "--show-current").Output()
|
||||
if err != nil {
|
||||
return defaulVersion, errors.New("git branch --show-current failed")
|
||||
} else {
|
||||
currentBranch := strings.TrimSpace(string(out))
|
||||
// finally, check if the current tag (if exists) correspond to the current commit
|
||||
// git describe --exact-match --tags <latestCommit>
|
||||
out, err := exec.Command("git", "describe", "--exact-match", "--tags", latestCommit).Output()
|
||||
if err == nil {
|
||||
return strings.TrimSpace(string(out)), nil
|
||||
} else {
|
||||
return currentBranch + "-" + latestCommit, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaulVersion, errors.New("git log failed")
|
||||
}
|
||||
|
||||
func Convert(composeFile, appVersion, appName, chartDir string, force bool) {
|
||||
composeFound := FindComposeFile()
|
||||
_, err := os.Stat(ComposeFile)
|
||||
if !composeFound && err != nil {
|
||||
fmt.Println("No compose file found")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
dirname := filepath.Join(chartDir, appName)
|
||||
if _, err := os.Stat(dirname); err == nil && !force {
|
||||
response := ""
|
||||
for response != "y" && response != "n" {
|
||||
response = "n"
|
||||
fmt.Printf(""+
|
||||
"The %s directory already exists, it will be \x1b[31;1mremoved\x1b[0m!\n"+
|
||||
"Do you really want to continue? [y/N]: ", dirname)
|
||||
fmt.Scanf("%s", &response)
|
||||
response = strings.ToLower(response)
|
||||
}
|
||||
if response == "n" {
|
||||
fmt.Println("Cancelled")
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup and create the chart directory (until "templates")
|
||||
if err := os.RemoveAll(dirname); err != nil {
|
||||
fmt.Printf("Error removing %s: %s\n", dirname, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// create the templates directory
|
||||
templatesDir := filepath.Join(dirname, "templates")
|
||||
if err := os.MkdirAll(templatesDir, 0755); err != nil {
|
||||
fmt.Printf("Error creating %s: %s\n", templatesDir, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Parse the compose file now
|
||||
p := compose.NewParser(ComposeFile)
|
||||
p.Parse(appName)
|
||||
|
||||
// start generator
|
||||
generator.Generate(p, Version, appName, appVersion, ComposeFile, dirname)
|
||||
|
||||
}
|
@@ -1,96 +0,0 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"katenary/helm"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
ICON_EXCLAMATION = "❕"
|
||||
)
|
||||
|
||||
// Parser is a docker-compose parser.
|
||||
type Parser struct {
|
||||
Data *Compose
|
||||
}
|
||||
|
||||
var Appname = ""
|
||||
|
||||
// NewParser create a Parser and parse the file given in filename.
|
||||
func NewParser(filename string) *Parser {
|
||||
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
c := NewCompose()
|
||||
dec := yaml.NewDecoder(f)
|
||||
dec.Decode(c)
|
||||
|
||||
p := &Parser{Data: c}
|
||||
|
||||
services := make(map[string][]string)
|
||||
// get the service list, to be sure that everything is ok
|
||||
|
||||
for name, s := range c.Services {
|
||||
if portlabel, ok := s.Labels[helm.LABEL_PORT]; ok {
|
||||
services := strings.Split(portlabel, ",")
|
||||
for _, serviceport := range services {
|
||||
portexists := false
|
||||
for _, found := range s.Ports {
|
||||
if found == serviceport {
|
||||
portexists = true
|
||||
}
|
||||
}
|
||||
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 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")
|
||||
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Parser) Parse(appname string) {
|
||||
Appname = appname
|
||||
}
|
@@ -1,39 +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:"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:"environment"`
|
||||
Labels map[string]string `yaml:"labels"`
|
||||
DependsOn []string `yaml:"depends_on"`
|
||||
Volumes []string `yaml:"volumes"`
|
||||
Expose []int `yaml:"expose"`
|
||||
EnvFiles []string `yaml:"env_file"`
|
||||
RawBuild interface{} `yaml:"build"`
|
||||
HealthCheck *HealthCheck `yaml:"healthcheck"`
|
||||
}
|
9
doc/docs/.markdownlint.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
MD012: false
|
||||
MD013: false
|
||||
MD022: false
|
||||
MD033: false
|
||||
MD041: false
|
||||
MD046: false
|
||||
# list indentation
|
||||
MD007:
|
||||
indent: 4
|
105
doc/docs/coding.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# How Katenary works behind the scene
|
||||
|
||||
This section is for developers who want to take part in Katenary. Here we describe how it works and the expected
|
||||
principles.
|
||||
|
||||
## A few important points
|
||||
|
||||
Katenary is developed in Go. The version currently supported is 1.20. For reasons of readability, the `any` type is
|
||||
preferred to `interface{}`.
|
||||
|
||||
Since version v3, Katenary uses, in addition to `go-compose`, the `k8s` library to generate objects that are guaranteed
|
||||
to work before transformation. Katenary adds Helm syntax entries to add loops, transformations, and conditions.
|
||||
|
||||
We really try to follow best practices and code principles. But, Katenary needs a lot of workarounds and string
|
||||
manipulation during the process. There are, also, some drawbacks using standard k8s packages that make a lot of type
|
||||
checks when generating the objects. We need to finalize the values after object generation.
|
||||
|
||||
**This makes the coding a bit harder than simply converting from YAML to YAML.**
|
||||
|
||||
> If Katenary only generated YAML objects, the algorithms would be much simpler and would require less generation work.
|
||||
|
||||
## General principle
|
||||
|
||||
During conversion, the `generator` package is primarily responsible for creating "objects". The principle is to generate
|
||||
one `Deployment` per `compose` service. If the container coming from "compose" exposes ports (explicitly), then a
|
||||
service is created.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
|
||||
D[Deployment]:::outputs@{shape: curv-trap}
|
||||
C[Container List]@{shape: docs}
|
||||
ConfigMap:::outputs@{shape: curv-trap}
|
||||
Secrets:::outputs@{shape: curv-trap}
|
||||
H[Helm Chart.yaml file]:::outputs@{shape: curv-trap}
|
||||
Val[Values files]:::outputs@{shape: curv-trap}
|
||||
PVC:::outputs@{shape: curv-trap}
|
||||
S[Service]:::outputs@{shape: curv-trap}
|
||||
|
||||
A[Compose file]:::inputs --> B[Compose parser]
|
||||
B --> G[Generator]
|
||||
G --> P[Ports exposed to services] ---> S
|
||||
G ------> H
|
||||
G --> C --> D
|
||||
G ------> Val
|
||||
G ....-> M[Merge Continainers if same-pod]
|
||||
M ..-> C
|
||||
G --> E[Environment variables] ----> Secrets & ConfigMap
|
||||
G--> V[Bind volumes] -------> PVC
|
||||
V -----> CF[ Create ConfigMap\nfor static files as\nconfigmap-files] --> ConfigMap
|
||||
|
||||
Secrets & ConfigMap -- create envFrom --> D
|
||||
V -- bind volumes --> D
|
||||
|
||||
```
|
||||
|
||||
If the declaration of a container is to be integrated into another pod (via the `same-pod` label), this `Deployment` and
|
||||
its associated service are still created. They are deleted last, once the merge has been completed.
|
||||
|
||||
## Conversion in "`generator`" package
|
||||
|
||||
The `generator` package is where object struct are defined, and where you can find the `Generate()` function.
|
||||
|
||||
The generation fills `HelmChart` object using a loop:
|
||||
|
||||
```golang
|
||||
for _, service := range project.Services {
|
||||
dep := NewDeployment(service)
|
||||
y, _ := dep.Yaml()
|
||||
chart.Templates[dep.Filename()] = &ChartTemplate{
|
||||
Content: y,
|
||||
Servicename: service.Name,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**A lot** of string manipulations are made by each `Yaml()` methods. This is where you find the complex and impacting
|
||||
operations. The `Yaml` methods **don't return a valid YAML content**. This is a Helm Chart YAML content with template
|
||||
conditions, values and calls to helper templates.
|
||||
|
||||
> The `Yaml()` methods, in each object, need contribution, help, fixes, enhancements... They work, but there is a lot of
|
||||
> complexity. Please, create issues, pull-requests and conversation in the GitHub repository.
|
||||
|
||||
The final step, before sending all templates to chart, is to bind the containers inside the same pod where it's
|
||||
specified.
|
||||
|
||||
For each source container linked to the destination:
|
||||
|
||||
- we get the deployment of the source
|
||||
- we copy the container to the destination deployment
|
||||
- we get the associated service (if any)
|
||||
- we then copy the service port to the destination service
|
||||
- we finally remove the source service and deployment
|
||||
|
||||
> The `Configmap`, secrets, variables... are kept.
|
||||
|
||||
It finally computes the `helper` file.
|
||||
|
||||
## Conversion command
|
||||
|
||||
The `generator` works the same as described above. But the "convert" command makes some final steps:
|
||||
|
||||
- generate `values.yaml` and `Chart.yaml` files from the `HelmChart` object
|
||||
- add comments to the `values.yaml` files
|
||||
- add comments to the `Chart.yaml` files
|
18
doc/docs/dependencies.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Why those dependencies?
|
||||
|
||||
Katenary uses `compose-go` and several Kubernetes official packages.
|
||||
|
||||
- `github.com/compose-spec/compose-go`: to parse compose files. It ensures :
|
||||
- that the project respects the "compose" specification
|
||||
- that Katenary uses the "compose" struct exactly the same way `podman compose` or `docker compose` does
|
||||
- `github.com/spf13/cobra`: to parse command line arguments, sub-commands and flags. It also generates completion for
|
||||
bash, zsh, fish and PowerShell.
|
||||
- `github.com/thediveo/netdb`: to get the standard names of a service from its port number
|
||||
- `gopkg.in/yaml.v3`:
|
||||
- to generate `Chart.yaml` and `values.yaml` files (only)
|
||||
- to parse Katenary labels in the compose file
|
||||
- `k8s.io/api` and `k8s.io/apimachinery` to create Kubernetes objects
|
||||
- `sigs.k8s.io/yaml`: to generate Katenary YAML files in the format of Kubernetes objects
|
||||
|
||||
There are also some other packages used in the project, like `gopkg.in/yaml` to parse labels. I'm sorry to not list the
|
||||
entire dependencies. You can check the `go.mod` file to see all the dependencies.
|
112
doc/docs/faq.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Frequently Asked Questions
|
||||
|
||||
## Why Katenary?
|
||||
|
||||
The main author[^1] of Katenary is a big fan of Podman, Docker and makes a huge use of Compose. He uses it a lot in his
|
||||
daily work. When he started to work with Kubernetes, he wanted to have the same experience as with Docker Compose.
|
||||
He wanted to have a tool that could convert his `docker-compose` files to Kubernetes manifests, but also to Helm charts.
|
||||
|
||||
Kompose was a good option. But the lacks of some options and configuration for the output Helm chart made him think
|
||||
about creating a new tool. He wanted to have a tool that could generate a complete Helm chart, with a lot of options
|
||||
and flexibility.
|
||||
|
||||
[^1]: I'm talking about myself :sunglasses: - Patrice FERLET, aka Metal3d, Tech Lead and DevOps Engineer at Klee Group.
|
||||
|
||||
## What's the difference between Katenary and Kompose?
|
||||
|
||||
[Kompose](https://kompose.io/) is a very nice tool, made by the Kubernetes community. It's a tool to convert
|
||||
`docker-compose` files to Kubernetes manifests. It's a very good tool, and it's more mature than Katenary.
|
||||
|
||||
Kompose is able to generate Helm charts, but [it could be not the case in future releases](https://github.com/kubernetes/kompose/issues/1716) for several reasons[^2].
|
||||
|
||||
[^2]:
|
||||
The author of Kompose explains that they have no bandwidth to maintain the Helm chart generation. It's a complex
|
||||
task, and we can confirm. Katenary takes a lot of time to be developed and maintained. This issue mentions Katenary as
|
||||
an alternative to Helm chart generation :smile:
|
||||
|
||||
The project is focused on Kubernetes manifests and proposes to use "Kustomize" to adapt the manifests. Helm seems to be
|
||||
not the priority.
|
||||
|
||||
Anyway, before this decision, the Helm chart generation was not what we expected. We wanted to have a more complete
|
||||
chart, with more options and more flexibility.
|
||||
|
||||
> That's why we decided to create Katenary.
|
||||
|
||||
Kompose didn't manage to generate a values file, complex volume binding, and many other things. It was also not able
|
||||
to manage dependencies between services.
|
||||
|
||||
> Be sure that we don't want to compete with Kompose. We just want to propose a different approach to the problem.
|
||||
|
||||
Kompose is an excellent tool, and we use it in some projects. It's a good choice if you want to convert
|
||||
your `docker-compose` files to Kubernetes manifests, but if you want to use Helm, Katenary is the tool you need.
|
||||
|
||||
## Why not using "one label" for all the configuration?
|
||||
|
||||
That was a discussion I had with my colleagues. The idea was to use a single label to store all the configuration.
|
||||
But, it's not a good idea.
|
||||
|
||||
Sometimes, you will have a long list of things to configure, like ports, ingress, dependencies, etc. It's better to have
|
||||
a clear and readable configuration. Segmented labels are easier to read and to maintain. It also avoids having too
|
||||
many indentation levels in the YAML file.
|
||||
|
||||
It is also more flexible. You can add or remove labels without changing the others.
|
||||
|
||||
## Why not using a configuration file?
|
||||
|
||||
The idea was to keep the configuration at a same place, and using the go-compose library to read the labels. It's
|
||||
easier to have a single file to manage.
|
||||
|
||||
By the way, Katenary auto accepts a `compose.katenary.yaml` file in the same directory. It's a way to separate the
|
||||
configuration from the compose file. It uses
|
||||
the [overrides' mechanism](https://docs.docker.com/compose/multiple-compose-files/merge/) like "compose" does.
|
||||
|
||||
## Why not developing with Rust?
|
||||
|
||||
Seriously...
|
||||
|
||||
OK, I will answer.
|
||||
|
||||
Rust is a good language. But, Podman, Docker, Kubernetes, Helm, and mostly all technologies around Kubernetes are
|
||||
written in Go. We have a large ecosystem in Go to manipulate, read, and write Kubernetes manifests as parsing
|
||||
Compose files.
|
||||
|
||||
> Go is better for this task.
|
||||
|
||||
There is no reason to use Rust for this project.
|
||||
|
||||
## Any chance to have a GUI?
|
||||
|
||||
Yes, it's a possibility. But, it's not a priority. We have a lot of things to do before. We need to stabilize the
|
||||
project, to have a good documentation, to have a good test coverage, and to have a good community.
|
||||
|
||||
But, in a not so far future, we could have a GUI. The choice of [Fyne.io](https://fyne.io) is already made, and we
|
||||
tested some concepts.
|
||||
|
||||
## I'm rich (or not), I want to help you. How can I do?
|
||||
|
||||
You can help us in many ways.
|
||||
|
||||
- The first things we really need, more than money, more than anything else, is to have feedback. If you use Katenary,
|
||||
if you have some issues, if you have some ideas, please open an issue on the [GitHub repository](https://github.com/Katenary/katenary).
|
||||
- The second thing is to help us to fix issues. If you're a Go developer, or if you want to fix the documentation,
|
||||
your help is greatly appreciated.
|
||||
- And then, of course, we need money, or sponsors.
|
||||
|
||||
### If you're a company
|
||||
|
||||
We will be happy to communicate your help by putting your logo on the website and in the documentation. You can sponsor
|
||||
us by giving us some money, or by giving us some time of your developers, or leaving us some time to work on the project.
|
||||
|
||||
### If you're an individual
|
||||
|
||||
All donators will be listed on the website and in the documentation. You can give us some money by using
|
||||
the [GitHub Sponsors]()
|
||||
|
||||
All main contributors[^3] will be listed on the website and in the documentation.
|
||||
|
||||
> If you want to be anonymous, please tell us.
|
||||
|
||||
[^3]:
|
||||
Main contributors are the people who have made a significant contribution to the project. It could be code,
|
||||
documentation, or any other help. There is no defined rules, at this time, to evaluate the contribution.
|
||||
It's a subjective decision.
|
162
doc/docs/index.md
Normal file
@@ -0,0 +1,162 @@
|
||||
<div class="md-center">
|
||||

|
||||
</div>
|
||||
|
||||
# Welcome to Katenary documentation
|
||||
|
||||
🚀 Unleash Productivity with Katenary! 🚀
|
||||
|
||||
Tired of manual conversions? Katenary harnesses the labels from your "compose" file to craft complete Helm Charts
|
||||
effortlessly, saving you time and energy.
|
||||
|
||||
🛠️ Simple automated CLI: Katenary handles the grunt work, generating everything needed for seamless service binding
|
||||
and Helm Chart creation.
|
||||
|
||||
💡 Effortless Efficiency: You only need to add labels when it's necessary to precise things. Then call `katenary convert`
|
||||
and let the magic happen.
|
||||
|
||||
<div style="margin: auto" class="zoomable">
|
||||

|
||||
</div>
|
||||
|
||||
# What is it?
|
||||
|
||||
Katenary is a tool made to help you to transform "compose" files (`compose.yaml`, `docker-compose.yml`,
|
||||
`podman-compose.yml`...) to complete and production ready [Helm Chart](https://helm.sh).
|
||||
|
||||
You'll be able to deploy your project in [:material-kubernetes: Kubernetes](https://kubernetes.io) in a few seconds
|
||||
(of course, more if you need to tweak with labels).
|
||||
|
||||
It uses your current file and optionally labels to configure the result.
|
||||
|
||||
It's an open source project, under MIT license, originally partially developed at [Smile](https://www.smile.eu).
|
||||
|
||||
Today, it's partially developed in collaboration with [Klee Group](https://www.kleegroup.com). Note that Katenary is
|
||||
and **will stay an open source and free (as freedom) project**. We are convinced that the best way to make it better is to
|
||||
share it with the community.
|
||||
|
||||
<div id="klee">
|
||||

|
||||
</div>
|
||||
|
||||
The main developer is [Patrice FERLET](https://repo.katenary.io/metal3d).
|
||||
|
||||
The project source
|
||||
code is hosted on the [:fontawesome-brands-git: Katenary Repository](https://repo.katenary.io/Katenary/katenary).
|
||||
|
||||
## Install Katenary
|
||||
|
||||
Katenary is developed using the :fontawesome-brands-golang:{ .gopher } [Go](https://go.dev) language.
|
||||
The binary is statically linked, so you can simply download it from the [release
|
||||
page](https://github.com/Katenary/katenary/releases) of the project in GitHub.
|
||||
|
||||
You need to select the right binary for your operating system and architecture, and copy the binary in a directory
|
||||
that is in your `PATH`.
|
||||
|
||||
If you are a Linux user, you can use the "one line installation command" which will download the binary in your
|
||||
`$HOME/.local/bin` directory if it exists.
|
||||
|
||||
```bash
|
||||
sh <(curl -sSL https://repo.katenary.io/Katenary/katenary/raw/branch/master/install.sh)
|
||||
```
|
||||
|
||||
!!! Info "Upgrading is integrated to the `katenary` command"
|
||||
Katenary propose a `upgrade` sub-command to update the current binary to the latest stable release.
|
||||
|
||||
Of course, you need to install Katenary once :smile:
|
||||
|
||||
!!! Note "You prefer to compile it, no need to install Go"
|
||||
You can also build and install it yourself, the provided Makefile has got a `build` command that uses `podman` or
|
||||
`docker` to build the binary.
|
||||
|
||||
So, you don't need to install Go compiler :+1:.
|
||||
|
||||
But, note that the "master" branch is not the "stable" version. It's preferable to switch to a tag, or to use the
|
||||
releases.
|
||||
|
||||
To compile it, you can use the following commands:
|
||||
|
||||
```bash
|
||||
git clone https://repo.katenary.io/Katenary/katenary.git
|
||||
cd katenary
|
||||
make build
|
||||
make install
|
||||
```
|
||||
|
||||
`make install` copies `./katenary` binary to your user binary path (`~/.local/bin`)
|
||||
|
||||
You can install it in other directory by changing the `PREFIX` variable. E.g.:
|
||||
|
||||
```bash
|
||||
make build
|
||||
sudo make install PREFIX=/usr/local
|
||||
```
|
||||
|
||||
Check if everything is OK using `katenary version` and / or `katenary help`
|
||||
|
||||
## Install completion
|
||||
|
||||
Katenary uses the very nice project named `cobra` to manage flags, argument and auto-completion.
|
||||
|
||||
You can activate it with:
|
||||
|
||||
```bash
|
||||
# replace "bash" by "zsh" if needed
|
||||
source <(katenary completion bash)
|
||||
```
|
||||
|
||||
Add this line in you `~/.profile`, `~/.bash_aliases` or `~/.bashrc` file to have completion at startup.
|
||||
|
||||
## What a name
|
||||
|
||||
A catenary is the curve that a hanging chain or cable assumes under its own weight when supported only at its ends.
|
||||
I, the maintainer, decided to name "Katenary" this project because it's like a chain that links a boat to a dock.
|
||||
Making the link between the "compose" world and the "Kubernetes" world is the main goal of this project.
|
||||
|
||||
Anyway, it's too late to change the name now :smile:
|
||||
|
||||
!!! Note "But I like this name!"
|
||||
|
||||
I spent time to find it :wink:
|
||||
|
||||
## Special thanks to
|
||||
|
||||
I really want to thank all the contributors, testers, and of course, the authors of the packages and tools that are used
|
||||
in this project. There is too many to list here. Katenary can work because of all these people. Open source is a great
|
||||
thing! :heart:
|
||||
|
||||
!!! Edit "Special thanks"
|
||||
|
||||
**Katenary is built with:** <br />
|
||||
|
||||
<a href="https://go.dev" target="_blank">:fontawesome-brands-golang:{ .go-logo }</a>
|
||||
|
||||
Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.
|
||||
Because Docker, Podman, Kubernetes, and Helm are written in Go, Katenary is also written in Go and borrows packages
|
||||
from these projects to make it as efficient as possible.
|
||||
|
||||
Thanks to Kubernetes to provide [Kind](https://kind.sigs.k8s.io) that is used to test Katenary locally.
|
||||
|
||||
**Thanks to everyone who contributes to all these projects.**
|
||||
|
||||
Katenary can progress because of all these people. All contributions, as comments, issues, pull requests and
|
||||
feedbacks are welcome.
|
||||
|
||||
**Everything was also possible because of:** <br />
|
||||
|
||||
<ul>
|
||||
<li><a href="https://helm.sh" target="_blank"><img src="https://helm.sh/img/helm.svg" style="height: 1rem"/>
|
||||
Helm</a> that is the main toppic of Katenary, Kubernetes is easier to use with it.</li>
|
||||
<li><a href="https://cobra.dev/"><img src="https://cobra.dev/home/logo.png" style="height: 1rem"/> Cobra</a> that
|
||||
makes command, subcommand and completion possible for Katenary with ease.</li>
|
||||
<li>Podman, Docker, Kubernetes that are the main tools that Katenary is made for.</li>
|
||||
</ul>
|
||||
|
||||
**Documentation is built with:** <br />
|
||||
|
||||
<a href="https://www.mkdocs.org/" target="_blank">MkDocs</a> using <a
|
||||
href="https://squidfunk.github.io/mkdocs-material/" target="_blank">Material for MkDocs</a> theme template.
|
||||
|
||||
## License
|
||||
|
||||
Katenary is an open source project under the MIT license. You can use it, modify it, and distribute it as you want.
|
489
doc/docs/labels.md
Normal file
@@ -0,0 +1,489 @@
|
||||
# Labels documentation
|
||||
|
||||
Katenary proposes labels to set in `compose.yaml` files (or override files) to configure the Helm Chart generation.
|
||||
Because it is sometimes needed to have structured values, it is necessary to use the YAML syntax.
|
||||
While compose labels are string, we can use _here-doc_ syntax using `|` to use YAML multiline as value.
|
||||
|
||||
```yaml
|
||||
label-name: |-
|
||||
# this is actually a multiline string here
|
||||
key1: value1
|
||||
key2: value2
|
||||
```
|
||||
|
||||
Katenary will try to _Unmarshal_ these labels.
|
||||
|
||||
## Label list and types
|
||||
|
||||
<!-- START_LABEL_DOC : do not remove this tag !-->
|
||||
| Label name | Description | Type |
|
||||
| ------------------------------ | ---------------------------------------------------------------- | -------------------------------- |
|
||||
| `katenary.v3/configmap-files` | Inject files as Configmap. | `[]string` |
|
||||
| `katenary.v3/cronjob` | Create a cronjob from the service. | `object` |
|
||||
| `katenary.v3/dependencies` | Add Helm dependencies to the service. | `[]object` |
|
||||
| `katenary.v3/description` | Description of the service | `string` |
|
||||
| `katenary.v3/env-from` | Add environment variables from another service. | `[]string` |
|
||||
| `katenary.v3/exchange-volumes` | Add exchange volumes (empty directory on the node) to share data | `[]object` |
|
||||
| `katenary.v3/health-check` | Health check to be added to the deployment. | `object` |
|
||||
| `katenary.v3/ignore` | Ignore the service | `bool` |
|
||||
| `katenary.v3/ingress` | Ingress rules to be added to the service. | `object` |
|
||||
| `katenary.v3/main-app` | Mark the service as the main app. | `bool` |
|
||||
| `katenary.v3/map-env` | Map env vars from the service to the deployment. | `map[string]string` |
|
||||
| `katenary.v3/ports` | Ports to be added to the service. | `[]uint32` |
|
||||
| `katenary.v3/same-pod` | Move the same-pod deployment to the target deployment. | `string` |
|
||||
| `katenary.v3/secrets` | Env vars to be set as secrets. | `[]string` |
|
||||
| `katenary.v3/values` | Environment variables to be added to the values.yaml | `[]string or map[string]string` |
|
||||
| `katenary.v3/values-from` | Add values from another service. | `map[string]string` |
|
||||
|
||||
<!-- STOP_LABEL_DOC : do not remove this tag !-->
|
||||
|
||||
## Detailed description
|
||||
|
||||
<!-- START_DETAILED_DOC : do not remove this tag !-->
|
||||
### katenary.v3/configmap-files
|
||||
|
||||
Inject files as Configmap.
|
||||
|
||||
**Type**: `[]string`
|
||||
|
||||
It makes a file or directory to be converted to one or more ConfigMaps
|
||||
and mounted in the pod. The file or directory is relative to the
|
||||
service directory.
|
||||
|
||||
If it is a directory, all files inside it are added to the ConfigMap.
|
||||
|
||||
If the directory as subdirectories, so one configmap per subpath are created.
|
||||
|
||||
!!! Warning
|
||||
It is not intended to be used to store an entire project in configmaps.
|
||||
It is intended to be used to store configuration files that are not managed
|
||||
by the application, like nginx configuration files. Keep in mind that your
|
||||
project sources should be stored in an application image or in a storage.
|
||||
|
||||
**Example:**
|
||||
|
||||
```yaml
|
||||
volumes
|
||||
- ./conf.d:/etc/nginx/conf.d
|
||||
labels:
|
||||
katenary.v3/configmap-files: |-
|
||||
- ./conf.d
|
||||
```
|
||||
|
||||
|
||||
### katenary.v3/cronjob
|
||||
|
||||
Create a cronjob from the service.
|
||||
|
||||
**Type**: `object`
|
||||
|
||||
This adds a cronjob to the chart.
|
||||
|
||||
The label value is a YAML object with the following attributes:
|
||||
- command: the command to be executed
|
||||
- schedule: the cron schedule (cron format or @every where "every" is a
|
||||
duration like 1h30m, daily, hourly...)
|
||||
- rbac: false (optionnal), if true, it will create a role, a rolebinding and
|
||||
a serviceaccount to make your cronjob able to connect the Kubernetes API
|
||||
|
||||
**Example:**
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
katenary.v3/cronjob: |-
|
||||
command: echo "hello world"
|
||||
schedule: "* */1 * * *" # or @hourly for example
|
||||
```
|
||||
|
||||
|
||||
### katenary.v3/dependencies
|
||||
|
||||
Add Helm dependencies to the service.
|
||||
|
||||
**Type**: `[]object`
|
||||
|
||||
Set the service to be, actually, a Helm dependency. This means that the
|
||||
service will not be exported as template. The dependencies are added to
|
||||
the Chart.yaml file and the values are added to the values.yaml file.
|
||||
|
||||
It's a list of objects with the following attributes:
|
||||
|
||||
- name: the name of the dependency
|
||||
- repository: the repository of the dependency
|
||||
- alias: the name of the dependency in values.yaml (optional)
|
||||
- values: the values to be set in values.yaml (optional)
|
||||
|
||||
!!! Info
|
||||
Katenary doesn't update the helm depenedencies by default.
|
||||
|
||||
Use `--helm-update` (or `-u`) flag to update the dependencies.
|
||||
|
||||
example: <code>katenary convert -u</code>
|
||||
|
||||
By setting an alias, it is possible to change the name of the dependency
|
||||
in values.yaml.
|
||||
|
||||
**Example:**
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
katenary.v3/dependencies: |-
|
||||
- name: mariadb
|
||||
repository: oci://registry-1.docker.io/bitnamicharts
|
||||
|
||||
## optional, it changes the name of the section in values.yaml
|
||||
# alias: mydatabase
|
||||
|
||||
## optional, it adds the values to values.yaml
|
||||
values:
|
||||
auth:
|
||||
database: mydatabasename
|
||||
username: myuser
|
||||
password: the secret password
|
||||
```
|
||||
|
||||
|
||||
### katenary.v3/description
|
||||
|
||||
Description of the service
|
||||
|
||||
**Type**: `string`
|
||||
|
||||
This replaces the default comment in values.yaml file to the given description.
|
||||
It is useful to document the service and configuration.
|
||||
|
||||
The value can be set with a documentation in multiline format.
|
||||
|
||||
**Example:**
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
katenary.v3/description: |-
|
||||
This is a description of the service.
|
||||
It can be multiline.
|
||||
```
|
||||
|
||||
|
||||
### katenary.v3/env-from
|
||||
|
||||
Add environment variables from another service.
|
||||
|
||||
**Type**: `[]string`
|
||||
|
||||
It adds environment variables from another service to the current service.
|
||||
|
||||
**Example:**
|
||||
|
||||
```yaml
|
||||
service1:
|
||||
image: nginx:1.19
|
||||
environment:
|
||||
FOO: bar
|
||||
|
||||
service2:
|
||||
image: php:7.4-fpm
|
||||
labels:
|
||||
# get the congigMap from service1 where FOO is
|
||||
# defined inside this service too
|
||||
katenary.v3/env-from: |-
|
||||
- myservice1
|
||||
```
|
||||
|
||||
|
||||
### katenary.v3/exchange-volumes
|
||||
|
||||
Add exchange volumes (empty directory on the node) to share data
|
||||
|
||||
**Type**: `[]object`
|
||||
|
||||
This label allows sharing data between containres. The volume is created in
|
||||
the node and mounted in the pod. It is useful to share data between containers
|
||||
in a "same pod" logic. For example to let PHP-FPM and Nginx share the same direcotory.
|
||||
|
||||
This will create:
|
||||
|
||||
- an `emptyDir` volume in the deployment
|
||||
- a `voumeMount` in the pod for **each container**
|
||||
- a `initContainer` for each definition
|
||||
|
||||
Fields:
|
||||
- name: the name of the volume (manadatory)
|
||||
- mountPath: the path where the volume is mounted in the pod (optional, default is `/opt`)
|
||||
- init: a command to run to initialize the volume with data (optional)
|
||||
|
||||
!!! Warning
|
||||
This is highly experimental. This is mainly useful when using the "same-pod" label.
|
||||
|
||||
**Example:**
|
||||
|
||||
```yaml
|
||||
nginx:
|
||||
# ...
|
||||
labels;
|
||||
katenary.v3/exchange-volumes: |-
|
||||
- name: php-fpm
|
||||
mountPath: /var/www/html
|
||||
php:
|
||||
# ...
|
||||
labels:
|
||||
katenary.v3/exchange-volumes: |-
|
||||
- name: php-fpm
|
||||
mountPath: /opt
|
||||
init: cp -ra /var/www/html/* /opt
|
||||
```
|
||||
|
||||
|
||||
### katenary.v3/health-check
|
||||
|
||||
Health check to be added to the deployment.
|
||||
|
||||
**Type**: `object`
|
||||
|
||||
Health check to be added to the deployment.
|
||||
|
||||
**Example:**
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
katenary.v3/health-check: |-
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
```
|
||||
|
||||
|
||||
### katenary.v3/ignore
|
||||
|
||||
Ignore the service
|
||||
|
||||
**Type**: `bool`
|
||||
|
||||
Ingoring a service to not be exported in helm chart.
|
||||
|
||||
**Example:**
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
katenary.v3/ignore: "true"
|
||||
```
|
||||
|
||||
|
||||
### katenary.v3/ingress
|
||||
|
||||
Ingress rules to be added to the service.
|
||||
|
||||
**Type**: `object`
|
||||
|
||||
Declare an ingress rule for the service. The port should be exposed or
|
||||
declared with `katenary.v3/ports`.
|
||||
|
||||
**Example:**
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
katenary.v3/ingress: |-
|
||||
port: 80
|
||||
hostname: mywebsite.com (optional)
|
||||
```
|
||||
|
||||
|
||||
### katenary.v3/main-app
|
||||
|
||||
Mark the service as the main app.
|
||||
|
||||
**Type**: `bool`
|
||||
|
||||
This makes the service to be the main application. Its image tag is
|
||||
considered to be the Chart appVersion and to be the defaultvalue in Pod
|
||||
container image attribute.
|
||||
|
||||
!!! Warning
|
||||
This label cannot be repeated in others services. If this label is
|
||||
set in more than one service as true, Katenary will return an error.
|
||||
|
||||
**Example:**
|
||||
|
||||
```yaml
|
||||
ghost:
|
||||
image: ghost:1.25.5
|
||||
labels:
|
||||
# The chart is now named ghost, and the appVersion is 1.25.5.
|
||||
# In Deployment, the image attribute is set to ghost:1.25.5 if
|
||||
# you don't change the "tag" attribute in values.yaml
|
||||
katenary.v3/main-app: true
|
||||
```
|
||||
|
||||
|
||||
### katenary.v3/map-env
|
||||
|
||||
Map env vars from the service to the deployment.
|
||||
|
||||
**Type**: `map[string]string`
|
||||
|
||||
Because you may need to change the variable for Kubernetes, this label
|
||||
forces the value to another. It is also particullary helpful to use a template
|
||||
value instead. For example, you could bind the value to a service name
|
||||
with Helm attributes:
|
||||
`{{ tpl .Release.Name . }}`.
|
||||
|
||||
If you use `__APP__` in the value, it will be replaced by the Chart name.
|
||||
|
||||
**Example:**
|
||||
|
||||
```yaml
|
||||
env:
|
||||
DB_HOST: database
|
||||
RUNNING: docker
|
||||
OTHER: value
|
||||
labels:
|
||||
katenary.v3/map-env: |-
|
||||
RUNNING: kubernetes
|
||||
DB_HOST: '{{ include "__APP__.fullname" . }}-database'
|
||||
```
|
||||
|
||||
|
||||
### katenary.v3/ports
|
||||
|
||||
Ports to be added to the service.
|
||||
|
||||
**Type**: `[]uint32`
|
||||
|
||||
Only useful for services without exposed port. It is mandatory if the
|
||||
service is a dependency of another service.
|
||||
|
||||
**Example:**
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
katenary.v3/ports: |-
|
||||
- 8080
|
||||
- 8081
|
||||
```
|
||||
|
||||
|
||||
### katenary.v3/same-pod
|
||||
|
||||
Move the same-pod deployment to the target deployment.
|
||||
|
||||
**Type**: `string`
|
||||
|
||||
This will make the service to be included in another service pod. Some services
|
||||
must work together in the same pod, like a sidecar or a proxy or nginx + php-fpm.
|
||||
|
||||
Note that volume and VolumeMount are copied from the source to the target
|
||||
deployment.
|
||||
|
||||
**Example:**
|
||||
|
||||
```yaml
|
||||
web:
|
||||
image: nginx:1.19
|
||||
|
||||
php:
|
||||
image: php:7.4-fpm
|
||||
labels:
|
||||
katenary.v3/same-pod: web
|
||||
```
|
||||
|
||||
|
||||
### katenary.v3/secrets
|
||||
|
||||
Env vars to be set as secrets.
|
||||
|
||||
**Type**: `[]string`
|
||||
|
||||
This label allows setting the environment variables as secrets. The variable
|
||||
is removed from the environment and added to a secret object.
|
||||
|
||||
The variable can be set to the `katenary.v3/values` too,
|
||||
so the secret value can be configured in values.yaml
|
||||
|
||||
**Example:**
|
||||
|
||||
```yaml
|
||||
env:
|
||||
PASSWORD: a very secret password
|
||||
NOT_A_SECRET: a public value
|
||||
labels:
|
||||
katenary.v3/secrets: |-
|
||||
- PASSWORD
|
||||
```
|
||||
|
||||
|
||||
### katenary.v3/values
|
||||
|
||||
Environment variables to be added to the values.yaml
|
||||
|
||||
**Type**: `[]string or map[string]string`
|
||||
|
||||
By default, all environment variables in the "env" and environment
|
||||
files are added to configmaps with the static values set. This label
|
||||
allows adding environment variables to the values.yaml file.
|
||||
|
||||
Note that the value inside the configmap is `{{ tpl vaname . }}`, so
|
||||
you can set the value to a template that will be rendered with the
|
||||
values.yaml file.
|
||||
|
||||
The value can be set with a documentation. This may help to understand
|
||||
the purpose of the variable.
|
||||
|
||||
**Example:**
|
||||
|
||||
```yaml
|
||||
env:
|
||||
FOO: bar
|
||||
DB_NAME: mydb
|
||||
TO_CONFIGURE: something that can be changed in values.yaml
|
||||
A_COMPLEX_VALUE: example
|
||||
labels:
|
||||
katenary.v3/values: |-
|
||||
# simple values, set as is in values.yaml
|
||||
- TO_CONFIGURE
|
||||
# complex values, set as a template in values.yaml with a documentation
|
||||
- A_COMPLEX_VALUE: |-
|
||||
This is the documentation for the variable to
|
||||
configure in values.yaml.
|
||||
It can be, of course, a multiline text.
|
||||
```
|
||||
|
||||
|
||||
### katenary.v3/values-from
|
||||
|
||||
Add values from another service.
|
||||
|
||||
**Type**: `map[string]string`
|
||||
|
||||
This label allows adding values from another service to the current service.
|
||||
It avoid duplicating values, environment or secrets that should be the same.
|
||||
|
||||
The key is the value to be added, and the value is the "key" to fetch in the
|
||||
form `service_name.environment_name`.
|
||||
|
||||
**Example:**
|
||||
|
||||
```yaml
|
||||
database:
|
||||
image: mariadb:10.5
|
||||
environment:
|
||||
MARIADB_USER: myuser
|
||||
MARIADB_PASSWORD: mypassword
|
||||
labels:
|
||||
# we can declare secrets
|
||||
katenary.v3/secrets: |-
|
||||
- MARIADB_PASSWORD
|
||||
php:
|
||||
image: php:7.4-fpm
|
||||
environment:
|
||||
# it's duplicated in docker / podman
|
||||
DB_USER: myuser
|
||||
DB_PASSWORD: mypassword
|
||||
labels:
|
||||
# removes the duplicated, use the configMap and secrets from "database"
|
||||
katenary.v3/values-from: |-
|
||||
DB_USER: database.MARIADB_USER
|
||||
DB_PASSWORD: database.MARIADB_PASSWORD
|
||||
```
|
||||
|
||||
|
||||
<!-- STOP_DETAILED_DOC : do not remove this tag !-->
|
12
doc/docs/packages/cmd/katenary.md
Normal file
@@ -0,0 +1,12 @@
|
||||
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
|
||||
|
||||
# katenary
|
||||
|
||||
```go
|
||||
import "repo.katenary.io/katenary/katenary/cmd/katenary"
|
||||
```
|
||||
|
||||
Katenary CLI, main package.
|
||||
|
||||
This package is not intended to be imported. It contains the main function that build the command line with \`cobra\` package.
|
||||
|
923
doc/docs/packages/internal/generator.md
Normal file
@@ -0,0 +1,923 @@
|
||||
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
|
||||
|
||||
# generator
|
||||
|
||||
```go
|
||||
import "repo.katenary.io/katenary/katenary/internal/generator"
|
||||
```
|
||||
|
||||
Package generator generates kubernetes objects from a "compose" file and transforms them into a helm chart.
|
||||
|
||||
The generator package is the core of katenary. It is responsible for generating kubernetes objects from a compose file and transforming them into a helm chart. Conversion manipulates Yaml representation of kubernetes object to add conditions, labels, annotations, etc. to the objects. It also create the values to be set to the values.yaml file.
|
||||
|
||||
The generate.Convert\(\) create an HelmChart object and call "Generate\(\)" method to convert from a compose file to a helm chart. It saves the helm chart in the given directory.
|
||||
|
||||
If you want to change or override the write behavior, you can use the HelmChart.Generate\(\) function and implement your own write function. This function returns the helm chart object containing all kubernetes objects and helm chart ingormation. It does not write the helm chart to the disk.
|
||||
|
||||
## Variables
|
||||
|
||||
<a name="Annotations"></a>
|
||||
|
||||
```go
|
||||
var (
|
||||
|
||||
// Standard annotationss
|
||||
Annotations = map[string]string{
|
||||
labels.LabelName("version"): Version,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
<a name="Version"></a>Version is the version of katenary. It is set at compile time.
|
||||
|
||||
```go
|
||||
var Version = "master" // changed at compile time
|
||||
```
|
||||
|
||||
<a name="Convert"></a>
|
||||
## func [Convert](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/converter.go#L100>)
|
||||
|
||||
```go
|
||||
func Convert(config ConvertOptions, dockerComposeFile ...string) error
|
||||
```
|
||||
|
||||
Convert a compose \(docker, podman...\) project to a helm chart. It calls Generate\(\) to generate the chart and then write it to the disk.
|
||||
|
||||
<a name="GetLabels"></a>
|
||||
## func [GetLabels](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels.go#L13>)
|
||||
|
||||
```go
|
||||
func GetLabels(serviceName, appName string) map[string]string
|
||||
```
|
||||
|
||||
GetLabels returns the labels for a service. It uses the appName to replace the \_\_replace\_\_ in the labels. This is used to generate the labels in the templates.
|
||||
|
||||
<a name="GetMatchLabels"></a>
|
||||
## func [GetMatchLabels](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels.go#L26>)
|
||||
|
||||
```go
|
||||
func GetMatchLabels(serviceName, appName string) map[string]string
|
||||
```
|
||||
|
||||
GetMatchLabels returns the matchLabels for a service. It uses the appName to replace the \_\_replace\_\_ in the labels. This is used to generate the matchLabels in the templates.
|
||||
|
||||
<a name="GetVersion"></a>
|
||||
## func [GetVersion](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/version.go#L14>)
|
||||
|
||||
```go
|
||||
func GetVersion() string
|
||||
```
|
||||
|
||||
GetVersion return the version of katneary. It's important to understand that the version is set at compile time for the github release. But, it the user get katneary using \`go install\`, the version should be different.
|
||||
|
||||
<a name="Helper"></a>
|
||||
## func [Helper](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/helper.go#L16>)
|
||||
|
||||
```go
|
||||
func Helper(name string) string
|
||||
```
|
||||
|
||||
Helper returns the \_helpers.tpl file for a chart.
|
||||
|
||||
<a name="NewCronJob"></a>
|
||||
## func [NewCronJob](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/cronJob.go#L29>)
|
||||
|
||||
```go
|
||||
func NewCronJob(service types.ServiceConfig, chart *HelmChart, appName string) (*CronJob, *RBAC)
|
||||
```
|
||||
|
||||
NewCronJob creates a new CronJob from a compose service. The appName is the name of the application taken from the project name.
|
||||
|
||||
<a name="ToK8SYaml"></a>
|
||||
## func [ToK8SYaml](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/utils.go#L91>)
|
||||
|
||||
```go
|
||||
func ToK8SYaml(obj any) ([]byte, error)
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="UnWrapTPL"></a>
|
||||
## func [UnWrapTPL](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/utils.go#L87>)
|
||||
|
||||
```go
|
||||
func UnWrapTPL(in []byte) []byte
|
||||
```
|
||||
|
||||
UnWrapTPL removes the line wrapping from a template.
|
||||
|
||||
<a name="ChartTemplate"></a>
|
||||
## type [ChartTemplate](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/chart.go#L22-L25>)
|
||||
|
||||
ChartTemplate is a template of a chart. It contains the content of the template and the name of the service. This is used internally to generate the templates.
|
||||
|
||||
```go
|
||||
type ChartTemplate struct {
|
||||
Servicename string
|
||||
Content []byte
|
||||
}
|
||||
```
|
||||
|
||||
<a name="ConfigMap"></a>
|
||||
## type [ConfigMap](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/configMap.go#L38-L43>)
|
||||
|
||||
ConfigMap is a kubernetes ConfigMap. Implements the DataMap interface.
|
||||
|
||||
```go
|
||||
type ConfigMap struct {
|
||||
*corev1.ConfigMap
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
```
|
||||
|
||||
<a name="NewConfigMap"></a>
|
||||
### func [NewConfigMap](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/configMap.go#L47>)
|
||||
|
||||
```go
|
||||
func NewConfigMap(service types.ServiceConfig, appName string, forFile bool) *ConfigMap
|
||||
```
|
||||
|
||||
NewConfigMap creates a new ConfigMap from a compose service. The appName is the name of the application taken from the project name. The ConfigMap is filled by environment variables and labels "map\-env".
|
||||
|
||||
<a name="NewConfigMapFromDirectory"></a>
|
||||
### func [NewConfigMapFromDirectory](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/configMap.go#L120>)
|
||||
|
||||
```go
|
||||
func NewConfigMapFromDirectory(service types.ServiceConfig, appName, path string) *ConfigMap
|
||||
```
|
||||
|
||||
NewConfigMapFromDirectory creates a new ConfigMap from a compose service. This path is the path to the file or directory. If the path is a directory, all files in the directory are added to the ConfigMap. Each subdirectory are ignored. Note that the Generate\(\) function will create the subdirectories ConfigMaps.
|
||||
|
||||
<a name="ConfigMap.AddBinaryData"></a>
|
||||
### func \(\*ConfigMap\) [AddBinaryData](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/configMap.go#L158>)
|
||||
|
||||
```go
|
||||
func (c *ConfigMap) AddBinaryData(key string, value []byte)
|
||||
```
|
||||
|
||||
AddBinaryData adds binary data to the configmap. Append or overwrite the value if the key already exists.
|
||||
|
||||
<a name="ConfigMap.AddData"></a>
|
||||
### func \(\*ConfigMap\) [AddData](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/configMap.go#L153>)
|
||||
|
||||
```go
|
||||
func (c *ConfigMap) AddData(key, value string)
|
||||
```
|
||||
|
||||
AddData adds a key value pair to the configmap. Append or overwrite the value if the key already exists.
|
||||
|
||||
<a name="ConfigMap.AppendDir"></a>
|
||||
### func \(\*ConfigMap\) [AppendDir](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/configMap.go#L167>)
|
||||
|
||||
```go
|
||||
func (c *ConfigMap) AppendDir(path string) error
|
||||
```
|
||||
|
||||
AppendDir adds files from given path to the configmap. It is not recursive, to add all files in a directory, you need to call this function for each subdirectory.
|
||||
|
||||
<a name="ConfigMap.AppendFile"></a>
|
||||
### func \(\*ConfigMap\) [AppendFile](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/configMap.go#L214>)
|
||||
|
||||
```go
|
||||
func (c *ConfigMap) AppendFile(path string) error
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="ConfigMap.Filename"></a>
|
||||
### func \(\*ConfigMap\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/configMap.go#L238>)
|
||||
|
||||
```go
|
||||
func (c *ConfigMap) Filename() string
|
||||
```
|
||||
|
||||
Filename returns the filename of the configmap. If the configmap is used for files, the filename contains the path.
|
||||
|
||||
<a name="ConfigMap.SetData"></a>
|
||||
### func \(\*ConfigMap\) [SetData](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/configMap.go#L248>)
|
||||
|
||||
```go
|
||||
func (c *ConfigMap) SetData(data map[string]string)
|
||||
```
|
||||
|
||||
SetData sets the data of the configmap. It replaces the entire data.
|
||||
|
||||
<a name="ConfigMap.Yaml"></a>
|
||||
### func \(\*ConfigMap\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/configMap.go#L253>)
|
||||
|
||||
```go
|
||||
func (c *ConfigMap) Yaml() ([]byte, error)
|
||||
```
|
||||
|
||||
Yaml returns the yaml representation of the configmap
|
||||
|
||||
<a name="ConfigMapMount"></a>
|
||||
## type [ConfigMapMount](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/deployment.go#L29-L32>)
|
||||
|
||||
|
||||
|
||||
```go
|
||||
type ConfigMapMount struct {
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
```
|
||||
|
||||
<a name="ConvertOptions"></a>
|
||||
## type [ConvertOptions](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/chart.go#L28-L37>)
|
||||
|
||||
ConvertOptions are the options to convert a compose project to a helm chart.
|
||||
|
||||
```go
|
||||
type ConvertOptions struct {
|
||||
AppVersion *string
|
||||
OutputDir string
|
||||
ChartVersion string
|
||||
Icon string
|
||||
Profiles []string
|
||||
EnvFiles []string
|
||||
Force bool
|
||||
HelmUpdate bool
|
||||
}
|
||||
```
|
||||
|
||||
<a name="CronJob"></a>
|
||||
## type [CronJob](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/cronJob.go#L23-L26>)
|
||||
|
||||
CronJob is a kubernetes CronJob.
|
||||
|
||||
```go
|
||||
type CronJob struct {
|
||||
*batchv1.CronJob
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
```
|
||||
|
||||
<a name="CronJob.Filename"></a>
|
||||
### func \(\*CronJob\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/cronJob.go#L115>)
|
||||
|
||||
```go
|
||||
func (c *CronJob) Filename() string
|
||||
```
|
||||
|
||||
Filename returns the filename of the cronjob.
|
||||
|
||||
Implements the Yaml interface.
|
||||
|
||||
<a name="CronJob.Yaml"></a>
|
||||
### func \(\*CronJob\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/cronJob.go#L122>)
|
||||
|
||||
```go
|
||||
func (c *CronJob) Yaml() ([]byte, error)
|
||||
```
|
||||
|
||||
Yaml returns the yaml representation of the cronjob.
|
||||
|
||||
Implements the Yaml interface.
|
||||
|
||||
<a name="CronJobValue"></a>
|
||||
## type [CronJobValue](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/values.go#L118-L123>)
|
||||
|
||||
CronJobValue is a cronjob configuration that will be saved in values.yaml.
|
||||
|
||||
```go
|
||||
type CronJobValue struct {
|
||||
Repository *RepositoryValue `yaml:"repository,omitempty"`
|
||||
Environment map[string]any `yaml:"environment,omitempty"`
|
||||
ImagePullPolicy string `yaml:"imagePullPolicy,omitempty"`
|
||||
Schedule string `yaml:"schedule"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="DataMap"></a>
|
||||
## type [DataMap](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/types.go#L4-L7>)
|
||||
|
||||
DataMap is a kubernetes ConfigMap or Secret. It can be used to add data to the ConfigMap or Secret.
|
||||
|
||||
```go
|
||||
type DataMap interface {
|
||||
SetData(map[string]string)
|
||||
AddData(string, string)
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Deployment"></a>
|
||||
## type [Deployment](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/deployment.go#L35-L45>)
|
||||
|
||||
Deployment is a kubernetes Deployment.
|
||||
|
||||
```go
|
||||
type Deployment struct {
|
||||
*appsv1.Deployment `yaml:",inline"`
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
```
|
||||
|
||||
<a name="NewDeployment"></a>
|
||||
### func [NewDeployment](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/deployment.go#L49>)
|
||||
|
||||
```go
|
||||
func NewDeployment(service types.ServiceConfig, chart *HelmChart) *Deployment
|
||||
```
|
||||
|
||||
NewDeployment creates a new Deployment from a compose service. The appName is the name of the application taken from the project name. It also creates the Values map that will be used to create the values.yaml file.
|
||||
|
||||
<a name="Deployment.AddContainer"></a>
|
||||
### func \(\*Deployment\) [AddContainer](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/deployment.go#L116>)
|
||||
|
||||
```go
|
||||
func (d *Deployment) AddContainer(service types.ServiceConfig)
|
||||
```
|
||||
|
||||
AddContainer adds a container to the deployment.
|
||||
|
||||
<a name="Deployment.AddHealthCheck"></a>
|
||||
### func \(\*Deployment\) [AddHealthCheck](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/deployment.go#L162>)
|
||||
|
||||
```go
|
||||
func (d *Deployment) AddHealthCheck(service types.ServiceConfig, container *corev1.Container)
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="Deployment.AddIngress"></a>
|
||||
### func \(\*Deployment\) [AddIngress](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/deployment.go#L191>)
|
||||
|
||||
```go
|
||||
func (d *Deployment) AddIngress(service types.ServiceConfig, appName string) *Ingress
|
||||
```
|
||||
|
||||
AddIngress adds an ingress to the deployment. It creates the ingress object.
|
||||
|
||||
<a name="Deployment.AddLegacyVolume"></a>
|
||||
### func \(\*Deployment\) [AddLegacyVolume](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/deployment.go#L221>)
|
||||
|
||||
```go
|
||||
func (d *Deployment) AddLegacyVolume(name, kind string)
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="Deployment.AddVolumes"></a>
|
||||
### func \(\*Deployment\) [AddVolumes](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/deployment.go#L197>)
|
||||
|
||||
```go
|
||||
func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string)
|
||||
```
|
||||
|
||||
AddVolumes adds a volume to the deployment. It does not create the PVC, it only adds the volumes to the deployment. If the volume is a bind volume it will warn the user that it is not supported yet.
|
||||
|
||||
<a name="Deployment.BindFrom"></a>
|
||||
### func \(\*Deployment\) [BindFrom](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/deployment.go#L242>)
|
||||
|
||||
```go
|
||||
func (d *Deployment) BindFrom(service types.ServiceConfig, binded *Deployment)
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="Deployment.BindMapFilesToContainer"></a>
|
||||
### func \(\*Deployment\) [BindMapFilesToContainer](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/deployment.go#L376>)
|
||||
|
||||
```go
|
||||
func (d *Deployment) BindMapFilesToContainer(service types.ServiceConfig, secrets []string, appName string) (*corev1.Container, int)
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="Deployment.DependsOn"></a>
|
||||
### func \(\*Deployment\) [DependsOn](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/deployment.go#L270>)
|
||||
|
||||
```go
|
||||
func (d *Deployment) DependsOn(to *Deployment, servicename string) error
|
||||
```
|
||||
|
||||
DependsOn adds a initContainer to the deployment that will wait for the service to be up.
|
||||
|
||||
<a name="Deployment.Filename"></a>
|
||||
### func \(\*Deployment\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/deployment.go#L301>)
|
||||
|
||||
```go
|
||||
func (d *Deployment) Filename() string
|
||||
```
|
||||
|
||||
Filename returns the filename of the deployment.
|
||||
|
||||
<a name="Deployment.MountExchangeVolumes"></a>
|
||||
### func \(\*Deployment\) [MountExchangeVolumes](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/deployment.go#L427>)
|
||||
|
||||
```go
|
||||
func (d *Deployment) MountExchangeVolumes()
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="Deployment.SetEnvFrom"></a>
|
||||
### func \(\*Deployment\) [SetEnvFrom](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/deployment.go#L306>)
|
||||
|
||||
```go
|
||||
func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string, samePod ...bool)
|
||||
```
|
||||
|
||||
SetEnvFrom sets the environment variables to a configmap. The configmap is created.
|
||||
|
||||
<a name="Deployment.Yaml"></a>
|
||||
### func \(\*Deployment\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/deployment.go#L451>)
|
||||
|
||||
```go
|
||||
func (d *Deployment) Yaml() ([]byte, error)
|
||||
```
|
||||
|
||||
Yaml returns the yaml representation of the deployment.
|
||||
|
||||
<a name="FileMapUsage"></a>
|
||||
## type [FileMapUsage](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/configMap.go#L22>)
|
||||
|
||||
FileMapUsage is the usage of the filemap.
|
||||
|
||||
```go
|
||||
type FileMapUsage uint8
|
||||
```
|
||||
|
||||
<a name="FileMapUsageConfigMap"></a>FileMapUsage constants.
|
||||
|
||||
```go
|
||||
const (
|
||||
FileMapUsageConfigMap FileMapUsage = iota // pure configmap for key:values.
|
||||
FileMapUsageFiles // files in a configmap.
|
||||
)
|
||||
```
|
||||
|
||||
<a name="HelmChart"></a>
|
||||
## type [HelmChart](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/chart.go#L41-L54>)
|
||||
|
||||
HelmChart is a Helm Chart representation. It contains all the templates, values, versions, helpers...
|
||||
|
||||
```go
|
||||
type HelmChart struct {
|
||||
Templates map[string]*ChartTemplate `yaml:"-"`
|
||||
Values map[string]any `yaml:"-"`
|
||||
VolumeMounts map[string]any `yaml:"-"`
|
||||
|
||||
Name string `yaml:"name"`
|
||||
Icon string `yaml:"icon,omitempty"`
|
||||
APIVersion string `yaml:"apiVersion"`
|
||||
Version string `yaml:"version"`
|
||||
AppVersion string `yaml:"appVersion"`
|
||||
Description string `yaml:"description"`
|
||||
Helper string `yaml:"-"`
|
||||
Dependencies []labelstructs.Dependency `yaml:"dependencies,omitempty"`
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Generate"></a>
|
||||
### func [Generate](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/generator.go#L32>)
|
||||
|
||||
```go
|
||||
func Generate(project *types.Project) (*HelmChart, error)
|
||||
```
|
||||
|
||||
Generate a chart from a compose project. This does not write files to disk, it only creates the HelmChart object.
|
||||
|
||||
The Generate function will create the HelmChart object this way:
|
||||
|
||||
- Detect the service port name or leave the port number if not found.
|
||||
- Create a deployment for each service that are not ingnore.
|
||||
- Create a service and ingresses for each service that has ports and/or declared ingresses.
|
||||
- Create a PVC or Configmap volumes for each volume.
|
||||
- Create init containers for each service which has dependencies to other services.
|
||||
- Create a chart dependencies.
|
||||
- Create a configmap and secrets from the environment variables.
|
||||
- Merge the same\-pod services.
|
||||
|
||||
<a name="NewChart"></a>
|
||||
### func [NewChart](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/chart.go#L57>)
|
||||
|
||||
```go
|
||||
func NewChart(name string) *HelmChart
|
||||
```
|
||||
|
||||
NewChart creates a new empty chart with the given name.
|
||||
|
||||
<a name="HelmChart.SaveTemplates"></a>
|
||||
### func \(\*HelmChart\) [SaveTemplates](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/chart.go#L72>)
|
||||
|
||||
```go
|
||||
func (chart *HelmChart) SaveTemplates(templateDir string)
|
||||
```
|
||||
|
||||
SaveTemplates the templates of the chart to the given directory.
|
||||
|
||||
<a name="Ingress"></a>
|
||||
## type [Ingress](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/ingress.go#L18-L22>)
|
||||
|
||||
|
||||
|
||||
```go
|
||||
type Ingress struct {
|
||||
*networkv1.Ingress
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
```
|
||||
|
||||
<a name="NewIngress"></a>
|
||||
### func [NewIngress](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/ingress.go#L25>)
|
||||
|
||||
```go
|
||||
func NewIngress(service types.ServiceConfig, Chart *HelmChart) *Ingress
|
||||
```
|
||||
|
||||
NewIngress creates a new Ingress from a compose service.
|
||||
|
||||
<a name="Ingress.Filename"></a>
|
||||
### func \(\*Ingress\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/ingress.go#L129>)
|
||||
|
||||
```go
|
||||
func (ingress *Ingress) Filename() string
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="Ingress.Yaml"></a>
|
||||
### func \(\*Ingress\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/ingress.go#L133>)
|
||||
|
||||
```go
|
||||
func (ingress *Ingress) Yaml() ([]byte, error)
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="IngressValue"></a>
|
||||
## type [IngressValue](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/values.go#L29-L36>)
|
||||
|
||||
IngressValue is a ingress configuration that will be saved in values.yaml.
|
||||
|
||||
```go
|
||||
type IngressValue struct {
|
||||
Annotations map[string]string `yaml:"annotations"`
|
||||
Host string `yaml:"host"`
|
||||
Path string `yaml:"path"`
|
||||
Class string `yaml:"class"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
TLS TLS `yaml:"tls"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="PersistenceValue"></a>
|
||||
## type [PersistenceValue](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/values.go#L16-L21>)
|
||||
|
||||
PersistenceValue is a persistence configuration that will be saved in values.yaml.
|
||||
|
||||
```go
|
||||
type PersistenceValue struct {
|
||||
StorageClass string `yaml:"storageClass"`
|
||||
Size string `yaml:"size"`
|
||||
AccessMode []string `yaml:"accessMode"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="RBAC"></a>
|
||||
## type [RBAC](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/rbac.go#L20-L24>)
|
||||
|
||||
RBAC is a kubernetes RBAC containing a role, a rolebinding and an associated serviceaccount.
|
||||
|
||||
```go
|
||||
type RBAC struct {
|
||||
RoleBinding *RoleBinding
|
||||
Role *Role
|
||||
ServiceAccount *ServiceAccount
|
||||
}
|
||||
```
|
||||
|
||||
<a name="NewRBAC"></a>
|
||||
### func [NewRBAC](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/rbac.go#L27>)
|
||||
|
||||
```go
|
||||
func NewRBAC(service types.ServiceConfig, appName string) *RBAC
|
||||
```
|
||||
|
||||
NewRBAC creates a new RBAC from a compose service. The appName is the name of the application taken from the project name.
|
||||
|
||||
<a name="RepositoryValue"></a>
|
||||
## type [RepositoryValue](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/values.go#L10-L13>)
|
||||
|
||||
RepositoryValue is a docker repository image and tag that will be saved in values.yaml.
|
||||
|
||||
```go
|
||||
type RepositoryValue struct {
|
||||
Image string `yaml:"image"`
|
||||
Tag string `yaml:"tag"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Role"></a>
|
||||
## type [Role](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/rbac.go#L114-L117>)
|
||||
|
||||
Role is a kubernetes Role.
|
||||
|
||||
```go
|
||||
type Role struct {
|
||||
*rbacv1.Role
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Role.Filename"></a>
|
||||
### func \(\*Role\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/rbac.go#L119>)
|
||||
|
||||
```go
|
||||
func (r *Role) Filename() string
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="Role.Yaml"></a>
|
||||
### func \(\*Role\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/rbac.go#L123>)
|
||||
|
||||
```go
|
||||
func (r *Role) Yaml() ([]byte, error)
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="RoleBinding"></a>
|
||||
## type [RoleBinding](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/rbac.go#L100-L103>)
|
||||
|
||||
RoleBinding is a kubernetes RoleBinding.
|
||||
|
||||
```go
|
||||
type RoleBinding struct {
|
||||
*rbacv1.RoleBinding
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
```
|
||||
|
||||
<a name="RoleBinding.Filename"></a>
|
||||
### func \(\*RoleBinding\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/rbac.go#L105>)
|
||||
|
||||
```go
|
||||
func (r *RoleBinding) Filename() string
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="RoleBinding.Yaml"></a>
|
||||
### func \(\*RoleBinding\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/rbac.go#L109>)
|
||||
|
||||
```go
|
||||
func (r *RoleBinding) Yaml() ([]byte, error)
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="Secret"></a>
|
||||
## type [Secret](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/secret.go#L23-L26>)
|
||||
|
||||
Secret is a kubernetes Secret.
|
||||
|
||||
Implements the DataMap interface.
|
||||
|
||||
```go
|
||||
type Secret struct {
|
||||
*corev1.Secret
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
```
|
||||
|
||||
<a name="NewSecret"></a>
|
||||
### func [NewSecret](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/secret.go#L29>)
|
||||
|
||||
```go
|
||||
func NewSecret(service types.ServiceConfig, appName string) *Secret
|
||||
```
|
||||
|
||||
NewSecret creates a new Secret from a compose service
|
||||
|
||||
<a name="Secret.AddData"></a>
|
||||
### func \(\*Secret\) [AddData](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/secret.go#L71>)
|
||||
|
||||
```go
|
||||
func (s *Secret) AddData(key, value string)
|
||||
```
|
||||
|
||||
AddData adds a key value pair to the secret.
|
||||
|
||||
<a name="Secret.Filename"></a>
|
||||
### func \(\*Secret\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/secret.go#L87>)
|
||||
|
||||
```go
|
||||
func (s *Secret) Filename() string
|
||||
```
|
||||
|
||||
Filename returns the filename of the secret.
|
||||
|
||||
<a name="Secret.SetData"></a>
|
||||
### func \(\*Secret\) [SetData](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/secret.go#L92>)
|
||||
|
||||
```go
|
||||
func (s *Secret) SetData(data map[string]string)
|
||||
```
|
||||
|
||||
SetData sets the data of the secret.
|
||||
|
||||
<a name="Secret.Yaml"></a>
|
||||
### func \(\*Secret\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/secret.go#L99>)
|
||||
|
||||
```go
|
||||
func (s *Secret) Yaml() ([]byte, error)
|
||||
```
|
||||
|
||||
Yaml returns the yaml representation of the secret.
|
||||
|
||||
<a name="Service"></a>
|
||||
## type [Service](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/service.go#L19-L22>)
|
||||
|
||||
Service is a kubernetes Service.
|
||||
|
||||
```go
|
||||
type Service struct {
|
||||
*v1.Service `yaml:",inline"`
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
```
|
||||
|
||||
<a name="NewService"></a>
|
||||
### func [NewService](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/service.go#L25>)
|
||||
|
||||
```go
|
||||
func NewService(service types.ServiceConfig, appName string) *Service
|
||||
```
|
||||
|
||||
NewService creates a new Service from a compose service.
|
||||
|
||||
<a name="Service.AddPort"></a>
|
||||
### func \(\*Service\) [AddPort](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/service.go#L54>)
|
||||
|
||||
```go
|
||||
func (s *Service) AddPort(port types.ServicePortConfig, serviceName ...string)
|
||||
```
|
||||
|
||||
AddPort adds a port to the service.
|
||||
|
||||
<a name="Service.Filename"></a>
|
||||
### func \(\*Service\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/service.go#L75>)
|
||||
|
||||
```go
|
||||
func (s *Service) Filename() string
|
||||
```
|
||||
|
||||
Filename returns the filename of the service.
|
||||
|
||||
<a name="Service.Yaml"></a>
|
||||
### func \(\*Service\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/service.go#L80>)
|
||||
|
||||
```go
|
||||
func (s *Service) Yaml() ([]byte, error)
|
||||
```
|
||||
|
||||
Yaml returns the yaml representation of the service.
|
||||
|
||||
<a name="ServiceAccount"></a>
|
||||
## type [ServiceAccount](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/rbac.go#L132-L135>)
|
||||
|
||||
ServiceAccount is a kubernetes ServiceAccount.
|
||||
|
||||
```go
|
||||
type ServiceAccount struct {
|
||||
*corev1.ServiceAccount
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
```
|
||||
|
||||
<a name="ServiceAccount.Filename"></a>
|
||||
### func \(\*ServiceAccount\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/rbac.go#L137>)
|
||||
|
||||
```go
|
||||
func (r *ServiceAccount) Filename() string
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="ServiceAccount.Yaml"></a>
|
||||
### func \(\*ServiceAccount\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/rbac.go#L141>)
|
||||
|
||||
```go
|
||||
func (r *ServiceAccount) Yaml() ([]byte, error)
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="TLS"></a>
|
||||
## type [TLS](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/values.go#L23-L26>)
|
||||
|
||||
|
||||
|
||||
```go
|
||||
type TLS struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
SecretName string `yaml:"secretName"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Value"></a>
|
||||
## type [Value](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/values.go#L39-L50>)
|
||||
|
||||
Value will be saved in values.yaml. It contains configuration for all deployment and services.
|
||||
|
||||
```go
|
||||
type Value struct {
|
||||
Repository *RepositoryValue `yaml:"repository,omitempty"`
|
||||
Persistence map[string]*PersistenceValue `yaml:"persistence,omitempty"`
|
||||
Ingress *IngressValue `yaml:"ingress,omitempty"`
|
||||
Environment map[string]any `yaml:"environment,omitempty"`
|
||||
Replicas *uint32 `yaml:"replicas,omitempty"`
|
||||
CronJob *CronJobValue `yaml:"cronjob,omitempty"`
|
||||
NodeSelector map[string]string `yaml:"nodeSelector"`
|
||||
Resources map[string]any `yaml:"resources"`
|
||||
ImagePullPolicy string `yaml:"imagePullPolicy,omitempty"`
|
||||
ServiceAccount string `yaml:"serviceAccount"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="NewValue"></a>
|
||||
### func [NewValue](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/values.go#L57>)
|
||||
|
||||
```go
|
||||
func NewValue(service types.ServiceConfig, main ...bool) *Value
|
||||
```
|
||||
|
||||
NewValue creates a new Value from a compose service. The value contains the necessary information to deploy the service \(image, tag, replicas, etc.\).
|
||||
|
||||
If \`main\` is true, the tag will be empty because it will be set in the helm chart appVersion.
|
||||
|
||||
<a name="Value.AddIngress"></a>
|
||||
### func \(\*Value\) [AddIngress](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/values.go#L90>)
|
||||
|
||||
```go
|
||||
func (v *Value) AddIngress(host, path string)
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="Value.AddPersistence"></a>
|
||||
### func \(\*Value\) [AddPersistence](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/values.go#L104>)
|
||||
|
||||
```go
|
||||
func (v *Value) AddPersistence(volumeName string)
|
||||
```
|
||||
|
||||
AddPersistence adds persistence configuration to the Value.
|
||||
|
||||
<a name="VolumeClaim"></a>
|
||||
## type [VolumeClaim](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/volume.go#L19-L24>)
|
||||
|
||||
VolumeClaim is a kubernetes VolumeClaim. This is a PersistentVolumeClaim.
|
||||
|
||||
```go
|
||||
type VolumeClaim struct {
|
||||
*v1.PersistentVolumeClaim
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
```
|
||||
|
||||
<a name="NewVolumeClaim"></a>
|
||||
### func [NewVolumeClaim](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/volume.go#L27>)
|
||||
|
||||
```go
|
||||
func NewVolumeClaim(service types.ServiceConfig, volumeName, appName string) *VolumeClaim
|
||||
```
|
||||
|
||||
NewVolumeClaim creates a new VolumeClaim from a compose service.
|
||||
|
||||
<a name="VolumeClaim.Filename"></a>
|
||||
### func \(\*VolumeClaim\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/volume.go#L63>)
|
||||
|
||||
```go
|
||||
func (v *VolumeClaim) Filename() string
|
||||
```
|
||||
|
||||
Filename returns the suggested filename for a VolumeClaim.
|
||||
|
||||
<a name="VolumeClaim.Yaml"></a>
|
||||
### func \(\*VolumeClaim\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/volume.go#L68>)
|
||||
|
||||
```go
|
||||
func (v *VolumeClaim) Yaml() ([]byte, error)
|
||||
```
|
||||
|
||||
Yaml marshals a VolumeClaim into yaml.
|
||||
|
||||
<a name="Yaml"></a>
|
||||
## type [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/types.go#L10-L13>)
|
||||
|
||||
Yaml is a kubernetes object that can be converted to yaml.
|
||||
|
||||
```go
|
||||
type Yaml interface {
|
||||
Yaml() ([]byte, error)
|
||||
Filename() string
|
||||
}
|
||||
```
|
||||
|
||||
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
|
28
doc/docs/packages/internal/generator/extrafiles.md
Normal file
@@ -0,0 +1,28 @@
|
||||
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
|
||||
|
||||
# extrafiles
|
||||
|
||||
```go
|
||||
import "repo.katenary.io/katenary/katenary/internal/generator/extrafiles"
|
||||
```
|
||||
|
||||
Package extrafiles provides function to generate the Chart files that are not objects. Like README.md and notes.txt...
|
||||
|
||||
## func [NotesFile](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/extrafiles/notes.go#L13>)
|
||||
|
||||
```go
|
||||
func NotesFile(services []string) string
|
||||
```
|
||||
|
||||
NotesFile returns the content of the note.txt file.
|
||||
|
||||
<a name="ReadMeFile"></a>
|
||||
## func [ReadMeFile](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/extrafiles/readme.go#L46>)
|
||||
|
||||
```go
|
||||
func ReadMeFile(charname, description string, values map[string]any) string
|
||||
```
|
||||
|
||||
ReadMeFile returns the content of the README.md file.
|
||||
|
||||
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
|
67
doc/docs/packages/internal/generator/katenaryfile.md
Normal file
@@ -0,0 +1,67 @@
|
||||
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
|
||||
|
||||
# katenaryfile
|
||||
|
||||
```go
|
||||
import "repo.katenary.io/katenary/katenary/internal/generator/katenaryfile"
|
||||
```
|
||||
|
||||
Package katenaryfile is a package for reading and writing katenary files.
|
||||
|
||||
A katenary file, named "katenary.yml" or "katenary.yaml", is a file where you can define the configuration of the conversion avoiding the use of labels in the compose file.
|
||||
|
||||
Formely, the file describe the same structure as in labels, and so that can be validated and completed by LSP. It also ease the use of katenary.
|
||||
|
||||
## func [GenerateSchema](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/katenaryfile/main.go#L138>)
|
||||
|
||||
```go
|
||||
func GenerateSchema() string
|
||||
```
|
||||
|
||||
GenerateSchema generates the schema for the katenary.yaml file.
|
||||
|
||||
<a name="OverrideWithConfig"></a>
|
||||
## func [OverrideWithConfig](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/katenaryfile/main.go#L50>)
|
||||
|
||||
```go
|
||||
func OverrideWithConfig(project *types.Project)
|
||||
```
|
||||
|
||||
OverrideWithConfig overrides the project with the katenary.yaml file. It will set the labels of the services with the values from the katenary.yaml file. It work in memory, so it will not modify the original project.
|
||||
|
||||
<a name="Service"></a>
|
||||
## type [Service](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/katenaryfile/main.go#L28-L45>)
|
||||
|
||||
Service is a struct that contains the service configuration for katenary
|
||||
|
||||
```go
|
||||
type Service struct {
|
||||
MainApp *bool `yaml:"main-app,omitempty" json:"main-app,omitempty" jsonschema:"title=Is this service the main application"`
|
||||
Values []StringOrMap `yaml:"values,omitempty" json:"values,omitempty" jsonschema:"description=Environment variables to be set in values.yaml with or without a description"`
|
||||
Secrets *labelstructs.Secrets `yaml:"secrets,omitempty" json:"secrets,omitempty" jsonschema:"title=Secrets,description=Environment variables to be set as secrets"`
|
||||
Ports *labelstructs.Ports `yaml:"ports,omitempty" json:"ports,omitempty" jsonschema:"title=Ports,description=Ports to be exposed in services"`
|
||||
Ingress *labelstructs.Ingress `yaml:"ingress,omitempty" json:"ingress,omitempty" jsonschema:"title=Ingress,description=Ingress configuration"`
|
||||
HealthCheck *labelstructs.HealthCheck `yaml:"health-check,omitempty" json:"health-check,omitempty" jsonschema:"title=Health Check,description=Health check configuration that respects the kubernetes api"`
|
||||
SamePod *string `yaml:"same-pod,omitempty" json:"same-pod,omitempty" jsonschema:"title=Same Pod,description=Service that should be in the same pod"`
|
||||
Description *string `yaml:"description,omitempty" json:"description,omitempty" jsonschema:"title=Description,description=Description of the service that will be injected in the values.yaml file"`
|
||||
Ignore *bool `yaml:"ignore,omitempty" json:"ignore,omitempty" jsonschema:"title=Ignore,description=Ignore the service in the conversion"`
|
||||
Dependencies []labelstructs.Dependency `yaml:"dependencies,omitempty" json:"dependencies,omitempty" jsonschema:"title=Dependencies,description=Services that should be injected in the Chart.yaml file"`
|
||||
ConfigMapFiles *labelstructs.ConfigMapFiles `yaml:"configmap-files,omitempty" json:"configmap-files,omitempty" jsonschema:"title=ConfigMap Files,description=Files that should be injected as ConfigMap"`
|
||||
MapEnv *labelstructs.MapEnv `yaml:"map-env,omitempty" json:"map-env,omitempty" jsonschema:"title=Map Env,description=Map environment variables to another value"`
|
||||
CronJob *labelstructs.CronJob `yaml:"cron-job,omitempty" json:"cron-job,omitempty" jsonschema:"title=Cron Job,description=Cron Job configuration"`
|
||||
EnvFrom *labelstructs.EnvFrom `yaml:"env-from,omitempty" json:"env-from,omitempty" jsonschema:"title=Env From,description=Inject environment variables from another service"`
|
||||
ExchangeVolumes []*labelstructs.ExchangeVolume `yaml:"exchange-volumes,omitempty" json:"exchange-volumes,omitempty" jsonschema:"title=Exchange Volumes,description=Exchange volumes between services"`
|
||||
ValuesFrom *labelstructs.ValueFrom `yaml:"values-from,omitempty" json:"values-from,omitempty" jsonschema:"title=Values From,description=Inject values from another service (secret or configmap environment variables)"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="StringOrMap"></a>
|
||||
## type [StringOrMap](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/katenaryfile/main.go#L25>)
|
||||
|
||||
StringOrMap is a struct that can be either a string or a map of strings. It's a helper struct to unmarshal the katenary.yaml file and produce the schema
|
||||
|
||||
```go
|
||||
type StringOrMap any
|
||||
```
|
||||
|
||||
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
|
110
doc/docs/packages/internal/generator/labels.md
Normal file
@@ -0,0 +1,110 @@
|
||||
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
|
||||
|
||||
# labels
|
||||
|
||||
```go
|
||||
import "repo.katenary.io/katenary/katenary/internal/generator/labels"
|
||||
```
|
||||
|
||||
Package labels provides functionality to parse and manipulate labels.
|
||||
|
||||
## Constants
|
||||
|
||||
<a name="KatenaryLabelPrefix"></a>
|
||||
|
||||
```go
|
||||
const KatenaryLabelPrefix = "katenary.v3"
|
||||
```
|
||||
|
||||
<a name="GetLabelHelp"></a>
|
||||
## func [GetLabelHelp](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/katenaryLabels.go#L89>)
|
||||
|
||||
```go
|
||||
func GetLabelHelp(asMarkdown bool) string
|
||||
```
|
||||
|
||||
GetLabelHelp return the help for the labels.
|
||||
|
||||
<a name="GetLabelHelpFor"></a>
|
||||
## func [GetLabelHelpFor](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/katenaryLabels.go#L98>)
|
||||
|
||||
```go
|
||||
func GetLabelHelpFor(labelname string, asMarkdown bool) string
|
||||
```
|
||||
|
||||
GetLabelHelpFor returns the help for a specific label.
|
||||
|
||||
<a name="GetLabelNames"></a>
|
||||
## func [GetLabelNames](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/katenaryLabels.go#L73>)
|
||||
|
||||
```go
|
||||
func GetLabelNames() []string
|
||||
```
|
||||
|
||||
GetLabelNames returns a sorted list of all katenary label names.
|
||||
|
||||
<a name="Prefix"></a>
|
||||
## func [Prefix](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/katenaryLabels.go#L236>)
|
||||
|
||||
```go
|
||||
func Prefix() string
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="Help"></a>
|
||||
## type [Help](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/katenaryLabels.go#L65-L70>)
|
||||
|
||||
Help is the documentation of a label.
|
||||
|
||||
```go
|
||||
type Help struct {
|
||||
Short string `yaml:"short"`
|
||||
Long string `yaml:"long"`
|
||||
Example string `yaml:"example"`
|
||||
Type string `yaml:"type"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Label"></a>
|
||||
## type [Label](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/katenaryLabels.go#L58>)
|
||||
|
||||
Label is a katenary label to find in compose files.
|
||||
|
||||
```go
|
||||
type Label = string
|
||||
```
|
||||
|
||||
<a name="LabelMainApp"></a>Known labels.
|
||||
|
||||
```go
|
||||
const (
|
||||
LabelMainApp Label = KatenaryLabelPrefix + "/main-app"
|
||||
LabelValues Label = KatenaryLabelPrefix + "/values"
|
||||
LabelSecrets Label = KatenaryLabelPrefix + "/secrets"
|
||||
LabelPorts Label = KatenaryLabelPrefix + "/ports"
|
||||
LabelIngress Label = KatenaryLabelPrefix + "/ingress"
|
||||
LabelMapEnv Label = KatenaryLabelPrefix + "/map-env"
|
||||
LabelHealthCheck Label = KatenaryLabelPrefix + "/health-check"
|
||||
LabelSamePod Label = KatenaryLabelPrefix + "/same-pod"
|
||||
LabelDescription Label = KatenaryLabelPrefix + "/description"
|
||||
LabelIgnore Label = KatenaryLabelPrefix + "/ignore"
|
||||
LabelDependencies Label = KatenaryLabelPrefix + "/dependencies"
|
||||
LabelConfigMapFiles Label = KatenaryLabelPrefix + "/configmap-files"
|
||||
LabelCronJob Label = KatenaryLabelPrefix + "/cronjob"
|
||||
LabelEnvFrom Label = KatenaryLabelPrefix + "/env-from"
|
||||
LabelExchangeVolume Label = KatenaryLabelPrefix + "/exchange-volumes"
|
||||
LabelValuesFrom Label = KatenaryLabelPrefix + "/values-from"
|
||||
)
|
||||
```
|
||||
|
||||
<a name="LabelName"></a>
|
||||
### func [LabelName](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/katenaryLabels.go#L60>)
|
||||
|
||||
```go
|
||||
func LabelName(name string) Label
|
||||
```
|
||||
|
||||
|
||||
|
||||
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
|
246
doc/docs/packages/internal/generator/labels/labelstructs.md
Normal file
@@ -0,0 +1,246 @@
|
||||
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
|
||||
|
||||
# labelstructs
|
||||
|
||||
```go
|
||||
import "repo.katenary.io/katenary/katenary/internal/generator/labels/labelstructs"
|
||||
```
|
||||
|
||||
Package labelstructs is a package that contains the structs used to represent the labels in the yaml files.
|
||||
|
||||
## type [ConfigMapFiles](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/configMap.go#L5>)
|
||||
|
||||
|
||||
|
||||
```go
|
||||
type ConfigMapFiles []string
|
||||
```
|
||||
|
||||
<a name="ConfigMapFileFrom"></a>
|
||||
### func [ConfigMapFileFrom](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/configMap.go#L7>)
|
||||
|
||||
```go
|
||||
func ConfigMapFileFrom(data string) (ConfigMapFiles, error)
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="CronJob"></a>
|
||||
## type [CronJob](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/cronJob.go#L5-L10>)
|
||||
|
||||
|
||||
|
||||
```go
|
||||
type CronJob struct {
|
||||
Image string `yaml:"image,omitempty" json:"image,omitempty"`
|
||||
Command string `yaml:"command" json:"command,omitempty"`
|
||||
Schedule string `yaml:"schedule" json:"schedule,omitempty"`
|
||||
Rbac bool `yaml:"rbac" json:"rbac,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="CronJobFrom"></a>
|
||||
### func [CronJobFrom](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/cronJob.go#L12>)
|
||||
|
||||
```go
|
||||
func CronJobFrom(data string) (*CronJob, error)
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="Dependency"></a>
|
||||
## type [Dependency](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/dependencies.go#L6-L12>)
|
||||
|
||||
Dependency is a dependency of a chart to other charts.
|
||||
|
||||
```go
|
||||
type Dependency struct {
|
||||
Values map[string]any `yaml:"-" json:"values,omitempty"`
|
||||
Name string `yaml:"name" json:"name"`
|
||||
Version string `yaml:"version" json:"version"`
|
||||
Repository string `yaml:"repository" json:"repository"`
|
||||
Alias string `yaml:"alias,omitempty" json:"alias,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="DependenciesFrom"></a>
|
||||
### func [DependenciesFrom](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/dependencies.go#L15>)
|
||||
|
||||
```go
|
||||
func DependenciesFrom(data string) ([]Dependency, error)
|
||||
```
|
||||
|
||||
DependenciesFrom returns a slice of dependencies from the given string.
|
||||
|
||||
<a name="EnvFrom"></a>
|
||||
## type [EnvFrom](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/envFrom.go#L5>)
|
||||
|
||||
|
||||
|
||||
```go
|
||||
type EnvFrom []string
|
||||
```
|
||||
|
||||
<a name="EnvFromFrom"></a>
|
||||
### func [EnvFromFrom](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/envFrom.go#L8>)
|
||||
|
||||
```go
|
||||
func EnvFromFrom(data string) (EnvFrom, error)
|
||||
```
|
||||
|
||||
EnvFromFrom returns a EnvFrom from the given string.
|
||||
|
||||
<a name="ExchangeVolume"></a>
|
||||
## type [ExchangeVolume](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/exchangeVolume.go#L5-L10>)
|
||||
|
||||
|
||||
|
||||
```go
|
||||
type ExchangeVolume struct {
|
||||
Name string `yaml:"name" json:"name"`
|
||||
MountPath string `yaml:"mountPath" json:"mountPath"`
|
||||
Type string `yaml:"type,omitempty" json:"type,omitempty"`
|
||||
Init string `yaml:"init,omitempty" json:"init,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="NewExchangeVolumes"></a>
|
||||
### func [NewExchangeVolumes](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/exchangeVolume.go#L12>)
|
||||
|
||||
```go
|
||||
func NewExchangeVolumes(data string) ([]*ExchangeVolume, error)
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="HealthCheck"></a>
|
||||
## type [HealthCheck](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/probes.go#L11-L14>)
|
||||
|
||||
|
||||
|
||||
```go
|
||||
type HealthCheck struct {
|
||||
LivenessProbe *corev1.Probe `yaml:"livenessProbe,omitempty" json:"livenessProbe,omitempty"`
|
||||
ReadinessProbe *corev1.Probe `yaml:"readinessProbe,omitempty" json:"readinessProbe,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="ProbeFrom"></a>
|
||||
### func [ProbeFrom](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/probes.go#L16>)
|
||||
|
||||
```go
|
||||
func ProbeFrom(data string) (*HealthCheck, error)
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="Ingress"></a>
|
||||
## type [Ingress](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/ingress.go#L15-L23>)
|
||||
|
||||
|
||||
|
||||
```go
|
||||
type Ingress struct {
|
||||
Port *int32 `yaml:"port,omitempty" json:"port,omitempty"`
|
||||
Annotations map[string]string `yaml:"annotations,omitempty" jsonschema:"nullable" json:"annotations,omitempty"`
|
||||
Hostname string `yaml:"hostname,omitempty" json:"hostname,omitempty"`
|
||||
Path *string `yaml:"path,omitempty" json:"path,omitempty"`
|
||||
Class *string `yaml:"class,omitempty" json:"class,omitempty" jsonschema:"default:-"`
|
||||
Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
|
||||
TLS *TLS `yaml:"tls,omitempty" json:"tls,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="IngressFrom"></a>
|
||||
### func [IngressFrom](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/ingress.go#L26>)
|
||||
|
||||
```go
|
||||
func IngressFrom(data string) (*Ingress, error)
|
||||
```
|
||||
|
||||
IngressFrom creates a new Ingress from a compose service.
|
||||
|
||||
<a name="MapEnv"></a>
|
||||
## type [MapEnv](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/mapenv.go#L5>)
|
||||
|
||||
|
||||
|
||||
```go
|
||||
type MapEnv map[string]string
|
||||
```
|
||||
|
||||
<a name="MapEnvFrom"></a>
|
||||
### func [MapEnvFrom](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/mapenv.go#L8>)
|
||||
|
||||
```go
|
||||
func MapEnvFrom(data string) (MapEnv, error)
|
||||
```
|
||||
|
||||
MapEnvFrom returns a MapEnv from the given string.
|
||||
|
||||
<a name="Ports"></a>
|
||||
## type [Ports](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/ports.go#L5>)
|
||||
|
||||
|
||||
|
||||
```go
|
||||
type Ports []uint32
|
||||
```
|
||||
|
||||
<a name="PortsFrom"></a>
|
||||
### func [PortsFrom](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/ports.go#L8>)
|
||||
|
||||
```go
|
||||
func PortsFrom(data string) (Ports, error)
|
||||
```
|
||||
|
||||
PortsFrom returns a Ports from the given string.
|
||||
|
||||
<a name="Secrets"></a>
|
||||
## type [Secrets](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/secrets.go#L5>)
|
||||
|
||||
|
||||
|
||||
```go
|
||||
type Secrets []string
|
||||
```
|
||||
|
||||
<a name="SecretsFrom"></a>
|
||||
### func [SecretsFrom](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/secrets.go#L7>)
|
||||
|
||||
```go
|
||||
func SecretsFrom(data string) (Secrets, error)
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="TLS"></a>
|
||||
## type [TLS](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/ingress.go#L11-L13>)
|
||||
|
||||
|
||||
|
||||
```go
|
||||
type TLS struct {
|
||||
Enabled bool `yaml:"enabled" json:"enabled,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="ValueFrom"></a>
|
||||
## type [ValueFrom](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/valueFrom.go#L5>)
|
||||
|
||||
|
||||
|
||||
```go
|
||||
type ValueFrom map[string]string
|
||||
```
|
||||
|
||||
<a name="GetValueFrom"></a>
|
||||
### func [GetValueFrom](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/generator/labels/labelstructs/valueFrom.go#L7>)
|
||||
|
||||
```go
|
||||
func GetValueFrom(data string) (*ValueFrom, error)
|
||||
```
|
||||
|
||||
|
||||
|
||||
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
|
19
doc/docs/packages/internal/parser.md
Normal file
@@ -0,0 +1,19 @@
|
||||
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
|
||||
|
||||
# parser
|
||||
|
||||
```go
|
||||
import "repo.katenary.io/katenary/katenary/internal/parser"
|
||||
```
|
||||
|
||||
Package parser is a wrapper around compose\-go to parse compose files.
|
||||
|
||||
## func [Parse](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/parser/main.go#L29>)
|
||||
|
||||
```go
|
||||
func Parse(profiles []string, envFiles []string, dockerComposeFile ...string) (*types.Project, error)
|
||||
```
|
||||
|
||||
Parse compose files and return a project. The project is parsed with dotenv, osenv and profiles.
|
||||
|
||||
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
|
221
doc/docs/packages/internal/utils.md
Normal file
@@ -0,0 +1,221 @@
|
||||
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
|
||||
|
||||
# utils
|
||||
|
||||
```go
|
||||
import "repo.katenary.io/katenary/katenary/internal/utils"
|
||||
```
|
||||
|
||||
Package utils provides some utility functions used in katenary. It defines some constants and functions used in the whole project.
|
||||
|
||||
## Constants
|
||||
|
||||
<a name="DirectoryPermission"></a>DirectoryPermission is the default values for permissions apply to created directories.
|
||||
|
||||
```go
|
||||
const DirectoryPermission = 0o755
|
||||
```
|
||||
|
||||
<a name="AsResourceName"></a>
|
||||
## func [AsResourceName](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/utils.go#L196>)
|
||||
|
||||
```go
|
||||
func AsResourceName(name string) string
|
||||
```
|
||||
|
||||
AsResourceName returns a resource name with underscores to respect the kubernetes naming convention. It's the opposite of FixedResourceName.
|
||||
|
||||
<a name="Confirm"></a>
|
||||
## func [Confirm](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/utils.go#L164>)
|
||||
|
||||
```go
|
||||
func Confirm(question string, icon ...Icon) bool
|
||||
```
|
||||
|
||||
Confirm asks a question and returns true if the answer is y.
|
||||
|
||||
<a name="CountStartingSpaces"></a>
|
||||
## func [CountStartingSpaces](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/utils.go#L41>)
|
||||
|
||||
```go
|
||||
func CountStartingSpaces(line string) int
|
||||
```
|
||||
|
||||
CountStartingSpaces counts the number of spaces at the beginning of a string.
|
||||
|
||||
<a name="EncodeBasicYaml"></a>
|
||||
## func [EncodeBasicYaml](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/utils.go#L178>)
|
||||
|
||||
```go
|
||||
func EncodeBasicYaml(data any) ([]byte, error)
|
||||
```
|
||||
|
||||
EncodeBasicYaml encodes a basic yaml from an interface.
|
||||
|
||||
<a name="FixedResourceName"></a>
|
||||
## func [FixedResourceName](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/utils.go#L190>)
|
||||
|
||||
```go
|
||||
func FixedResourceName(name string) string
|
||||
```
|
||||
|
||||
FixedResourceName returns a resource name without underscores to respect the kubernetes naming convention.
|
||||
|
||||
<a name="GetContainerByName"></a>
|
||||
## func [GetContainerByName](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/utils.go#L87>)
|
||||
|
||||
```go
|
||||
func GetContainerByName(name string, containers []corev1.Container) (*corev1.Container, int)
|
||||
```
|
||||
|
||||
GetContainerByName returns a container by name and its index in the array. It returns nil, \-1 if not found.
|
||||
|
||||
<a name="GetKind"></a>
|
||||
## func [GetKind](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/utils.go#L54>)
|
||||
|
||||
```go
|
||||
func GetKind(path string) (kind string)
|
||||
```
|
||||
|
||||
GetKind returns the kind of the resource from the file path.
|
||||
|
||||
<a name="GetServiceNameByPort"></a>
|
||||
## func [GetServiceNameByPort](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/utils.go#L77>)
|
||||
|
||||
```go
|
||||
func GetServiceNameByPort(port int) string
|
||||
```
|
||||
|
||||
GetServiceNameByPort returns the service name for a port. It the service name is not found, it returns an empty string.
|
||||
|
||||
<a name="GetValuesFromLabel"></a>
|
||||
## func [GetValuesFromLabel](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/utils.go#L129>)
|
||||
|
||||
```go
|
||||
func GetValuesFromLabel(service types.ServiceConfig, LabelValues string) map[string]*EnvConfig
|
||||
```
|
||||
|
||||
GetValuesFromLabel returns a map of values from a label.
|
||||
|
||||
<a name="HashComposefiles"></a>
|
||||
## func [HashComposefiles](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/hash.go#L12>)
|
||||
|
||||
```go
|
||||
func HashComposefiles(files []string) (string, error)
|
||||
```
|
||||
|
||||
HashComposefiles returns a hash of the compose files.
|
||||
|
||||
<a name="Int32Ptr"></a>
|
||||
## func [Int32Ptr](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/utils.go#L35>)
|
||||
|
||||
```go
|
||||
func Int32Ptr(i int32) *int32
|
||||
```
|
||||
|
||||
Int32Ptr returns a pointer to an int32.
|
||||
|
||||
<a name="PathToName"></a>
|
||||
## func [PathToName](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/utils.go#L106>)
|
||||
|
||||
```go
|
||||
func PathToName(path string) string
|
||||
```
|
||||
|
||||
PathToName converts a path to a kubernetes complient name.
|
||||
|
||||
<a name="StrPtr"></a>
|
||||
## func [StrPtr](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/utils.go#L38>)
|
||||
|
||||
```go
|
||||
func StrPtr(s string) *string
|
||||
```
|
||||
|
||||
StrPtr returns a pointer to a string.
|
||||
|
||||
<a name="TplName"></a>
|
||||
## func [TplName](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/utils.go#L22>)
|
||||
|
||||
```go
|
||||
func TplName(serviceName, appname string, suffix ...string) string
|
||||
```
|
||||
|
||||
TplName returns the name of the kubernetes resource as a template string. It is used in the templates and defined in \_helper.tpl file.
|
||||
|
||||
<a name="TplValue"></a>
|
||||
## func [TplValue](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/utils.go#L97>)
|
||||
|
||||
```go
|
||||
func TplValue(serviceName, variable string, pipes ...string) string
|
||||
```
|
||||
|
||||
TplValue returns a string that can be used in a template to access a value from the values file.
|
||||
|
||||
<a name="Warn"></a>
|
||||
## func [Warn](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/icons.go#L25>)
|
||||
|
||||
```go
|
||||
func Warn(msg ...any)
|
||||
```
|
||||
|
||||
Warn prints a warning message
|
||||
|
||||
<a name="WordWrap"></a>
|
||||
## func [WordWrap](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/utils.go#L159>)
|
||||
|
||||
```go
|
||||
func WordWrap(text string, lineWidth int) string
|
||||
```
|
||||
|
||||
WordWrap wraps a string to a given line width. Warning: it may break the string. You need to check the result.
|
||||
|
||||
<a name="Wrap"></a>
|
||||
## func [Wrap](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/utils.go#L71>)
|
||||
|
||||
```go
|
||||
func Wrap(src, above, below string) string
|
||||
```
|
||||
|
||||
Wrap wraps a string with a string above and below. It will respect the indentation of the src string.
|
||||
|
||||
<a name="EnvConfig"></a>
|
||||
## type [EnvConfig](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/utils.go#L123-L126>)
|
||||
|
||||
EnvConfig is a struct to hold the description of an environment variable.
|
||||
|
||||
```go
|
||||
type EnvConfig struct {
|
||||
Service types.ServiceConfig
|
||||
Description string
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Icon"></a>
|
||||
## type [Icon](<https://repo.katenary.io/Katenary/katenary/blob/feat-move-to-gitea/internal/utils/icons.go#L6>)
|
||||
|
||||
Icon is a unicode icon
|
||||
|
||||
```go
|
||||
type Icon string
|
||||
```
|
||||
|
||||
<a name="IconSuccess"></a>Icons used in katenary.
|
||||
|
||||
```go
|
||||
const (
|
||||
IconSuccess Icon = "✅"
|
||||
IconFailure Icon = "❌"
|
||||
IconWarning Icon = "❕"
|
||||
IconNote Icon = "📝"
|
||||
IconWorld Icon = "🌐"
|
||||
IconPlug Icon = "🔌"
|
||||
IconPackage Icon = "📦"
|
||||
IconCabinet Icon = "🗄️"
|
||||
IconInfo Icon = "🔵"
|
||||
IconSecret Icon = "🔒"
|
||||
IconConfig Icon = "🔧"
|
||||
IconDependency Icon = "🔗"
|
||||
)
|
||||
```
|
||||
|
||||
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
|
48
doc/docs/statics/addons.js
Normal file
@@ -0,0 +1,48 @@
|
||||
// Install the highlight.js in the documentation. Then
|
||||
// highlight all the source code.
|
||||
function hljsInstall() {
|
||||
const version = "11.9.0";
|
||||
const theme = "github-dark";
|
||||
|
||||
const script = document.createElement("script");
|
||||
script.src = `//cdnjs.cloudflare.com/ajax/libs/highlight.js/${version}/highlight.min.js`;
|
||||
script.onload = () => {
|
||||
const style = document.createElement("link");
|
||||
style.rel = "stylesheet";
|
||||
style.href = `//cdnjs.cloudflare.com/ajax/libs/highlight.js/${version}/styles/${theme}.min.css`;
|
||||
document.head.appendChild(style);
|
||||
hljs.highlightAll();
|
||||
};
|
||||
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
// All images in an .zoomable div is zoomable, that
|
||||
// meanse that we can click to zoom and unzoom.
|
||||
// This needs specific CSS (see main.css).
|
||||
function makeImagesZoomable() {
|
||||
const zone = document.querySelectorAll(".zoomable");
|
||||
|
||||
zone.forEach((z, i) => {
|
||||
const im = z.querySelectorAll("img,svg");
|
||||
if (im.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const input = document.createElement("input");
|
||||
input.setAttribute("type", "checkbox");
|
||||
input.setAttribute("id", `image-zoom-${i}`);
|
||||
z.appendChild(input);
|
||||
|
||||
const label = document.createElement("label");
|
||||
label.setAttribute("for", `image-zoom-${i}`);
|
||||
z.appendChild(label);
|
||||
|
||||
label.appendChild(im[0]);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
hljsInstall();
|
||||
makeImagesZoomable();
|
||||
});
|
121
doc/docs/statics/icon.svg
Normal file
@@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="svg211948"
|
||||
viewBox="0 0 95.440796 85.01416"
|
||||
height="85.01416"
|
||||
width="95.440796"
|
||||
version="1.1"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata211954">
|
||||
<rdf:rdf>
|
||||
<cc:work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:work>
|
||||
</rdf:rdf>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs211952" />
|
||||
<linearGradient
|
||||
spreadMethod="pad"
|
||||
y2="0.30000001"
|
||||
x2="-0.1"
|
||||
y1="1.2"
|
||||
x1="0.30000001"
|
||||
id="3d_gradient2-logo-24885591-b378-4c55-b87b-b7d42ed10694">
|
||||
<stop
|
||||
id="stop211929"
|
||||
stop-opacity="1"
|
||||
stop-color="#ffffff"
|
||||
offset="0%" />
|
||||
<stop
|
||||
id="stop211931"
|
||||
stop-opacity="1"
|
||||
stop-color="#000000"
|
||||
offset="100%" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientTransform="rotate(-30)"
|
||||
spreadMethod="pad"
|
||||
y2="0.30000001"
|
||||
x2="-0.1"
|
||||
y1="1.2"
|
||||
x1="0.30000001"
|
||||
id="3d_gradient3-logo-24885591-b378-4c55-b87b-b7d42ed10694">
|
||||
<stop
|
||||
id="stop211934"
|
||||
stop-opacity="1"
|
||||
stop-color="#ffffff"
|
||||
offset="0%" />
|
||||
<stop
|
||||
id="stop211936"
|
||||
stop-opacity="1"
|
||||
stop-color="#cccccc"
|
||||
offset="50%" />
|
||||
<stop
|
||||
id="stop211938"
|
||||
stop-opacity="1"
|
||||
stop-color="#000000"
|
||||
offset="100%" />
|
||||
</linearGradient>
|
||||
<g
|
||||
id="logo-group"
|
||||
transform="translate(-394.01147,-211.65063)">
|
||||
<path
|
||||
d="m 773.83594,234.02344 c -0.4545,0.0375 -0.92821,0.1629 -1.40821,0.3789 -1.91999,0.864 -2.68851,2.68743 -1.72851,4.60743 l 21.4082,44.35351 0.0957,0.0957 -9.5996,21.02344 c -0.96,2.112 -0.28778,3.74342 1.82421,4.60742 0.576,0.288 1.15252,0.38477 1.72852,0.38477 1.248,0 2.2072,-0.76703 2.7832,-2.20703 l 31.10352,-68.35352 c 0.96,-2.112 0.28778,-3.64772 -1.82422,-4.51172 -2.112,-0.864 -3.64742,-0.28748 -4.60742,1.72852 l -17.85547,39.35937 -18.7207,-39.45507 c -0.648,-1.44 -1.83572,-2.12422 -3.19922,-2.01172 z"
|
||||
style="font-size:96px;line-height:0;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ff7f2a;stroke-width:51.0236;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0"
|
||||
id="path9" />
|
||||
<path
|
||||
d="m 692.07617,233.63477 c -15.26398,0 -26.68945,11.51921 -26.68945,26.7832 0,15.26398 11.13594,26.68945 25.91992,26.68945 8.44799,0 15.64875,-3.84038 19.96875,-9.98437 v 5.85547 c 0,2.11199 1.53614,3.64843 3.74414,3.64843 2.112,0 3.74414,-1.53644 3.74414,-3.64843 v -22.56055 c -0.096,-15.26399 -11.51951,-26.7832 -26.6875,-26.7832 z m 0,6.7207 c 11.03999,0 19.39063,8.63851 19.39063,20.0625 0,11.42399 -8.35064,19.96875 -19.39063,19.96875 -11.03999,0 -19.48828,-8.54476 -19.48828,-19.96875 0,-11.42399 8.44829,-20.0625 19.48828,-20.0625 z"
|
||||
style="font-size:96px;line-height:0;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ff7f2a;stroke-width:51.0236;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0"
|
||||
id="path8" />
|
||||
<path
|
||||
d="m 566.60352,233.63477 c -14.78399,0 -25.15235,11.13521 -25.15235,26.7832 0,15.64798 11.03981,26.68945 26.5918,26.68945 5.95199,0 13.24866,-2.59224 17.47265,-6.24023 1.536,-1.344 1.4406,-3.36079 -0.1914,-4.80078 -1.344,-1.056 -3.26508,-0.9606 -4.70508,0.1914 -2.784,2.4 -7.96818,4.22461 -12.57617,4.22461 -10.65599,0 -18.52799,-7.20007 -19.58399,-17.66406 h 38.78516 c 2.016,0 3.45508,-1.34338 3.45508,-3.35938 0,-15.07198 -9.59972,-25.82421 -24.0957,-25.82421 z m 0,6.62304 c 9.69599,0 16.22359,6.72003 17.18359,16.41602 h -35.13477 c 1.344,-9.69599 8.15919,-16.41602 17.95118,-16.41602 z"
|
||||
style="font-size:96px;line-height:0;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ff7f2a;stroke-width:51.0236;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0"
|
||||
id="path7" />
|
||||
<path
|
||||
d="m 756.875,233.53906 c -6.71999,0 -12.67202,3.16757 -16.41602,8.35156 v -4.1289 c 0,-2.304 -1.34248,-3.64649 -3.64648,-3.64649 -2.304,0 -3.64844,1.34249 -3.64844,3.64649 v 45.2168 c 0,2.30399 1.34444,3.64843 3.64844,3.64843 2.304,0 3.64648,-1.34444 3.64648,-3.64843 v -28.70313 c 0,-8.92799 7.87302,-14.68851 18.625,-13.72852 3.168,0.192 5.75965,0.76867 6.43164,-2.11132 0.768,-3.168 -2.68863,-4.89649 -8.64062,-4.89649 z"
|
||||
style="font-size:96px;line-height:0;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ff7f2a;stroke-width:51.0236;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0"
|
||||
id="path6" />
|
||||
<path
|
||||
d="m 628.04297,233.53906 c -7.10399,0 -13.34271,2.87999 -17.4707,7.58399 v -3.26368 c 0,-2.20799 -1.44044,-3.74414 -3.64844,-3.74414 -2.208,0 -3.74414,1.53615 -3.74414,3.74414 v 45.11915 c 0,2.20799 1.53614,3.64843 3.74414,3.64843 2.208,0 3.64844,-1.44044 3.64844,-3.64843 V 254.5625 c 0,-7.96799 7.19913,-14.01563 16.70312,-14.01563 9.79199,0 17.18359,5.66467 17.18359,17.47266 v 24.95899 c 0,2.11199 1.63215,3.64843 3.74414,3.64843 2.016,0 3.64844,-1.53644 3.64844,-3.64843 v -24.95899 c 0,-15.83998 -10.2726,-24.48047 -23.80859,-24.48047 z"
|
||||
style="font-size:96px;line-height:0;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ff7f2a;stroke-width:51.0236;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0"
|
||||
id="path5" />
|
||||
<path
|
||||
d="m 512.17187,217.41016 c -2.11199,0 -3.64843,1.53614 -3.64843,3.74414 v 14.88086 h -6.24024 c -2.01599,0 -3.35937,1.34367 -3.35937,3.26367 0,1.824 1.34338,3.16797 3.35937,3.16797 h 6.24024 v 25.63281 c 0,10.65599 7.39207,18.43134 17.66406,18.52734 h 2.01562 c 2.304,0 4.03321,-1.53644 4.03321,-3.64843 0,-2.208 -1.44104,-3.74415 -3.45703,-3.74415 h -2.5918 c -5.95199,0 -10.27148,-4.60677 -10.27148,-11.13476 V 242.4668 h 10.84765 c 2.016,0 3.35938,-1.34397 3.35938,-3.16797 0,-1.92 -1.34338,-3.26367 -3.35938,-3.26367 H 515.91602 V 221.1543 c 0,-2.208 -1.53615,-3.74414 -3.74415,-3.74414 z"
|
||||
style="font-size:96px;line-height:0;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ff7f2a;stroke-width:51.0236;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0"
|
||||
id="path4" />
|
||||
<image
|
||||
xlink:href=""
|
||||
id="container"
|
||||
x="272"
|
||||
y="144"
|
||||
width="480"
|
||||
height="480"
|
||||
style="display:none" />
|
||||
<image
|
||||
xlink:href=""
|
||||
id="icon_container"
|
||||
style="display:none"
|
||||
x="0"
|
||||
y="0"
|
||||
width="0"
|
||||
height="0" />
|
||||
<path
|
||||
style="fill:#388ec7;fill-opacity:1;stroke:none;stroke-width:1.41128"
|
||||
d="m 488.71354,251.36014 -33.31908,17.65778 -13.63054,6.42952 -13.63053,-6.42952 -33.31907,-17.65778 c 0,5.32502 -2.24747,16.12101 0.77828,20.72433 1.84892,2.81293 6.84753,4.46784 9.82323,6.09432 8.26868,4.51957 16.72019,8.751 24.98932,13.27082 3.1083,1.69895 7.62988,5.16897 11.35877,5.21476 3.70778,0.0455 8.2451,-3.55102 11.35878,-5.19752 8.33909,-4.40967 16.72005,-8.76818 24.98932,-13.28806 3.05286,-1.66866 8.68533,-3.49778 10.14461,-6.69253 2.3911,-5.23485 0.45691,-14.46175 0.45691,-20.12612 m -46.19236,16.00215 c 3.59731,-0.51544 7.48607,-3.47354 10.60152,-5.12097 7.58911,-4.0131 15.20174,-8.00941 22.71755,-12.13797 3.53683,-1.94283 9.05374,-3.7013 11.65911,-6.69847 3.28982,-3.78453 1.93068,-13.45759 -2.58668,-15.74886 -4.7363,-2.40229 -8.80251,1.12086 -12.85868,3.25251 l -21.20306,11.2665 c -2.56736,1.35182 -6.01463,4.01777 -9.08702,4.01777 -3.29073,0 -7.12524,-2.97742 -9.84427,-4.47101 -6.50984,-3.57597 -13.17851,-6.90091 -19.68854,-10.47698 -2.85668,-1.56921 -7.10587,-4.85415 -10.60153,-4.52437 -7.2756,0.6864 -9.69308,11.97712 -5.60111,16.68444 2.43875,2.80547 7.56529,4.49972 10.90188,6.26408 7.83167,4.14137 15.6667,8.28474 23.4748,12.46456 3.21433,1.72068 8.18431,5.79216 12.11603,5.22877 m 30.29007,-45.33762 v -1.39693 l -18.17404,-8.90973 -12.11603,4.65427 -12.87329,-4.72164 -18.93129,8.9771 v 1.39693 l 21.20305,11.17542 9.84427,4.34169 9.84428,-4.33415 z"
|
||||
id="path1" />
|
||||
<path
|
||||
d="m 334.95508,211.65039 c -2.016,0 -3.74414,1.63214 -3.74414,3.74414 v 67.48828 c 0,2.112 1.72814,3.74414 3.74414,3.74414 2.112,0 3.74414,-1.63214 3.74414,-3.74414 v -24.57617 l 8.54492,-8.64062 26.11133,35.32812 c 0.768,1.152 1.82397,1.63281 3.16797,1.63281 2.68799,0 4.5115,-3.36112 2.6875,-5.95312 l -26.5918,-36.28711 26.30469,-26.30469 c 2.304,-2.592 1.24701,-6.43164 -2.20899,-6.43164 -1.152,0 -2.01461,0.38375 -2.97461,1.34375 l -35.04101,35.04102 v -32.64063 c 0,-2.112 -1.63214,-3.74414 -3.74414,-3.74414 z"
|
||||
style="fill:#ff7f2a;stroke-width:51.0236;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0"
|
||||
id="path3" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.7 KiB |
1
doc/docs/statics/klee.svg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
doc/docs/statics/logo-bright.png
Normal file
After Width: | Height: | Size: 15 KiB |
118
doc/docs/statics/logo-bright.svg
Normal file
@@ -0,0 +1,118 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="svg211948"
|
||||
viewBox="0 0 544.44238 97.824005"
|
||||
height="97.824005"
|
||||
width="544.44238"
|
||||
version="1.1"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata211954">
|
||||
<rdf:rdf>
|
||||
<cc:work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:work>
|
||||
</rdf:rdf>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs211952" />
|
||||
<linearGradient
|
||||
spreadMethod="pad"
|
||||
y2="0.30000001"
|
||||
x2="-0.1"
|
||||
y1="1.2"
|
||||
x1="0.30000001"
|
||||
id="3d_gradient2-logo-24885591-b378-4c55-b87b-b7d42ed10694">
|
||||
<stop
|
||||
id="stop211929"
|
||||
stop-opacity="1"
|
||||
stop-color="#ffffff"
|
||||
offset="0%" />
|
||||
<stop
|
||||
id="stop211931"
|
||||
stop-opacity="1"
|
||||
stop-color="#000000"
|
||||
offset="100%" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientTransform="rotate(-30)"
|
||||
spreadMethod="pad"
|
||||
y2="0.30000001"
|
||||
x2="-0.1"
|
||||
y1="1.2"
|
||||
x1="0.30000001"
|
||||
id="3d_gradient3-logo-24885591-b378-4c55-b87b-b7d42ed10694">
|
||||
<stop
|
||||
id="stop211934"
|
||||
stop-opacity="1"
|
||||
stop-color="#ffffff"
|
||||
offset="0%" />
|
||||
<stop
|
||||
id="stop211936"
|
||||
stop-opacity="1"
|
||||
stop-color="#cccccc"
|
||||
offset="50%" />
|
||||
<stop
|
||||
id="stop211938"
|
||||
stop-opacity="1"
|
||||
stop-color="#000000"
|
||||
offset="100%" />
|
||||
</linearGradient>
|
||||
<g
|
||||
id="logo-group"
|
||||
transform="translate(-185.54797,-175.3735)">
|
||||
<image
|
||||
xlink:href=""
|
||||
id="container"
|
||||
x="272"
|
||||
y="144"
|
||||
width="480"
|
||||
height="480"
|
||||
style="display:none" />
|
||||
<image
|
||||
xlink:href=""
|
||||
id="icon_container"
|
||||
style="display:none"
|
||||
x="0"
|
||||
y="0"
|
||||
width="0"
|
||||
height="0" />
|
||||
<g
|
||||
id="g18"
|
||||
transform="translate(-91.179677,-177.97015)">
|
||||
<path
|
||||
style="fill:#388ec7;fill-opacity:1;stroke:none;stroke-width:1.41128"
|
||||
d="m 650.16134,532.23713 -33.31908,17.65778 -13.63054,6.42952 -13.63053,-6.42952 -33.31907,-17.65778 c 0,5.32502 -2.24747,16.12101 0.77828,20.72433 1.84892,2.81293 6.84753,4.46784 9.82323,6.09432 8.26868,4.51957 16.72019,8.751 24.98932,13.27082 3.1083,1.69895 7.62988,5.16897 11.35877,5.21476 3.70778,0.0455 8.2451,-3.55102 11.35878,-5.19752 8.33909,-4.40967 16.72005,-8.76818 24.98932,-13.28806 3.05286,-1.66866 8.68533,-3.49778 10.14461,-6.69253 2.3911,-5.23485 0.45691,-14.46175 0.45691,-20.12612 m -46.19236,16.00215 c 3.59731,-0.51544 7.48607,-3.47354 10.60152,-5.12097 7.58911,-4.0131 15.20174,-8.00941 22.71755,-12.13797 3.53683,-1.94283 9.05374,-3.7013 11.65911,-6.69847 3.28982,-3.78453 1.93068,-13.45759 -2.58668,-15.74886 -4.7363,-2.40229 -8.80251,1.12086 -12.85868,3.25251 l -21.20306,11.2665 c -2.56736,1.35182 -6.01463,4.01777 -9.08702,4.01777 -3.29073,0 -7.12524,-2.97742 -9.84427,-4.47101 -6.50984,-3.57597 -13.17851,-6.90091 -19.68854,-10.47698 -2.85668,-1.56921 -7.10587,-4.85415 -10.60153,-4.52437 -7.2756,0.6864 -9.69308,11.97712 -5.60111,16.68444 2.43875,2.80547 7.56529,4.49972 10.90188,6.26408 7.83167,4.14137 15.6667,8.28474 23.4748,12.46456 3.21433,1.72068 8.18431,5.79216 12.11603,5.22877 m 30.29007,-45.33762 v -1.39693 l -18.17404,-8.90973 -12.11603,4.65427 -12.87329,-4.72164 -18.93129,8.9771 v 1.39693 l 21.20305,11.17542 9.84427,4.34169 9.84428,-4.33415 z"
|
||||
id="path17" />
|
||||
<path
|
||||
d="m 388.93329,654.58136 c 2.112,0 3.744,-1.632 3.744,-3.744 v -24.576 l 8.544,-8.64 26.112,35.328 c 0.768,1.152 1.824,1.632 3.168,1.632 2.688,0 4.512,-3.36 2.688,-5.952 l -26.592,-36.288 26.304,-26.304 c 2.304,-2.592 1.248,-6.432 -2.208,-6.432 -1.152,0 -2.016,0.384 -2.976,1.344 l -35.04,35.04 v -32.64 c 0,-2.112 -1.632,-3.744 -3.744,-3.744 -2.016,0 -3.744,1.632 -3.744,3.744 v 67.488 c 0,2.112 1.728,3.744 3.744,3.744 z m 75.55212,0.48 c 8.448,0 15.648,-3.84 19.968,-9.984 v 5.856 c 0,2.112 1.536,3.648 3.744,3.648 2.112,0 3.744,-1.536 3.744,-3.648 v -22.56 c -0.096,-15.264 -11.52,-26.784 -26.688,-26.784 -15.264,0 -26.688,11.52 -26.688,26.784 0,15.264 11.136,26.688 25.92,26.688 z m 0.768,-6.72 c -11.04,0 -19.488,-8.544 -19.488,-19.968 0,-11.424 8.448,-20.064 19.488,-20.064 11.04,0 19.392,8.64 19.392,20.064 0,11.424 -8.352,19.968 -19.392,19.968 z m 61.63201,6.24 h 2.016 c 2.304,0 4.032,-1.536 4.032,-3.648 0,-2.208 -1.44,-3.744 -3.456,-3.744 h -2.592 c -5.952,0 -10.272,-4.608 -10.272,-11.136 v -25.632 h 10.848 c 2.016,0 3.36,-1.344 3.36,-3.168 0,-1.92 -1.344,-3.264 -3.36,-3.264 h -10.848 v -14.88 c 0,-2.208 -1.536,-3.744 -3.744,-3.744 -2.112,0 -3.648,1.536 -3.648,3.744 v 14.88 h -6.24 c -2.016,0 -3.36,1.344 -3.36,3.264 0,1.824 1.344,3.168 3.36,3.168 h 6.24 v 25.632 c 0,10.656 7.392,18.432 17.664,18.528 z m 41.85604,0.48 c 5.952,0 13.248,-2.592 17.472,-6.24 1.536,-1.344 1.44,-3.36 -0.192,-4.8 -1.344,-1.056 -3.264,-0.96 -4.704,0.192 -2.784,2.4 -7.968,4.224 -12.576,4.224 -10.656,0 -18.528,-7.2 -19.584,-17.664 h 38.784 c 2.016,0 3.456,-1.344 3.456,-3.36 0,-15.072 -9.6,-25.824 -24.096,-25.824 -14.784,0 -25.152,11.136 -25.152,26.784 0,15.648 11.04,26.688 26.592,26.688 z m -1.44,-46.848 c 9.696,0 16.224,6.72 17.184,16.416 h -35.136 c 1.344,-9.696 8.16,-16.416 17.952,-16.416 z m 40.32003,46.368 c 2.208,0 3.648,-1.44 3.648,-3.648 v -28.416 c 0,-7.968 7.2,-14.016 16.704,-14.016 9.792,0 17.184,5.664 17.184,17.472 v 24.96 c 0,2.112 1.632,3.648 3.744,3.648 2.016,0 3.648,-1.536 3.648,-3.648 v -24.96 c 0,-15.84 -10.272,-24.48 -23.808,-24.48 -7.104,0 -13.344,2.88 -17.472,7.584 v -3.264 c 0,-2.208 -1.44,-3.744 -3.648,-3.744 -2.208,0 -3.744,1.536 -3.744,3.744 v 45.12 c 0,2.208 1.536,3.648 3.744,3.648 z m 84.384,0.48 c 8.448,0 15.648,-3.84 19.968,-9.984 v 5.856 c 0,2.112 1.536,3.648 3.744,3.648 2.112,0 3.744,-1.536 3.744,-3.648 v -22.56 c -0.096,-15.264 -11.52,-26.784 -26.688,-26.784 -15.264,0 -26.688,11.52 -26.688,26.784 0,15.264 11.136,26.688 25.92,26.688 z m 0.768,-6.72 c -11.04,0 -19.488,-8.544 -19.488,-19.968 0,-11.424 8.448,-20.064 19.488,-20.064 11.04,0 19.392,8.64 19.392,20.064 0,11.424 -8.352,19.968 -19.392,19.968 z m 44.73601,6.24 c 2.304,0 3.648,-1.344 3.648,-3.648 v -28.704 c 0,-8.928 7.872,-14.688 18.624,-13.728 3.168,0.192 5.76,0.768 6.432,-2.112 0.768,-3.168 -2.688,-4.896 -8.64,-4.896 -6.72,0 -12.672,3.168 -16.416,8.352 v -4.128 c 0,-2.304 -1.344,-3.648 -3.648,-3.648 -2.304,0 -3.648,1.344 -3.648,3.648 v 45.216 c 0,2.304 1.344,3.648 3.648,3.648 z m 49.34401,22.848 c 1.248,0 2.208,-0.768 2.784,-2.208 l 31.104,-68.352 c 0.96,-2.112 0.288,-3.648 -1.824,-4.512 -2.112,-0.864 -3.648,-0.288 -4.608,1.728 l -17.856,39.36 -18.72,-39.456 c -0.864,-1.92 -2.688,-2.496 -4.608,-1.632 -1.92,0.864 -2.688,2.688 -1.728,4.608 l 21.408,44.352 0.096,0.096 -9.6,21.024 c -0.96,2.112 -0.288,3.744 1.824,4.608 0.576,0.288 1.152,0.384 1.728,0.384 z"
|
||||
id="text18"
|
||||
style="font-size:96px;line-height:0;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ff7f2a;stroke-width:51.0236;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0"
|
||||
aria-label="Katenary" />
|
||||
</g>
|
||||
<path
|
||||
style="fill:#388ec7;fill-opacity:1;stroke:none;stroke-width:1.41128"
|
||||
d="m 558.98167,600.50526 -33.31908,17.65778 -13.63054,6.42952 -13.63053,-6.42952 -33.31907,-17.65778 c 0,5.32502 -2.24747,16.12101 0.77828,20.72433 1.84892,2.81293 6.84753,4.46784 9.82323,6.09432 8.26868,4.51957 16.72019,8.751 24.98932,13.27082 3.1083,1.69895 7.62988,5.16897 11.35877,5.21476 3.70778,0.0455 8.2451,-3.55102 11.35878,-5.19752 8.33909,-4.40967 16.72005,-8.76818 24.98932,-13.28806 3.05286,-1.66866 8.68533,-3.49778 10.14461,-6.69253 2.3911,-5.23485 0.45691,-14.46175 0.45691,-20.12612 m -46.19236,16.00215 c 3.59731,-0.51544 7.48607,-3.47354 10.60152,-5.12097 7.58911,-4.0131 15.20174,-8.00941 22.71755,-12.13797 3.53683,-1.94283 9.05374,-3.7013 11.65911,-6.69847 3.28982,-3.78453 1.93068,-13.45759 -2.58668,-15.74886 -4.7363,-2.40229 -8.80251,1.12086 -12.85868,3.25251 l -21.20306,11.2665 c -2.56736,1.35182 -6.01463,4.01777 -9.08702,4.01777 -3.29073,0 -7.12524,-2.97742 -9.84427,-4.47101 -6.50984,-3.57597 -13.17851,-6.90091 -19.68854,-10.47698 -2.85668,-1.56921 -7.10587,-4.85415 -10.60153,-4.52437 -7.2756,0.6864 -9.69308,11.97712 -5.60111,16.68444 2.43875,2.80547 7.56529,4.49972 10.90188,6.26408 7.83167,4.14137 15.6667,8.28474 23.4748,12.46456 3.21433,1.72068 8.18431,5.79216 12.11603,5.22877 m 30.29007,-45.33762 v -1.39693 l -18.17404,-8.90973 -12.11603,4.65427 -12.87329,-4.72164 -18.93129,8.9771 v 1.39693 l 21.20305,11.17542 9.84427,4.34169 9.84428,-4.33415 z"
|
||||
id="path18" />
|
||||
<g
|
||||
id="g19">
|
||||
<path
|
||||
style="fill:#388ec7;fill-opacity:1;stroke:none;stroke-width:1.41128"
|
||||
d="m 280.25003,221.48792 -33.31908,17.65778 -13.63054,6.42952 -13.63053,-6.42952 -33.31907,-17.65778 c 0,5.32502 -2.24747,16.12101 0.77828,20.72433 1.84892,2.81293 6.84753,4.46784 9.82323,6.09432 8.26868,4.51957 16.72019,8.751 24.98932,13.27082 3.1083,1.69895 7.62988,5.16897 11.35877,5.21476 3.70778,0.0455 8.2451,-3.55102 11.35878,-5.19752 8.33909,-4.40967 16.72005,-8.76818 24.98932,-13.28806 3.05286,-1.66866 8.68533,-3.49778 10.14461,-6.69253 2.3911,-5.23485 0.45691,-14.46175 0.45691,-20.12612 m -46.19236,16.00215 c 3.59731,-0.51544 7.48607,-3.47354 10.60152,-5.12097 7.58911,-4.0131 15.20174,-8.00941 22.71755,-12.13797 3.53683,-1.94283 9.05374,-3.7013 11.65911,-6.69847 3.28982,-3.78453 1.93068,-13.45759 -2.58668,-15.74886 -4.7363,-2.40229 -8.80251,1.12086 -12.85868,3.25251 l -21.20306,11.2665 c -2.56736,1.35182 -6.01463,4.01777 -9.08702,4.01777 -3.29073,0 -7.12524,-2.97742 -9.84427,-4.47101 -6.50984,-3.57597 -13.17851,-6.90091 -19.68854,-10.47698 -2.85668,-1.56921 -7.10587,-4.85415 -10.60153,-4.52437 -7.2756,0.6864 -9.69308,11.97712 -5.60111,16.68444 2.43875,2.80547 7.56529,4.49972 10.90188,6.26408 7.83167,4.14137 15.6667,8.28474 23.4748,12.46456 3.21433,1.72068 8.18431,5.79216 12.11603,5.22877 m 30.29007,-45.33762 v -1.39693 l -18.17404,-8.90973 -12.11603,4.65427 -12.87329,-4.72164 -18.93129,8.9771 v 1.39693 l 21.20305,11.17542 9.84427,4.34169 9.84428,-4.33415 z"
|
||||
id="path19" />
|
||||
<path
|
||||
d="m 297.75361,250.3495 c 2.112,0 3.744,-1.632 3.744,-3.744 v -24.576 l 8.544,-8.64 26.112,35.328 c 0.768,1.152 1.824,1.632 3.168,1.632 2.688,0 4.512,-3.36 2.688,-5.952 l -26.592,-36.288 26.304,-26.304 c 2.304,-2.592 1.248,-6.432 -2.208,-6.432 -1.152,0 -2.016,0.384 -2.976,1.344 l -35.04,35.04 v -32.64 c 0,-2.112 -1.632,-3.744 -3.744,-3.744 -2.016,0 -3.744,1.632 -3.744,3.744 v 67.488 c 0,2.112 1.728,3.744 3.744,3.744 z m 75.55211,0.48 c 8.448,0 15.648,-3.84 19.968,-9.984 v 5.856 c 0,2.112 1.536,3.648 3.744,3.648 2.112,0 3.744,-1.536 3.744,-3.648 v -22.56 c -0.096,-15.264 -11.52,-26.784 -26.688,-26.784 -15.264,0 -26.688,11.52 -26.688,26.784 0,15.264 11.136,26.688 25.92,26.688 z m 0.768,-6.72 c -11.04,0 -19.488,-8.544 -19.488,-19.968 0,-11.424 8.448,-20.064 19.488,-20.064 11.04,0 19.392,8.64 19.392,20.064 0,11.424 -8.352,19.968 -19.392,19.968 z m 61.63201,6.24 h 2.016 c 2.304,0 4.032,-1.536 4.032,-3.648 0,-2.208 -1.44,-3.744 -3.456,-3.744 h -2.592 c -5.952,0 -10.272,-4.608 -10.272,-11.136 v -25.632 h 10.848 c 2.016,0 3.36,-1.344 3.36,-3.168 0,-1.92 -1.344,-3.264 -3.36,-3.264 h -10.848 v -14.88 c 0,-2.208 -1.536,-3.744 -3.744,-3.744 -2.112,0 -3.648,1.536 -3.648,3.744 v 14.88 h -6.24 c -2.016,0 -3.36,1.344 -3.36,3.264 0,1.824 1.344,3.168 3.36,3.168 h 6.24 v 25.632 c 0,10.656 7.392,18.432 17.664,18.528 z m 41.85604,0.48 c 5.952,0 13.248,-2.592 17.472,-6.24 1.536,-1.344 1.44,-3.36 -0.192,-4.8 -1.344,-1.056 -3.264,-0.96 -4.704,0.192 -2.784,2.4 -7.968,4.224 -12.576,4.224 -10.656,0 -18.528,-7.2 -19.584,-17.664 h 38.784 c 2.016,0 3.456,-1.344 3.456,-3.36 0,-15.072 -9.6,-25.824 -24.096,-25.824 -14.784,0 -25.152,11.136 -25.152,26.784 0,15.648 11.04,26.688 26.592,26.688 z m -1.44,-46.848 c 9.696,0 16.224,6.72 17.184,16.416 h -35.136 c 1.344,-9.696 8.16,-16.416 17.952,-16.416 z m 40.32003,46.368 c 2.208,0 3.648,-1.44 3.648,-3.648 v -28.416 c 0,-7.968 7.2,-14.016 16.704,-14.016 9.792,0 17.184,5.664 17.184,17.472 v 24.96 c 0,2.112 1.632,3.648 3.744,3.648 2.016,0 3.648,-1.536 3.648,-3.648 v -24.96 c 0,-15.84 -10.272,-24.48 -23.808,-24.48 -7.104,0 -13.344,2.88 -17.472,7.584 v -3.264 c 0,-2.208 -1.44,-3.744 -3.648,-3.744 -2.208,0 -3.744,1.536 -3.744,3.744 v 45.12 c 0,2.208 1.536,3.648 3.744,3.648 z m 84.384,0.48 c 8.448,0 15.648,-3.84 19.968,-9.984 v 5.856 c 0,2.112 1.536,3.648 3.744,3.648 2.112,0 3.744,-1.536 3.744,-3.648 v -22.56 c -0.096,-15.264 -11.52,-26.784 -26.688,-26.784 -15.264,0 -26.688,11.52 -26.688,26.784 0,15.264 11.136,26.688 25.92,26.688 z m 0.768,-6.72 c -11.04,0 -19.488,-8.544 -19.488,-19.968 0,-11.424 8.448,-20.064 19.488,-20.064 11.04,0 19.392,8.64 19.392,20.064 0,11.424 -8.352,19.968 -19.392,19.968 z m 44.73601,6.24 c 2.304,0 3.648,-1.344 3.648,-3.648 v -28.704 c 0,-8.928 7.872,-14.688 18.624,-13.728 3.168,0.192 5.76,0.768 6.432,-2.112 0.768,-3.168 -2.688,-4.896 -8.64,-4.896 -6.72,0 -12.672,3.168 -16.416,8.352 v -4.128 c 0,-2.304 -1.344,-3.648 -3.648,-3.648 -2.304,0 -3.648,1.344 -3.648,3.648 v 45.216 c 0,2.304 1.344,3.648 3.648,3.648 z m 49.34402,22.848 c 1.248,0 2.208,-0.768 2.784,-2.208 l 31.104,-68.352 c 0.96,-2.112 0.288,-3.648 -1.824,-4.512 -2.112,-0.864 -3.648,-0.288 -4.608,1.728 l -17.856,39.36 -18.72,-39.456 c -0.864,-1.92 -2.688,-2.496 -4.608,-1.632 -1.92,0.864 -2.688,2.688 -1.728,4.608 l 21.408,44.352 0.096,0.096 -9.6,21.024 c -0.96,2.112 -0.288,3.744 1.824,4.608 0.576,0.288 1.152,0.384 1.728,0.384 z"
|
||||
id="text19"
|
||||
style="font-size:96px;line-height:0;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ff7f2a;stroke-width:51.0236;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0"
|
||||
aria-label="Katenary" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 14 KiB |
121
doc/docs/statics/logo-dark.svg
Normal file
@@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="svg211948"
|
||||
viewBox="0 0 489.26056 97.824219"
|
||||
height="97.824219"
|
||||
width="489.26056"
|
||||
version="1.1"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata211954">
|
||||
<rdf:rdf>
|
||||
<cc:work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:work>
|
||||
</rdf:rdf>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs211952" />
|
||||
<linearGradient
|
||||
spreadMethod="pad"
|
||||
y2="0.30000001"
|
||||
x2="-0.1"
|
||||
y1="1.2"
|
||||
x1="0.30000001"
|
||||
id="3d_gradient2-logo-24885591-b378-4c55-b87b-b7d42ed10694">
|
||||
<stop
|
||||
id="stop211929"
|
||||
stop-opacity="1"
|
||||
stop-color="#ffffff"
|
||||
offset="0%" />
|
||||
<stop
|
||||
id="stop211931"
|
||||
stop-opacity="1"
|
||||
stop-color="#000000"
|
||||
offset="100%" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientTransform="rotate(-30)"
|
||||
spreadMethod="pad"
|
||||
y2="0.30000001"
|
||||
x2="-0.1"
|
||||
y1="1.2"
|
||||
x1="0.30000001"
|
||||
id="3d_gradient3-logo-24885591-b378-4c55-b87b-b7d42ed10694">
|
||||
<stop
|
||||
id="stop211934"
|
||||
stop-opacity="1"
|
||||
stop-color="#ffffff"
|
||||
offset="0%" />
|
||||
<stop
|
||||
id="stop211936"
|
||||
stop-opacity="1"
|
||||
stop-color="#cccccc"
|
||||
offset="50%" />
|
||||
<stop
|
||||
id="stop211938"
|
||||
stop-opacity="1"
|
||||
stop-color="#000000"
|
||||
offset="100%" />
|
||||
</linearGradient>
|
||||
<g
|
||||
id="logo-group"
|
||||
transform="translate(-331.21094,-211.65039)">
|
||||
<path
|
||||
d="m 773.83594,234.02344 c -0.4545,0.0375 -0.92821,0.1629 -1.40821,0.3789 -1.91999,0.864 -2.68851,2.68743 -1.72851,4.60743 l 21.4082,44.35351 0.0957,0.0957 -9.5996,21.02344 c -0.96,2.112 -0.28778,3.74342 1.82421,4.60742 0.576,0.288 1.15252,0.38477 1.72852,0.38477 1.248,0 2.2072,-0.76703 2.7832,-2.20703 l 31.10352,-68.35352 c 0.96,-2.112 0.28778,-3.64772 -1.82422,-4.51172 -2.112,-0.864 -3.64742,-0.28748 -4.60742,1.72852 l -17.85547,39.35937 -18.7207,-39.45507 c -0.648,-1.44 -1.83572,-2.12422 -3.19922,-2.01172 z"
|
||||
style="font-size:96px;line-height:0;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ff7f2a;stroke-width:51.0236;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0"
|
||||
id="path9" />
|
||||
<path
|
||||
d="m 692.07617,233.63477 c -15.26398,0 -26.68945,11.51921 -26.68945,26.7832 0,15.26398 11.13594,26.68945 25.91992,26.68945 8.44799,0 15.64875,-3.84038 19.96875,-9.98437 v 5.85547 c 0,2.11199 1.53614,3.64843 3.74414,3.64843 2.112,0 3.74414,-1.53644 3.74414,-3.64843 v -22.56055 c -0.096,-15.26399 -11.51951,-26.7832 -26.6875,-26.7832 z m 0,6.7207 c 11.03999,0 19.39063,8.63851 19.39063,20.0625 0,11.42399 -8.35064,19.96875 -19.39063,19.96875 -11.03999,0 -19.48828,-8.54476 -19.48828,-19.96875 0,-11.42399 8.44829,-20.0625 19.48828,-20.0625 z"
|
||||
style="font-size:96px;line-height:0;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ff7f2a;stroke-width:51.0236;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0"
|
||||
id="path8" />
|
||||
<path
|
||||
d="m 566.60352,233.63477 c -14.78399,0 -25.15235,11.13521 -25.15235,26.7832 0,15.64798 11.03981,26.68945 26.5918,26.68945 5.95199,0 13.24866,-2.59224 17.47265,-6.24023 1.536,-1.344 1.4406,-3.36079 -0.1914,-4.80078 -1.344,-1.056 -3.26508,-0.9606 -4.70508,0.1914 -2.784,2.4 -7.96818,4.22461 -12.57617,4.22461 -10.65599,0 -18.52799,-7.20007 -19.58399,-17.66406 h 38.78516 c 2.016,0 3.45508,-1.34338 3.45508,-3.35938 0,-15.07198 -9.59972,-25.82421 -24.0957,-25.82421 z m 0,6.62304 c 9.69599,0 16.22359,6.72003 17.18359,16.41602 h -35.13477 c 1.344,-9.69599 8.15919,-16.41602 17.95118,-16.41602 z"
|
||||
style="font-size:96px;line-height:0;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ff7f2a;stroke-width:51.0236;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0"
|
||||
id="path7" />
|
||||
<path
|
||||
d="m 756.875,233.53906 c -6.71999,0 -12.67202,3.16757 -16.41602,8.35156 v -4.1289 c 0,-2.304 -1.34248,-3.64649 -3.64648,-3.64649 -2.304,0 -3.64844,1.34249 -3.64844,3.64649 v 45.2168 c 0,2.30399 1.34444,3.64843 3.64844,3.64843 2.304,0 3.64648,-1.34444 3.64648,-3.64843 v -28.70313 c 0,-8.92799 7.87302,-14.68851 18.625,-13.72852 3.168,0.192 5.75965,0.76867 6.43164,-2.11132 0.768,-3.168 -2.68863,-4.89649 -8.64062,-4.89649 z"
|
||||
style="font-size:96px;line-height:0;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ff7f2a;stroke-width:51.0236;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0"
|
||||
id="path6" />
|
||||
<path
|
||||
d="m 628.04297,233.53906 c -7.10399,0 -13.34271,2.87999 -17.4707,7.58399 v -3.26368 c 0,-2.20799 -1.44044,-3.74414 -3.64844,-3.74414 -2.208,0 -3.74414,1.53615 -3.74414,3.74414 v 45.11915 c 0,2.20799 1.53614,3.64843 3.74414,3.64843 2.208,0 3.64844,-1.44044 3.64844,-3.64843 V 254.5625 c 0,-7.96799 7.19913,-14.01563 16.70312,-14.01563 9.79199,0 17.18359,5.66467 17.18359,17.47266 v 24.95899 c 0,2.11199 1.63215,3.64843 3.74414,3.64843 2.016,0 3.64844,-1.53644 3.64844,-3.64843 v -24.95899 c 0,-15.83998 -10.2726,-24.48047 -23.80859,-24.48047 z"
|
||||
style="font-size:96px;line-height:0;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ff7f2a;stroke-width:51.0236;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0"
|
||||
id="path5" />
|
||||
<path
|
||||
d="m 512.17187,217.41016 c -2.11199,0 -3.64843,1.53614 -3.64843,3.74414 v 14.88086 h -6.24024 c -2.01599,0 -3.35937,1.34367 -3.35937,3.26367 0,1.824 1.34338,3.16797 3.35937,3.16797 h 6.24024 v 25.63281 c 0,10.65599 7.39207,18.43134 17.66406,18.52734 h 2.01562 c 2.304,0 4.03321,-1.53644 4.03321,-3.64843 0,-2.208 -1.44104,-3.74415 -3.45703,-3.74415 h -2.5918 c -5.95199,0 -10.27148,-4.60677 -10.27148,-11.13476 V 242.4668 h 10.84765 c 2.016,0 3.35938,-1.34397 3.35938,-3.16797 0,-1.92 -1.34338,-3.26367 -3.35938,-3.26367 H 515.91602 V 221.1543 c 0,-2.208 -1.53615,-3.74414 -3.74415,-3.74414 z"
|
||||
style="font-size:96px;line-height:0;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ff7f2a;stroke-width:51.0236;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0"
|
||||
id="path4" />
|
||||
<image
|
||||
xlink:href=""
|
||||
id="container"
|
||||
x="272"
|
||||
y="144"
|
||||
width="480"
|
||||
height="480"
|
||||
style="display:none" />
|
||||
<image
|
||||
xlink:href=""
|
||||
id="icon_container"
|
||||
style="display:none"
|
||||
x="0"
|
||||
y="0"
|
||||
width="0"
|
||||
height="0" />
|
||||
<path
|
||||
style="fill:#388ec7;fill-opacity:1;stroke:none;stroke-width:1.41128"
|
||||
d="m 488.71354,251.36014 -33.31908,17.65778 -13.63054,6.42952 -13.63053,-6.42952 -33.31907,-17.65778 c 0,5.32502 -2.24747,16.12101 0.77828,20.72433 1.84892,2.81293 6.84753,4.46784 9.82323,6.09432 8.26868,4.51957 16.72019,8.751 24.98932,13.27082 3.1083,1.69895 7.62988,5.16897 11.35877,5.21476 3.70778,0.0455 8.2451,-3.55102 11.35878,-5.19752 8.33909,-4.40967 16.72005,-8.76818 24.98932,-13.28806 3.05286,-1.66866 8.68533,-3.49778 10.14461,-6.69253 2.3911,-5.23485 0.45691,-14.46175 0.45691,-20.12612 m -46.19236,16.00215 c 3.59731,-0.51544 7.48607,-3.47354 10.60152,-5.12097 7.58911,-4.0131 15.20174,-8.00941 22.71755,-12.13797 3.53683,-1.94283 9.05374,-3.7013 11.65911,-6.69847 3.28982,-3.78453 1.93068,-13.45759 -2.58668,-15.74886 -4.7363,-2.40229 -8.80251,1.12086 -12.85868,3.25251 l -21.20306,11.2665 c -2.56736,1.35182 -6.01463,4.01777 -9.08702,4.01777 -3.29073,0 -7.12524,-2.97742 -9.84427,-4.47101 -6.50984,-3.57597 -13.17851,-6.90091 -19.68854,-10.47698 -2.85668,-1.56921 -7.10587,-4.85415 -10.60153,-4.52437 -7.2756,0.6864 -9.69308,11.97712 -5.60111,16.68444 2.43875,2.80547 7.56529,4.49972 10.90188,6.26408 7.83167,4.14137 15.6667,8.28474 23.4748,12.46456 3.21433,1.72068 8.18431,5.79216 12.11603,5.22877 m 30.29007,-45.33762 v -1.39693 l -18.17404,-8.90973 -12.11603,4.65427 -12.87329,-4.72164 -18.93129,8.9771 v 1.39693 l 21.20305,11.17542 9.84427,4.34169 9.84428,-4.33415 z"
|
||||
id="path1" />
|
||||
<path
|
||||
d="m 334.95508,211.65039 c -2.016,0 -3.74414,1.63214 -3.74414,3.74414 v 67.48828 c 0,2.112 1.72814,3.74414 3.74414,3.74414 2.112,0 3.74414,-1.63214 3.74414,-3.74414 v -24.57617 l 8.54492,-8.64062 26.11133,35.32812 c 0.768,1.152 1.82397,1.63281 3.16797,1.63281 2.68799,0 4.5115,-3.36112 2.6875,-5.95312 l -26.5918,-36.28711 26.30469,-26.30469 c 2.304,-2.592 1.24701,-6.43164 -2.20899,-6.43164 -1.152,0 -2.01461,0.38375 -2.97461,1.34375 l -35.04101,35.04102 v -32.64063 c 0,-2.112 -1.63214,-3.74414 -3.74414,-3.74414 z"
|
||||
style="fill:#ff7f2a;stroke-width:51.0236;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0"
|
||||
id="path3" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.7 KiB |
BIN
doc/docs/statics/logo-vertical.png
Normal file
After Width: | Height: | Size: 14 KiB |
118
doc/docs/statics/logo-vertical.svg
Normal file
@@ -0,0 +1,118 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="svg211948"
|
||||
viewBox="0 0 435.98074 184.9017"
|
||||
height="184.9017"
|
||||
width="435.98074"
|
||||
version="1.1"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata211954">
|
||||
<rdf:rdf>
|
||||
<cc:work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:work>
|
||||
</rdf:rdf>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs211952" />
|
||||
<linearGradient
|
||||
spreadMethod="pad"
|
||||
y2="0.30000001"
|
||||
x2="-0.1"
|
||||
y1="1.2"
|
||||
x1="0.30000001"
|
||||
id="3d_gradient2-logo-24885591-b378-4c55-b87b-b7d42ed10694">
|
||||
<stop
|
||||
id="stop211929"
|
||||
stop-opacity="1"
|
||||
stop-color="#ffffff"
|
||||
offset="0%" />
|
||||
<stop
|
||||
id="stop211931"
|
||||
stop-opacity="1"
|
||||
stop-color="#000000"
|
||||
offset="100%" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientTransform="rotate(-30)"
|
||||
spreadMethod="pad"
|
||||
y2="0.30000001"
|
||||
x2="-0.1"
|
||||
y1="1.2"
|
||||
x1="0.30000001"
|
||||
id="3d_gradient3-logo-24885591-b378-4c55-b87b-b7d42ed10694">
|
||||
<stop
|
||||
id="stop211934"
|
||||
stop-opacity="1"
|
||||
stop-color="#ffffff"
|
||||
offset="0%" />
|
||||
<stop
|
||||
id="stop211936"
|
||||
stop-opacity="1"
|
||||
stop-color="#cccccc"
|
||||
offset="50%" />
|
||||
<stop
|
||||
id="stop211938"
|
||||
stop-opacity="1"
|
||||
stop-color="#000000"
|
||||
offset="100%" />
|
||||
</linearGradient>
|
||||
<g
|
||||
id="logo-group"
|
||||
transform="translate(-294.00961,-314.5575)">
|
||||
<image
|
||||
xlink:href=""
|
||||
id="container"
|
||||
x="272"
|
||||
y="144"
|
||||
width="480"
|
||||
height="480"
|
||||
style="display:none" />
|
||||
<image
|
||||
xlink:href=""
|
||||
id="icon_container"
|
||||
style="display:none"
|
||||
x="0"
|
||||
y="0"
|
||||
width="0"
|
||||
height="0" />
|
||||
<g
|
||||
id="g18"
|
||||
transform="translate(-91.179677,-177.97015)">
|
||||
<path
|
||||
style="fill:#388ec7;fill-opacity:1;stroke:none;stroke-width:1.41128"
|
||||
d="m 650.16134,532.23713 -33.31908,17.65778 -13.63054,6.42952 -13.63053,-6.42952 -33.31907,-17.65778 c 0,5.32502 -2.24747,16.12101 0.77828,20.72433 1.84892,2.81293 6.84753,4.46784 9.82323,6.09432 8.26868,4.51957 16.72019,8.751 24.98932,13.27082 3.1083,1.69895 7.62988,5.16897 11.35877,5.21476 3.70778,0.0455 8.2451,-3.55102 11.35878,-5.19752 8.33909,-4.40967 16.72005,-8.76818 24.98932,-13.28806 3.05286,-1.66866 8.68533,-3.49778 10.14461,-6.69253 2.3911,-5.23485 0.45691,-14.46175 0.45691,-20.12612 m -46.19236,16.00215 c 3.59731,-0.51544 7.48607,-3.47354 10.60152,-5.12097 7.58911,-4.0131 15.20174,-8.00941 22.71755,-12.13797 3.53683,-1.94283 9.05374,-3.7013 11.65911,-6.69847 3.28982,-3.78453 1.93068,-13.45759 -2.58668,-15.74886 -4.7363,-2.40229 -8.80251,1.12086 -12.85868,3.25251 l -21.20306,11.2665 c -2.56736,1.35182 -6.01463,4.01777 -9.08702,4.01777 -3.29073,0 -7.12524,-2.97742 -9.84427,-4.47101 -6.50984,-3.57597 -13.17851,-6.90091 -19.68854,-10.47698 -2.85668,-1.56921 -7.10587,-4.85415 -10.60153,-4.52437 -7.2756,0.6864 -9.69308,11.97712 -5.60111,16.68444 2.43875,2.80547 7.56529,4.49972 10.90188,6.26408 7.83167,4.14137 15.6667,8.28474 23.4748,12.46456 3.21433,1.72068 8.18431,5.79216 12.11603,5.22877 m 30.29007,-45.33762 v -1.39693 l -18.17404,-8.90973 -12.11603,4.65427 -12.87329,-4.72164 -18.93129,8.9771 v 1.39693 l 21.20305,11.17542 9.84427,4.34169 9.84428,-4.33415 z"
|
||||
id="path17" />
|
||||
<path
|
||||
d="m 388.93329,654.58136 c 2.112,0 3.744,-1.632 3.744,-3.744 v -24.576 l 8.544,-8.64 26.112,35.328 c 0.768,1.152 1.824,1.632 3.168,1.632 2.688,0 4.512,-3.36 2.688,-5.952 l -26.592,-36.288 26.304,-26.304 c 2.304,-2.592 1.248,-6.432 -2.208,-6.432 -1.152,0 -2.016,0.384 -2.976,1.344 l -35.04,35.04 v -32.64 c 0,-2.112 -1.632,-3.744 -3.744,-3.744 -2.016,0 -3.744,1.632 -3.744,3.744 v 67.488 c 0,2.112 1.728,3.744 3.744,3.744 z m 75.55212,0.48 c 8.448,0 15.648,-3.84 19.968,-9.984 v 5.856 c 0,2.112 1.536,3.648 3.744,3.648 2.112,0 3.744,-1.536 3.744,-3.648 v -22.56 c -0.096,-15.264 -11.52,-26.784 -26.688,-26.784 -15.264,0 -26.688,11.52 -26.688,26.784 0,15.264 11.136,26.688 25.92,26.688 z m 0.768,-6.72 c -11.04,0 -19.488,-8.544 -19.488,-19.968 0,-11.424 8.448,-20.064 19.488,-20.064 11.04,0 19.392,8.64 19.392,20.064 0,11.424 -8.352,19.968 -19.392,19.968 z m 61.63201,6.24 h 2.016 c 2.304,0 4.032,-1.536 4.032,-3.648 0,-2.208 -1.44,-3.744 -3.456,-3.744 h -2.592 c -5.952,0 -10.272,-4.608 -10.272,-11.136 v -25.632 h 10.848 c 2.016,0 3.36,-1.344 3.36,-3.168 0,-1.92 -1.344,-3.264 -3.36,-3.264 h -10.848 v -14.88 c 0,-2.208 -1.536,-3.744 -3.744,-3.744 -2.112,0 -3.648,1.536 -3.648,3.744 v 14.88 h -6.24 c -2.016,0 -3.36,1.344 -3.36,3.264 0,1.824 1.344,3.168 3.36,3.168 h 6.24 v 25.632 c 0,10.656 7.392,18.432 17.664,18.528 z m 41.85604,0.48 c 5.952,0 13.248,-2.592 17.472,-6.24 1.536,-1.344 1.44,-3.36 -0.192,-4.8 -1.344,-1.056 -3.264,-0.96 -4.704,0.192 -2.784,2.4 -7.968,4.224 -12.576,4.224 -10.656,0 -18.528,-7.2 -19.584,-17.664 h 38.784 c 2.016,0 3.456,-1.344 3.456,-3.36 0,-15.072 -9.6,-25.824 -24.096,-25.824 -14.784,0 -25.152,11.136 -25.152,26.784 0,15.648 11.04,26.688 26.592,26.688 z m -1.44,-46.848 c 9.696,0 16.224,6.72 17.184,16.416 h -35.136 c 1.344,-9.696 8.16,-16.416 17.952,-16.416 z m 40.32003,46.368 c 2.208,0 3.648,-1.44 3.648,-3.648 v -28.416 c 0,-7.968 7.2,-14.016 16.704,-14.016 9.792,0 17.184,5.664 17.184,17.472 v 24.96 c 0,2.112 1.632,3.648 3.744,3.648 2.016,0 3.648,-1.536 3.648,-3.648 v -24.96 c 0,-15.84 -10.272,-24.48 -23.808,-24.48 -7.104,0 -13.344,2.88 -17.472,7.584 v -3.264 c 0,-2.208 -1.44,-3.744 -3.648,-3.744 -2.208,0 -3.744,1.536 -3.744,3.744 v 45.12 c 0,2.208 1.536,3.648 3.744,3.648 z m 84.384,0.48 c 8.448,0 15.648,-3.84 19.968,-9.984 v 5.856 c 0,2.112 1.536,3.648 3.744,3.648 2.112,0 3.744,-1.536 3.744,-3.648 v -22.56 c -0.096,-15.264 -11.52,-26.784 -26.688,-26.784 -15.264,0 -26.688,11.52 -26.688,26.784 0,15.264 11.136,26.688 25.92,26.688 z m 0.768,-6.72 c -11.04,0 -19.488,-8.544 -19.488,-19.968 0,-11.424 8.448,-20.064 19.488,-20.064 11.04,0 19.392,8.64 19.392,20.064 0,11.424 -8.352,19.968 -19.392,19.968 z m 44.73601,6.24 c 2.304,0 3.648,-1.344 3.648,-3.648 v -28.704 c 0,-8.928 7.872,-14.688 18.624,-13.728 3.168,0.192 5.76,0.768 6.432,-2.112 0.768,-3.168 -2.688,-4.896 -8.64,-4.896 -6.72,0 -12.672,3.168 -16.416,8.352 v -4.128 c 0,-2.304 -1.344,-3.648 -3.648,-3.648 -2.304,0 -3.648,1.344 -3.648,3.648 v 45.216 c 0,2.304 1.344,3.648 3.648,3.648 z m 49.34401,22.848 c 1.248,0 2.208,-0.768 2.784,-2.208 l 31.104,-68.352 c 0.96,-2.112 0.288,-3.648 -1.824,-4.512 -2.112,-0.864 -3.648,-0.288 -4.608,1.728 l -17.856,39.36 -18.72,-39.456 c -0.864,-1.92 -2.688,-2.496 -4.608,-1.632 -1.92,0.864 -2.688,2.688 -1.728,4.608 l 21.408,44.352 0.096,0.096 -9.6,21.024 c -0.96,2.112 -0.288,3.744 1.824,4.608 0.576,0.288 1.152,0.384 1.728,0.384 z"
|
||||
id="text18"
|
||||
style="font-size:96px;line-height:0;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ff7f2a;stroke-width:51.0236;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0"
|
||||
aria-label="Katenary" />
|
||||
</g>
|
||||
<path
|
||||
style="fill:#388ec7;fill-opacity:1;stroke:none;stroke-width:1.41128"
|
||||
d="m 558.98167,600.50526 -33.31908,17.65778 -13.63054,6.42952 -13.63053,-6.42952 -33.31907,-17.65778 c 0,5.32502 -2.24747,16.12101 0.77828,20.72433 1.84892,2.81293 6.84753,4.46784 9.82323,6.09432 8.26868,4.51957 16.72019,8.751 24.98932,13.27082 3.1083,1.69895 7.62988,5.16897 11.35877,5.21476 3.70778,0.0455 8.2451,-3.55102 11.35878,-5.19752 8.33909,-4.40967 16.72005,-8.76818 24.98932,-13.28806 3.05286,-1.66866 8.68533,-3.49778 10.14461,-6.69253 2.3911,-5.23485 0.45691,-14.46175 0.45691,-20.12612 m -46.19236,16.00215 c 3.59731,-0.51544 7.48607,-3.47354 10.60152,-5.12097 7.58911,-4.0131 15.20174,-8.00941 22.71755,-12.13797 3.53683,-1.94283 9.05374,-3.7013 11.65911,-6.69847 3.28982,-3.78453 1.93068,-13.45759 -2.58668,-15.74886 -4.7363,-2.40229 -8.80251,1.12086 -12.85868,3.25251 l -21.20306,11.2665 c -2.56736,1.35182 -6.01463,4.01777 -9.08702,4.01777 -3.29073,0 -7.12524,-2.97742 -9.84427,-4.47101 -6.50984,-3.57597 -13.17851,-6.90091 -19.68854,-10.47698 -2.85668,-1.56921 -7.10587,-4.85415 -10.60153,-4.52437 -7.2756,0.6864 -9.69308,11.97712 -5.60111,16.68444 2.43875,2.80547 7.56529,4.49972 10.90188,6.26408 7.83167,4.14137 15.6667,8.28474 23.4748,12.46456 3.21433,1.72068 8.18431,5.79216 12.11603,5.22877 m 30.29007,-45.33762 v -1.39693 l -18.17404,-8.90973 -12.11603,4.65427 -12.87329,-4.72164 -18.93129,8.9771 v 1.39693 l 21.20305,11.17542 9.84427,4.34169 9.84428,-4.33415 z"
|
||||
id="path18" />
|
||||
<g
|
||||
id="g19">
|
||||
<path
|
||||
style="fill:#388ec7;fill-opacity:1;stroke:none;stroke-width:1.41128"
|
||||
d="m 280.25003,221.48792 -33.31908,17.65778 -13.63054,6.42952 -13.63053,-6.42952 -33.31907,-17.65778 c 0,5.32502 -2.24747,16.12101 0.77828,20.72433 1.84892,2.81293 6.84753,4.46784 9.82323,6.09432 8.26868,4.51957 16.72019,8.751 24.98932,13.27082 3.1083,1.69895 7.62988,5.16897 11.35877,5.21476 3.70778,0.0455 8.2451,-3.55102 11.35878,-5.19752 8.33909,-4.40967 16.72005,-8.76818 24.98932,-13.28806 3.05286,-1.66866 8.68533,-3.49778 10.14461,-6.69253 2.3911,-5.23485 0.45691,-14.46175 0.45691,-20.12612 m -46.19236,16.00215 c 3.59731,-0.51544 7.48607,-3.47354 10.60152,-5.12097 7.58911,-4.0131 15.20174,-8.00941 22.71755,-12.13797 3.53683,-1.94283 9.05374,-3.7013 11.65911,-6.69847 3.28982,-3.78453 1.93068,-13.45759 -2.58668,-15.74886 -4.7363,-2.40229 -8.80251,1.12086 -12.85868,3.25251 l -21.20306,11.2665 c -2.56736,1.35182 -6.01463,4.01777 -9.08702,4.01777 -3.29073,0 -7.12524,-2.97742 -9.84427,-4.47101 -6.50984,-3.57597 -13.17851,-6.90091 -19.68854,-10.47698 -2.85668,-1.56921 -7.10587,-4.85415 -10.60153,-4.52437 -7.2756,0.6864 -9.69308,11.97712 -5.60111,16.68444 2.43875,2.80547 7.56529,4.49972 10.90188,6.26408 7.83167,4.14137 15.6667,8.28474 23.4748,12.46456 3.21433,1.72068 8.18431,5.79216 12.11603,5.22877 m 30.29007,-45.33762 v -1.39693 l -18.17404,-8.90973 -12.11603,4.65427 -12.87329,-4.72164 -18.93129,8.9771 v 1.39693 l 21.20305,11.17542 9.84427,4.34169 9.84428,-4.33415 z"
|
||||
id="path19" />
|
||||
<path
|
||||
d="m 297.75361,250.3495 c 2.112,0 3.744,-1.632 3.744,-3.744 v -24.576 l 8.544,-8.64 26.112,35.328 c 0.768,1.152 1.824,1.632 3.168,1.632 2.688,0 4.512,-3.36 2.688,-5.952 l -26.592,-36.288 26.304,-26.304 c 2.304,-2.592 1.248,-6.432 -2.208,-6.432 -1.152,0 -2.016,0.384 -2.976,1.344 l -35.04,35.04 v -32.64 c 0,-2.112 -1.632,-3.744 -3.744,-3.744 -2.016,0 -3.744,1.632 -3.744,3.744 v 67.488 c 0,2.112 1.728,3.744 3.744,3.744 z m 75.55211,0.48 c 8.448,0 15.648,-3.84 19.968,-9.984 v 5.856 c 0,2.112 1.536,3.648 3.744,3.648 2.112,0 3.744,-1.536 3.744,-3.648 v -22.56 c -0.096,-15.264 -11.52,-26.784 -26.688,-26.784 -15.264,0 -26.688,11.52 -26.688,26.784 0,15.264 11.136,26.688 25.92,26.688 z m 0.768,-6.72 c -11.04,0 -19.488,-8.544 -19.488,-19.968 0,-11.424 8.448,-20.064 19.488,-20.064 11.04,0 19.392,8.64 19.392,20.064 0,11.424 -8.352,19.968 -19.392,19.968 z m 61.63201,6.24 h 2.016 c 2.304,0 4.032,-1.536 4.032,-3.648 0,-2.208 -1.44,-3.744 -3.456,-3.744 h -2.592 c -5.952,0 -10.272,-4.608 -10.272,-11.136 v -25.632 h 10.848 c 2.016,0 3.36,-1.344 3.36,-3.168 0,-1.92 -1.344,-3.264 -3.36,-3.264 h -10.848 v -14.88 c 0,-2.208 -1.536,-3.744 -3.744,-3.744 -2.112,0 -3.648,1.536 -3.648,3.744 v 14.88 h -6.24 c -2.016,0 -3.36,1.344 -3.36,3.264 0,1.824 1.344,3.168 3.36,3.168 h 6.24 v 25.632 c 0,10.656 7.392,18.432 17.664,18.528 z m 41.85604,0.48 c 5.952,0 13.248,-2.592 17.472,-6.24 1.536,-1.344 1.44,-3.36 -0.192,-4.8 -1.344,-1.056 -3.264,-0.96 -4.704,0.192 -2.784,2.4 -7.968,4.224 -12.576,4.224 -10.656,0 -18.528,-7.2 -19.584,-17.664 h 38.784 c 2.016,0 3.456,-1.344 3.456,-3.36 0,-15.072 -9.6,-25.824 -24.096,-25.824 -14.784,0 -25.152,11.136 -25.152,26.784 0,15.648 11.04,26.688 26.592,26.688 z m -1.44,-46.848 c 9.696,0 16.224,6.72 17.184,16.416 h -35.136 c 1.344,-9.696 8.16,-16.416 17.952,-16.416 z m 40.32003,46.368 c 2.208,0 3.648,-1.44 3.648,-3.648 v -28.416 c 0,-7.968 7.2,-14.016 16.704,-14.016 9.792,0 17.184,5.664 17.184,17.472 v 24.96 c 0,2.112 1.632,3.648 3.744,3.648 2.016,0 3.648,-1.536 3.648,-3.648 v -24.96 c 0,-15.84 -10.272,-24.48 -23.808,-24.48 -7.104,0 -13.344,2.88 -17.472,7.584 v -3.264 c 0,-2.208 -1.44,-3.744 -3.648,-3.744 -2.208,0 -3.744,1.536 -3.744,3.744 v 45.12 c 0,2.208 1.536,3.648 3.744,3.648 z m 84.384,0.48 c 8.448,0 15.648,-3.84 19.968,-9.984 v 5.856 c 0,2.112 1.536,3.648 3.744,3.648 2.112,0 3.744,-1.536 3.744,-3.648 v -22.56 c -0.096,-15.264 -11.52,-26.784 -26.688,-26.784 -15.264,0 -26.688,11.52 -26.688,26.784 0,15.264 11.136,26.688 25.92,26.688 z m 0.768,-6.72 c -11.04,0 -19.488,-8.544 -19.488,-19.968 0,-11.424 8.448,-20.064 19.488,-20.064 11.04,0 19.392,8.64 19.392,20.064 0,11.424 -8.352,19.968 -19.392,19.968 z m 44.73601,6.24 c 2.304,0 3.648,-1.344 3.648,-3.648 v -28.704 c 0,-8.928 7.872,-14.688 18.624,-13.728 3.168,0.192 5.76,0.768 6.432,-2.112 0.768,-3.168 -2.688,-4.896 -8.64,-4.896 -6.72,0 -12.672,3.168 -16.416,8.352 v -4.128 c 0,-2.304 -1.344,-3.648 -3.648,-3.648 -2.304,0 -3.648,1.344 -3.648,3.648 v 45.216 c 0,2.304 1.344,3.648 3.648,3.648 z m 49.34402,22.848 c 1.248,0 2.208,-0.768 2.784,-2.208 l 31.104,-68.352 c 0.96,-2.112 0.288,-3.648 -1.824,-4.512 -2.112,-0.864 -3.648,-0.288 -4.608,1.728 l -17.856,39.36 -18.72,-39.456 c -0.864,-1.92 -2.688,-2.496 -4.608,-1.632 -1.92,0.864 -2.688,2.688 -1.728,4.608 l 21.408,44.352 0.096,0.096 -9.6,21.024 c -0.96,2.112 -0.288,3.744 1.824,4.608 0.576,0.288 1.152,0.384 1.728,0.384 z"
|
||||
id="text19"
|
||||
style="font-size:96px;line-height:0;font-family:Comfortaa;-inkscape-font-specification:Comfortaa;fill:#ff7f2a;stroke-width:51.0236;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0"
|
||||
aria-label="Katenary" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 14 KiB |
145
doc/docs/statics/logo.svg
Normal file
After Width: | Height: | Size: 21 KiB |
117
doc/docs/statics/main.css
Normal file
@@ -0,0 +1,117 @@
|
||||
:root {
|
||||
--code-bg-color: var(--lt-color-gray-800);
|
||||
--code-fg-color: var(--lt-color-gray-300);
|
||||
--md-primary-fg-color: var(--lt-color-gray-900);
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] {
|
||||
--md-primary-fg-color: var(--md-code-fg-color);
|
||||
}
|
||||
|
||||
div.smile-logo {
|
||||
display: flex;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
div.smile-logo img {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
button.md-clipboard::after {
|
||||
transition: all 0.5s ease;
|
||||
color: var(--lt-color-gray-500);
|
||||
}
|
||||
button.md-clipboard:hover::after {
|
||||
color: var(--md-primary-bg-color);
|
||||
}
|
||||
|
||||
article a,
|
||||
article a:visited {
|
||||
color: var(--md-code-hl-number-color) !important;
|
||||
}
|
||||
|
||||
.md-center {
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.md-center img {
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.md-nav__item .md-nav__link--active {
|
||||
color: var(--md-code-hl-string-color) !important;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.go-logo {
|
||||
font-size: 4em;
|
||||
}
|
||||
|
||||
/* HLJS */
|
||||
pre code.hljs {
|
||||
background-color: var(--code-bg-color);
|
||||
color: var(--code-fg-color);
|
||||
}
|
||||
|
||||
table tbody code {
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
font-size: 1em !important;
|
||||
background-color: transparent !important;
|
||||
color: var(--md-code-hl-special-color) !important;
|
||||
}
|
||||
|
||||
h3[id*="katenaryio"] {
|
||||
color: var(--md-code-hl-special-color);
|
||||
}
|
||||
|
||||
#logo {
|
||||
background-image: url("logo-vertical.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
height: 8em;
|
||||
width: 100%;
|
||||
margin: 0 auto 2rem auto;
|
||||
}
|
||||
|
||||
/*Zoomable images*/
|
||||
.zoomable svg {
|
||||
background-color: var(--md-default-bg-color);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .zoomable svg {
|
||||
background-color: var(--md-default-bg-color);
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .zoomable svg .colorize {
|
||||
fill: var(--md-typeset-color) !important;
|
||||
}
|
||||
|
||||
.zoomable input[type="checkbox"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media all and (min-width: 1399px) {
|
||||
.zoomable label > * {
|
||||
cursor: zoom-in;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
.zoomable input[type="checkbox"]:checked ~ label > * {
|
||||
transform: scale(2);
|
||||
cursor: zoom-out;
|
||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.3);
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#klee {
|
||||
filter: drop-shadow(0 0 16px var(--md-default-fg-color));
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
#klee svg {
|
||||
zoom: 2;
|
||||
}
|
BIN
doc/docs/statics/workflow.png
Normal file
After Width: | Height: | Size: 124 KiB |
538
doc/docs/statics/workflow.svg
Normal file
After Width: | Height: | Size: 310 KiB |
262
doc/docs/usage.md
Normal file
@@ -0,0 +1,262 @@
|
||||
# Basic Usage
|
||||
|
||||
Basically, you can use `katenary` to transpose a docker-compose file (or any compose file compatible with
|
||||
`podman-compose` and `docker-compose`) to a configurable Helm Chart. This resulting helm chart can be installed with
|
||||
`helm` command to your Kubernetes cluster.
|
||||
|
||||
For very basic compose files, without any specific configuration, Katenary will create a working helm chart using the
|
||||
simple command line:
|
||||
|
||||
```bash
|
||||
katenary convert
|
||||
```
|
||||
|
||||
This will create a `chart` directory with the helm chart inside.
|
||||
|
||||
But, in general, you will need to add a few configurations to help Katenary to transpose the compose file to a working
|
||||
helm chart.
|
||||
|
||||
There are two ways to configure Katenary:
|
||||
|
||||
- Using the compose files, adding labels to the services
|
||||
- Using a specific file named `katenary.yaml`
|
||||
|
||||
The Katenary file `katenary.yaml` has benefits over the labels in the compose file:
|
||||
|
||||
- you can validate the configuration with a schema, and use completion in your editor
|
||||
- you separate the configuration and leave the compose file "intact"
|
||||
- the syntax is a bit simpler, instead of using `katenary.v3/xxx: |-` you can use `xxx: ...`
|
||||
|
||||
But: **this implies that you have to maintain two files if the compose file changes.**
|
||||
|
||||
For example. With "labels", you should do:
|
||||
|
||||
```yaml
|
||||
# in compose file
|
||||
services:
|
||||
webapp:
|
||||
image: php:7-apache
|
||||
ports:
|
||||
- 8080:80
|
||||
environment:
|
||||
DB_HOST: database
|
||||
labels:
|
||||
katenary.v3/ingress: |-
|
||||
hostname: myapp.example.com
|
||||
port: 8080
|
||||
katenary.v3/map-env: |-
|
||||
DB_HOST: "{{ .Release.Name }}-database"
|
||||
```
|
||||
|
||||
Using a Katenary file, you can do:
|
||||
|
||||
```yaml
|
||||
# in compose file, no need to add labels
|
||||
services:
|
||||
webapp:
|
||||
image: php:7-apache
|
||||
ports:
|
||||
- 8080:80
|
||||
environment:
|
||||
DB_HOST: database
|
||||
|
||||
# in katenary.yaml
|
||||
webapp:
|
||||
ingress:
|
||||
hostname: myapp.example.com
|
||||
port: 8080
|
||||
|
||||
map-env:
|
||||
DB_HOST: "{{ .Release.Name }}-database"
|
||||
```
|
||||
|
||||
!!! Warning "YAML in multiline label"
|
||||
|
||||
Compose only accept text label. So, to put a complete YAML content in the target label,
|
||||
you need to use a pipe char (`|` or `|-`) and to **indent** your content.
|
||||
|
||||
For example :
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
# your labels
|
||||
foo: bar
|
||||
# katenary labels with multiline
|
||||
katenary.v3/ingress: |-
|
||||
hostname: my.website.tld
|
||||
port: 80
|
||||
katenary.v3/ports: |-
|
||||
- 1234
|
||||
```
|
||||
|
||||
Katenary transforms compose services this way:
|
||||
|
||||
- Takes the service and create a "Deployment" file
|
||||
- if a port is declared, Katenary creates a service (`ClusterIP`)
|
||||
- if a port is exposed, Katenary creates a service (`NodePort`)
|
||||
- environment variables will be stored inside a `ConfigMap`
|
||||
- image, tags, and ingresses configuration are also stored in `values.yaml` file
|
||||
- if named volumes are declared, Katenary create `PersistentVolumeClaims` - not enabled in values file
|
||||
- `depends_on` needs that the pointed service declared a port. If not, you can use labels to inform Katenary
|
||||
|
||||
For any other specific configuration, like binding local files as `ConfigMap`, bind variables, add values with
|
||||
documentation, etc. You'll need to use labels.
|
||||
|
||||
Katenary can also configure containers grouping in pods, declare dependencies, ignore some services, force variables as
|
||||
secrets, mount files as `configMap`, and many others things. To adapt the helm chart generation, you will need to use
|
||||
some specific labels.
|
||||
|
||||
For more complete label usage, see [the labels page](labels.md).
|
||||
|
||||
!!! Info "Overriding file"
|
||||
|
||||
It could be sometimes more convinient to separate the
|
||||
configuration related to Katenary inside a secondary file.
|
||||
|
||||
Instead of adding labels inside the `compose.yaml` file,
|
||||
you can create a file named `compose.katenary.yaml` and
|
||||
declare your labels inside. Katenary will detect it by
|
||||
default.
|
||||
|
||||
**No need to precise the file in the command line.**
|
||||
|
||||
## Make conversion
|
||||
|
||||
After having installed `katenary`, the standard usage is to call:
|
||||
|
||||
katenary convert
|
||||
|
||||
It will search standard compose files in the current directory and try to create a helm chart in "chart" directory.
|
||||
|
||||
!!! Info
|
||||
Katenary uses the compose-go library which respects the Docker and Docker-Compose specification. Keep in mind that
|
||||
it will find files exactly the same way as `docker-compose` and `podman-compose` do it.
|
||||
|
||||
Of course, you can provide others files than the default with (cumulative) `-c` options:
|
||||
|
||||
katenary convert -c file1.yaml -c file2.yaml
|
||||
|
||||
## Some common labels to use
|
||||
|
||||
Katenary proposes a lot of labels to configure the helm chart generation, but some are very important.
|
||||
|
||||
!!! Info
|
||||
For more complete label usage, see [the labels page](labels.md).
|
||||
|
||||
### Work with Depends On?
|
||||
|
||||
Kubernetes does not provide service or pod starting detection from others pods. But Katenary will create `initContainer`
|
||||
to make you able to wait for a service to respond. But you'll probably need to adapt a bit the compose file.
|
||||
|
||||
See this compose file:
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
webapp:
|
||||
image: php:8-apache
|
||||
depends_on:
|
||||
- database
|
||||
|
||||
database:
|
||||
image: mariadb
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: foobar
|
||||
```
|
||||
|
||||
In this case, `webapp` needs to know the `database` port because the `depends_on` points on it and Kubernetes has not
|
||||
(yet) solution to check the database startup. Katenary wants to create a `initContainer` to hit on the related service.
|
||||
So, instead of exposing the port in the compose definition, let's declare this to Katenary with labels:
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
webapp:
|
||||
image: php:8-apache
|
||||
depends_on:
|
||||
- database
|
||||
|
||||
database:
|
||||
image: mariadb
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: foobar
|
||||
labels:
|
||||
katenary.v3/ports: |-
|
||||
- 3306
|
||||
```
|
||||
|
||||
### Declare ingresses
|
||||
|
||||
It's very common to have an Ingress resource on web application to deploy on Kubernetes. It allows exposing the
|
||||
service to the outside of the cluster (you need to install an ingress controller).
|
||||
|
||||
Katenary can create this resource for you. You just need to declare the hostname and the port to bind.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
webapp:
|
||||
image: ...
|
||||
ports: 8080:5050
|
||||
labels:
|
||||
katenary.v3/ingress: |-
|
||||
# the target port is 5050 wich is the "service" port
|
||||
port: 5050
|
||||
hostname: myapp.example.com
|
||||
```
|
||||
|
||||
Note that the port to bind is the one used by the container, not the used locally. This is because Katenary create a
|
||||
service to bind the container itself.
|
||||
|
||||
### Map environment to helm values
|
||||
|
||||
A lot of framework needs to receive service host or IP in an environment variable to configure the connection. For
|
||||
example, to connect a PHP application to a database.
|
||||
|
||||
With a compose file, there is no problem as Docker/Podman allows resolving the name by container name:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
webapp:
|
||||
image: php:7-apache
|
||||
environment:
|
||||
DB_HOST: database
|
||||
|
||||
database:
|
||||
image: mariadb
|
||||
```
|
||||
|
||||
Katenary prefixes the services with `{{ .Release.Name }}` (to make it possible to install the application several times
|
||||
in a namespace), so you need to "remap" the environment variable to the right one.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
webapp:
|
||||
image: php:7-apache
|
||||
environment:
|
||||
DB_HOST: database
|
||||
labels:
|
||||
katenary.v3/mapenv: |-
|
||||
DB_HOST: "{{ .Release.Name }}-database"
|
||||
|
||||
database:
|
||||
image: mariadb
|
||||
```
|
||||
|
||||
This label can be used to map others environment for any others reason. E.g. to change an informational environment
|
||||
variable.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
webapp:
|
||||
#...
|
||||
environment:
|
||||
RUNNING: docker
|
||||
labels:
|
||||
katenary.v3/mapenv: |-
|
||||
RUNNING: kubernetes
|
||||
```
|
||||
|
||||
In the above example, `RUNNING` will be set to `kubernetes` when you'll deploy the application with helm, and it's
|
||||
`docker` for "Podman" and "Docker" executions.
|
48
doc/fix.py
Normal file
@@ -0,0 +1,48 @@
|
||||
""" Fix the markdown files to replace code blocs by lists when the code blocs are lists."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from typing import Tuple
|
||||
|
||||
# get markdown bloc code
|
||||
re_code = re.compile(r"```(.*?)```", re.DOTALL)
|
||||
|
||||
|
||||
def fix(text: str) -> Tuple[str, bool]:
|
||||
"""Fix the markdown text to replace code blocs by lists when the code blocs are lists."""
|
||||
# in the text, get the code blocs
|
||||
code_blocs = re_code.findall(text)
|
||||
# for each code bloc, if lines begin by a "-", this is a list. So,
|
||||
# make it a mkdocs list and remove the block code
|
||||
fixed = False
|
||||
for code in code_blocs:
|
||||
lines = code.split("\n")
|
||||
lines = [line.strip() for line in lines if line.strip()]
|
||||
if all(line.startswith("-") for line in lines):
|
||||
fixed = True
|
||||
# make a mkdocs list
|
||||
lines = [f"- {line[1:]}" for line in lines]
|
||||
# replace the code bloc by the list
|
||||
text = text.replace(f"```{code}```", "\n".join(lines))
|
||||
return text, fixed
|
||||
|
||||
|
||||
def main(filename: str):
|
||||
"""Fix and rewrite the markdown file."""
|
||||
with open(filename, "r", encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
content, fixed = fix(text)
|
||||
|
||||
if not fixed:
|
||||
return
|
||||
|
||||
with open(sys.argv[1], "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: python fix.py <file>")
|
||||
sys.exit(1)
|
||||
|
||||
main(sys.argv[1])
|
74
doc/mkdocs.yml
Normal file
@@ -0,0 +1,74 @@
|
||||
site_name: Katenary documentation
|
||||
docs_dir: ./docs
|
||||
plugins:
|
||||
- search
|
||||
- inline-svg
|
||||
- manpage:
|
||||
enabled: !ENV [MANPAGE, false]
|
||||
preprocess: preprocess.py
|
||||
pages:
|
||||
- title: Katenary
|
||||
header: Katenary helm chart generator
|
||||
output: share/man/man1/katenary.1
|
||||
inputs:
|
||||
- usage.md
|
||||
- labels.md
|
||||
theme:
|
||||
name: material
|
||||
custom_dir: overrides
|
||||
logo: statics/logo-bright.svg
|
||||
favicon: statics/icon.svg
|
||||
palette:
|
||||
- scheme: slate
|
||||
toggle:
|
||||
icon: material/brightness-4
|
||||
name: Switch to light mode
|
||||
|
||||
- scheme: default
|
||||
toggle:
|
||||
icon: material/brightness-7
|
||||
name: Switch to dark mode
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- footnotes
|
||||
- attr_list
|
||||
- pymdownx.emoji:
|
||||
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
||||
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
||||
- pymdownx.highlight:
|
||||
anchor_linenums: true
|
||||
use_pygments: false
|
||||
- pymdownx.superfences:
|
||||
custom_fences:
|
||||
- name: mermaid
|
||||
class: mermaid
|
||||
format: !!python/name:pymdownx.superfences.fence_code_format
|
||||
extra_css:
|
||||
- statics/main.css
|
||||
extra_javascript:
|
||||
- statics/addons.js
|
||||
copyright: Copyright © 2021 - 2024 - Katenary authors
|
||||
extra:
|
||||
generator: false
|
||||
social:
|
||||
- icon: fontawesome/brands/git
|
||||
link: https://repo.katenary.io/katenary/katenary
|
||||
nav:
|
||||
- "Home": index.md
|
||||
- usage.md
|
||||
- labels.md
|
||||
- Behind the scene:
|
||||
- coding.md
|
||||
- dependencies.md
|
||||
- FAQ: faq.md
|
||||
- Go Packages:
|
||||
- packages/cmd/katenary.md
|
||||
- packages/internal/parser.md
|
||||
- packages/internal/utils.md
|
||||
- Generator:
|
||||
- Index: packages/internal/generator.md
|
||||
- ExtraFiles: packages/internal/generator/extrafiles.md
|
||||
- labels:
|
||||
- packages/internal/generator/labels.md
|
||||
- Labelstructs: packages/internal/generator/labels/labelstructs.md
|
||||
- KatenaryFile: packages/internal/generator/katenaryfile.md
|
56
doc/overrides/partials/footer.html
Normal file
@@ -0,0 +1,56 @@
|
||||
{#- This file was automatically generated - do not edit -#}
|
||||
<footer class="md-footer">
|
||||
{% if page.previous_page or page.next_page %} {% if page.meta and
|
||||
page.meta.hide %} {% set hidden = "hidden" if "footer" in page.meta.hide %} {%
|
||||
endif %}
|
||||
<nav
|
||||
class="md-footer__inner md-grid"
|
||||
aria-label="{{ lang.t('footer.title') }}"
|
||||
{{
|
||||
hidden
|
||||
}}
|
||||
>
|
||||
{% if page.previous_page %} {% set direction = lang.t("footer.previous") %}
|
||||
<a
|
||||
href="{{ page.previous_page.url | url }}"
|
||||
class="md-footer__link md-footer__link--prev"
|
||||
aria-label="{{ direction }}: {{ page.previous_page.title | e }}"
|
||||
rel="prev"
|
||||
>
|
||||
<div class="md-footer__button md-icon">
|
||||
{% include ".icons/material/arrow-left.svg" %}
|
||||
</div>
|
||||
<div class="md-footer__title">
|
||||
<div class="md-ellipsis">
|
||||
<span class="md-footer__direction"> {{ direction }} </span>
|
||||
{{ page.previous_page.title }}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% endif %} {% if page.next_page %} {% set direction = lang.t("footer.next")
|
||||
%}
|
||||
<a
|
||||
href="{{ page.next_page.url | url }}"
|
||||
class="md-footer__link md-footer__link--next"
|
||||
aria-label="{{ direction }}: {{ page.next_page.title | e }}"
|
||||
rel="next"
|
||||
>
|
||||
<div class="md-footer__title">
|
||||
<div class="md-ellipsis">
|
||||
<span class="md-footer__direction"> {{ direction }} </span>
|
||||
{{ page.next_page.title }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="md-footer__button md-icon">
|
||||
{% include ".icons/material/arrow-right.svg" %}
|
||||
</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% endif %}
|
||||
<div class="md-footer-meta md-typeset">
|
||||
<div class="md-footer-meta__inner md-grid">
|
||||
{% include "partials/copyright.html" %} {% if config.extra.social %} {%
|
||||
include "partials/social.html" %} {% endif %}
|
||||
</div>
|
||||
</footer>
|
22
doc/preprocess.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""Called by mkdocs to preprocess files and build manpages"""
|
||||
|
||||
from bs4 import BeautifulSoup, Tag
|
||||
|
||||
|
||||
def to_remove(tag: Tag) -> bool:
|
||||
"""Removes images, SVGs, links containing images or SVGs, and permalinks from the BeautifulSoup object."""
|
||||
if tag.name in {"img", "svg"}:
|
||||
return True
|
||||
# remove links containing images or SVGs
|
||||
if tag.name == "a" and tag.img and to_remove(tag.img):
|
||||
return True
|
||||
# remove permalinks
|
||||
if tag.name == "a" and "headerlink" in tag.get("class", ()):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def preprocess(soup: BeautifulSoup, output: str) -> None:
|
||||
"""Preprocess the BeautifulSoup object to remove unwanted elements."""
|
||||
for element in soup.find_all(to_remove):
|
||||
element.decompose()
|
9
doc/requirements.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
mkdocs==1.*
|
||||
Jinja2==3.*
|
||||
MarkupSafe==3.*
|
||||
pymdown-extensions==10.*
|
||||
mkdocs-material==9.*
|
||||
mkdocs-material-extensions==1.*
|
||||
mkdocs-plugin-inline-svg-mod
|
||||
beautifulsoup4==4.*
|
||||
mkdocs-manpage[preprocess]
|
@@ -1,10 +0,0 @@
|
||||
# Basic example
|
||||
|
||||
This is a basic example of what can do Katenary with standard docker-compose file.
|
||||
|
||||
In this example:
|
||||
|
||||
- `depends_on` yield a `initContainer` in the webapp ddeployment to wait for database
|
||||
- so we need to declare the listened port inside `database` container as we don't use it with docker-compose- also, we needed to declare that `DB_HOST` is actually a service name
|
||||
|
||||
Take a look on [chart/basic](chart/basic) directory to see what `katenary convert` command has generated.
|
@@ -1,8 +0,0 @@
|
||||
# Create on 2022-02-17T10:27:30+01:00
|
||||
# Katenary command line: katenary convert
|
||||
apiVersion: v2
|
||||
appVersion: 0.0.1
|
||||
description: A helm chart for basic
|
||||
name: basic
|
||||
type: application
|
||||
version: 0.1.0
|
@@ -1,8 +0,0 @@
|
||||
|
||||
Congratulations,
|
||||
|
||||
Your application is now deployed. This may take a while to be up and responding.
|
||||
|
||||
{{ if .Values.webapp.ingress.enabled -}}
|
||||
- webapp is accessible on : http://{{ .Values.webapp.ingress.host }}
|
||||
{{- end }}
|
@@ -1,39 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-database'
|
||||
labels:
|
||||
katenary.io/component: database
|
||||
katenary.io/project: basic
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: b9f12bb7d1e97901c1d7680394209525763f6640
|
||||
katenary.io/version: master-3619cc4
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
katenary.io/component: database
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
katenary.io/component: database
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
spec:
|
||||
containers:
|
||||
- name: database
|
||||
image: '{{ .Values.database.image }}'
|
||||
ports:
|
||||
- name: database
|
||||
containerPort: 3306
|
||||
env:
|
||||
- name: MARIADB_PASSWORD
|
||||
value: foo
|
||||
- name: MARIADB_DATABASE
|
||||
value: myapp
|
||||
- name: MARIADB_ROOT_PASSWORD
|
||||
value: foobar
|
||||
- name: MARIADB_USER
|
||||
value: foo
|
||||
|
@@ -1,19 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-database'
|
||||
labels:
|
||||
katenary.io/component: database
|
||||
katenary.io/project: basic
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: b9f12bb7d1e97901c1d7680394209525763f6640
|
||||
katenary.io/version: master-3619cc4
|
||||
spec:
|
||||
selector:
|
||||
katenary.io/component: database
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 3306
|
||||
targetPort: 3306
|
@@ -1,48 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-webapp'
|
||||
labels:
|
||||
katenary.io/component: webapp
|
||||
katenary.io/project: basic
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: b9f12bb7d1e97901c1d7680394209525763f6640
|
||||
katenary.io/version: master-3619cc4
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
katenary.io/component: webapp
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
katenary.io/component: webapp
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
spec:
|
||||
initContainers:
|
||||
- name: check-database
|
||||
image: busybox
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |-
|
||||
OK=0
|
||||
echo "Checking database port"
|
||||
while [ $OK != 1 ]; do
|
||||
echo -n "."
|
||||
nc -z {{ .Release.Name }}-database 3306 2>&1 >/dev/null && OK=1 || sleep 1
|
||||
done
|
||||
echo
|
||||
echo "Done"
|
||||
containers:
|
||||
- name: webapp
|
||||
image: '{{ .Values.webapp.image }}'
|
||||
ports:
|
||||
- name: webapp
|
||||
containerPort: 80
|
||||
env:
|
||||
- name: DB_HOST
|
||||
value: '{{ .Release.Name }}-database'
|
||||
|
@@ -1,34 +0,0 @@
|
||||
{{- if .Values.webapp.ingress.enabled -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-webapp'
|
||||
labels:
|
||||
katenary.io/component: webapp
|
||||
katenary.io/project: basic
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: b9f12bb7d1e97901c1d7680394209525763f6640
|
||||
katenary.io/version: master-3619cc4
|
||||
spec:
|
||||
{{- if and .Values.webapp.ingress.class (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: '{{ .Values.webapp.ingress.class }}'
|
||||
{{- end }}
|
||||
rules:
|
||||
- host: '{{ .Values.webapp.ingress.host }}'
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
{{- if semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: '{{ .Release.Name }}-webapp'
|
||||
port:
|
||||
number: 80
|
||||
{{- else }}
|
||||
serviceName: '{{ .Release.Name }}-webapp'
|
||||
servicePort: 80
|
||||
{{- end }}
|
||||
|
||||
{{- end -}}
|
@@ -1,19 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-webapp'
|
||||
labels:
|
||||
katenary.io/component: webapp
|
||||
katenary.io/project: basic
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: b9f12bb7d1e97901c1d7680394209525763f6640
|
||||
katenary.io/version: master-3619cc4
|
||||
spec:
|
||||
selector:
|
||||
katenary.io/component: webapp
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 80
|
@@ -1,8 +0,0 @@
|
||||
database:
|
||||
image: mariadb:10
|
||||
webapp:
|
||||
image: php:7-apache
|
||||
ingress:
|
||||
class: nginx
|
||||
enabled: false
|
||||
host: webapp.basic.tld
|
@@ -1,30 +0,0 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
|
||||
webapp:
|
||||
image: php:7-apache
|
||||
|
||||
environment:
|
||||
DB_HOST: database
|
||||
ports:
|
||||
- "8080:80"
|
||||
labels:
|
||||
# expose an ingress
|
||||
katenary.io/ingress: 80
|
||||
# DB_HOST is actually a service name
|
||||
katenary.io/env-to-service: DB_HOST
|
||||
depends_on:
|
||||
- database
|
||||
|
||||
database:
|
||||
image: mariadb:10
|
||||
environment:
|
||||
MARIADB_ROOT_PASSWORD: foobar
|
||||
MARIADB_USER: foo
|
||||
MARIADB_PASSWORD: foo
|
||||
MARIADB_DATABASE: myapp
|
||||
labels:
|
||||
# because we don't provide "ports" or "expose", alert katenary
|
||||
# to use the mysql port for service declaration
|
||||
katenary.io/ports: 3306
|
@@ -1,13 +0,0 @@
|
||||
# Make it possible to bind several containers in one pod
|
||||
|
||||
In this example, we need to make nginx and php-fpm to run inside the same "pod". The reason is that we configured FPM to listen an unix socket instead of the 9000 port.
|
||||
|
||||
Because NGinx will need to connect to the unix socket wich is a file, both containers should share the same node and work together.
|
||||
|
||||
So, in the docker-compose file, we need to declare:
|
||||
- `katenary.io/empty-dirs: socket` where `socket` is the "volume name", this will avoid the creation of a PVC
|
||||
- `katenary.io/same-pod: http` in `php` container to declare that this will be added in the `containers` section of the `http` deployment
|
||||
|
||||
You can note that we also use `configmap-volumes` to declare our configuration as `configMap`.
|
||||
|
||||
Take a look on [chart/same-pod](chart/same-pod) directory to see the result of the `katenary convert` command.
|
@@ -1,8 +0,0 @@
|
||||
# Create on 2022-02-17T11:36:02+01:00
|
||||
# Katenary command line: katenary convert --force
|
||||
apiVersion: v2
|
||||
appVersion: 0.0.1
|
||||
description: A helm chart for same-pod
|
||||
name: same-pod
|
||||
type: application
|
||||
version: 0.1.0
|
@@ -1,8 +0,0 @@
|
||||
|
||||
Congratulations,
|
||||
|
||||
Your application is now deployed. This may take a while to be up and responding.
|
||||
|
||||
{{ if .Values.http.ingress.enabled -}}
|
||||
- http is accessible on : http://{{ .Values.http.ingress.host }}
|
||||
{{- end }}
|
@@ -1,23 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-config-nginx-http'
|
||||
labels:
|
||||
katenary.io/component: ""
|
||||
katenary.io/project: same-pod
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: 74e67695bfdbb829f15531321e158808018280e0
|
||||
katenary.io/version: master-bf44d44
|
||||
data:
|
||||
default.conf: |
|
||||
upstream _php {
|
||||
server unix:/sock/fpm.sock;
|
||||
}
|
||||
server {
|
||||
listen 80;
|
||||
location ~ ^/index\.php(/|$) {
|
||||
fastcgi_pass _php;
|
||||
include fastcgi_params;
|
||||
}
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-config-php-php'
|
||||
labels:
|
||||
katenary.io/component: ""
|
||||
katenary.io/project: same-pod
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: 74e67695bfdbb829f15531321e158808018280e0
|
||||
katenary.io/version: master-bf44d44
|
||||
data:
|
||||
www.conf: |
|
||||
[www]
|
||||
user = www-data
|
||||
group = www-data
|
||||
|
||||
listen = /sock/fpm.sock
|
||||
|
||||
pm = dynamic
|
||||
pm.max_children = 5
|
||||
pm.start_servers = 2
|
||||
pm.min_spare_servers = 1
|
||||
pm.max_spare_servers = 3
|
||||
|
||||
access.log = /proc/self/fd/2
|
||||
log_limit = 8192
|
||||
clear_env = no
|
||||
catch_workers_output = yes
|
||||
decorate_workers_output = no
|
@@ -1,52 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-http'
|
||||
labels:
|
||||
katenary.io/component: http
|
||||
katenary.io/project: same-pod
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: 74e67695bfdbb829f15531321e158808018280e0
|
||||
katenary.io/version: master-bf44d44
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
katenary.io/component: http
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
katenary.io/component: http
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
spec:
|
||||
containers:
|
||||
- name: http
|
||||
image: '{{ .Values.http.image }}'
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
volumeMounts:
|
||||
- mountPath: /sock
|
||||
name: sock
|
||||
- mountPath: /etc/nginx/conf.d
|
||||
name: config-nginx
|
||||
- name: php
|
||||
image: '{{ .Values.php.image }}'
|
||||
volumeMounts:
|
||||
- mountPath: /sock
|
||||
name: sock
|
||||
- mountPath: /usr/local/etc/php-fpm.d/www.conf
|
||||
name: config-php
|
||||
subPath: www.conf
|
||||
volumes:
|
||||
- emptyDir: {}
|
||||
name: sock
|
||||
- configMap:
|
||||
name: '{{ .Release.Name }}-config-nginx-http'
|
||||
name: config-nginx
|
||||
- configMap:
|
||||
name: '{{ .Release.Name }}-config-php-php'
|
||||
name: config-php
|
||||
|
@@ -1,34 +0,0 @@
|
||||
{{- if .Values.http.ingress.enabled -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-http'
|
||||
labels:
|
||||
katenary.io/component: http
|
||||
katenary.io/project: same-pod
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: 74e67695bfdbb829f15531321e158808018280e0
|
||||
katenary.io/version: master-bf44d44
|
||||
spec:
|
||||
{{- if and .Values.http.ingress.class (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: '{{ .Values.http.ingress.class }}'
|
||||
{{- end }}
|
||||
rules:
|
||||
- host: '{{ .Values.http.ingress.host }}'
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
{{- if semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: '{{ .Release.Name }}-http'
|
||||
port:
|
||||
number: 80
|
||||
{{- else }}
|
||||
serviceName: '{{ .Release.Name }}-http'
|
||||
servicePort: 80
|
||||
{{- end }}
|
||||
|
||||
{{- end -}}
|
@@ -1,19 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: '{{ .Release.Name }}-http'
|
||||
labels:
|
||||
katenary.io/component: http
|
||||
katenary.io/project: same-pod
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
annotations:
|
||||
katenary.io/docker-compose-sha1: 74e67695bfdbb829f15531321e158808018280e0
|
||||
katenary.io/version: master-bf44d44
|
||||
spec:
|
||||
selector:
|
||||
katenary.io/component: http
|
||||
katenary.io/release: '{{ .Release.Name }}'
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 80
|
@@ -1,8 +0,0 @@
|
||||
http:
|
||||
image: nginx:alpine
|
||||
ingress:
|
||||
class: nginx
|
||||
enabled: false
|
||||
host: http.same-pod.tld
|
||||
php:
|
||||
image: php:fpm
|
@@ -1,10 +0,0 @@
|
||||
upstream _php {
|
||||
server unix:/sock/fpm.sock;
|
||||
}
|
||||
server {
|
||||
listen 80;
|
||||
location ~ ^/index\.php(/|$) {
|
||||
fastcgi_pass _php;
|
||||
include fastcgi_params;
|
||||
}
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
[www]
|
||||
user = www-data
|
||||
group = www-data
|
||||
|
||||
listen = /sock/fpm.sock
|
||||
|
||||
pm = dynamic
|
||||
pm.max_children = 5
|
||||
pm.start_servers = 2
|
||||
pm.min_spare_servers = 1
|
||||
pm.max_spare_servers = 3
|
||||
|
||||
access.log = /proc/self/fd/2
|
||||
log_limit = 8192
|
||||
clear_env = no
|
||||
catch_workers_output = yes
|
||||
decorate_workers_output = no
|
@@ -1,38 +0,0 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
|
||||
http:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "8080:80"
|
||||
volumes:
|
||||
- "sock:/sock"
|
||||
- "./config/nginx:/etc/nginx/conf.d:z"
|
||||
|
||||
labels:
|
||||
# the "sock" volume will need to be shared to the same pod, so let's
|
||||
# declare that this is not a PVC
|
||||
katenary.io/empty-dirs: sock
|
||||
|
||||
# use ./config/nginx as a configMap
|
||||
katenary.io/configmap-volumes: ./config/nginx
|
||||
|
||||
# declare an ingress
|
||||
katenary.io/ingress: 80
|
||||
|
||||
php:
|
||||
image: php:fpm
|
||||
volumes:
|
||||
- "sock:/sock"
|
||||
- "./config/php/www.conf:/usr/local/etc/php-fpm.d/www.conf:z"
|
||||
labels:
|
||||
# fpm will need to use a unix socket shared
|
||||
# with nginx (http service above), so we want here
|
||||
# make a single pod containing nginx and php
|
||||
katenary.io/same-pod: http
|
||||
# use the ./config/php files as a configMap
|
||||
katenary.io/configmap-volumes: ./config/php/www.conf
|
||||
|
||||
volumes:
|
||||
sock:
|
@@ -1,8 +0,0 @@
|
||||
package generator
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestColor(t *testing.T) {
|
||||
Red("Red text")
|
||||
Grey("Grey text")
|
||||
}
|
@@ -1,655 +0,0 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"katenary/compose"
|
||||
"katenary/helm"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/google/shlex"
|
||||
)
|
||||
|
||||
var servicesMap = make(map[string]int)
|
||||
var serviceWaiters = make(map[string][]chan int)
|
||||
var locker = &sync.Mutex{}
|
||||
|
||||
const (
|
||||
ICON_PACKAGE = "📦"
|
||||
ICON_SERVICE = "🔌"
|
||||
ICON_SECRET = "🔏"
|
||||
ICON_CONF = "📝"
|
||||
ICON_STORE = "⚡"
|
||||
ICON_INGRESS = "🌐"
|
||||
)
|
||||
|
||||
const (
|
||||
RELEASE_NAME = helm.RELEASE_NAME
|
||||
)
|
||||
|
||||
// Values is kept in memory to create a values.yaml file.
|
||||
var Values = make(map[string]map[string]interface{})
|
||||
var VolumeValues = make(map[string]map[string]map[string]interface{})
|
||||
var EmptyDirs = []string{}
|
||||
|
||||
var dependScript = `
|
||||
OK=0
|
||||
echo "Checking __service__ port"
|
||||
while [ $OK != 1 ]; do
|
||||
echo -n "."
|
||||
nc -z ` + RELEASE_NAME + `-__service__ __port__ 2>&1 >/dev/null && OK=1 || sleep 1
|
||||
done
|
||||
echo
|
||||
echo "Done"
|
||||
`
|
||||
|
||||
var madeDeployments = make(map[string]helm.Deployment, 0)
|
||||
|
||||
// Create a Deployment for a given compose.Service. It returns a list of objects: a Deployment and a possible Service (kubernetes represnetation as maps).
|
||||
func CreateReplicaObject(name string, s *compose.Service, linked map[string]*compose.Service) chan interface{} {
|
||||
ret := make(chan interface{}, len(s.Ports)+len(s.Expose)+1)
|
||||
go parseService(name, s, linked, ret)
|
||||
return ret
|
||||
}
|
||||
|
||||
// This function will try to yied deployment and services based on a service from the compose file structure.
|
||||
func parseService(name string, s *compose.Service, linked map[string]*compose.Service, ret chan interface{}) {
|
||||
Magenta(ICON_PACKAGE+" Generating deployment for ", name)
|
||||
|
||||
o := helm.NewDeployment(name)
|
||||
|
||||
container := helm.NewContainer(name, s.Image, s.Environment, s.Labels)
|
||||
|
||||
// prepare cm and secrets
|
||||
prepareEnvFromFiles(name, s, container, ret)
|
||||
|
||||
// check the image, and make it "variable" in values.yaml
|
||||
container.Image = "{{ .Values." + name + ".image }}"
|
||||
Values[name] = map[string]interface{}{
|
||||
"image": s.Image,
|
||||
}
|
||||
|
||||
// manage the healthcheck property, if any
|
||||
prepareProbes(name, s, container)
|
||||
// manage ports
|
||||
generateContainerPorts(s, name, container)
|
||||
|
||||
// Set the container to the deployment
|
||||
o.Spec.Template.Spec.Containers = []*helm.Container{container}
|
||||
|
||||
// Prepare volumes
|
||||
madePVC := make(map[string]bool)
|
||||
o.Spec.Template.Spec.Volumes = prepareVolumes(name, name, s, container, madePVC, ret)
|
||||
|
||||
// Now, for "depends_on" section, it's a bit tricky to get dependencies, see the function below.
|
||||
o.Spec.Template.Spec.InitContainers = prepareInitContainers(name, s, container)
|
||||
|
||||
// Add selectors
|
||||
selectors := buildSelector(name, s)
|
||||
o.Spec.Selector = map[string]interface{}{
|
||||
"matchLabels": selectors,
|
||||
}
|
||||
o.Spec.Template.Metadata.Labels = selectors
|
||||
|
||||
// Now, the linked services
|
||||
for lname, link := range linked {
|
||||
container := helm.NewContainer(lname, link.Image, link.Environment, link.Labels)
|
||||
container.Image = "{{ .Values." + lname + ".image }}"
|
||||
Values[lname] = map[string]interface{}{
|
||||
"image": link.Image,
|
||||
}
|
||||
prepareProbes(lname, link, container)
|
||||
generateContainerPorts(link, lname, container)
|
||||
o.Spec.Template.Spec.Containers = append(o.Spec.Template.Spec.Containers, container)
|
||||
o.Spec.Template.Spec.Volumes = append(o.Spec.Template.Spec.Volumes, prepareVolumes(name, lname, link, container, madePVC, ret)...)
|
||||
o.Spec.Template.Spec.InitContainers = append(o.Spec.Template.Spec.InitContainers, prepareInitContainers(lname, link, container)...)
|
||||
//append ports and expose ports to the deployment, to be able to generate them in the Service file
|
||||
if len(link.Ports) > 0 || len(link.Expose) > 0 {
|
||||
s.Ports = append(s.Ports, link.Ports...)
|
||||
s.Expose = append(s.Expose, link.Expose...)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicates in volumes
|
||||
volumes := make([]map[string]interface{}, 0)
|
||||
done := make(map[string]bool)
|
||||
for _, vol := range o.Spec.Template.Spec.Volumes {
|
||||
name := vol["name"].(string)
|
||||
if _, ok := done[name]; ok {
|
||||
continue
|
||||
} else {
|
||||
done[name] = true
|
||||
volumes = append(volumes, vol)
|
||||
}
|
||||
}
|
||||
o.Spec.Template.Spec.Volumes = volumes
|
||||
|
||||
// Then, create Services and possible Ingresses for ingress labels, "ports" and "expose" section
|
||||
if len(s.Ports) > 0 || len(s.Expose) > 0 {
|
||||
for _, s := range generateServicesAndIngresses(name, s) {
|
||||
ret <- s
|
||||
}
|
||||
}
|
||||
|
||||
// Special case, it there is no "ports", so there is no associated services...
|
||||
// But... some other deployment can wait for it, so we alert that this deployment hasn't got any
|
||||
// associated service.
|
||||
if len(s.Ports) == 0 {
|
||||
// alert any current or **future** waiters that this service is not exposed
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-time.Tick(1 * time.Millisecond):
|
||||
locker.Lock()
|
||||
for _, c := range serviceWaiters[name] {
|
||||
c <- -1
|
||||
close(c)
|
||||
}
|
||||
locker.Unlock()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// add the volumes in Values
|
||||
if len(VolumeValues[name]) > 0 {
|
||||
locker.Lock()
|
||||
Values[name]["persistence"] = VolumeValues[name]
|
||||
locker.Unlock()
|
||||
}
|
||||
|
||||
// the deployment is ready, give it
|
||||
ret <- o
|
||||
|
||||
// and then, we can say that it's the end
|
||||
ret <- nil
|
||||
}
|
||||
|
||||
// Create a service (k8s).
|
||||
func generateServicesAndIngresses(name string, s *compose.Service) []interface{} {
|
||||
|
||||
ret := make([]interface{}, 0) // can handle helm.Service or helm.Ingress
|
||||
Magenta(ICON_SERVICE+" Generating service for ", name)
|
||||
ks := helm.NewService(name)
|
||||
|
||||
for i, p := range s.Ports {
|
||||
port := strings.Split(p, ":")
|
||||
src, _ := strconv.Atoi(port[0])
|
||||
target := src
|
||||
if len(port) > 1 {
|
||||
target, _ = strconv.Atoi(port[1])
|
||||
}
|
||||
ks.Spec.Ports = append(ks.Spec.Ports, helm.NewServicePort(target, target))
|
||||
if i == 0 {
|
||||
detected(name, target)
|
||||
}
|
||||
}
|
||||
ks.Spec.Selector = buildSelector(name, s)
|
||||
|
||||
ret = append(ret, ks)
|
||||
if v, ok := s.Labels[helm.LABEL_INGRESS]; ok {
|
||||
port, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
log.Fatalf("The given port \"%v\" as ingress port in \"%s\" service is not an integer\n", v, name)
|
||||
}
|
||||
Cyanf(ICON_INGRESS+" Create an ingress for port %d on %s service\n", port, name)
|
||||
ing := createIngress(name, port, s)
|
||||
ret = append(ret, ing)
|
||||
}
|
||||
|
||||
if len(s.Expose) > 0 {
|
||||
Magenta(ICON_SERVICE+" Generating service for ", name+"-external")
|
||||
ks := helm.NewService(name + "-external")
|
||||
ks.Spec.Type = "NodePort"
|
||||
for _, p := range s.Expose {
|
||||
ks.Spec.Ports = append(ks.Spec.Ports, helm.NewServicePort(p, p))
|
||||
}
|
||||
ks.Spec.Selector = buildSelector(name, s)
|
||||
ret = append(ret, ks)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Create an ingress.
|
||||
func createIngress(name string, port int, s *compose.Service) *helm.Ingress {
|
||||
ingress := helm.NewIngress(name)
|
||||
Values[name]["ingress"] = map[string]interface{}{
|
||||
"class": "nginx",
|
||||
"host": name + "." + helm.Appname + ".tld",
|
||||
"enabled": false,
|
||||
}
|
||||
ingress.Spec.Rules = []helm.IngressRule{
|
||||
{
|
||||
Host: fmt.Sprintf("{{ .Values.%s.ingress.host }}", name),
|
||||
Http: helm.IngressHttp{
|
||||
Paths: []helm.IngressPath{{
|
||||
Path: "/",
|
||||
PathType: "Prefix",
|
||||
Backend: &helm.IngressBackend{
|
||||
Service: helm.IngressService{
|
||||
Name: RELEASE_NAME + "-" + name,
|
||||
Port: map[string]interface{}{
|
||||
"number": port,
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
ingress.SetIngressClass(name)
|
||||
|
||||
return ingress
|
||||
}
|
||||
|
||||
// This function is called when a possible service is detected, it append the port in a map to make others
|
||||
// to be able to get the service name. It also try to send the data to any "waiter" for this service.
|
||||
func detected(name string, port int) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
if _, ok := servicesMap[name]; ok {
|
||||
return
|
||||
}
|
||||
servicesMap[name] = port
|
||||
go func() {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
if cx, ok := serviceWaiters[name]; ok {
|
||||
for _, c := range cx {
|
||||
c <- port
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func getPort(name string) (int, error) {
|
||||
if v, ok := servicesMap[name]; ok {
|
||||
return v, nil
|
||||
}
|
||||
return -1, errors.New("Not found")
|
||||
}
|
||||
|
||||
// Waits for a service to be discovered. Sometimes, a deployment depends on another one. See the detected() function.
|
||||
func waitPort(name string) chan int {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
c := make(chan int, 0)
|
||||
serviceWaiters[name] = append(serviceWaiters[name], c)
|
||||
go func() {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
if v, ok := servicesMap[name]; ok {
|
||||
c <- v
|
||||
}
|
||||
}()
|
||||
return c
|
||||
}
|
||||
|
||||
// Build the selector for the service.
|
||||
func buildSelector(name string, s *compose.Service) map[string]string {
|
||||
return map[string]string{
|
||||
"katenary.io/component": name,
|
||||
"katenary.io/release": RELEASE_NAME,
|
||||
}
|
||||
}
|
||||
|
||||
// buildCMFromPath generates a ConfigMap from a path.
|
||||
func buildCMFromPath(path string) *helm.ConfigMap {
|
||||
stat, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
files := make(map[string]string, 0)
|
||||
if stat.IsDir() {
|
||||
found, _ := filepath.Glob(path + "/*")
|
||||
for _, f := range found {
|
||||
if s, err := os.Stat(f); err != nil || s.IsDir() {
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "An error occured reading volume path %s\n", err.Error())
|
||||
} else {
|
||||
ActivateColors = true
|
||||
Yellowf("Warning, %s is a directory, at this time we only "+
|
||||
"can create configmap for first level file list\n", f)
|
||||
ActivateColors = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, filename := filepath.Split(f)
|
||||
c, _ := ioutil.ReadFile(f)
|
||||
files[filename] = string(c)
|
||||
}
|
||||
}
|
||||
|
||||
cm := helm.NewConfigMap("")
|
||||
cm.Data = files
|
||||
return cm
|
||||
}
|
||||
|
||||
// generateContainerPorts add the container ports of a service.
|
||||
func generateContainerPorts(s *compose.Service, name string, container *helm.Container) {
|
||||
|
||||
exists := make(map[int]string)
|
||||
for _, port := range s.Ports {
|
||||
_p := strings.Split(port, ":")
|
||||
port = _p[0]
|
||||
if len(_p) > 1 {
|
||||
port = _p[1]
|
||||
}
|
||||
portNumber, _ := strconv.Atoi(port)
|
||||
portName := name
|
||||
for _, n := range exists {
|
||||
if name == n {
|
||||
portName = fmt.Sprintf("%s-%d", name, portNumber)
|
||||
}
|
||||
}
|
||||
container.Ports = append(container.Ports, &helm.ContainerPort{
|
||||
Name: portName,
|
||||
ContainerPort: portNumber,
|
||||
})
|
||||
exists[portNumber] = name
|
||||
}
|
||||
|
||||
// manage the "expose" section to be a NodePort in Kubernetes
|
||||
for _, port := range s.Expose {
|
||||
if _, exist := exists[port]; exist {
|
||||
continue
|
||||
}
|
||||
container.Ports = append(container.Ports, &helm.ContainerPort{
|
||||
Name: name,
|
||||
ContainerPort: port,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// prepareVolumes add the volumes of a service.
|
||||
func prepareVolumes(deployment, name string, s *compose.Service, container *helm.Container, madePVC map[string]bool, ret chan interface{}) []map[string]interface{} {
|
||||
|
||||
volumes := make([]map[string]interface{}, 0)
|
||||
mountPoints := make([]interface{}, 0)
|
||||
configMapsVolumes := make([]string, 0)
|
||||
if v, ok := s.Labels[helm.LABEL_VOL_CM]; ok {
|
||||
configMapsVolumes = strings.Split(v, ",")
|
||||
}
|
||||
for _, volume := range s.Volumes {
|
||||
parts := strings.Split(volume, ":")
|
||||
volname := parts[0]
|
||||
volepath := parts[1]
|
||||
|
||||
isCM := false
|
||||
for _, cmVol := range configMapsVolumes {
|
||||
cmVol = strings.TrimSpace(cmVol)
|
||||
if volname == cmVol {
|
||||
isCM = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isCM && (strings.HasPrefix(volname, ".") || strings.HasPrefix(volname, "/")) {
|
||||
// local volume cannt be mounted
|
||||
ActivateColors = true
|
||||
Redf("You cannot, at this time, have local volume in %s deployment\n", name)
|
||||
ActivateColors = false
|
||||
continue
|
||||
}
|
||||
if isCM {
|
||||
// check if the volname path points on a file, if so, we need to add subvolume to the interface
|
||||
stat, _ := os.Stat(volname)
|
||||
pointToFile := ""
|
||||
if !stat.IsDir() {
|
||||
pointToFile = filepath.Base(volname)
|
||||
volname = filepath.Dir(volname)
|
||||
}
|
||||
|
||||
// the volume is a path and it's explicitally asked to be a configmap in labels
|
||||
cm := buildCMFromPath(volname)
|
||||
volname = strings.Replace(volname, "./", "", 1)
|
||||
volname = strings.ReplaceAll(volname, "/", "-")
|
||||
volname = strings.ReplaceAll(volname, ".", "-")
|
||||
cm.K8sBase.Metadata.Name = RELEASE_NAME + "-" + volname + "-" + name
|
||||
|
||||
// build a configmap from the volume path
|
||||
volumes = append(volumes, map[string]interface{}{
|
||||
"name": volname,
|
||||
"configMap": map[string]string{
|
||||
"name": cm.K8sBase.Metadata.Name,
|
||||
},
|
||||
})
|
||||
if len(pointToFile) > 0 {
|
||||
mountPoints = append(mountPoints, map[string]interface{}{
|
||||
"name": volname,
|
||||
"mountPath": volepath,
|
||||
"subPath": pointToFile,
|
||||
})
|
||||
} else {
|
||||
mountPoints = append(mountPoints, map[string]interface{}{
|
||||
"name": volname,
|
||||
"mountPath": volepath,
|
||||
})
|
||||
}
|
||||
ret <- cm
|
||||
} else {
|
||||
// rmove minus sign from volume name
|
||||
volname = strings.ReplaceAll(volname, "-", "")
|
||||
|
||||
isEmptyDir := false
|
||||
for _, v := range EmptyDirs {
|
||||
v = strings.ReplaceAll(v, "-", "")
|
||||
if v == volname {
|
||||
volumes = append(volumes, map[string]interface{}{
|
||||
"name": volname,
|
||||
"emptyDir": map[string]string{},
|
||||
})
|
||||
mountPoints = append(mountPoints, map[string]interface{}{
|
||||
"name": volname,
|
||||
"mountPath": volepath,
|
||||
})
|
||||
container.VolumeMounts = mountPoints
|
||||
isEmptyDir = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if isEmptyDir {
|
||||
continue
|
||||
}
|
||||
|
||||
volumes = append(volumes, map[string]interface{}{
|
||||
"name": volname,
|
||||
"persistentVolumeClaim": map[string]string{
|
||||
"claimName": RELEASE_NAME + "-" + volname,
|
||||
},
|
||||
})
|
||||
mountPoints = append(mountPoints, map[string]interface{}{
|
||||
"name": volname,
|
||||
"mountPath": volepath,
|
||||
})
|
||||
|
||||
Yellow(ICON_STORE+" Generate volume values", volname, "for container named", name, "in deployment", deployment)
|
||||
locker.Lock()
|
||||
if _, ok := VolumeValues[deployment]; !ok {
|
||||
VolumeValues[deployment] = make(map[string]map[string]interface{})
|
||||
}
|
||||
VolumeValues[deployment][volname] = map[string]interface{}{
|
||||
"enabled": false,
|
||||
"capacity": "1Gi",
|
||||
}
|
||||
locker.Unlock()
|
||||
|
||||
if _, ok := madePVC[deployment+volname]; !ok {
|
||||
madePVC[deployment+volname] = true
|
||||
pvc := helm.NewPVC(deployment, volname)
|
||||
ret <- pvc
|
||||
}
|
||||
}
|
||||
}
|
||||
container.VolumeMounts = mountPoints
|
||||
return volumes
|
||||
}
|
||||
|
||||
// prepareInitContainers add the init containers of a service.
|
||||
func prepareInitContainers(name string, s *compose.Service, container *helm.Container) []*helm.Container {
|
||||
|
||||
// We need to detect others services, but we probably not have parsed them yet, so
|
||||
// we will wait for them for a while.
|
||||
initContainers := make([]*helm.Container, 0)
|
||||
for _, dp := range s.DependsOn {
|
||||
c := helm.NewContainer("check-"+dp, "busybox", nil, s.Labels)
|
||||
command := strings.ReplaceAll(strings.TrimSpace(dependScript), "__service__", dp)
|
||||
|
||||
foundPort := -1
|
||||
if defaultPort, err := getPort(dp); err != nil {
|
||||
// BUG: Sometimes the chan remains opened
|
||||
foundPort = <-waitPort(dp)
|
||||
} else {
|
||||
foundPort = defaultPort
|
||||
}
|
||||
if foundPort == -1 {
|
||||
log.Fatalf(
|
||||
"ERROR, the %s service is waiting for %s port number, "+
|
||||
"but it is never discovered. You must declare at least one port in "+
|
||||
"the \"ports\" section of the service in the docker-compose file",
|
||||
name,
|
||||
dp,
|
||||
)
|
||||
}
|
||||
command = strings.ReplaceAll(command, "__port__", strconv.Itoa(foundPort))
|
||||
|
||||
c.Command = []string{
|
||||
"sh",
|
||||
"-c",
|
||||
command,
|
||||
}
|
||||
initContainers = append(initContainers, c)
|
||||
}
|
||||
return initContainers
|
||||
}
|
||||
|
||||
// prepareProbes generate http/tcp/command probes for a service.
|
||||
func prepareProbes(name string, s *compose.Service, container *helm.Container) {
|
||||
|
||||
// manage the healthcheck property, if any
|
||||
if s.HealthCheck != nil {
|
||||
if s.HealthCheck.Interval == "" {
|
||||
s.HealthCheck.Interval = "10s"
|
||||
}
|
||||
interval, err := time.ParseDuration(s.HealthCheck.Interval)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if s.HealthCheck.StartPeriod == "" {
|
||||
s.HealthCheck.StartPeriod = "0s"
|
||||
}
|
||||
|
||||
initialDelaySeconds, err := time.ParseDuration(s.HealthCheck.StartPeriod)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
probe := helm.NewProbe(int(interval.Seconds()), int(initialDelaySeconds.Seconds()), 1, s.HealthCheck.Retries)
|
||||
|
||||
healthCheckLabel := s.Labels[helm.LABEL_HEALTHCHECK]
|
||||
|
||||
if healthCheckLabel != "" {
|
||||
|
||||
path := "/"
|
||||
port := 80
|
||||
|
||||
u, err := url.Parse(healthCheckLabel)
|
||||
if err == nil {
|
||||
path = u.Path
|
||||
port, _ = strconv.Atoi(u.Port())
|
||||
} else {
|
||||
path = "/"
|
||||
port = 80
|
||||
}
|
||||
|
||||
if strings.HasPrefix(healthCheckLabel, "http://") {
|
||||
probe.HttpGet = &helm.HttpGet{
|
||||
Path: path,
|
||||
Port: port,
|
||||
}
|
||||
} else if strings.HasPrefix(healthCheckLabel, "tcp://") {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
probe.TCP = &helm.TCP{
|
||||
Port: port,
|
||||
}
|
||||
} else {
|
||||
c, _ := shlex.Split(healthCheckLabel)
|
||||
probe.Exec = &helm.Exec{
|
||||
|
||||
Command: c,
|
||||
}
|
||||
}
|
||||
} else if s.HealthCheck.Test[0] == "CMD" {
|
||||
probe.Exec = &helm.Exec{
|
||||
Command: s.HealthCheck.Test[1:],
|
||||
}
|
||||
}
|
||||
container.LivenessProbe = probe
|
||||
}
|
||||
}
|
||||
|
||||
// prepareEnvFromFiles generate configMap or secrets from environment files.
|
||||
func prepareEnvFromFiles(name string, s *compose.Service, container *helm.Container, ret chan interface{}) {
|
||||
|
||||
// prepare secrets
|
||||
secretsFiles := make([]string, 0)
|
||||
if v, ok := s.Labels[helm.LABEL_ENV_SECRET]; ok {
|
||||
secretsFiles = strings.Split(v, ",")
|
||||
}
|
||||
|
||||
// manage environment files (env_file in compose)
|
||||
for _, envfile := range s.EnvFiles {
|
||||
f := strings.ReplaceAll(envfile, "_", "-")
|
||||
f = strings.ReplaceAll(f, ".env", "")
|
||||
f = strings.ReplaceAll(f, ".", "")
|
||||
f = strings.ReplaceAll(f, "/", "")
|
||||
cf := f + "-" + name
|
||||
isSecret := false
|
||||
for _, s := range secretsFiles {
|
||||
if s == envfile {
|
||||
isSecret = true
|
||||
}
|
||||
}
|
||||
var store helm.InlineConfig
|
||||
if !isSecret {
|
||||
Bluef(ICON_CONF+" Generating configMap %s\n", cf)
|
||||
store = helm.NewConfigMap(cf)
|
||||
} else {
|
||||
Bluef(ICON_SECRET+" Generating secret %s\n", cf)
|
||||
store = helm.NewSecret(cf)
|
||||
}
|
||||
if err := store.AddEnvFile(envfile); err != nil {
|
||||
ActivateColors = true
|
||||
Red(err.Error())
|
||||
ActivateColors = false
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
section := "configMapRef"
|
||||
if isSecret {
|
||||
section = "secretRef"
|
||||
}
|
||||
|
||||
container.EnvFrom = append(container.EnvFrom, map[string]map[string]string{
|
||||
section: {
|
||||
"name": store.Metadata().Name,
|
||||
},
|
||||
})
|
||||
|
||||
ret <- store
|
||||
}
|
||||
}
|
@@ -1,89 +0,0 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Color int
|
||||
|
||||
var ActivateColors = false
|
||||
|
||||
const (
|
||||
GREY Color = 30 + iota
|
||||
RED
|
||||
GREEN
|
||||
YELLOW
|
||||
BLUE
|
||||
MAGENTA
|
||||
CYAN
|
||||
)
|
||||
|
||||
var waiter = sync.Mutex{}
|
||||
|
||||
func color(c Color, args ...interface{}) {
|
||||
if !ActivateColors {
|
||||
fmt.Println(args...)
|
||||
return
|
||||
}
|
||||
waiter.Lock()
|
||||
fmt.Fprintf(os.Stdout, "\x1b[%dm", c)
|
||||
fmt.Fprint(os.Stdout, args...)
|
||||
fmt.Fprintf(os.Stdout, "\x1b[0m\n")
|
||||
waiter.Unlock()
|
||||
}
|
||||
|
||||
func colorf(c Color, format string, args ...interface{}) {
|
||||
if !ActivateColors {
|
||||
fmt.Printf(format, args...)
|
||||
return
|
||||
}
|
||||
waiter.Lock()
|
||||
fmt.Fprintf(os.Stdout, "\x1b[%dm", c)
|
||||
fmt.Fprintf(os.Stdout, format, args...)
|
||||
fmt.Fprintf(os.Stdout, "\x1b[0m")
|
||||
waiter.Unlock()
|
||||
}
|
||||
|
||||
func Grey(args ...interface{}) {
|
||||
color(GREY, args...)
|
||||
}
|
||||
|
||||
func Red(args ...interface{}) {
|
||||
color(RED, args...)
|
||||
}
|
||||
func Green(args ...interface{}) {
|
||||
color(GREEN, args...)
|
||||
}
|
||||
func Yellow(args ...interface{}) {
|
||||
color(YELLOW, args...)
|
||||
}
|
||||
func Blue(args ...interface{}) {
|
||||
color(BLUE, args...)
|
||||
}
|
||||
func Magenta(args ...interface{}) {
|
||||
color(MAGENTA, args...)
|
||||
}
|
||||
func Greyf(format string, args ...interface{}) {
|
||||
colorf(GREY, format, args...)
|
||||
}
|
||||
|
||||
func Redf(format string, args ...interface{}) {
|
||||
colorf(RED, format, args...)
|
||||
}
|
||||
func Greenf(format string, args ...interface{}) {
|
||||
colorf(GREEN, format, args...)
|
||||
}
|
||||
func Yellowf(format string, args ...interface{}) {
|
||||
colorf(YELLOW, format, args...)
|
||||
}
|
||||
func Bluef(format string, args ...interface{}) {
|
||||
colorf(BLUE, format, args...)
|
||||
}
|
||||
func Magentaf(format string, args ...interface{}) {
|
||||
colorf(MAGENTA, format, args...)
|
||||
}
|
||||
func Cyanf(format string, args ...interface{}) {
|
||||
colorf(CYAN, format, args...)
|
||||
}
|
@@ -1,141 +0,0 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"katenary/compose"
|
||||
"katenary/generator/writers"
|
||||
"katenary/helm"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var PrefixRE = regexp.MustCompile(`\{\{.*\}\}-?`)
|
||||
|
||||
func Generate(p *compose.Parser, katernayVersion, appName, appVersion, composeFile, dirName string) {
|
||||
|
||||
// make the appname global (yes... ugly but easy)
|
||||
helm.Appname = appName
|
||||
helm.Version = katernayVersion
|
||||
templatesDir := filepath.Join(dirName, "templates")
|
||||
files := make(map[string]chan interface{})
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
for name, s := range p.Data.Services {
|
||||
|
||||
// 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 _, found := avoids[name]; found {
|
||||
continue
|
||||
}
|
||||
files[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 {
|
||||
break
|
||||
}
|
||||
kind := c.(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)
|
||||
|
||||
// Some types need special fixes in yaml generation
|
||||
switch c := c.(type) {
|
||||
case *helm.Storage:
|
||||
// For storage, we need to add a "condition" to activate it
|
||||
writers.BuildStorage(c, n, templatesDir)
|
||||
|
||||
case *helm.Deployment:
|
||||
// for the deployment, we need to fix persitence volumes
|
||||
// to be activated only when the storage is "enabled",
|
||||
// either we use an "emptyDir"
|
||||
writers.BuildDeployment(c, n, templatesDir)
|
||||
|
||||
case *helm.Service:
|
||||
// Change the type for service if it's an "exposed" port
|
||||
writers.BuildService(c, n, templatesDir)
|
||||
|
||||
case *helm.Ingress:
|
||||
// we need to make ingresses "activable" from values
|
||||
ingresses[n] = c // keep it to generate notes
|
||||
writers.BuildIngress(c, n, templatesDir)
|
||||
|
||||
case *helm.ConfigMap, *helm.Secret:
|
||||
// there could be several files, so let's force the filename
|
||||
name := c.(helm.Named).Name()
|
||||
name = PrefixRE.ReplaceAllString(name, "")
|
||||
writers.BuildConfigMap(c, kind, n, name, templatesDir)
|
||||
|
||||
default:
|
||||
fname := filepath.Join(templatesDir, n+"."+kind+".yaml")
|
||||
fp, _ := os.Create(fname)
|
||||
enc := yaml.NewEncoder(fp)
|
||||
enc.SetIndent(2)
|
||||
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)
|
||||
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)
|
||||
enc.SetIndent(2)
|
||||
enc.Encode(map[string]interface{}{
|
||||
"apiVersion": "v2",
|
||||
"name": appName,
|
||||
"description": "A helm chart for " + appName,
|
||||
"type": "application",
|
||||
"version": "0.1.0",
|
||||
"appVersion": appVersion,
|
||||
})
|
||||
fp.Close()
|
||||
|
||||
// And finally, create a NOTE.txt file
|
||||
fp, _ = os.Create(filepath.Join(templatesDir, "NOTES.txt"))
|
||||
fp.WriteString(helm.GenerateNotesFile(ingresses))
|
||||
fp.Close()
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
package writers
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func BuildConfigMap(c interface{}, kind, servicename, name, templatesDir string) {
|
||||
fname := filepath.Join(templatesDir, servicename+"."+name+"."+kind+".yaml")
|
||||
fp, _ := os.Create(fname)
|
||||
enc := yaml.NewEncoder(fp)
|
||||
enc.SetIndent(IndentSize)
|
||||
enc.Encode(c)
|
||||
fp.Close()
|
||||
}
|
@@ -1,43 +0,0 @@
|
||||
package writers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"katenary/helm"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func BuildDeployment(deployment *helm.Deployment, name, templatesDir string) {
|
||||
kind := "deployment"
|
||||
fname := filepath.Join(templatesDir, name+"."+kind+".yaml")
|
||||
fp, _ := os.Create(fname)
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
enc := yaml.NewEncoder(buffer)
|
||||
enc.SetIndent(IndentSize)
|
||||
enc.Encode(deployment)
|
||||
_content := string(buffer.Bytes())
|
||||
content := strings.Split(string(_content), "\n")
|
||||
dataname := ""
|
||||
component := deployment.Spec.Selector["matchLabels"].(map[string]string)[helm.K+"/component"]
|
||||
n := 0 // will be count of lines only on "persistentVolumeClaim" line, to indent "else" and "end" at the right place
|
||||
for _, line := range content {
|
||||
if strings.Contains(line, "name:") {
|
||||
dataname = strings.Split(line, ":")[1]
|
||||
dataname = strings.TrimSpace(dataname)
|
||||
} else if strings.Contains(line, "persistentVolumeClaim") {
|
||||
n = CountSpaces(line)
|
||||
line = strings.Repeat(" ", n) + "{{- if .Values." + component + ".persistence." + dataname + ".enabled }}\n" + line
|
||||
} else if strings.Contains(line, "claimName") {
|
||||
spaces := strings.Repeat(" ", n)
|
||||
line += "\n" + spaces + "{{ else }}"
|
||||
line += "\n" + spaces + "emptyDir: {}"
|
||||
line += "\n" + spaces + "{{- end }}"
|
||||
}
|
||||
fp.WriteString(line + "\n")
|
||||
}
|
||||
fp.Close()
|
||||
|
||||
}
|
@@ -1,66 +0,0 @@
|
||||
package writers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"katenary/helm"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const classAndVersionCondition = `{{- if and .Values.__name__.ingress.class (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}` + "\n"
|
||||
const versionCondition = `{{- if semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion }}` + "\n"
|
||||
|
||||
func BuildIngress(ingress *helm.Ingress, name, templatesDir string) {
|
||||
// Set the backend for 1.18
|
||||
for _, b := range ingress.Spec.Rules {
|
||||
for _, p := range b.Http.Paths {
|
||||
p.Backend.ServiceName = p.Backend.Service.Name
|
||||
if n, ok := p.Backend.Service.Port["number"]; ok {
|
||||
p.Backend.ServicePort = n
|
||||
}
|
||||
}
|
||||
}
|
||||
kind := "ingress"
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
fname := filepath.Join(templatesDir, name+"."+kind+".yaml")
|
||||
enc := yaml.NewEncoder(buffer)
|
||||
enc.SetIndent(IndentSize)
|
||||
buffer.WriteString("{{- if .Values." + name + ".ingress.enabled -}}\n")
|
||||
enc.Encode(ingress)
|
||||
buffer.WriteString("{{- end -}}")
|
||||
|
||||
fp, _ := os.Create(fname)
|
||||
content := string(buffer.Bytes())
|
||||
lines := strings.Split(content, "\n")
|
||||
|
||||
backendHit := false
|
||||
for _, l := range lines {
|
||||
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
|
||||
if strings.Contains(l, "service:") {
|
||||
n := CountSpaces(l)
|
||||
l = strings.Repeat(" ", n) + versionCondition + l
|
||||
}
|
||||
if strings.Contains(l, "serviceName:") || strings.Contains(l, "servicePort:") {
|
||||
n := CountSpaces(l)
|
||||
if !backendHit {
|
||||
l = strings.Repeat(" ", n) + "{{- else }}\n" + l
|
||||
} else {
|
||||
l = l + "\n" + strings.Repeat(" ", n) + "{{- end }}\n"
|
||||
}
|
||||
backendHit = true
|
||||
}
|
||||
|
||||
fp.WriteString(l + "\n")
|
||||
}
|
||||
|
||||
fp.Close()
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
package writers
|
||||
|
||||
import (
|
||||
"katenary/helm"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func BuildService(service *helm.Service, name, templatesDir string) {
|
||||
kind := "service"
|
||||
suffix := ""
|
||||
if service.Spec.Type == "NodePort" {
|
||||
suffix = "-external"
|
||||
}
|
||||
fname := filepath.Join(templatesDir, name+suffix+"."+kind+".yaml")
|
||||
fp, _ := os.Create(fname)
|
||||
enc := yaml.NewEncoder(fp)
|
||||
enc.SetIndent(IndentSize)
|
||||
enc.Encode(service)
|
||||
fp.Close()
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
package writers
|
||||
|
||||
import (
|
||||
"katenary/helm"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
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)
|
||||
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)
|
||||
fp.WriteString("{{- end -}}")
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
package writers
|
||||
|
||||
// IndentSize set the indentation size for yaml output.
|
||||
var IndentSize = 2
|
||||
|
||||
// CountSpaces returns the number of spaces from the begining of the line.
|
||||
func CountSpaces(line string) int {
|
||||
var spaces int
|
||||
for _, char := range line {
|
||||
if char == ' ' {
|
||||
spaces++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return spaces
|
||||
}
|
58
go.mod
@@ -1,9 +1,57 @@
|
||||
module katenary
|
||||
module katenary.io
|
||||
|
||||
go 1.16
|
||||
go 1.25
|
||||
|
||||
require (
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/spf13/cobra v1.3.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
github.com/compose-spec/compose-go v1.20.2
|
||||
github.com/invopop/jsonschema v0.13.0
|
||||
github.com/mitchellh/go-wordwrap v1.0.1
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/thediveo/netdb v1.1.2
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.33.4
|
||||
k8s.io/apimachinery v0.33.4
|
||||
sigs.k8s.io/yaml v1.6.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spf13/pflag v1.0.7 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
|
||||
)
|
||||
|
844
go.sum
@@ -1,764 +1,168 @@
|
||||
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=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
|
||||
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
|
||||
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
|
||||
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
|
||||
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
||||
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
|
||||
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
|
||||
cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
|
||||
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/compose-spec/compose-go v1.20.2 h1:u/yfZHn4EaHGdidrZycWpxXgFffjYULlTbRfJ51ykjQ=
|
||||
github.com/compose-spec/compose-go v1.20.2/go.mod h1:+MdqXV4RA7wdFsahh/Kb8U0pAJqkg7mr4PM9tFKU8RM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
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/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
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.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
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/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
|
||||
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
|
||||
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
||||
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
|
||||
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
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/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
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/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
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/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||
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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
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/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
|
||||
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
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/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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
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/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0=
|
||||
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
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.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/thediveo/netdb v1.1.2 h1:XdLx/YJPutxrSkPYtmCAIY5sgAvxtkS1Tz+Z0UX2I+U=
|
||||
github.com/thediveo/netdb v1.1.2/go.mod h1:KJczM//7VIIiovQO1qDooHvM8+0pt6RdRt3rVDZxEGM=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
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/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
|
||||
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE=
|
||||
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/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-20181023162649-9b4f9f5ad519/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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/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-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/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-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
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-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/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-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
|
||||
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
|
||||
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
|
||||
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
|
||||
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
|
||||
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
|
||||
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
|
||||
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
|
||||
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
|
||||
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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
||||
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
|
||||
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
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-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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
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.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
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=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
||||
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
||||
k8s.io/api v0.33.4 h1:oTzrFVNPXBjMu0IlpA2eDDIU49jsuEorGHB4cvKupkk=
|
||||
k8s.io/api v0.33.4/go.mod h1:VHQZ4cuxQ9sCUMESJV5+Fe8bGnqAARZ08tSTdHWfeAc=
|
||||
k8s.io/apimachinery v0.33.4 h1:SOf/JW33TP0eppJMkIgQ+L6atlDiP/090oaX0y9pd9s=
|
||||
k8s.io/apimachinery v0.33.4/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
||||
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||
|
@@ -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
|
||||
}
|
@@ -1,121 +0,0 @@
|
||||
package helm
|
||||
|
||||
import "strings"
|
||||
|
||||
// Deployment is a k8s deployment.
|
||||
type Deployment struct {
|
||||
*K8sBase `yaml:",inline"`
|
||||
Spec *DepSpec `yaml:"spec"`
|
||||
}
|
||||
|
||||
func NewDeployment(name string) *Deployment {
|
||||
d := &Deployment{K8sBase: NewBase(), Spec: NewDepSpec()}
|
||||
d.K8sBase.Metadata.Name = RELEASE_NAME + "-" + name
|
||||
d.K8sBase.ApiVersion = "apps/v1"
|
||||
d.K8sBase.Kind = "Deployment"
|
||||
d.K8sBase.Metadata.Labels[K+"/component"] = name
|
||||
return d
|
||||
}
|
||||
|
||||
type DepSpec struct {
|
||||
Replicas int `yaml:"replicas"`
|
||||
Selector map[string]interface{} `yaml:"selector"`
|
||||
Template PodTemplate `yaml:"template"`
|
||||
}
|
||||
|
||||
func NewDepSpec() *DepSpec {
|
||||
return &DepSpec{
|
||||
Replicas: 1,
|
||||
}
|
||||
}
|
||||
|
||||
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"`
|
||||
Volumes []map[string]interface{} `yaml:"volumes,omitempty"`
|
||||
}
|
||||
|
||||
type PodTemplate struct {
|
||||
Metadata Metadata `yaml:"metadata"`
|
||||
Spec PodSpec `yaml:"spec"`
|
||||
}
|
@@ -1,53 +0,0 @@
|
||||
package helm
|
||||
|
||||
type Ingress struct {
|
||||
*K8sBase `yaml:",inline"`
|
||||
Spec IngressSpec
|
||||
}
|
||||
|
||||
func NewIngress(name string) *Ingress {
|
||||
i := &Ingress{}
|
||||
i.K8sBase = NewBase()
|
||||
i.K8sBase.Metadata.Name = RELEASE_NAME + "-" + name
|
||||
i.K8sBase.Kind = "Ingress"
|
||||
i.ApiVersion = "networking.k8s.io/v1"
|
||||
i.K8sBase.Metadata.Labels[K+"/component"] = name
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *Ingress) SetIngressClass(name string) {
|
||||
class := "{{ .Values." + name + ".ingress.class }}"
|
||||
i.Spec.IngressClassName = class
|
||||
}
|
||||
|
||||
type IngressSpec struct {
|
||||
IngressClassName string `yaml:"ingressClassName,omitempty"`
|
||||
Rules []IngressRule
|
||||
}
|
||||
|
||||
type IngressRule struct {
|
||||
Host string
|
||||
Http IngressHttp
|
||||
}
|
||||
|
||||
type IngressHttp struct {
|
||||
Paths []IngressPath
|
||||
}
|
||||
|
||||
type IngressPath struct {
|
||||
Path string
|
||||
PathType string `yaml:"pathType"`
|
||||
Backend *IngressBackend
|
||||
}
|
||||
|
||||
type IngressBackend struct {
|
||||
Service IngressService
|
||||
ServiceName string `yaml:"serviceName"` // for kubernetes version < 1.18
|
||||
ServicePort interface{} `yaml:"servicePort"` // for kubernetes version < 1.18
|
||||
}
|
||||
|
||||
type IngressService struct {
|
||||
Name string `yaml:"name"`
|
||||
Port map[string]interface{} `yaml:"port"`
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
package helm
|
||||
|
||||
import "strings"
|
||||
|
||||
var NOTES = `
|
||||
Congratulations,
|
||||
|
||||
Your application is now deployed. This may take a while to be up and responding.
|
||||
|
||||
__list__
|
||||
`
|
||||
|
||||
func GenerateNotesFile(ingressess map[string]*Ingress) string {
|
||||
|
||||
list := make([]string, 0)
|
||||
|
||||
for name, ing := range ingressess {
|
||||
for _, r := range ing.Spec.Rules {
|
||||
list = append(list, "{{ if .Values."+name+".ingress.enabled -}}\n- "+name+" is accessible on : http://"+r.Host+"\n{{- end }}")
|
||||
}
|
||||
}
|
||||
|
||||
return strings.ReplaceAll(NOTES, "__list__", strings.Join(list, "\n"))
|
||||
}
|
@@ -1,45 +0,0 @@
|
||||
package helm
|
||||
|
||||
type Service struct {
|
||||
*K8sBase `yaml:",inline"`
|
||||
Spec *ServiceSpec `yaml:"spec"`
|
||||
}
|
||||
|
||||
func NewService(name string) *Service {
|
||||
s := &Service{
|
||||
K8sBase: NewBase(),
|
||||
Spec: NewServiceSpec(),
|
||||
}
|
||||
s.K8sBase.Metadata.Name = RELEASE_NAME + "-" + name
|
||||
s.K8sBase.Kind = "Service"
|
||||
s.K8sBase.ApiVersion = "v1"
|
||||
s.K8sBase.Metadata.Labels[K+"/component"] = name
|
||||
return s
|
||||
}
|
||||
|
||||
type ServicePort struct {
|
||||
Protocol string `yaml:"protocol"`
|
||||
Port int `yaml:"port"`
|
||||
TargetPort int `yaml:"targetPort"`
|
||||
}
|
||||
|
||||
func NewServicePort(port, target int) *ServicePort {
|
||||
return &ServicePort{
|
||||
Protocol: "TCP",
|
||||
Port: port,
|
||||
TargetPort: port,
|
||||
}
|
||||
}
|
||||
|
||||
type ServiceSpec struct {
|
||||
Selector map[string]string
|
||||
Ports []*ServicePort
|
||||
Type string `yaml:"type,omitempty"`
|
||||
}
|
||||
|
||||
func NewServiceSpec() *ServiceSpec {
|
||||
return &ServiceSpec{
|
||||
Selector: make(map[string]string),
|
||||
Ports: make([]*ServicePort, 0),
|
||||
}
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
package helm
|
||||
|
||||
type Storage struct {
|
||||
*K8sBase `yaml:",inline"`
|
||||
Spec *PVCSpec
|
||||
}
|
||||
|
||||
func NewPVC(name, storageName string) *Storage {
|
||||
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.Labels[K+"/component"] = name
|
||||
pvc.Spec = &PVCSpec{
|
||||
Resouces: map[string]interface{}{
|
||||
"requests": map[string]string{
|
||||
"storage": "{{ .Values." + name + ".persistence." + storageName + ".capacity }}",
|
||||
},
|
||||
},
|
||||
AccessModes: []string{"ReadWriteOnce"},
|
||||
}
|
||||
return pvc
|
||||
}
|
||||
|
||||
type PVCSpec struct {
|
||||
Resouces map[string]interface{} `yaml:"resources"`
|
||||
AccessModes []string `yaml:"accessModes"`
|
||||
}
|
127
helm/types.go
@@ -1,127 +0,0 @@
|
||||
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 = ""
|
||||
Version = "1.0" // should be set from main.Version
|
||||
)
|
||||
|
||||
type Kinded interface {
|
||||
Get() string
|
||||
}
|
||||
|
||||
type Signable interface {
|
||||
BuildSHA(filename string)
|
||||
}
|
||||
|
||||
type Named interface {
|
||||
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
|
||||
}
|
||||
|
||||
func GetProjectName() string {
|
||||
if len(Appname) > 0 {
|
||||
return Appname
|
||||
}
|
||||
p, _ := os.Getwd()
|
||||
path := strings.Split(p, string(os.PathSeparator))
|
||||
return path[len(path)-1]
|
||||
}
|
65
install.sh
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Install the latest version of the Katenary detecting the right OS and architecture.
|
||||
# Can be launched with the following command:
|
||||
# sh <(curl -sSL https://raw.githubusercontent.com/Katenary/katenary/master/install.sh)
|
||||
|
||||
set -e
|
||||
|
||||
# Detect the OS and architecture
|
||||
OS=$(uname)
|
||||
ARCH=$(uname -m)
|
||||
|
||||
# Detect the home directory "bin" directory, it is commonly:
|
||||
# - $HOME/.local/bin
|
||||
# - $HOME/.bin
|
||||
# - $HOME/bin
|
||||
COMON_INSTALL_PATHS="$HOME/.local/bin $HOME/.bin $HOME/bin"
|
||||
|
||||
INSTALL_PATH=""
|
||||
for p in $COMON_INSTALL_PATHS; do
|
||||
if [ -d $p ]; then
|
||||
INSTALL_PATH=$p
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# check if the user has write access to the INSTALL_PATH
|
||||
if [ -z "$INSTALL_PATH" ]; then
|
||||
INSTALL_PATH="/usr/local/bin"
|
||||
if [ ! -w $INSTALL_PATH ]; then
|
||||
echo "You don't have write access to $INSTALL_PATH"
|
||||
echo "Please, run with sudo or install locally"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# ensure that $INSTALL_PATH is in the PATH
|
||||
if ! echo "$PATH" | grep -q "$INSTALL_PATH"; then
|
||||
echo "Sorry, ${INSTALL_PATH} is not in the PATH"
|
||||
echo "Please, add it to your PATH in your shell configuration file"
|
||||
echo "then restart your shell and run this script again"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Where to download the binary
|
||||
BASE="https://github.com/Katenary/katenary/releases/latest/download/"
|
||||
|
||||
# for compatibility with older ARM versions
|
||||
if [ $ARCH = "x86_64" ]; then
|
||||
ARCH="amd64"
|
||||
fi
|
||||
|
||||
BIN_URL="$BASE/katenary-$OS-$ARCH"
|
||||
|
||||
echo
|
||||
echo "Downloading $BIN_URL"
|
||||
|
||||
T=$(mktemp -u)
|
||||
curl -SL -# $BIN_URL -o $T || (echo "Failed to download katenary" && rm -f $T && exit 1)
|
||||
|
||||
mv "$T" "${INSTALL_PATH}/katenary"
|
||||
chmod +x "${INSTALL_PATH}/katenary"
|
||||
echo
|
||||
echo "Installed to $INSTALL_PATH/katenary"
|
||||
echo "Installation complete! Run 'katenary help' to get started."
|
427
internal/generator/chart.go
Normal file
@@ -0,0 +1,427 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"katenary.io/internal/generator/labels"
|
||||
"katenary.io/internal/generator/labels/labelstructs"
|
||||
"katenary.io/internal/utils"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// ChartTemplate is a template of a chart. It contains the content of the template and the name of the service.
|
||||
// This is used internally to generate the templates.
|
||||
type ChartTemplate struct {
|
||||
Servicename string
|
||||
Content []byte
|
||||
}
|
||||
|
||||
// ConvertOptions are the options to convert a compose project to a helm chart.
|
||||
type ConvertOptions struct {
|
||||
AppVersion *string
|
||||
OutputDir string
|
||||
ChartVersion string
|
||||
Icon string
|
||||
Profiles []string
|
||||
EnvFiles []string
|
||||
Force bool
|
||||
HelmUpdate bool
|
||||
}
|
||||
|
||||
// HelmChart is a Helm Chart representation. It contains all the
|
||||
// templates, values, versions, helpers...
|
||||
type HelmChart struct {
|
||||
Templates map[string]*ChartTemplate `yaml:"-"`
|
||||
Values map[string]any `yaml:"-"`
|
||||
VolumeMounts map[string]any `yaml:"-"`
|
||||
composeHash *string `yaml:"-"`
|
||||
Name string `yaml:"name"`
|
||||
Icon string `yaml:"icon,omitempty"`
|
||||
APIVersion string `yaml:"apiVersion"`
|
||||
Version string `yaml:"version"`
|
||||
AppVersion string `yaml:"appVersion"`
|
||||
Description string `yaml:"description"`
|
||||
Helper string `yaml:"-"`
|
||||
Dependencies []labelstructs.Dependency `yaml:"dependencies,omitempty"`
|
||||
}
|
||||
|
||||
// NewChart creates a new empty chart with the given name.
|
||||
func NewChart(name string) *HelmChart {
|
||||
return &HelmChart{
|
||||
Name: name,
|
||||
Templates: make(map[string]*ChartTemplate, 0),
|
||||
Description: "A Helm chart for " + name,
|
||||
APIVersion: "v2",
|
||||
Version: "",
|
||||
AppVersion: "", // set to 0.1.0 by default if no "main-app" label is found
|
||||
Values: map[string]any{
|
||||
"pullSecrets": []string{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SaveTemplates the templates of the chart to the given directory.
|
||||
func (chart *HelmChart) SaveTemplates(templateDir string) {
|
||||
for name, template := range chart.Templates {
|
||||
t := template.Content
|
||||
t = removeNewlinesInsideBrackets(t)
|
||||
t = removeUnwantedLines(t)
|
||||
// t = addModeline(t)
|
||||
|
||||
kind := utils.GetKind(name)
|
||||
var icon utils.Icon
|
||||
switch kind {
|
||||
case "deployment":
|
||||
icon = utils.IconPackage
|
||||
case "service":
|
||||
icon = utils.IconPlug
|
||||
case "ingress":
|
||||
icon = utils.IconWorld
|
||||
case "volumeclaim":
|
||||
icon = utils.IconCabinet
|
||||
case "configmap":
|
||||
icon = utils.IconConfig
|
||||
case "secret":
|
||||
icon = utils.IconSecret
|
||||
default:
|
||||
icon = utils.IconInfo
|
||||
}
|
||||
|
||||
servicename := template.Servicename
|
||||
if err := os.MkdirAll(filepath.Join(templateDir, servicename), utils.DirectoryPermission); err != nil {
|
||||
fmt.Println(utils.IconFailure, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(icon, "Creating", kind, servicename)
|
||||
// if the name is a path, create the directory
|
||||
if strings.Contains(name, string(filepath.Separator)) {
|
||||
name = filepath.Join(templateDir, name)
|
||||
err := os.MkdirAll(filepath.Dir(name), utils.DirectoryPermission)
|
||||
if err != nil {
|
||||
fmt.Println(utils.IconFailure, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
// remove the serivce name from the template name
|
||||
name = strings.Replace(name, servicename+".", "", 1)
|
||||
name = filepath.Join(templateDir, servicename, name)
|
||||
}
|
||||
f, err := os.Create(name)
|
||||
if err != nil {
|
||||
fmt.Println(utils.IconFailure, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := f.Write(t); err != nil {
|
||||
log.Fatal("error writing template file:", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// generateConfigMapsAndSecrets creates the configmaps and secrets from the environment variables.
|
||||
func (chart *HelmChart) generateConfigMapsAndSecrets(project *types.Project) error {
|
||||
appName := chart.Name
|
||||
for _, s := range project.Services {
|
||||
if len(s.Environment) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
originalEnv := types.MappingWithEquals{}
|
||||
secretsVar := types.MappingWithEquals{}
|
||||
|
||||
// copy env to originalEnv
|
||||
maps.Copy(originalEnv, s.Environment)
|
||||
|
||||
if v, ok := s.Labels[labels.LabelSecrets]; ok {
|
||||
list, err := labelstructs.SecretsFrom(v)
|
||||
if err != nil {
|
||||
log.Fatal("error unmarshaling secrets label:", err)
|
||||
}
|
||||
for _, secret := range list {
|
||||
if secret == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := s.Environment[secret]; !ok {
|
||||
fmt.Printf("%s secret %s not found in environment", utils.IconWarning, secret)
|
||||
continue
|
||||
}
|
||||
secretsVar[secret] = s.Environment[secret]
|
||||
}
|
||||
}
|
||||
|
||||
if len(secretsVar) > 0 {
|
||||
s.Environment = secretsVar
|
||||
sec := NewSecret(s, appName)
|
||||
y, _ := sec.Yaml()
|
||||
name := sec.service.Name
|
||||
chart.Templates[name+".secret.yaml"] = &ChartTemplate{
|
||||
Content: y,
|
||||
Servicename: s.Name,
|
||||
}
|
||||
}
|
||||
|
||||
// remove secrets from env
|
||||
s.Environment = originalEnv // back to original
|
||||
for k := range secretsVar {
|
||||
delete(s.Environment, k)
|
||||
}
|
||||
if len(s.Environment) > 0 {
|
||||
cm := NewConfigMap(s, appName, false)
|
||||
y, _ := cm.Yaml()
|
||||
name := cm.service.Name
|
||||
chart.Templates[name+".configmap.yaml"] = &ChartTemplate{
|
||||
Content: y,
|
||||
Servicename: s.Name,
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (chart *HelmChart) generateDeployment(service types.ServiceConfig, deployments map[string]*Deployment, services map[string]*Service, podToMerge map[string]*types.ServiceConfig, appName string) error {
|
||||
// check the "ports" label from container and add it to the service
|
||||
if err := fixPorts(&service); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// isgnored service
|
||||
if isIgnored(service) {
|
||||
fmt.Printf("%s Ignoring service %s\n", utils.IconInfo, service.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// helm dependency
|
||||
if isHelmDependency, err := chart.setDependencies(service); err != nil {
|
||||
return err
|
||||
} else if isHelmDependency {
|
||||
return nil
|
||||
}
|
||||
|
||||
// create all deployments
|
||||
d := NewDeployment(service, chart)
|
||||
deployments[service.Name] = d
|
||||
|
||||
// generate the cronjob if needed
|
||||
chart.setCronJob(service, appName)
|
||||
|
||||
if exchange, ok := service.Labels[labels.LabelExchangeVolume]; ok {
|
||||
// we need to add a volume and a mount point
|
||||
ex, err := labelstructs.NewExchangeVolumes(exchange)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, exchangeVolume := range ex {
|
||||
d.AddLegacyVolume("exchange-"+exchangeVolume.Name, exchangeVolume.Type)
|
||||
d.exchangesVolumes[service.Name] = exchangeVolume
|
||||
}
|
||||
}
|
||||
|
||||
// get the same-pod label if exists, add it to the list.
|
||||
// We later will copy some parts to the target deployment and remove this one.
|
||||
if samePod, ok := service.Labels[labels.LabelSamePod]; ok && samePod != "" {
|
||||
podToMerge[samePod] = &service
|
||||
}
|
||||
|
||||
// create the needed service for the container port
|
||||
if len(service.Ports) > 0 {
|
||||
s := NewService(service, appName)
|
||||
services[service.Name] = s
|
||||
}
|
||||
|
||||
// create all ingresses
|
||||
if ingress := d.AddIngress(service, appName); ingress != nil {
|
||||
y, _ := ingress.Yaml()
|
||||
chart.Templates[ingress.Filename()] = &ChartTemplate{
|
||||
Content: y,
|
||||
Servicename: service.Name,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setChartVersion sets the chart version from the service image tag.
|
||||
func (chart *HelmChart) setChartVersion(service types.ServiceConfig) {
|
||||
if chart.Version == "" {
|
||||
image := service.Image
|
||||
parts := strings.Split(image, ":")
|
||||
if len(parts) > 1 {
|
||||
chart.AppVersion = parts[1]
|
||||
} else {
|
||||
chart.AppVersion = "0.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setCronJob creates a cronjob from the service labels.
|
||||
func (chart *HelmChart) setCronJob(service types.ServiceConfig, appName string) *CronJob {
|
||||
if _, ok := service.Labels[labels.LabelCronJob]; !ok {
|
||||
return nil
|
||||
}
|
||||
cronjob, rbac := NewCronJob(service, chart, appName)
|
||||
y, _ := cronjob.Yaml()
|
||||
chart.Templates[cronjob.Filename()] = &ChartTemplate{
|
||||
Content: y,
|
||||
Servicename: service.Name,
|
||||
}
|
||||
|
||||
if rbac != nil {
|
||||
y, _ := rbac.RoleBinding.Yaml()
|
||||
chart.Templates[rbac.RoleBinding.Filename()] = &ChartTemplate{
|
||||
Content: y,
|
||||
Servicename: service.Name,
|
||||
}
|
||||
y, _ = rbac.Role.Yaml()
|
||||
chart.Templates[rbac.Role.Filename()] = &ChartTemplate{
|
||||
Content: y,
|
||||
Servicename: service.Name,
|
||||
}
|
||||
y, _ = rbac.ServiceAccount.Yaml()
|
||||
chart.Templates[rbac.ServiceAccount.Filename()] = &ChartTemplate{
|
||||
Content: y,
|
||||
Servicename: service.Name,
|
||||
}
|
||||
}
|
||||
|
||||
return cronjob
|
||||
}
|
||||
|
||||
// setDependencies sets the dependencies from the service labels.
|
||||
func (chart *HelmChart) setDependencies(service types.ServiceConfig) (bool, error) {
|
||||
// helm dependency
|
||||
if v, ok := service.Labels[labels.LabelDependencies]; ok {
|
||||
d, err := labelstructs.DependenciesFrom(v)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, dep := range d {
|
||||
fmt.Printf("%s Adding dependency to %s\n", utils.IconDependency, dep.Name)
|
||||
chart.Dependencies = append(chart.Dependencies, dep)
|
||||
name := dep.Name
|
||||
if dep.Alias != "" {
|
||||
name = dep.Alias
|
||||
}
|
||||
// add the dependency env vars to the values.yaml
|
||||
chart.Values[name] = dep.Values
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// setSharedConf sets the shared configmap to the service.
|
||||
func (chart *HelmChart) setSharedConf(service types.ServiceConfig, deployments map[string]*Deployment) {
|
||||
// if the service has the "shared-conf" label, we need to add the configmap
|
||||
// to the chart and add the env vars to the service
|
||||
if _, ok := service.Labels[labels.LabelEnvFrom]; !ok {
|
||||
return
|
||||
}
|
||||
fromservices, err := labelstructs.EnvFromFrom(service.Labels[labels.LabelEnvFrom])
|
||||
if err != nil {
|
||||
log.Fatal("error unmarshaling env-from label:", err)
|
||||
}
|
||||
// find the configmap in the chart templates
|
||||
for _, fromservice := range fromservices {
|
||||
if _, ok := chart.Templates[fromservice+".configmap.yaml"]; !ok {
|
||||
log.Printf("configmap %s not found in chart templates", fromservice)
|
||||
continue
|
||||
}
|
||||
// find the corresponding target deployment
|
||||
target := findDeployment(service.Name, deployments)
|
||||
if target == nil {
|
||||
continue
|
||||
}
|
||||
// add the configmap to the service
|
||||
addConfigMapToService(service.Name, fromservice, chart.Name, target)
|
||||
}
|
||||
}
|
||||
|
||||
// setEnvironmentValuesFrom sets the environment values from another service.
|
||||
func (chart *HelmChart) setEnvironmentValuesFrom(service types.ServiceConfig, deployments map[string]*Deployment) {
|
||||
if _, ok := service.Labels[labels.LabelValuesFrom]; !ok {
|
||||
return
|
||||
}
|
||||
mapping, err := labelstructs.GetValueFrom(service.Labels[labels.LabelValuesFrom])
|
||||
if err != nil {
|
||||
log.Fatal("error unmarshaling values-from label:", err)
|
||||
}
|
||||
|
||||
findDeployment := func(name string) *Deployment {
|
||||
for _, dep := range deployments {
|
||||
if dep.service.Name == name {
|
||||
return dep
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// each mapping key is the environment, and the value is serivename.variable name
|
||||
for env, from := range *mapping {
|
||||
// find the deployment that has the variable
|
||||
depName := strings.Split(from, ".")
|
||||
dep := findDeployment(depName[0])
|
||||
target := findDeployment(service.Name)
|
||||
if dep == nil || target == nil {
|
||||
log.Fatalf("deployment %s or %s not found", depName[0], service.Name)
|
||||
}
|
||||
container, index := utils.GetContainerByName(target.service.ContainerName, target.Spec.Template.Spec.Containers)
|
||||
if container == nil {
|
||||
log.Fatalf("Container %s not found", target.GetName())
|
||||
}
|
||||
reourceName := fmt.Sprintf(`{{ include "%s.fullname" . }}-%s`, chart.Name, depName[0])
|
||||
// add environment with from
|
||||
|
||||
// is it a secret?
|
||||
isSecret := false
|
||||
secrets, err := labelstructs.SecretsFrom(dep.service.Labels[labels.LabelSecrets])
|
||||
if err == nil {
|
||||
if slices.Contains(secrets, depName[1]) {
|
||||
isSecret = true
|
||||
}
|
||||
}
|
||||
|
||||
if !isSecret {
|
||||
container.Env = append(container.Env, corev1.EnvVar{
|
||||
Name: env,
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: reourceName,
|
||||
},
|
||||
Key: depName[1],
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
container.Env = append(container.Env, corev1.EnvVar{
|
||||
Name: env,
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
SecretKeyRef: &corev1.SecretKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: reourceName,
|
||||
},
|
||||
Key: depName[1],
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
// the environment is bound, so we shouldn't add it to the values.yaml or in any other place
|
||||
delete(service.Environment, env)
|
||||
// also, remove the values
|
||||
target.boundEnvVar = append(target.boundEnvVar, env)
|
||||
// and save the container
|
||||
target.Spec.Template.Spec.Containers[index] = *container
|
||||
|
||||
}
|
||||
}
|
158
internal/generator/chart_test.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"katenary.io/internal/generator/labels"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
func TestValuesFrom(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
aa:
|
||||
image: nginx:latest
|
||||
environment:
|
||||
AA_USER: foo
|
||||
bb:
|
||||
image: nginx:latest
|
||||
labels:
|
||||
%[1]s/values-from: |-
|
||||
BB_USER: aa.USER
|
||||
`
|
||||
composeFile = fmt.Sprintf(composeFile, labels.KatenaryLabelPrefix)
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
|
||||
output := internalCompileTest(t, "-s", "templates/aa/configmap.yaml")
|
||||
configMap := v1.ConfigMap{}
|
||||
if err := yaml.Unmarshal([]byte(output), &configMap); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
data := configMap.Data
|
||||
if v, ok := data["AA_USER"]; !ok || v != "foo" {
|
||||
t.Errorf("Expected AA_USER to be foo, got %s", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValuesFromCopy(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
aa:
|
||||
image: nginx:latest
|
||||
environment:
|
||||
AA_USER: foo
|
||||
bb:
|
||||
image: nginx:latest
|
||||
labels:
|
||||
%[1]s/values-from: |-
|
||||
BB_USER: aa.AA_USER
|
||||
`
|
||||
composeFile = fmt.Sprintf(composeFile, labels.KatenaryLabelPrefix)
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
|
||||
output := internalCompileTest(t, "-s", "templates/bb/deployment.yaml")
|
||||
dep := appsv1.Deployment{}
|
||||
if err := yaml.Unmarshal([]byte(output), &dep); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
containers := dep.Spec.Template.Spec.Containers
|
||||
environment := containers[0].Env[0]
|
||||
|
||||
envFrom := environment.ValueFrom.ConfigMapKeyRef
|
||||
if envFrom.Key != "AA_USER" {
|
||||
t.Errorf("Expected AA_USER, got %s", envFrom.Key)
|
||||
}
|
||||
if !strings.Contains(envFrom.Name, "aa") {
|
||||
t.Errorf("Expected aa, got %s", envFrom.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValuesFromSecret(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
aa:
|
||||
image: nginx:latest
|
||||
environment:
|
||||
AA_USER: foo
|
||||
labels:
|
||||
%[1]s/secrets: |-
|
||||
- AA_USER
|
||||
bb:
|
||||
image: nginx:latest
|
||||
labels:
|
||||
%[1]s/values-from: |-
|
||||
BB_USER: aa.AA_USER
|
||||
`
|
||||
composeFile = fmt.Sprintf(composeFile, labels.KatenaryLabelPrefix)
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
|
||||
output := internalCompileTest(t, "-s", "templates/bb/deployment.yaml")
|
||||
dep := appsv1.Deployment{}
|
||||
if err := yaml.Unmarshal([]byte(output), &dep); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
containers := dep.Spec.Template.Spec.Containers
|
||||
environment := containers[0].Env[0]
|
||||
|
||||
envFrom := environment.ValueFrom.SecretKeyRef
|
||||
if envFrom.Key != "AA_USER" {
|
||||
t.Errorf("Expected AA_USER, got %s", envFrom.Key)
|
||||
}
|
||||
if !strings.Contains(envFrom.Name, "aa") {
|
||||
t.Errorf("Expected aa, got %s", envFrom.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvFrom(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
web:
|
||||
image: nginx:1.29
|
||||
environment:
|
||||
Foo: bar
|
||||
BAZ: qux
|
||||
db:
|
||||
image: postgres
|
||||
labels:
|
||||
%[1]s/env-from: |-
|
||||
- web
|
||||
`
|
||||
composeFile = fmt.Sprintf(composeFile, labels.KatenaryLabelPrefix)
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
|
||||
output := internalCompileTest(t, "-s", "templates/db/deployment.yaml")
|
||||
dep := appsv1.Deployment{}
|
||||
if err := yaml.Unmarshal([]byte(output), &dep); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
envFrom := dep.Spec.Template.Spec.Containers[0].EnvFrom
|
||||
if len(envFrom) != 1 {
|
||||
t.Fatalf("Expected 1 envFrom, got %d", len(envFrom))
|
||||
}
|
||||
}
|
255
internal/generator/configMap.go
Normal file
@@ -0,0 +1,255 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"katenary.io/internal/generator/labels"
|
||||
"katenary.io/internal/generator/labels/labelstructs"
|
||||
"katenary.io/internal/utils"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// FileMapUsage is the usage of the filemap.
|
||||
type FileMapUsage uint8
|
||||
|
||||
// FileMapUsage constants.
|
||||
const (
|
||||
FileMapUsageConfigMap FileMapUsage = iota // pure configmap for key:values.
|
||||
FileMapUsageFiles // files in a configmap.
|
||||
)
|
||||
|
||||
// only used to check interface implementation
|
||||
var (
|
||||
_ DataMap = (*ConfigMap)(nil)
|
||||
_ Yaml = (*ConfigMap)(nil)
|
||||
)
|
||||
|
||||
// ConfigMap is a kubernetes ConfigMap.
|
||||
// Implements the DataMap interface.
|
||||
type ConfigMap struct {
|
||||
*corev1.ConfigMap
|
||||
service *types.ServiceConfig
|
||||
path string
|
||||
usage FileMapUsage
|
||||
}
|
||||
|
||||
// NewConfigMap creates a new ConfigMap from a compose service. The appName is the name of the application taken from the project name.
|
||||
// The ConfigMap is filled by environment variables and labels "map-env".
|
||||
func NewConfigMap(service types.ServiceConfig, appName string, forFile bool) *ConfigMap {
|
||||
done := map[string]bool{}
|
||||
drop := map[string]bool{}
|
||||
labelValues := []string{}
|
||||
|
||||
cm := &ConfigMap{
|
||||
service: &service,
|
||||
ConfigMap: &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ConfigMap",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: utils.TplName(service.Name, appName),
|
||||
Labels: GetLabels(service.Name, appName),
|
||||
Annotations: Annotations,
|
||||
},
|
||||
Data: make(map[string]string),
|
||||
},
|
||||
}
|
||||
|
||||
// get the secrets from the labels
|
||||
secrets, err := labelstructs.SecretsFrom(service.Labels[labels.LabelSecrets])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// drop the secrets from the environment
|
||||
for _, secret := range secrets {
|
||||
drop[secret] = true
|
||||
}
|
||||
// get the label values from the labels
|
||||
varDescriptons := utils.GetValuesFromLabel(service, labels.LabelValues)
|
||||
for value := range varDescriptons {
|
||||
labelValues = append(labelValues, value)
|
||||
}
|
||||
|
||||
// change the environment variables to the values defined in the values.yaml
|
||||
for _, value := range labelValues {
|
||||
if _, ok := service.Environment[value]; !ok {
|
||||
done[value] = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if !forFile {
|
||||
// do not bind env variables to the configmap
|
||||
// remove the variables that are already defined in the environment
|
||||
if l, ok := service.Labels[labels.LabelMapEnv]; ok {
|
||||
envmap, err := labelstructs.MapEnvFrom(l)
|
||||
if err != nil {
|
||||
log.Fatal("Error parsing map-env", err)
|
||||
}
|
||||
for key, value := range envmap {
|
||||
cm.AddData(key, strings.ReplaceAll(value, "__APP__", appName))
|
||||
done[key] = true
|
||||
}
|
||||
}
|
||||
for key, env := range service.Environment {
|
||||
_, isDropped := drop[key]
|
||||
_, isDone := done[key]
|
||||
if isDropped || isDone {
|
||||
continue
|
||||
}
|
||||
cm.AddData(key, *env)
|
||||
}
|
||||
}
|
||||
|
||||
return cm
|
||||
}
|
||||
|
||||
// NewConfigMapFromDirectory creates a new ConfigMap from a compose service. This path is the path to the
|
||||
// file or directory. If the path is a directory, all files in the directory are added to the ConfigMap.
|
||||
// Each subdirectory are ignored. Note that the Generate() function will create the subdirectories ConfigMaps.
|
||||
func NewConfigMapFromDirectory(service types.ServiceConfig, appName, path string) *ConfigMap {
|
||||
normalized := path
|
||||
normalized = strings.TrimLeft(normalized, ".")
|
||||
normalized = strings.TrimLeft(normalized, "/")
|
||||
normalized = regexp.MustCompile(`[^a-zA-Z0-9-]+`).ReplaceAllString(normalized, "-")
|
||||
|
||||
cm := &ConfigMap{
|
||||
path: path,
|
||||
service: &service,
|
||||
usage: FileMapUsageFiles,
|
||||
ConfigMap: &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ConfigMap",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: utils.TplName(service.Name, appName) + "-" + normalized,
|
||||
Labels: GetLabels(service.Name, appName),
|
||||
Annotations: Annotations,
|
||||
},
|
||||
Data: make(map[string]string),
|
||||
},
|
||||
}
|
||||
// cumulate the path to the WorkingDir
|
||||
path = filepath.Join(service.WorkingDir, path)
|
||||
path = filepath.Clean(path)
|
||||
if err := cm.AppendDir(path); err != nil {
|
||||
log.Fatal("Error adding files to configmap:", err)
|
||||
}
|
||||
return cm
|
||||
}
|
||||
|
||||
// AddData adds a key value pair to the configmap. Append or overwrite the value if the key already exists.
|
||||
func (c *ConfigMap) AddData(key, value string) {
|
||||
c.Data[key] = value
|
||||
}
|
||||
|
||||
// AddBinaryData adds binary data to the configmap. Append or overwrite the value if the key already exists.
|
||||
func (c *ConfigMap) AddBinaryData(key string, value []byte) {
|
||||
if c.BinaryData == nil {
|
||||
c.BinaryData = make(map[string][]byte)
|
||||
}
|
||||
c.BinaryData[key] = value
|
||||
}
|
||||
|
||||
// AppendDir adds files from given path to the configmap. It is not recursive, to add all files in a directory,
|
||||
// you need to call this function for each subdirectory.
|
||||
func (c *ConfigMap) AppendDir(path string) error {
|
||||
// read all files in the path and add them to the configmap
|
||||
stat, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("path %s does not exist, %w", path, err)
|
||||
}
|
||||
// recursively read all files in the path and add them to the configmap
|
||||
if stat.IsDir() {
|
||||
files, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
utils.Warn("Subdirectories are ignored for the moment, skipping", filepath.Join(path, file.Name()))
|
||||
continue
|
||||
}
|
||||
path := filepath.Join(path, file.Name())
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// remove the path from the file
|
||||
filename := filepath.Base(path)
|
||||
if utf8.Valid(content) {
|
||||
c.AddData(filename, string(content))
|
||||
} else {
|
||||
c.AddBinaryData(filename, content)
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
// add the file to the configmap
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filename := filepath.Base(path)
|
||||
if utf8.Valid(content) {
|
||||
c.AddData(filename, string(content))
|
||||
} else {
|
||||
c.AddBinaryData(filename, content)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ConfigMap) AppendFile(path string) error {
|
||||
// read all files in the path and add them to the configmap
|
||||
stat, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("path %s doesn not exists, %w", path, err)
|
||||
}
|
||||
// recursively read all files in the path and add them to the configmap
|
||||
if !stat.IsDir() {
|
||||
// add the file to the configmap
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if utf8.Valid(content) {
|
||||
c.AddData(filepath.Base(path), string(content))
|
||||
} else {
|
||||
c.AddBinaryData(filepath.Base(path), content)
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Filename returns the filename of the configmap. If the configmap is used for files, the filename contains the path.
|
||||
func (c *ConfigMap) Filename() string {
|
||||
switch c.usage {
|
||||
case FileMapUsageFiles:
|
||||
return filepath.Join(c.service.Name, "statics", c.path, "configmap.yaml")
|
||||
default:
|
||||
return c.service.Name + ".configmap.yaml"
|
||||
}
|
||||
}
|
||||
|
||||
// SetData sets the data of the configmap. It replaces the entire data.
|
||||
func (c *ConfigMap) SetData(data map[string]string) {
|
||||
c.Data = data
|
||||
}
|
||||
|
||||
// Yaml returns the yaml representation of the configmap
|
||||
func (c *ConfigMap) Yaml() ([]byte, error) {
|
||||
return ToK8SYaml(c)
|
||||
}
|
133
internal/generator/configMap_test.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"katenary.io/internal/generator/labels"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
func TestEnvInConfigMap(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
web:
|
||||
image: nginx:1.29
|
||||
environment:
|
||||
- FOO=bar
|
||||
- BAR=baz
|
||||
`
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
|
||||
output := internalCompileTest(t, "-s", "templates/web/configmap.yaml")
|
||||
configMap := v1.ConfigMap{}
|
||||
if err := yaml.Unmarshal([]byte(output), &configMap); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
data := configMap.Data
|
||||
if len(data) != 2 {
|
||||
t.Errorf("Expected 2 data, got %d", len(data))
|
||||
}
|
||||
if data["FOO"] != "bar" {
|
||||
t.Errorf("Expected FOO to be bar, got %s", data["FOO"])
|
||||
}
|
||||
if data["BAR"] != "baz" {
|
||||
t.Errorf("Expected BAR to be baz, got %s", data["BAR"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapEnv(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
web:
|
||||
image: nginx:1.29
|
||||
environment:
|
||||
FOO: bar
|
||||
labels:
|
||||
%[1]s/map-env: |-
|
||||
FOO: 'baz'
|
||||
`
|
||||
|
||||
composeFile = fmt.Sprintf(composeFile, labels.KatenaryLabelPrefix)
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
|
||||
output := internalCompileTest(t, "-s", "templates/web/configmap.yaml")
|
||||
configMap := v1.ConfigMap{}
|
||||
if err := yaml.Unmarshal([]byte(output), &configMap); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
data := configMap.Data
|
||||
if v, ok := data["FOO"]; !ok || v != "baz" {
|
||||
t.Errorf("Expected FOO to be baz, got %s", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendBadFile(t *testing.T) {
|
||||
cm := NewConfigMap(types.ServiceConfig{}, "app", true)
|
||||
err := cm.AppendFile("foo")
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendBadDir(t *testing.T) {
|
||||
cm := NewConfigMap(types.ServiceConfig{}, "app", true)
|
||||
err := cm.AppendDir("foo")
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRootConfigmapfile(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
web:
|
||||
image: nginx
|
||||
volumes:
|
||||
- ./foo.txt:/etc/foo.txt
|
||||
labels:
|
||||
%[1]s/configmap-files: |-
|
||||
- ./foo.txt
|
||||
`
|
||||
composeFile = fmt.Sprintf(composeFile, labels.KatenaryLabelPrefix)
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
fooTxt := "foo content"
|
||||
fooFp, _ := os.Create("foo.txt")
|
||||
io.WriteString(fooFp, fooTxt)
|
||||
fooFp.Close()
|
||||
|
||||
output := internalCompileTest(t, "-s", "templates/web/statics/configmap.yaml")
|
||||
configMap := v1.ConfigMap{}
|
||||
if err := yaml.Unmarshal([]byte(output), &configMap); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
if configMap.Data == nil {
|
||||
t.Error("Expected configmap data to not be nil")
|
||||
}
|
||||
// if the configmap.Name ends by anything that is not alphanumeric, there is a problem
|
||||
valid := regexp.MustCompile(`.*[a-zA-Z0-9]+$`)
|
||||
if !valid.MatchString(configMap.Name) {
|
||||
t.Errorf("ConfigMap name %s is not valid", configMap.Name)
|
||||
}
|
||||
}
|
700
internal/generator/converter.go
Normal file
@@ -0,0 +1,700 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"katenary.io/internal/generator/extrafiles"
|
||||
"katenary.io/internal/generator/katenaryfile"
|
||||
"katenary.io/internal/generator/labels"
|
||||
"katenary.io/internal/generator/labels/labelstructs"
|
||||
"katenary.io/internal/parser"
|
||||
"katenary.io/internal/utils"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
)
|
||||
|
||||
const ingressClassHelp = `# Default value for ingress.class annotation
|
||||
# class: "-"
|
||||
# If the value is "-", controller will not set ingressClassName
|
||||
# If the value is "", Ingress will be set to an empty string, so
|
||||
# controller will use the default value for ingressClass
|
||||
# If the value is specified, controller will set the named class e.g. "nginx"
|
||||
`
|
||||
|
||||
const storageClassHelp = `# Storage class to use for PVCs
|
||||
# storageClass: "-" means use default
|
||||
# storageClass: "" means do not specify
|
||||
# storageClass: "foo" means use that storageClass
|
||||
`
|
||||
|
||||
const headerHelp = `# This file is autogenerated by katenary
|
||||
#
|
||||
# DO NOT EDIT IT BY HAND UNLESS YOU KNOW WHAT YOU ARE DOING
|
||||
#
|
||||
# If you want to change the content of this file, you should edit the
|
||||
# compose file and run katenary again.
|
||||
# If you need to override some values, you can do it in a override file
|
||||
# and use the -f flag to specify it when running the helm command.
|
||||
|
||||
|
||||
`
|
||||
|
||||
const imagePullSecretHelp = `
|
||||
# imagePullSecrets allows you to specify a name of an image pull secret.
|
||||
# You must provide a list of object with the name field set to the name of the
|
||||
# e.g.
|
||||
# pullSecrets:
|
||||
# - name: regcred
|
||||
# You are, for now, responsible for creating the secret.
|
||||
`
|
||||
|
||||
const imagePullPolicyHelp = `# imagePullPolicy allows you to specify a policy to cache or always pull an image.
|
||||
# You must provide a string value with one of the following values:
|
||||
# - Always -> will always pull the image
|
||||
# - Never -> will never pull the image, the image should be present on the node
|
||||
# - IfNotPresent -> will pull the image only if it is not present on the node
|
||||
`
|
||||
|
||||
const resourceHelp = `# Resources allows you to specify the resource requests and limits for a service.
|
||||
# Resources are used to specify the amount of CPU and memory that
|
||||
# a container needs.
|
||||
#
|
||||
# e.g.
|
||||
# resources:
|
||||
# requests:
|
||||
# memory: "64Mi"
|
||||
# cpu: "250m"
|
||||
# limits:
|
||||
# memory: "128Mi"
|
||||
# cpu: "500m"
|
||||
`
|
||||
|
||||
const mainTagAppDoc = `This is the version of the main application.
|
||||
Leave it to blank to use the Chart "AppVersion" value.`
|
||||
|
||||
var unwantedLines = []string{
|
||||
"creationTimestamp:",
|
||||
"status:",
|
||||
}
|
||||
|
||||
var ingressTLSHelp = `# Ingress TLS configuration
|
||||
# If enabled, a secret containing the certificate and the key should be
|
||||
# created by the ingress controller. If the name if emtpy, so the secret
|
||||
# name is generated. You can specify the secret name to use your own secret.
|
||||
`
|
||||
|
||||
// keyRegExp checks if the line starts by a #
|
||||
var keyRegExp = regexp.MustCompile(`^\s*[^#]+:.*`)
|
||||
|
||||
// Convert a compose (docker, podman...) project to a helm chart.
|
||||
// It calls Generate() to generate the chart and then write it to the disk.
|
||||
func Convert(config ConvertOptions, dockerComposeFile ...string) error {
|
||||
var (
|
||||
templateDir = filepath.Join(config.OutputDir, "templates")
|
||||
helpersPath = filepath.Join(config.OutputDir, "templates", "_helpers.tpl")
|
||||
chartPath = filepath.Join(config.OutputDir, "Chart.yaml")
|
||||
valuesPath = filepath.Join(config.OutputDir, "values.yaml")
|
||||
readmePath = filepath.Join(config.OutputDir, "README.md")
|
||||
notesPath = filepath.Join(templateDir, "NOTES.txt")
|
||||
)
|
||||
|
||||
// the current working directory is the directory
|
||||
currentDir, _ := os.Getwd()
|
||||
// go to the root of the project
|
||||
if err := os.Chdir(filepath.Dir(dockerComposeFile[0])); err != nil {
|
||||
fmt.Println(utils.IconFailure, err)
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := os.Chdir(currentDir); err != nil { // after the generation, go back to the original directory
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// repove the directory part of the docker-compose files
|
||||
for i, f := range dockerComposeFile {
|
||||
dockerComposeFile[i] = filepath.Base(f)
|
||||
}
|
||||
|
||||
// parse the compose files
|
||||
project, err := parser.Parse(config.Profiles, config.EnvFiles, dockerComposeFile...)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// check older version of labels
|
||||
if err := checkOldLabels(project); err != nil {
|
||||
fmt.Println(utils.IconFailure, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: use katenary.yaml file here to set the labels
|
||||
katenaryfile.OverrideWithConfig(project)
|
||||
|
||||
if !config.Force {
|
||||
// check if the chart directory exists
|
||||
// if yes, prevent the user from overwriting it and ask for confirmation
|
||||
if _, err := os.Stat(config.OutputDir); err == nil {
|
||||
overwrite := utils.Confirm(
|
||||
"The chart directory "+config.OutputDir+" already exists, do you want to overwrite it?",
|
||||
utils.IconWarning,
|
||||
)
|
||||
if !overwrite {
|
||||
fmt.Println("Aborting")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
fmt.Println() // clean line
|
||||
}
|
||||
|
||||
// Build the objects !
|
||||
chart, err := Generate(project)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// if the app version is set from the command line, use it
|
||||
if config.AppVersion != nil {
|
||||
chart.AppVersion = *config.AppVersion
|
||||
}
|
||||
chart.Version = config.ChartVersion
|
||||
|
||||
// remove the chart directory if it exists
|
||||
os.RemoveAll(config.OutputDir)
|
||||
|
||||
// create the chart directory
|
||||
if err := os.MkdirAll(templateDir, utils.DirectoryPermission); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// add icon from the command line
|
||||
if config.Icon != "" {
|
||||
chart.Icon = config.Icon
|
||||
}
|
||||
|
||||
// write the templates to the disk
|
||||
chart.SaveTemplates(templateDir)
|
||||
|
||||
// write the Chart.yaml file
|
||||
buildCharYamlFile(chart, project, chartPath)
|
||||
|
||||
// build and write the values.yaml file
|
||||
buildValues(chart, project, valuesPath)
|
||||
|
||||
// write the _helpers.tpl to the disk
|
||||
writeContent(helpersPath, []byte(chart.Helper))
|
||||
|
||||
// write the readme to the disk
|
||||
readme := extrafiles.ReadMeFile(chart.Name, chart.Description, chart.Values)
|
||||
writeContent(readmePath, []byte(readme))
|
||||
|
||||
// get the list of services to write in the notes
|
||||
buildNotesFile(project, notesPath)
|
||||
|
||||
// call helm update if needed
|
||||
callHelmUpdate(config)
|
||||
return nil
|
||||
}
|
||||
|
||||
func addChartDoc(values []byte, project *types.Project) []byte {
|
||||
chartDoc := fmt.Sprintf(`# This is the main values.yaml file for the %s chart.
|
||||
# More information can be found in the chart's README.md file.
|
||||
#
|
||||
`, project.Name)
|
||||
|
||||
lines := strings.Split(string(values), "\n")
|
||||
for i, line := range lines {
|
||||
if regexp.MustCompile(`(?m)^name:`).MatchString(line) {
|
||||
doc := "\n# Name of the chart (required), basically the name of the project.\n"
|
||||
lines[i] = doc + line
|
||||
} else if regexp.MustCompile(`(?m)^version:`).MatchString(line) {
|
||||
doc := "\n# Version of the chart (required)\n"
|
||||
lines[i] = doc + line
|
||||
} else if strings.Contains(line, "appVersion:") {
|
||||
spaces := utils.CountStartingSpaces(line)
|
||||
doc := fmt.Sprintf(
|
||||
"\n%s# Version of the application (required).\n%s# This should be the main application version.\n",
|
||||
strings.Repeat(" ", spaces),
|
||||
strings.Repeat(" ", spaces),
|
||||
)
|
||||
lines[i] = doc + line
|
||||
} else if strings.Contains(line, "dependencies:") {
|
||||
spaces := utils.CountStartingSpaces(line)
|
||||
doc := fmt.Sprintf("\n"+
|
||||
"%s# Dependencies are external charts that this chart will depend on.\n"+
|
||||
"%s# More information can be found in the chart's README.md file.\n",
|
||||
strings.Repeat(" ", spaces),
|
||||
strings.Repeat(" ", spaces),
|
||||
)
|
||||
lines[i] = doc + line
|
||||
}
|
||||
}
|
||||
return []byte(chartDoc + strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
func addCommentsToValues(values []byte) []byte {
|
||||
lines := strings.Split(string(values), "\n")
|
||||
for i, line := range lines {
|
||||
if strings.Contains(line, "ingress:") {
|
||||
spaces := utils.CountStartingSpaces(line)
|
||||
spacesString := strings.Repeat(" ", spaces)
|
||||
// indent ingressClassHelper comment
|
||||
ingressClassHelp := strings.ReplaceAll(ingressClassHelp, "\n", "\n"+spacesString)
|
||||
ingressClassHelp = strings.TrimRight(ingressClassHelp, " ")
|
||||
ingressClassHelp = spacesString + ingressClassHelp
|
||||
lines[i] = ingressClassHelp + line
|
||||
}
|
||||
}
|
||||
return []byte(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
func addDependencyDescription(values []byte, dependencies []labelstructs.Dependency) []byte {
|
||||
for _, d := range dependencies {
|
||||
name := d.Name
|
||||
if d.Alias != "" {
|
||||
name = d.Alias
|
||||
}
|
||||
|
||||
values = regexp.MustCompile(
|
||||
`(?m)^`+name+`:$`,
|
||||
).ReplaceAll(
|
||||
values,
|
||||
[]byte("\n# "+d.Name+" helm dependency configuration\n"+name+":"),
|
||||
)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// addDescriptions adds the description from the label to the values.yaml file on top
|
||||
// of the service definition.
|
||||
func addDescriptions(values []byte, project types.Project) []byte {
|
||||
for _, service := range project.Services {
|
||||
if description, ok := service.Labels[labels.LabelDescription]; ok {
|
||||
// set it as comment
|
||||
description = "\n# " + strings.ReplaceAll(description, "\n", "\n# ")
|
||||
|
||||
values = regexp.MustCompile(
|
||||
`(?m)^`+service.Name+`:$`,
|
||||
).ReplaceAll(values, []byte(description+"\n"+service.Name+":"))
|
||||
} else {
|
||||
// set it as comment
|
||||
description = "\n# " + service.Name + " configuration"
|
||||
|
||||
values = regexp.MustCompile(
|
||||
`(?m)^`+service.Name+`:$`,
|
||||
).ReplaceAll(
|
||||
values,
|
||||
[]byte(description+"\n"+service.Name+":"),
|
||||
)
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func addDocToVariable(service types.ServiceConfig, lines []string) []string {
|
||||
currentService := ""
|
||||
variables := utils.GetValuesFromLabel(service, labels.LabelValues)
|
||||
for i, line := range lines {
|
||||
// if the line is a service, it is a name followed by a colon
|
||||
if regexp.MustCompile(`(?m)^` + service.Name + `:`).MatchString(line) {
|
||||
currentService = service.Name
|
||||
}
|
||||
// for each variable in the service, add the description
|
||||
for varname, variable := range variables {
|
||||
if variable == nil {
|
||||
continue
|
||||
}
|
||||
spaces := utils.CountStartingSpaces(line)
|
||||
if regexp.MustCompile(`(?m)\s*`+varname+`:`).MatchString(line) && currentService == service.Name {
|
||||
|
||||
// add # to the beginning of the Description
|
||||
doc := strings.ReplaceAll("\n"+variable.Description, "\n", "\n"+strings.Repeat(" ", spaces)+"# ")
|
||||
doc = strings.TrimRight(doc, " ")
|
||||
doc += "\n" + line
|
||||
|
||||
lines[i] = doc
|
||||
}
|
||||
}
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func addImagePullPolicyHelp(values []byte) []byte {
|
||||
// add imagePullPolicy help
|
||||
lines := strings.Split(string(values), "\n")
|
||||
for i, line := range lines {
|
||||
if strings.Contains(line, "imagePullPolicy:") {
|
||||
spaces := utils.CountStartingSpaces(line)
|
||||
spacesString := strings.Repeat(" ", spaces)
|
||||
// indent imagePullPolicyHelp comment
|
||||
imagePullPolicyHelp := strings.ReplaceAll(imagePullPolicyHelp, "\n", "\n"+spacesString)
|
||||
imagePullPolicyHelp = strings.TrimRight(imagePullPolicyHelp, " ")
|
||||
imagePullPolicyHelp = spacesString + imagePullPolicyHelp
|
||||
lines[i] = imagePullPolicyHelp + line
|
||||
}
|
||||
}
|
||||
return []byte(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
func addImagePullSecretsHelp(values []byte) []byte {
|
||||
// add imagePullSecrets help
|
||||
lines := strings.Split(string(values), "\n")
|
||||
|
||||
for i, line := range lines {
|
||||
if strings.Contains(line, "pullSecrets:") {
|
||||
spaces := utils.CountStartingSpaces(line)
|
||||
spacesString := strings.Repeat(" ", spaces)
|
||||
// indent imagePullSecretHelp comment
|
||||
imagePullSecretHelp := strings.ReplaceAll(imagePullSecretHelp, "\n", "\n"+spacesString)
|
||||
imagePullSecretHelp = strings.TrimRight(imagePullSecretHelp, " ")
|
||||
imagePullSecretHelp = spacesString + imagePullSecretHelp
|
||||
lines[i] = imagePullSecretHelp + line
|
||||
}
|
||||
}
|
||||
return []byte(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
func addMainAppDoc(lines []string, service types.ServiceConfig) []string {
|
||||
inService := false
|
||||
inRegistry := false
|
||||
for i, line := range lines {
|
||||
if regexp.MustCompile(`^` + service.Name + `:`).MatchString(line) {
|
||||
inService = true
|
||||
}
|
||||
if inService && regexp.MustCompile(`^\s*repository:.*`).MatchString(line) {
|
||||
inRegistry = true
|
||||
}
|
||||
if inService && inRegistry {
|
||||
if regexp.MustCompile(`^\s*tag: .*`).MatchString(line) {
|
||||
spaces := utils.CountStartingSpaces(line)
|
||||
doc := strings.ReplaceAll(mainTagAppDoc, "\n", "\n"+strings.Repeat(" ", spaces)+"# ")
|
||||
doc = strings.Repeat(" ", spaces) + "# " + doc
|
||||
|
||||
lines[i] = doc + "\n" + line + "\n"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func addMainTagAppDoc(values []byte, project *types.Project) []byte {
|
||||
lines := strings.Split(string(values), "\n")
|
||||
|
||||
for _, service := range project.Services {
|
||||
// read the label LabelMainApp
|
||||
if v, ok := service.Labels[labels.LabelMainApp]; !ok {
|
||||
continue
|
||||
} else if v == "false" || v == "no" || v == "0" {
|
||||
continue
|
||||
} else {
|
||||
fmt.Printf("%s Adding main tag app doc %s\n", utils.IconConfig, service.Name)
|
||||
}
|
||||
|
||||
lines = addMainAppDoc(lines, service)
|
||||
}
|
||||
|
||||
return []byte(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
func addResourceHelp(values []byte) []byte {
|
||||
lines := strings.Split(string(values), "\n")
|
||||
for i, line := range lines {
|
||||
if strings.Contains(line, "resources:") {
|
||||
spaces := utils.CountStartingSpaces(line)
|
||||
spacesString := strings.Repeat(" ", spaces)
|
||||
// indent resourceHelp comment
|
||||
resourceHelp := strings.ReplaceAll(resourceHelp, "\n", "\n"+spacesString)
|
||||
resourceHelp = strings.TrimRight(resourceHelp, " ")
|
||||
resourceHelp = spacesString + resourceHelp
|
||||
lines[i] = resourceHelp + line
|
||||
}
|
||||
}
|
||||
return []byte(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
// addStorageClassHelp adds a comment to the values.yaml file to explain how to
|
||||
// use the storageClass option.
|
||||
func addStorageClassHelp(values []byte) []byte {
|
||||
lines := strings.Split(string(values), "\n")
|
||||
for i, line := range lines {
|
||||
if strings.Contains(line, "storageClass:") {
|
||||
spaces := utils.CountStartingSpaces(line)
|
||||
spacesString := strings.Repeat(" ", spaces)
|
||||
// indent ingressClassHelper comment
|
||||
storageClassHelp := strings.ReplaceAll(storageClassHelp, "\n", "\n"+spacesString)
|
||||
storageClassHelp = strings.TrimRight(storageClassHelp, " ")
|
||||
storageClassHelp = spacesString + storageClassHelp
|
||||
lines[i] = storageClassHelp + line
|
||||
}
|
||||
}
|
||||
return []byte(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
func addVariablesDoc(values []byte, project *types.Project) []byte {
|
||||
lines := strings.Split(string(values), "\n")
|
||||
|
||||
for _, service := range project.Services {
|
||||
lines = addDocToVariable(service, lines)
|
||||
}
|
||||
return []byte(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
// addYAMLSelectorPath adds a selector path to the yaml file for each key
|
||||
// as comment. E.g. foo.ingress.host
|
||||
func addYAMLSelectorPath(values []byte) []byte {
|
||||
lines := strings.Split(string(values), "\n")
|
||||
currentKey := ""
|
||||
currentLevel := 0
|
||||
toReturn := []string{}
|
||||
for _, line := range lines {
|
||||
// if the line is a not a key, continue
|
||||
if !keyRegExp.MatchString(line) {
|
||||
toReturn = append(toReturn, line)
|
||||
continue
|
||||
}
|
||||
// get the key
|
||||
key := strings.TrimSpace(strings.Split(line, ":")[0])
|
||||
|
||||
// get the spaces
|
||||
spaces := utils.CountStartingSpaces(line)
|
||||
|
||||
if spaces/2 > currentLevel {
|
||||
currentLevel++
|
||||
} else if spaces/2 < currentLevel {
|
||||
currentLevel--
|
||||
}
|
||||
currentKey = strings.Join(strings.Split(currentKey, ".")[:spaces/2], ".")
|
||||
|
||||
if currentLevel == 0 {
|
||||
currentKey = key
|
||||
toReturn = append(toReturn, line)
|
||||
continue
|
||||
}
|
||||
// if the key is not empty, add the selector path
|
||||
if currentKey != "" {
|
||||
currentKey += "."
|
||||
}
|
||||
currentKey += key
|
||||
// add the selector path as comment
|
||||
toReturn = append(
|
||||
toReturn,
|
||||
strings.Repeat(" ", spaces)+"# key: "+currentKey+"\n"+line,
|
||||
)
|
||||
}
|
||||
return []byte(strings.Join(toReturn, "\n"))
|
||||
}
|
||||
|
||||
// addTLSHelp adds a comment to the values.yaml file to explain how to
|
||||
// use the tls option.
|
||||
func addTLSHelp(values []byte) []byte {
|
||||
lines := strings.Split(string(values), "\n")
|
||||
for i, line := range lines {
|
||||
if strings.Contains(line, "tls:") {
|
||||
spaces := utils.CountStartingSpaces(line)
|
||||
spacesString := strings.Repeat(" ", spaces)
|
||||
// indent ingressClassHelper comment
|
||||
ingressTLSHelp := strings.ReplaceAll(ingressTLSHelp, "\n", "\n"+spacesString)
|
||||
ingressTLSHelp = strings.TrimRight(ingressTLSHelp, " ")
|
||||
ingressTLSHelp = spacesString + ingressTLSHelp
|
||||
lines[i] = ingressTLSHelp + line
|
||||
}
|
||||
}
|
||||
return []byte(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
func buildCharYamlFile(chart *HelmChart, project *types.Project, chartPath string) {
|
||||
// calculate the sha1 hash of the services
|
||||
yamlChart, err := utils.EncodeBasicYaml(chart)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// concat chart adding a comment with hash of services on top
|
||||
yamlChart = append(fmt.Appendf(nil, "# compose hash (sha1): %s\n", *chart.composeHash), yamlChart...)
|
||||
// add the list of compose files
|
||||
files := []string{}
|
||||
for _, file := range project.ComposeFiles {
|
||||
base := filepath.Base(file)
|
||||
files = append(files, base)
|
||||
}
|
||||
yamlChart = append(fmt.Appendf(nil, "# compose files: %s\n", strings.Join(files, ", ")), yamlChart...)
|
||||
// add generated date
|
||||
yamlChart = append(fmt.Appendf(nil, "# generated at: %s\n", time.Now().Format(time.RFC3339)), yamlChart...)
|
||||
|
||||
// document Chart.yaml file
|
||||
yamlChart = addChartDoc(yamlChart, project)
|
||||
|
||||
writeContent(chartPath, yamlChart)
|
||||
}
|
||||
|
||||
func buildNotesFile(project *types.Project, notesPath string) {
|
||||
// get the list of services to write in the notes
|
||||
services := make([]string, 0)
|
||||
for _, service := range project.Services {
|
||||
services = append(services, service.Name)
|
||||
}
|
||||
// write the notes to the disk
|
||||
notes := extrafiles.NotesFile(services)
|
||||
writeContent(notesPath, []byte(notes))
|
||||
}
|
||||
|
||||
func buildValues(chart *HelmChart, project *types.Project, valuesPath string) {
|
||||
values, err := utils.EncodeBasicYaml(&chart.Values)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
values = addDescriptions(values, *project)
|
||||
values = addDependencyDescription(values, chart.Dependencies)
|
||||
values = addCommentsToValues(values)
|
||||
values = addStorageClassHelp(values)
|
||||
values = addImagePullSecretsHelp(values)
|
||||
values = addImagePullPolicyHelp(values)
|
||||
values = addVariablesDoc(values, project)
|
||||
values = addMainTagAppDoc(values, project)
|
||||
values = addResourceHelp(values)
|
||||
values = addTLSHelp(values)
|
||||
values = addYAMLSelectorPath(values)
|
||||
values = append([]byte(headerHelp), values...)
|
||||
|
||||
// add vim modeline
|
||||
values = append(values, []byte("\n# vim: ft=yaml\n")...)
|
||||
|
||||
// write the values to the disk
|
||||
writeContent(valuesPath, values)
|
||||
}
|
||||
|
||||
func callHelmUpdate(config ConvertOptions) {
|
||||
executeAndHandleError := func(fn func(ConvertOptions) error, config ConvertOptions, message string) {
|
||||
if err := fn(config); err != nil {
|
||||
fmt.Println(utils.IconFailure, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(utils.IconSuccess, message)
|
||||
}
|
||||
if config.HelmUpdate {
|
||||
executeAndHandleError(helmUpdate, config, "Helm dependencies updated")
|
||||
executeAndHandleError(helmLint, config, "Helm chart linted")
|
||||
fmt.Println(utils.IconSuccess, "Helm chart created successfully")
|
||||
}
|
||||
}
|
||||
|
||||
func removeNewlinesInsideBrackets(values []byte) []byte {
|
||||
re, err := regexp.Compile(`(?s)\{\{(.*?)\}\}`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return re.ReplaceAllFunc(values, func(b []byte) []byte {
|
||||
// get the first match
|
||||
matches := re.FindSubmatch(b)
|
||||
replacement := bytes.ReplaceAll(matches[1], []byte("\n"), []byte(" "))
|
||||
// remove repeated spaces
|
||||
replacement = regexp.MustCompile(`\s+`).ReplaceAll(replacement, []byte(" "))
|
||||
// remove newlines inside brackets
|
||||
return bytes.ReplaceAll(b, matches[1], replacement)
|
||||
})
|
||||
}
|
||||
|
||||
func removeUnwantedLines(values []byte) []byte {
|
||||
lines := strings.Split(string(values), "\n")
|
||||
output := []string{}
|
||||
for _, line := range lines {
|
||||
next := false
|
||||
for _, unwanted := range unwantedLines {
|
||||
if strings.Contains(line, unwanted) {
|
||||
next = true
|
||||
}
|
||||
}
|
||||
if !next {
|
||||
output = append(output, line)
|
||||
}
|
||||
}
|
||||
return []byte(strings.Join(output, "\n"))
|
||||
}
|
||||
|
||||
func writeContent(path string, content []byte) {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
fmt.Println(utils.IconFailure, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer f.Close()
|
||||
defer func() {
|
||||
if _, err := f.Write(content); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// helmLint runs "helm lint" on the output directory.
|
||||
func helmLint(config ConvertOptions) error {
|
||||
fmt.Println(utils.IconInfo, "Linting...")
|
||||
helm, err := exec.LookPath("helm")
|
||||
if err != nil {
|
||||
fmt.Println(utils.IconFailure, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
cmd := exec.Command(helm, "lint", config.OutputDir)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// helmUpdate runs "helm dependency update" on the output directory.
|
||||
func helmUpdate(config ConvertOptions) error {
|
||||
// lookup for "helm" binary
|
||||
fmt.Println(utils.IconInfo, "Updating helm dependencies...")
|
||||
helm, err := exec.LookPath("helm")
|
||||
if err != nil {
|
||||
fmt.Println(utils.IconFailure, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// run "helm dependency update"
|
||||
cmd := exec.Command(helm, "dependency", "update", config.OutputDir)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// check if the project makes use of older labels (kanetary.[^v3])
|
||||
func checkOldLabels(project *types.Project) error {
|
||||
badServices := make([]string, 0)
|
||||
for _, service := range project.Services {
|
||||
for label := range service.Labels {
|
||||
if strings.Contains(label, "katenary.") && !strings.Contains(label, labels.KatenaryLabelPrefix) {
|
||||
badServices = append(badServices, fmt.Sprintf("- %s: %s", service.Name, label))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(badServices) > 0 {
|
||||
message := fmt.Sprintf(` Old labels detected in project "%s".
|
||||
|
||||
The current version of katenary uses labels with the prefix "%s" which are not compatible with previous versions.
|
||||
Your project is not compatible with this version.
|
||||
|
||||
Please upgrade your labels to follow the current version
|
||||
|
||||
Services to upgrade:
|
||||
%s`,
|
||||
project.Name,
|
||||
labels.KatenaryLabelPrefix[0:len(labels.KatenaryLabelPrefix)-1],
|
||||
strings.Join(badServices, "\n"),
|
||||
)
|
||||
|
||||
return errors.New(utils.WordWrap(message, 80))
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
124
internal/generator/cronJob.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"katenary.io/internal/generator/labels"
|
||||
"katenary.io/internal/generator/labels/labelstructs"
|
||||
"katenary.io/internal/utils"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// only used to check interface implementation
|
||||
var (
|
||||
_ Yaml = (*CronJob)(nil)
|
||||
)
|
||||
|
||||
// CronJob is a kubernetes CronJob.
|
||||
type CronJob struct {
|
||||
*batchv1.CronJob
|
||||
service *types.ServiceConfig
|
||||
}
|
||||
|
||||
// NewCronJob creates a new CronJob from a compose service. The appName is the name of the application taken from the project name.
|
||||
func NewCronJob(service types.ServiceConfig, chart *HelmChart, appName string) (*CronJob, *RBAC) {
|
||||
labels, ok := service.Labels[labels.LabelCronJob]
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
mapping, err := labelstructs.CronJobFrom(labels)
|
||||
if err != nil {
|
||||
log.Fatalf("Error parsing cronjob labels: %s", err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if _, ok := chart.Values[service.Name]; !ok {
|
||||
chart.Values[service.Name] = NewValue(service, false)
|
||||
}
|
||||
if chart.Values[service.Name].(*Value).CronJob == nil {
|
||||
chart.Values[service.Name].(*Value).CronJob = &CronJobValue{}
|
||||
}
|
||||
chart.Values[service.Name].(*Value).CronJob.Schedule = mapping.Schedule
|
||||
chart.Values[service.Name].(*Value).CronJob.ImagePullPolicy = "IfNotPresent"
|
||||
chart.Values[service.Name].(*Value).CronJob.Environment = map[string]any{}
|
||||
|
||||
image, tag := mapping.Image, ""
|
||||
if image == "" { // if image is not set, use the image from the service
|
||||
image = service.Image
|
||||
}
|
||||
|
||||
if strings.Contains(image, ":") {
|
||||
image = strings.Split(service.Image, ":")[0]
|
||||
tag = strings.Split(service.Image, ":")[1]
|
||||
}
|
||||
|
||||
chart.Values[service.Name].(*Value).CronJob.Repository = &RepositoryValue{
|
||||
Image: image,
|
||||
Tag: tag,
|
||||
}
|
||||
|
||||
cronjob := &CronJob{
|
||||
CronJob: &batchv1.CronJob{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "CronJob",
|
||||
APIVersion: "batch/v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: utils.TplName(service.Name, appName),
|
||||
Labels: GetLabels(service.Name, appName),
|
||||
Annotations: Annotations,
|
||||
},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
Schedule: "{{ .Values." + service.Name + ".cronjob.schedule }}",
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "cronjob",
|
||||
Image: "{{ .Values." + service.Name + ".cronjob.repository.image }}:{{ default .Values." + service.Name + ".cronjob.repository.tag \"latest\" }}",
|
||||
Command: []string{
|
||||
"sh",
|
||||
"-c",
|
||||
mapping.Command,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
service: &service,
|
||||
}
|
||||
|
||||
var rbac *RBAC
|
||||
if mapping.Rbac {
|
||||
rbac = NewRBAC(service, appName)
|
||||
// add the service account to the cronjob
|
||||
cronjob.Spec.JobTemplate.Spec.Template.Spec.ServiceAccountName = utils.TplName(service.Name, appName)
|
||||
}
|
||||
|
||||
return cronjob, rbac
|
||||
}
|
||||
|
||||
// Filename returns the filename of the cronjob.
|
||||
//
|
||||
// Implements the Yaml interface.
|
||||
func (c *CronJob) Filename() string {
|
||||
return c.service.Name + ".cronjob.yaml"
|
||||
}
|
||||
|
||||
// Yaml returns the yaml representation of the cronjob.
|
||||
//
|
||||
// Implements the Yaml interface.
|
||||
func (c *CronJob) Yaml() ([]byte, error) {
|
||||
return ToK8SYaml(c)
|
||||
}
|
115
internal/generator/cronJob_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
v1 "k8s.io/api/apps/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
func TestBasicCronJob(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
cron:
|
||||
image: fedora
|
||||
labels:
|
||||
katenary.v3/cronjob: |
|
||||
image: alpine
|
||||
command: echo hello
|
||||
schedule: "*/1 * * * *"
|
||||
rbac: false
|
||||
`
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
|
||||
output := internalCompileTest(t, "-s", "templates/cron/cronjob.yaml")
|
||||
cronJob := batchv1.CronJob{}
|
||||
if err := yaml.Unmarshal([]byte(output), &cronJob); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
if cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Image != "alpine:latest" {
|
||||
t.Errorf("Expected image to be alpine, got %s", cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Image)
|
||||
}
|
||||
combinedCommand := strings.Join(cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Command, " ")
|
||||
if combinedCommand != "sh -c echo hello" {
|
||||
t.Errorf("Expected command to be sh -c echo hello, got %s", combinedCommand)
|
||||
}
|
||||
if cronJob.Spec.Schedule != "*/1 * * * *" {
|
||||
t.Errorf("Expected schedule to be */1 * * * *, got %s", cronJob.Spec.Schedule)
|
||||
}
|
||||
|
||||
// ensure that there are a deployment for the fedora Container
|
||||
var err error
|
||||
output, err = helmTemplate(ConvertOptions{
|
||||
OutputDir: "./chart",
|
||||
}, "-s", "templates/cron/deployment.yaml")
|
||||
if err != nil {
|
||||
t.Errorf("Error: %s", err)
|
||||
}
|
||||
deployment := v1.Deployment{}
|
||||
if err := yaml.Unmarshal([]byte(output), &deployment); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
if deployment.Spec.Template.Spec.Containers[0].Image != "fedora:latest" {
|
||||
t.Errorf("Expected image to be fedora, got %s", deployment.Spec.Template.Spec.Containers[0].Image)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCronJobbWithRBAC(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
cron:
|
||||
image: fedora
|
||||
labels:
|
||||
katenary.v3/cronjob: |
|
||||
image: alpine
|
||||
command: echo hello
|
||||
schedule: "*/1 * * * *"
|
||||
rbac: true
|
||||
`
|
||||
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
|
||||
output := internalCompileTest(t, "-s", "templates/cron/cronjob.yaml")
|
||||
cronJob := batchv1.CronJob{}
|
||||
if err := yaml.Unmarshal([]byte(output), &cronJob); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
if cronJob.Spec.JobTemplate.Spec.Template.Spec.ServiceAccountName == "" {
|
||||
t.Errorf("Expected ServiceAccountName to be set")
|
||||
}
|
||||
|
||||
// find the service account file
|
||||
output, err := helmTemplate(ConvertOptions{
|
||||
OutputDir: "./chart",
|
||||
}, "-s", "templates/cron/serviceaccount.yaml")
|
||||
if err != nil {
|
||||
t.Errorf("Error: %s", err)
|
||||
}
|
||||
serviceAccount := corev1.ServiceAccount{}
|
||||
|
||||
if err := yaml.Unmarshal([]byte(output), &serviceAccount); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
if serviceAccount.Name == "" {
|
||||
t.Errorf("Expected ServiceAccountName to be set")
|
||||
}
|
||||
|
||||
// ensure that the serviceAccount is equal to the cronJob
|
||||
if serviceAccount.Name != cronJob.Spec.JobTemplate.Spec.Template.Spec.ServiceAccountName {
|
||||
t.Errorf("Expected ServiceAccountName to be %s, got %s", cronJob.Spec.JobTemplate.Spec.Template.Spec.ServiceAccountName, serviceAccount.Name)
|
||||
}
|
||||
}
|
737
internal/generator/deployment.go
Normal file
@@ -0,0 +1,737 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"katenary.io/internal/generator/labels"
|
||||
"katenary.io/internal/generator/labels/labelstructs"
|
||||
"katenary.io/internal/utils"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var _ Yaml = (*Deployment)(nil)
|
||||
|
||||
type mountPathConfig struct {
|
||||
mountPath string
|
||||
subPath string
|
||||
}
|
||||
|
||||
type ConfigMapMount struct {
|
||||
configMap *ConfigMap
|
||||
mountPath []mountPathConfig
|
||||
}
|
||||
|
||||
// Deployment is a kubernetes Deployment.
|
||||
type Deployment struct {
|
||||
*appsv1.Deployment `yaml:",inline"`
|
||||
chart *HelmChart `yaml:"-"`
|
||||
configMaps map[string]*ConfigMapMount `yaml:"-"`
|
||||
volumeMap map[string]string `yaml:"-"` // keep map of fixed named to original volume name
|
||||
service *types.ServiceConfig `yaml:"-"`
|
||||
defaultTag string `yaml:"-"`
|
||||
isMainApp bool `yaml:"-"`
|
||||
exchangesVolumes map[string]*labelstructs.ExchangeVolume `yaml:"-"`
|
||||
boundEnvVar []string `yaml:"-"` // environement to remove
|
||||
}
|
||||
|
||||
// NewDeployment creates a new Deployment from a compose service. The appName is the name of the application taken from the project name.
|
||||
// It also creates the Values map that will be used to create the values.yaml file.
|
||||
func NewDeployment(service types.ServiceConfig, chart *HelmChart) *Deployment {
|
||||
isMainApp := false
|
||||
if mainLabel, ok := service.Labels[labels.LabelMainApp]; ok {
|
||||
main := strings.ToLower(mainLabel)
|
||||
isMainApp = main == "true" || main == "yes" || main == "1"
|
||||
}
|
||||
|
||||
defaultTag := `default "latest"`
|
||||
if isMainApp {
|
||||
defaultTag = `default .Chart.AppVersion`
|
||||
}
|
||||
|
||||
chart.Values[service.Name] = NewValue(service, isMainApp)
|
||||
appName := chart.Name
|
||||
|
||||
dep := &Deployment{
|
||||
isMainApp: isMainApp,
|
||||
defaultTag: defaultTag,
|
||||
service: &service,
|
||||
chart: chart,
|
||||
Deployment: &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "apps/v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: utils.TplName(service.Name, appName),
|
||||
Labels: GetLabels(service.Name, appName),
|
||||
Annotations: Annotations,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Replicas: utils.Int32Ptr(1),
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: GetMatchLabels(service.Name, appName),
|
||||
},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: GetMatchLabels(service.Name, appName),
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
NodeSelector: map[string]string{
|
||||
labels.LabelName("node-selector"): "replace",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
configMaps: make(map[string]*ConfigMapMount),
|
||||
volumeMap: make(map[string]string),
|
||||
exchangesVolumes: map[string]*labelstructs.ExchangeVolume{},
|
||||
boundEnvVar: []string{},
|
||||
}
|
||||
|
||||
// add containers
|
||||
dep.AddContainer(service)
|
||||
|
||||
// add volumes
|
||||
dep.AddVolumes(service, appName)
|
||||
|
||||
if service.Environment != nil {
|
||||
dep.SetEnvFrom(service, appName)
|
||||
}
|
||||
|
||||
return dep
|
||||
}
|
||||
|
||||
// AddContainer adds a container to the deployment.
|
||||
func (d *Deployment) AddContainer(service types.ServiceConfig) {
|
||||
ports := []corev1.ContainerPort{}
|
||||
|
||||
for _, port := range service.Ports {
|
||||
name := utils.GetServiceNameByPort(int(port.Target))
|
||||
if name == "" {
|
||||
utils.Warn("Port name not found for port ", port.Target, " in service ", service.Name, ". Using port number instead")
|
||||
name = fmt.Sprintf("port-%d", port.Target)
|
||||
}
|
||||
ports = append(ports, corev1.ContainerPort{
|
||||
ContainerPort: int32(port.Target),
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
|
||||
container := corev1.Container{
|
||||
Image: utils.TplValue(service.Name, "repository.image") + ":" +
|
||||
utils.TplValue(service.Name, "repository.tag", d.defaultTag),
|
||||
Ports: ports,
|
||||
Name: service.ContainerName,
|
||||
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{},
|
||||
},
|
||||
Command: service.Command,
|
||||
}
|
||||
if _, ok := d.chart.Values[service.Name]; !ok {
|
||||
d.chart.Values[service.Name] = NewValue(service, d.isMainApp)
|
||||
}
|
||||
d.chart.Values[service.Name].(*Value).ImagePullPolicy = string(corev1.PullIfNotPresent)
|
||||
|
||||
// add an imagePullSecret, it actually does not work because the secret is not
|
||||
// created but it add the reference in the YAML file. We'll change it in Yaml()
|
||||
// method.
|
||||
d.Spec.Template.Spec.ImagePullSecrets = []corev1.LocalObjectReference{{
|
||||
Name: `{{ .Values.pullSecrets | toYaml | indent __indent__ }}`,
|
||||
}}
|
||||
|
||||
// add ServiceAccount to the deployment
|
||||
d.Spec.Template.Spec.ServiceAccountName = `{{ .Values.` + service.Name + `.serviceAccount | quote }}`
|
||||
|
||||
d.AddHealthCheck(service, &container)
|
||||
|
||||
d.Spec.Template.Spec.Containers = append(d.Spec.Template.Spec.Containers, container)
|
||||
}
|
||||
|
||||
func (d *Deployment) AddHealthCheck(service types.ServiceConfig, container *corev1.Container) {
|
||||
// get the label for healthcheck
|
||||
if v, ok := service.Labels[labels.LabelHealthCheck]; ok {
|
||||
probes, err := labelstructs.ProbeFrom(v)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
container.LivenessProbe = probes.LivenessProbe
|
||||
container.ReadinessProbe = probes.ReadinessProbe
|
||||
return
|
||||
}
|
||||
|
||||
if service.HealthCheck != nil {
|
||||
period := 30.0
|
||||
if service.HealthCheck.Interval != nil {
|
||||
period = time.Duration(*service.HealthCheck.Interval).Seconds()
|
||||
}
|
||||
container.LivenessProbe = &corev1.Probe{
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
Exec: &corev1.ExecAction{
|
||||
Command: service.HealthCheck.Test[1:],
|
||||
},
|
||||
},
|
||||
PeriodSeconds: int32(period),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddIngress adds an ingress to the deployment. It creates the ingress object.
|
||||
func (d *Deployment) AddIngress(service types.ServiceConfig, appName string) *Ingress {
|
||||
return NewIngress(service, d.chart)
|
||||
}
|
||||
|
||||
// AddVolumes adds a volume to the deployment. It does not create the PVC, it only adds the volumes to the deployment.
|
||||
// If the volume is a bind volume it will warn the user that it is not supported yet.
|
||||
func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string) {
|
||||
tobind := map[string]bool{}
|
||||
if v, ok := service.Labels[labels.LabelConfigMapFiles]; ok {
|
||||
binds, err := labelstructs.ConfigMapFileFrom(v)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, bind := range binds {
|
||||
tobind[bind] = true
|
||||
}
|
||||
}
|
||||
|
||||
isSamePod := false
|
||||
if v, ok := service.Labels[labels.LabelSamePod]; !ok {
|
||||
isSamePod = false
|
||||
} else {
|
||||
isSamePod = v != ""
|
||||
}
|
||||
|
||||
for _, volume := range service.Volumes {
|
||||
d.bindVolumes(volume, isSamePod, tobind, service, appName)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Deployment) AddLegacyVolume(name, kind string) {
|
||||
// ensure the volume is not present
|
||||
for _, v := range d.Spec.Template.Spec.Volumes {
|
||||
if v.Name == name {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// init
|
||||
if d.Spec.Template.Spec.Volumes == nil {
|
||||
d.Spec.Template.Spec.Volumes = []corev1.Volume{}
|
||||
}
|
||||
|
||||
d.Spec.Template.Spec.Volumes = append(d.Spec.Template.Spec.Volumes, corev1.Volume{
|
||||
Name: name,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Deployment) BindFrom(service types.ServiceConfig, binded *Deployment) {
|
||||
// find the volume in the binded deployment
|
||||
for _, bindedVolume := range binded.Spec.Template.Spec.Volumes {
|
||||
skip := false
|
||||
for _, targetVol := range d.Spec.Template.Spec.Volumes {
|
||||
if targetVol.Name == bindedVolume.Name {
|
||||
skip = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !skip {
|
||||
// add the volume to the current deployment
|
||||
d.Spec.Template.Spec.Volumes = append(d.Spec.Template.Spec.Volumes, bindedVolume)
|
||||
// get the container
|
||||
}
|
||||
// add volume mount to the container
|
||||
targetContainer, ti := utils.GetContainerByName(service.ContainerName, d.Spec.Template.Spec.Containers)
|
||||
sourceContainer, _ := utils.GetContainerByName(service.ContainerName, binded.Spec.Template.Spec.Containers)
|
||||
for _, bindedMount := range sourceContainer.VolumeMounts {
|
||||
if bindedMount.Name == bindedVolume.Name {
|
||||
targetContainer.VolumeMounts = append(targetContainer.VolumeMounts, bindedMount)
|
||||
}
|
||||
}
|
||||
d.Spec.Template.Spec.Containers[ti] = *targetContainer
|
||||
}
|
||||
}
|
||||
|
||||
// DependsOn adds a initContainer to the deployment that will wait for the service to be up.
|
||||
func (d *Deployment) DependsOn(to *Deployment, servicename string) error {
|
||||
// Add a initContainer with busybox:latest using netcat to check if the service is up
|
||||
// it will wait until the service responds to all ports
|
||||
for _, container := range to.Spec.Template.Spec.Containers {
|
||||
commands := []string{}
|
||||
if len(container.Ports) == 0 {
|
||||
utils.Warn("No ports found for service ",
|
||||
servicename,
|
||||
". You should declare a port in the service or use "+
|
||||
labels.LabelPorts+
|
||||
" label.",
|
||||
)
|
||||
os.Exit(1)
|
||||
}
|
||||
for _, port := range container.Ports {
|
||||
command := fmt.Sprintf("until nc -z %s %d; do\n sleep 1;\ndone", to.Name, port.ContainerPort)
|
||||
commands = append(commands, command)
|
||||
}
|
||||
|
||||
command := []string{"/bin/sh", "-c", strings.Join(commands, "\n")}
|
||||
d.Spec.Template.Spec.InitContainers = append(d.Spec.Template.Spec.InitContainers, corev1.Container{
|
||||
Name: "wait-for-" + to.service.Name,
|
||||
Image: "busybox:latest",
|
||||
Command: command,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Filename returns the filename of the deployment.
|
||||
func (d *Deployment) Filename() string {
|
||||
return d.service.Name + ".deployment.yaml"
|
||||
}
|
||||
|
||||
// SetEnvFrom sets the environment variables to a configmap. The configmap is created.
|
||||
func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string, samePod ...bool) {
|
||||
if len(service.Environment) == 0 {
|
||||
return
|
||||
}
|
||||
inSamePod := len(samePod) > 0 && samePod[0]
|
||||
|
||||
drop := []string{}
|
||||
secrets := []string{}
|
||||
|
||||
defer func() {
|
||||
c, index := d.BindMapFilesToContainer(service, secrets, appName)
|
||||
if c == nil || index == -1 {
|
||||
log.Println("Container not found for service ", service.Name)
|
||||
return
|
||||
}
|
||||
d.Spec.Template.Spec.Containers[index] = *c
|
||||
}()
|
||||
|
||||
// secrets from label
|
||||
labelSecrets, err := labelstructs.SecretsFrom(service.Labels[labels.LabelSecrets])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// values from label
|
||||
varDescriptons := utils.GetValuesFromLabel(service, labels.LabelValues)
|
||||
labelValues := []string{}
|
||||
for v := range varDescriptons {
|
||||
labelValues = append(labelValues, v)
|
||||
}
|
||||
|
||||
for _, secret := range labelSecrets {
|
||||
// get the secret name
|
||||
_, ok := service.Environment[secret]
|
||||
if !ok {
|
||||
drop = append(drop, secret)
|
||||
utils.Warn("Secret " + secret + " not found in service " + service.Name + " - skpped")
|
||||
continue
|
||||
}
|
||||
secrets = append(secrets, secret)
|
||||
}
|
||||
|
||||
if inSamePod {
|
||||
return
|
||||
}
|
||||
|
||||
// for each values from label "values", add it to Values map and change the envFrom
|
||||
// value to {{ .Values.<service>.<value> }}
|
||||
for _, value := range labelValues {
|
||||
// get the environment variable name
|
||||
val, ok := service.Environment[value]
|
||||
if !ok {
|
||||
drop = append(drop, value)
|
||||
utils.Warn("Environment variable " + value + " not found in service " + service.Name + " - skpped")
|
||||
continue
|
||||
}
|
||||
if d.chart.Values[service.Name].(*Value).Environment == nil {
|
||||
d.chart.Values[service.Name].(*Value).Environment = make(map[string]any)
|
||||
}
|
||||
d.chart.Values[service.Name].(*Value).Environment[value] = *val
|
||||
// set the environment variable to bind to the values.yaml file
|
||||
v := utils.TplValue(service.Name, "environment."+value)
|
||||
service.Environment[value] = &v
|
||||
}
|
||||
|
||||
for _, value := range drop {
|
||||
delete(service.Environment, value)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Deployment) BindMapFilesToContainer(service types.ServiceConfig, secrets []string, appName string) (*corev1.Container, int) {
|
||||
fromSources := []corev1.EnvFromSource{}
|
||||
|
||||
envSize := len(service.Environment)
|
||||
|
||||
for _, secret := range secrets {
|
||||
for k := range service.Environment {
|
||||
if k == secret {
|
||||
envSize--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if envSize > 0 {
|
||||
if service.Name == "db" {
|
||||
log.Println("Service ", service.Name, " has environment variables")
|
||||
log.Println(service.Environment)
|
||||
}
|
||||
fromSources = append(fromSources, corev1.EnvFromSource{
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: utils.TplName(service.Name, appName),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if len(secrets) > 0 {
|
||||
fromSources = append(fromSources, corev1.EnvFromSource{
|
||||
SecretRef: &corev1.SecretEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: utils.TplName(service.Name, appName),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
container, index := utils.GetContainerByName(service.ContainerName, d.Spec.Template.Spec.Containers)
|
||||
if container == nil {
|
||||
utils.Warn("Container not found for service " + service.Name)
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
container.EnvFrom = append(container.EnvFrom, fromSources...)
|
||||
|
||||
if container.Env == nil {
|
||||
container.Env = []corev1.EnvVar{}
|
||||
}
|
||||
return container, index
|
||||
}
|
||||
|
||||
func (d *Deployment) MountExchangeVolumes() {
|
||||
for name, ex := range d.exchangesVolumes {
|
||||
for i, c := range d.Spec.Template.Spec.Containers {
|
||||
c.VolumeMounts = append(c.VolumeMounts, corev1.VolumeMount{
|
||||
Name: "exchange-" + ex.Name,
|
||||
MountPath: ex.MountPath,
|
||||
})
|
||||
if len(ex.Init) > 0 && name == c.Name {
|
||||
d.Spec.Template.Spec.InitContainers = append(d.Spec.Template.Spec.InitContainers, corev1.Container{
|
||||
Command: []string{"/bin/sh", "-c", ex.Init},
|
||||
Image: c.Image,
|
||||
Name: "exhange-init-" + name,
|
||||
VolumeMounts: []corev1.VolumeMount{{
|
||||
Name: "exchange-" + ex.Name,
|
||||
MountPath: ex.MountPath,
|
||||
}},
|
||||
})
|
||||
}
|
||||
d.Spec.Template.Spec.Containers[i] = c
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Yaml returns the yaml representation of the deployment.
|
||||
func (d *Deployment) Yaml() ([]byte, error) {
|
||||
var y []byte
|
||||
var err error
|
||||
serviceName := d.service.Name
|
||||
|
||||
if y, err = ToK8SYaml(d); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// for each volume mount, add a condition "if values has persistence"
|
||||
changing := false
|
||||
content := strings.Split(string(y), "\n")
|
||||
spaces := ""
|
||||
volumeName := ""
|
||||
|
||||
nameDirective := "name: "
|
||||
|
||||
// this loop add condition for each volume mount
|
||||
for line, volume := range content {
|
||||
// find the volume name
|
||||
for i := line; i < len(content); i++ {
|
||||
if strings.Contains(content[i], nameDirective) {
|
||||
volumeName = strings.TrimSpace(strings.Replace(content[i], "name: ", "", 1))
|
||||
break
|
||||
}
|
||||
}
|
||||
if volumeName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := d.configMaps[volumeName]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(volume, "mountPath: ") {
|
||||
spaces = strings.Repeat(" ", utils.CountStartingSpaces(volume))
|
||||
varName, ok := d.volumeMap[volumeName]
|
||||
if !ok {
|
||||
// this case happens when the volume is a "bind" volume comming from a "same-pod" service.
|
||||
continue
|
||||
}
|
||||
varName = strings.ReplaceAll(varName, "-", "_")
|
||||
content[line] = spaces + `{{- if .Values.` + serviceName + `.persistence.` + varName + `.enabled }}` + "\n" + volume
|
||||
changing = true
|
||||
}
|
||||
if strings.Contains(volume, nameDirective) && changing {
|
||||
content[line] = volume + "\n" + spaces + "{{- end }}"
|
||||
changing = false
|
||||
}
|
||||
}
|
||||
|
||||
changing = false
|
||||
inVolumes := false
|
||||
volumeName = ""
|
||||
// this loop changes imagePullPolicy to {{ .Values.<service>.imagePullPolicy }}
|
||||
// and the volume definition adding the condition "if values has persistence"
|
||||
for i, line := range content {
|
||||
|
||||
if strings.Contains(line, "imagePullPolicy:") {
|
||||
spaces = strings.Repeat(" ", utils.CountStartingSpaces(line))
|
||||
content[i] = spaces + "imagePullPolicy: {{ .Values." + serviceName + ".imagePullPolicy }}"
|
||||
}
|
||||
|
||||
// find the volume name
|
||||
for i := i; i < len(content); i++ {
|
||||
if strings.Contains(content[i], "- name: ") {
|
||||
volumeName = strings.TrimSpace(strings.Replace(content[i], "- name: ", "", 1))
|
||||
break
|
||||
}
|
||||
}
|
||||
if strings.Contains(line, "volumes:") {
|
||||
inVolumes = true
|
||||
}
|
||||
|
||||
if volumeName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := d.configMaps[volumeName]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(line, "- name: ") && inVolumes {
|
||||
spaces = strings.Repeat(" ", utils.CountStartingSpaces(line))
|
||||
varName := d.volumeMap[volumeName]
|
||||
varName = strings.ReplaceAll(varName, "-", "_")
|
||||
content[i] = spaces + `{{- if .Values.` + serviceName + `.persistence.` + varName + `.enabled }}` + "\n" + line
|
||||
changing = true
|
||||
}
|
||||
if strings.Contains(line, "claimName: ") && changing {
|
||||
content[i] = line + "\n" + spaces + "{{- end }}"
|
||||
changing = false
|
||||
}
|
||||
}
|
||||
|
||||
// for impagePullSecrets, replace the name with the value from values.yaml
|
||||
for i, line := range content {
|
||||
if strings.Contains(line, "imagePullSecrets:") {
|
||||
spaces = strings.Repeat(" ", utils.CountStartingSpaces(line))
|
||||
line = spaces + "{{- if .Values.pullSecrets }}"
|
||||
line += "\n" + spaces + "imagePullSecrets:\n"
|
||||
line += spaces + "{{- .Values.pullSecrets | toYaml | nindent __indent__ }}"
|
||||
line += "\n" + spaces + "{{- end }}"
|
||||
content[i] = line
|
||||
}
|
||||
}
|
||||
|
||||
// Find the replicas line and replace it with the value from values.yaml
|
||||
for i, line := range content {
|
||||
// manage nodeSelector
|
||||
if strings.Contains(line, "nodeSelector:") {
|
||||
spaces = strings.Repeat(" ", utils.CountStartingSpaces(line))
|
||||
pre := spaces + `{{- if .Values.` + serviceName + `.nodeSelector }}`
|
||||
post := spaces + "{{- end }}"
|
||||
ns := spaces + "nodeSelector:\n"
|
||||
ns += spaces + ` {{- .Values.` + serviceName + `.nodeSelector | toYaml | nindent __indent__ }}`
|
||||
line = pre + "\n" + ns + "\n" + post
|
||||
}
|
||||
// manage replicas
|
||||
if strings.Contains(line, "replicas:") {
|
||||
line = regexp.MustCompile("replicas: .*$").ReplaceAllString(line, "replicas: {{ .Values."+serviceName+".replicas }}")
|
||||
}
|
||||
|
||||
// manage serviceAccount, add condition to use the serviceAccount from values.yaml
|
||||
if strings.Contains(line, "serviceAccountName:") {
|
||||
spaces = strings.Repeat(" ", utils.CountStartingSpaces(line))
|
||||
pre := spaces + `{{- if ne .Values.` + serviceName + `.serviceAccount "" }}`
|
||||
post := spaces + "{{- end }}"
|
||||
line = strings.ReplaceAll(line, "'", "")
|
||||
line = pre + "\n" + line + "\n" + post
|
||||
}
|
||||
|
||||
if strings.Contains(line, "resources: {}") {
|
||||
spaces = strings.Repeat(" ", utils.CountStartingSpaces(line))
|
||||
pre := spaces + `{{- if .Values.` + serviceName + `.resources }}`
|
||||
post := spaces + "{{- end }}"
|
||||
|
||||
line = strings.ReplaceAll(line, "resources: {}", "resources:")
|
||||
line += "\n" + spaces + " {{ .Values." + serviceName + ".resources | toYaml | nindent __indent__ }}"
|
||||
line = pre + "\n" + line + "\n" + post
|
||||
}
|
||||
|
||||
content[i] = line
|
||||
}
|
||||
|
||||
// find the katenary.v3/node-selector line, and remove it
|
||||
for i, line := range content {
|
||||
if strings.Contains(line, labels.LabelName("node-selector")) {
|
||||
content = append(content[:i], content[i+1:]...)
|
||||
continue
|
||||
}
|
||||
if strings.Contains(line, "- name: '{{ .Values.pullSecrets ") {
|
||||
content = append(content[:i], content[i+1:]...)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return []byte(strings.Join(content, "\n")), nil
|
||||
}
|
||||
|
||||
func (d *Deployment) appendDirectoryToConfigMap(service types.ServiceConfig, appName string, volume types.ServiceVolumeConfig) {
|
||||
pathnme := utils.PathToName(volume.Source)
|
||||
if _, ok := d.configMaps[pathnme]; !ok {
|
||||
d.configMaps[pathnme] = &ConfigMapMount{
|
||||
mountPath: []mountPathConfig{},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make it recursive to add all files in the directory and subdirectories
|
||||
_, err := os.ReadDir(volume.Source)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
cm := NewConfigMapFromDirectory(service, appName, volume.Source)
|
||||
d.configMaps[pathnme] = &ConfigMapMount{
|
||||
configMap: cm,
|
||||
mountPath: append(d.configMaps[pathnme].mountPath, mountPathConfig{
|
||||
mountPath: volume.Target,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Deployment) appendFileToConfigMap(service types.ServiceConfig, appName string, volume types.ServiceVolumeConfig) {
|
||||
// In case of a file, add it to the configmap and use "subPath" to mount it
|
||||
// Note that the volumes and volume mounts are not added to the deployment yet, they will be added later
|
||||
// in generate.go
|
||||
dirname := filepath.Dir(volume.Source)
|
||||
pathname := utils.PathToName(dirname)
|
||||
pathname = strings.TrimSpace(pathname)
|
||||
if len(pathname) != 0 {
|
||||
pathname += "-" + pathname
|
||||
}
|
||||
var cm *ConfigMap
|
||||
if v, ok := d.configMaps[pathname]; !ok {
|
||||
cm = NewConfigMap(*d.service, appName, true)
|
||||
cm.usage = FileMapUsageFiles
|
||||
cm.path = dirname
|
||||
cm.Name = utils.TplName(service.Name, appName) + pathname
|
||||
d.configMaps[pathname] = &ConfigMapMount{
|
||||
configMap: cm,
|
||||
mountPath: []mountPathConfig{{
|
||||
mountPath: volume.Target,
|
||||
subPath: filepath.Base(volume.Source),
|
||||
}},
|
||||
}
|
||||
} else {
|
||||
cm = v.configMap
|
||||
mp := d.configMaps[pathname].mountPath
|
||||
mp = append(mp, mountPathConfig{
|
||||
mountPath: volume.Target,
|
||||
subPath: filepath.Base(volume.Source),
|
||||
})
|
||||
d.configMaps[pathname].mountPath = mp
|
||||
|
||||
}
|
||||
if err := cm.AppendFile(volume.Source); err != nil {
|
||||
log.Fatal("Error adding file to configmap:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Deployment) bindVolumes(volume types.ServiceVolumeConfig, isSamePod bool, tobind map[string]bool, service types.ServiceConfig, appName string) {
|
||||
container, index := utils.GetContainerByName(service.ContainerName, d.Spec.Template.Spec.Containers)
|
||||
|
||||
defer func(d *Deployment, container *corev1.Container, index int) {
|
||||
d.Spec.Template.Spec.Containers[index] = *container
|
||||
}(d, container, index)
|
||||
|
||||
if _, found := tobind[volume.Source]; !isSamePod && volume.Type == "bind" && !found {
|
||||
utils.Warn(
|
||||
"Bind volumes are not supported yet, " +
|
||||
"excepting for those declared as " +
|
||||
labels.LabelConfigMapFiles +
|
||||
", skipping volume " + volume.Source +
|
||||
" from service " + service.Name,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if container == nil {
|
||||
utils.Warn("Container not found for volume", volume.Source)
|
||||
return
|
||||
}
|
||||
|
||||
// ensure that the volume is not already present in the container
|
||||
for _, vm := range container.VolumeMounts {
|
||||
if vm.Name == volume.Source {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch volume.Type {
|
||||
case "volume":
|
||||
// Add volume to container
|
||||
fixedName := utils.FixedResourceName(volume.Source)
|
||||
d.volumeMap[fixedName] = volume.Source
|
||||
container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{
|
||||
Name: fixedName,
|
||||
MountPath: volume.Target,
|
||||
})
|
||||
// Add volume to values.yaml only if it the service is not in the same pod that another service.
|
||||
// If it is in the same pod, the volume will be added to the other service later
|
||||
if _, ok := service.Labels[labels.LabelSamePod]; !ok {
|
||||
d.chart.Values[service.Name].(*Value).AddPersistence(volume.Source)
|
||||
}
|
||||
// Add volume to deployment
|
||||
d.Spec.Template.Spec.Volumes = append(d.Spec.Template.Spec.Volumes, corev1.Volume{
|
||||
Name: fixedName,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: utils.TplName(service.Name, appName, volume.Source),
|
||||
},
|
||||
},
|
||||
})
|
||||
case "bind":
|
||||
// Add volume to container
|
||||
stat, err := os.Stat(volume.Source)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if stat.IsDir() {
|
||||
d.appendDirectoryToConfigMap(service, appName, volume)
|
||||
} else {
|
||||
d.appendFileToConfigMap(service, appName, volume)
|
||||
}
|
||||
}
|
||||
}
|
534
internal/generator/deployment_test.go
Normal file
@@ -0,0 +1,534 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"katenary.io/internal/generator/labels"
|
||||
|
||||
yaml3 "gopkg.in/yaml.v3"
|
||||
v1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
const webTemplateOutput = `templates/web/deployment.yaml`
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
web:
|
||||
image: nginx:1.29
|
||||
`
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
|
||||
output := internalCompileTest(t, "-s", webTemplateOutput)
|
||||
|
||||
// dt := DeploymentTest{}
|
||||
dt := v1.Deployment{}
|
||||
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
|
||||
if *dt.Spec.Replicas != 1 {
|
||||
t.Errorf("Expected replicas to be 1, got %d", dt.Spec.Replicas)
|
||||
t.Errorf("Output: %s", output)
|
||||
}
|
||||
|
||||
if dt.Spec.Template.Spec.Containers[0].Image != "nginx:1.29" {
|
||||
t.Errorf("Expected image to be nginx:1.29, got %s", dt.Spec.Template.Spec.Containers[0].Image)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateOneDeploymentWithSamePod(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
web:
|
||||
image: nginx:1.29
|
||||
ports:
|
||||
- 80:80
|
||||
|
||||
fpm:
|
||||
image: php:fpm
|
||||
ports:
|
||||
- 9000:9000
|
||||
labels:
|
||||
katenary.v3/same-pod: web
|
||||
`
|
||||
|
||||
outDir := "./chart"
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
|
||||
output := internalCompileTest(t, "-s", webTemplateOutput)
|
||||
dt := v1.Deployment{}
|
||||
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
|
||||
if len(dt.Spec.Template.Spec.Containers) != 2 {
|
||||
t.Errorf("Expected 2 containers, got %d", len(dt.Spec.Template.Spec.Containers))
|
||||
}
|
||||
// endsure that the fpm service is not created
|
||||
|
||||
var err error
|
||||
_, err = helmTemplate(ConvertOptions{
|
||||
OutputDir: outDir,
|
||||
}, "-s", "templates/fpm/deployment.yaml")
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, got nil")
|
||||
}
|
||||
|
||||
// ensure that the web service is created and has got 2 ports
|
||||
output, err = helmTemplate(ConvertOptions{
|
||||
OutputDir: outDir,
|
||||
}, "-s", "templates/web/service.yaml")
|
||||
if err != nil {
|
||||
t.Errorf("Error: %s", err)
|
||||
}
|
||||
service := corev1.Service{}
|
||||
if err := yaml.Unmarshal([]byte(output), &service); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
|
||||
if len(service.Spec.Ports) != 2 {
|
||||
t.Errorf("Expected 2 ports, got %d", len(service.Spec.Ports))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDependsOn(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
web:
|
||||
image: nginx:1.29
|
||||
ports:
|
||||
- 80:80
|
||||
depends_on:
|
||||
- database
|
||||
|
||||
database:
|
||||
image: mariadb:10.5
|
||||
ports:
|
||||
- 3306:3306
|
||||
`
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
|
||||
output := internalCompileTest(t, "-s", webTemplateOutput)
|
||||
dt := v1.Deployment{}
|
||||
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
|
||||
if len(dt.Spec.Template.Spec.Containers) != 1 {
|
||||
t.Errorf("Expected 1 container, got %d", len(dt.Spec.Template.Spec.Containers))
|
||||
}
|
||||
// find an init container
|
||||
if len(dt.Spec.Template.Spec.InitContainers) != 1 {
|
||||
t.Errorf("Expected 1 init container, got %d", len(dt.Spec.Template.Spec.InitContainers))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelmDependencies(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
web:
|
||||
image: nginx:1.29
|
||||
ports:
|
||||
- 80:80
|
||||
|
||||
mariadb:
|
||||
image: mariadb:10.5
|
||||
ports:
|
||||
- 3306:3306
|
||||
labels:
|
||||
%s/dependencies: |
|
||||
- name: mariadb
|
||||
repository: oci://registry-1.docker.io/bitnamicharts
|
||||
version: 18.x.X
|
||||
|
||||
`
|
||||
composeFile = fmt.Sprintf(composeFile, labels.Prefix())
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
|
||||
output := internalCompileTest(t, "-s", webTemplateOutput)
|
||||
dt := v1.Deployment{}
|
||||
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
|
||||
// ensure that there is no mariasb deployment
|
||||
_, err := helmTemplate(ConvertOptions{
|
||||
OutputDir: "./chart",
|
||||
}, "-s", "templates/mariadb/deployment.yaml")
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, got nil")
|
||||
}
|
||||
|
||||
// check that Chart.yaml has the dependency
|
||||
chart := HelmChart{}
|
||||
chartFile := "./chart/Chart.yaml"
|
||||
if _, err := os.Stat(chartFile); os.IsNotExist(err) {
|
||||
t.Errorf("Chart.yaml does not exist")
|
||||
}
|
||||
chartContent, err := os.ReadFile(chartFile)
|
||||
if err != nil {
|
||||
t.Errorf("Error reading Chart.yaml: %s", err)
|
||||
}
|
||||
if err := yaml.Unmarshal(chartContent, &chart); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
|
||||
if len(chart.Dependencies) != 1 {
|
||||
t.Errorf("Expected 1 dependency, got %d", len(chart.Dependencies))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLivenessProbesFromHealthCheck(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
web:
|
||||
image: nginx:1.29
|
||||
ports:
|
||||
- 80:80
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
`
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
|
||||
output := internalCompileTest(t, "-s", webTemplateOutput)
|
||||
dt := v1.Deployment{}
|
||||
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
|
||||
if dt.Spec.Template.Spec.Containers[0].LivenessProbe == nil {
|
||||
t.Errorf("Expected liveness probe to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProbesFromLabels(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
web:
|
||||
image: nginx:1.29
|
||||
ports:
|
||||
- 80:80
|
||||
labels:
|
||||
%s/health-check: |
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 80
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: 80
|
||||
`
|
||||
composeFile = fmt.Sprintf(composeFile, labels.Prefix())
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
|
||||
output := internalCompileTest(t, "-s", webTemplateOutput)
|
||||
dt := v1.Deployment{}
|
||||
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
|
||||
if dt.Spec.Template.Spec.Containers[0].LivenessProbe == nil {
|
||||
t.Errorf("Expected liveness probe to be set")
|
||||
}
|
||||
if dt.Spec.Template.Spec.Containers[0].ReadinessProbe == nil {
|
||||
t.Errorf("Expected readiness probe to be set")
|
||||
}
|
||||
t.Logf("LivenessProbe: %+v", dt.Spec.Template.Spec.Containers[0].LivenessProbe)
|
||||
|
||||
// ensure that the liveness probe is set to /healthz
|
||||
if dt.Spec.Template.Spec.Containers[0].LivenessProbe.HTTPGet.Path != "/healthz" {
|
||||
t.Errorf("Expected liveness probe path to be /healthz, got %s", dt.Spec.Template.Spec.Containers[0].LivenessProbe.HTTPGet.Path)
|
||||
}
|
||||
|
||||
// ensure that the readiness probe is set to /ready
|
||||
if dt.Spec.Template.Spec.Containers[0].ReadinessProbe.HTTPGet.Path != "/ready" {
|
||||
t.Errorf("Expected readiness probe path to be /ready, got %s", dt.Spec.Template.Spec.Containers[0].ReadinessProbe.HTTPGet.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetValues(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
web:
|
||||
image: nginx:1.29
|
||||
environment:
|
||||
FOO: bar
|
||||
BAZ: qux
|
||||
labels:
|
||||
%s/values: |
|
||||
- FOO
|
||||
`
|
||||
|
||||
composeFile = fmt.Sprintf(composeFile, labels.Prefix())
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
|
||||
output := internalCompileTest(t, "-s", webTemplateOutput)
|
||||
dt := v1.Deployment{}
|
||||
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
|
||||
// readh the values.yaml, we must have FOO in web environment but not BAZ
|
||||
valuesFile := "./chart/values.yaml"
|
||||
if _, err := os.Stat(valuesFile); os.IsNotExist(err) {
|
||||
t.Errorf("values.yaml does not exist")
|
||||
}
|
||||
valuesContent, err := os.ReadFile(valuesFile)
|
||||
if err != nil {
|
||||
t.Errorf("Error reading values.yaml: %s", err)
|
||||
}
|
||||
mapping := struct {
|
||||
Web struct {
|
||||
Environment map[string]string `yaml:"environment"`
|
||||
} `yaml:"web"`
|
||||
}{}
|
||||
if err := yaml3.Unmarshal(valuesContent, &mapping); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
|
||||
if v, ok := mapping.Web.Environment["FOO"]; !ok {
|
||||
t.Errorf("Expected FOO in web environment")
|
||||
if v != "bar" {
|
||||
t.Errorf("Expected FOO to be bar, got %s", v)
|
||||
}
|
||||
}
|
||||
if v, ok := mapping.Web.Environment["BAZ"]; ok {
|
||||
t.Errorf("Expected BAZ not in web environment")
|
||||
if v != "qux" {
|
||||
t.Errorf("Expected BAZ to be qux, got %s", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithUnderscoreInContainerName(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
web-app:
|
||||
image: nginx:1.29
|
||||
container_name: web_app_container
|
||||
environment:
|
||||
FOO: BAR
|
||||
labels:
|
||||
%s/values: |
|
||||
- FOO
|
||||
`
|
||||
composeFile = fmt.Sprintf(composeFile, labels.Prefix())
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
|
||||
output := internalCompileTest(t, "-s", "templates/web_app/deployment.yaml")
|
||||
dt := v1.Deployment{}
|
||||
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
// find container.name
|
||||
containerName := dt.Spec.Template.Spec.Containers[0].Name
|
||||
if strings.Contains(containerName, "_") {
|
||||
t.Errorf("Expected container name to not contain underscores, got %s", containerName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithDashes(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
web-app:
|
||||
image: nginx:1.29
|
||||
environment:
|
||||
FOO: BAR
|
||||
labels:
|
||||
%s/values: |
|
||||
- FOO
|
||||
`
|
||||
|
||||
composeFile = fmt.Sprintf(composeFile, labels.Prefix())
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
|
||||
output := internalCompileTest(t, "-s", "templates/web_app/deployment.yaml")
|
||||
dt := v1.Deployment{}
|
||||
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
|
||||
valuesFile := "./chart/values.yaml"
|
||||
if _, err := os.Stat(valuesFile); os.IsNotExist(err) {
|
||||
t.Errorf("values.yaml does not exist")
|
||||
}
|
||||
valuesContent, err := os.ReadFile(valuesFile)
|
||||
if err != nil {
|
||||
t.Errorf("Error reading values.yaml: %s", err)
|
||||
}
|
||||
mapping := struct {
|
||||
Web struct {
|
||||
Environment map[string]string `yaml:"environment"`
|
||||
} `yaml:"web_app"`
|
||||
}{}
|
||||
if err := yaml3.Unmarshal(valuesContent, &mapping); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
|
||||
// we must have FOO in web_app environment (not web-app)
|
||||
// this validates that the service name is converted to a valid k8s name
|
||||
if v, ok := mapping.Web.Environment["FOO"]; !ok {
|
||||
t.Errorf("Expected FOO in web_app environment")
|
||||
if v != "BAR" {
|
||||
t.Errorf("Expected FOO to be BAR, got %s", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDashesWithValueFrom(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
web-app:
|
||||
image: nginx:1.29
|
||||
environment:
|
||||
FOO: BAR
|
||||
labels:
|
||||
%[1]s/values: |
|
||||
- FOO
|
||||
web2:
|
||||
image: nginx:1.29
|
||||
labels:
|
||||
%[1]s/values-from: |
|
||||
BAR: web-app.FOO
|
||||
`
|
||||
|
||||
composeFile = fmt.Sprintf(composeFile, labels.Prefix())
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
|
||||
output := internalCompileTest(t, "-s", "templates/web2/deployment.yaml")
|
||||
dt := v1.Deployment{}
|
||||
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
|
||||
valuesFile := "./chart/values.yaml"
|
||||
if _, err := os.Stat(valuesFile); os.IsNotExist(err) {
|
||||
t.Errorf("values.yaml does not exist")
|
||||
}
|
||||
valuesContent, err := os.ReadFile(valuesFile)
|
||||
if err != nil {
|
||||
t.Errorf("Error reading values.yaml: %s", err)
|
||||
}
|
||||
mapping := struct {
|
||||
Web struct {
|
||||
Environment map[string]string `yaml:"environment"`
|
||||
} `yaml:"web_app"`
|
||||
}{}
|
||||
if err := yaml3.Unmarshal(valuesContent, &mapping); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
|
||||
// we must have FOO in web_app environment (not web-app)
|
||||
// this validates that the service name is converted to a valid k8s name
|
||||
if v, ok := mapping.Web.Environment["FOO"]; !ok {
|
||||
t.Errorf("Expected FOO in web_app environment")
|
||||
if v != "BAR" {
|
||||
t.Errorf("Expected FOO to be BAR, got %s", v)
|
||||
}
|
||||
}
|
||||
|
||||
// ensure that the deployment has the value from the other service
|
||||
barenv := dt.Spec.Template.Spec.Containers[0].Env[0]
|
||||
if barenv.Value != "" {
|
||||
t.Errorf("Expected value to be empty")
|
||||
}
|
||||
if barenv.ValueFrom == nil {
|
||||
t.Errorf("Expected valueFrom to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCommand(t *testing.T) {
|
||||
composeFile := `
|
||||
services:
|
||||
web-app:
|
||||
image: nginx:1.29
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |-
|
||||
echo "Hello, World!"
|
||||
echo "Done"
|
||||
`
|
||||
|
||||
// composeFile = fmt.Sprintf(composeFile, labels.Prefix())
|
||||
tmpDir := setup(composeFile)
|
||||
defer teardown(tmpDir)
|
||||
|
||||
currentDir, _ := os.Getwd()
|
||||
os.Chdir(tmpDir)
|
||||
defer os.Chdir(currentDir)
|
||||
|
||||
output := internalCompileTest(t, "-s", "templates/web_app/deployment.yaml")
|
||||
dt := v1.Deployment{}
|
||||
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
|
||||
t.Errorf(unmarshalError, err)
|
||||
}
|
||||
// find the command in the container
|
||||
command := dt.Spec.Template.Spec.Containers[0].Command
|
||||
if len(command) != 3 {
|
||||
t.Errorf("Expected command to have 3 elements, got %d", len(command))
|
||||
}
|
||||
if command[0] != "sh" || command[1] != "-c" {
|
||||
t.Errorf("Expected command to be 'sh -c', got %s", strings.Join(command, " "))
|
||||
}
|
||||
}
|
17
internal/generator/doc.go
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
Package generator generates kubernetes objects from a "compose" file and transforms them into a helm chart.
|
||||
|
||||
The generator package is the core of katenary. It is responsible for generating kubernetes objects from a compose file
|
||||
and transforming them into a helm chart.
|
||||
Conversion manipulates Yaml representation of kubernetes object to add conditions, labels, annotations, etc. to the
|
||||
objects. It also create the values to be set to the values.yaml file.
|
||||
|
||||
The generate.Convert() create an HelmChart object and call "Generate()" method to convert from a compose file to a helm
|
||||
chart. It saves the helm chart in the given directory.
|
||||
|
||||
If you want to change or override the write behavior, you can use the HelmChart.Generate() function and implement your
|
||||
own write function. This function returns
|
||||
the helm chart object containing all kubernetes objects and helm chart ingormation. It does not write the helm chart to
|
||||
the disk.
|
||||
*/
|
||||
package generator
|