Fix problems and adding functionnalities
Many fixes and enhancements: - Add icon option - Add env file managment - Ordering compose parsing options - Fix path with underscores - Fix image and tag discovery - Better documentation for labels
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -18,3 +18,7 @@ configs/
|
|||||||
cover*
|
cover*
|
||||||
.sq
|
.sq
|
||||||
./katenary
|
./katenary
|
||||||
|
.aider*
|
||||||
|
.python_history
|
||||||
|
.bash_history
|
||||||
|
katenary
|
||||||
|
11
Makefile
11
Makefile
@@ -23,6 +23,7 @@ BINARIES=dist/katenary-linux-amd64 dist/katenary-linux-arm64 dist/katenary.exe d
|
|||||||
ASC_BINARIES=$(patsubst %,%.asc,$(BINARIES))
|
ASC_BINARIES=$(patsubst %,%.asc,$(BINARIES))
|
||||||
|
|
||||||
# defaults
|
# defaults
|
||||||
|
BROWSER=$(shell command -v epiphany || echo xdg-open)
|
||||||
SHELL := bash
|
SHELL := bash
|
||||||
# strict mode
|
# strict mode
|
||||||
.SHELLFLAGS := -eu -o pipefail -c
|
.SHELLFLAGS := -eu -o pipefail -c
|
||||||
@@ -35,6 +36,7 @@ MAKEFLAGS += --no-builtin-rules
|
|||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@cat <<EOF | fold -s -w 80
|
@cat <<EOF | fold -s -w 80
|
||||||
=== HELP ===
|
=== HELP ===
|
||||||
@@ -166,7 +168,14 @@ serve-doc: __label_doc
|
|||||||
tests: test
|
tests: test
|
||||||
test:
|
test:
|
||||||
@echo -e "\033[1;33mTesting katenary $(VERSION)...\033[0m"
|
@echo -e "\033[1;33mTesting katenary $(VERSION)...\033[0m"
|
||||||
go test -v ./...
|
go test -coverprofile=cover.out ./...
|
||||||
|
go tool cover -func=cover.out | grep "total:"
|
||||||
|
go tool cover -html=cover.out -o cover.html
|
||||||
|
if [ "$(BROWSER)" = "xdg-open" ]; then
|
||||||
|
xdg-open cover.html
|
||||||
|
else
|
||||||
|
$(BROWSER) -i --new-window cover.html
|
||||||
|
fi
|
||||||
|
|
||||||
push-release: build-all
|
push-release: build-all
|
||||||
@rm -f release.id
|
@rm -f release.id
|
||||||
|
44
README.md
44
README.md
@@ -8,10 +8,10 @@
|
|||||||
|
|
||||||
🚀 Unleash Productivity with Katenary! 🚀
|
🚀 Unleash Productivity with Katenary! 🚀
|
||||||
|
|
||||||
Tired of manual conversions? Katenary harnesses the labels from your "compose" file to craft complete Helm Charts
|
Tired of manual conversions? Katenary harnesses the labels from your "`compose`" file to craft complete Helm Charts
|
||||||
effortlessly, saving you time and energy.
|
effortlessly, saving you time and energy.
|
||||||
|
|
||||||
🛠️ Simple autmated CLI: Katenary handles the grunt work, generating everything needed for seamless service binding
|
🛠️ Simple automated CLI: Katenary handles the grunt work, generating everything needed for seamless service binding
|
||||||
and Helm Chart creation.
|
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.
|
💡 Effortless Efficiency: You only need to add labels when it's necessary to precise things. Then call `katenary convert` and let the magic happen.
|
||||||
@@ -24,9 +24,8 @@ Katenary is a tool to help to transform `docker-compose` files to a working Helm
|
|||||||
> doesn't propose as many features as what can do Kubernetes. So, we strongly recommend to use Katenary as a "bootstrap"
|
> 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.
|
> tool and then to manually enhance the generated helm chart.
|
||||||
|
|
||||||
|
Today, it's partially developed in collaboration with [Klee Group](https://www.kleegroup.com). Note that Katenary is
|
||||||
Today, it's partially developped 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
|
||||||
and **will stay an opensource and free (as freedom) project**. We are convinced that the best way to make it better is to
|
|
||||||
share it with the community.
|
share it with the community.
|
||||||
|
|
||||||
The main developer is [Patrice FERLET](https://github.com/metal3d).
|
The main developer is [Patrice FERLET](https://github.com/metal3d).
|
||||||
@@ -45,7 +44,7 @@ You can use this commands on Linux:
|
|||||||
sh <(curl -sSL https://raw.githubusercontent.com/metal3d/katenary/master/install.sh)
|
sh <(curl -sSL https://raw.githubusercontent.com/metal3d/katenary/master/install.sh)
|
||||||
```
|
```
|
||||||
|
|
||||||
# Else... Build yourself
|
# Or, build yourself
|
||||||
|
|
||||||
If you've got `podman` or `docker`, you can build `katenary` by using:
|
If you've got `podman` or `docker`, you can build `katenary` by using:
|
||||||
|
|
||||||
@@ -54,6 +53,7 @@ make build
|
|||||||
```
|
```
|
||||||
|
|
||||||
You can then install it with:
|
You can then install it with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make install
|
make install
|
||||||
```
|
```
|
||||||
@@ -76,13 +76,12 @@ make build GO=local GOOS=linux GOARCH=arm64
|
|||||||
|
|
||||||
Then place the `katenary` binary file inside your PATH.
|
Then place the `katenary` binary file inside your PATH.
|
||||||
|
|
||||||
|
|
||||||
# Tips
|
# Tips
|
||||||
|
|
||||||
We strongly recommand to add the "completion" call to you SHELL using the common bashrc, or whatever the profile file
|
We strongly recommend adding the completion call to you SHELL using the common `bashrc`, or whatever the profile file
|
||||||
you use.
|
you use.
|
||||||
|
|
||||||
E.g.:
|
E.g.,
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# bash in ~/.bashrc file
|
# bash in ~/.bashrc file
|
||||||
@@ -102,7 +101,7 @@ katenary completion fish | source
|
|||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
```
|
```text
|
||||||
Katenary is a tool to convert compose files to Helm Charts.
|
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.
|
Each [command] and subcommand has got an "help" and "--help" flag to show more information.
|
||||||
@@ -134,22 +133,11 @@ Use "katenary [command] --help" for more information about a command.
|
|||||||
|
|
||||||
It creates a subdirectory inside `chart` that is named with the `appname` option (default is `MyApp`)
|
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
|
> 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
|
> 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.
|
> Katenary to build a correct helm chart.
|
||||||
|
|
||||||
What can be interpreted by Katenary:
|
Example of a possible `docker-compose.yaml` file:
|
||||||
|
|
||||||
- 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 on service (using the first port)
|
|
||||||
- `env_file` list will create a configMap object per environemnt file (⚠ to-do: the "to-service" label doesn't work with
|
|
||||||
configMap for now)
|
|
||||||
- some labels can help to bind values, see examples below
|
|
||||||
|
|
||||||
Exemple of a possible `docker-compose.yaml` file:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
@@ -196,9 +184,9 @@ services:
|
|||||||
|
|
||||||
# Labels
|
# Labels
|
||||||
|
|
||||||
These labels could be found by `katenary help-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:
|
||||||
|
|
||||||
```
|
```text
|
||||||
To get more information about a label, use `katenary help-label <name_without_prefix>
|
To get more information about a label, use `katenary help-label <name_without_prefix>
|
||||||
e.g. katenary help-label dependencies
|
e.g. katenary help-label dependencies
|
||||||
|
|
||||||
@@ -218,11 +206,11 @@ katenary.v3/secrets: list of string Env vars to be set as secrets.
|
|||||||
katenary.v3/values: list of string or map Environment variables to be added to the values.yaml
|
katenary.v3/values: list of string or map Environment variables to be added to the values.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
# What a name...
|
# What a name…
|
||||||
|
|
||||||
Katenary is the stylized name of the project that comes from the "catenary" word.
|
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
|
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.
|
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 "streched link from docker-compose to helm chart".
|
This curved link represents what we try to do, the project is a stretched link from docker-compose to helm chart.
|
||||||
|
@@ -6,14 +6,13 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"katenary/generator"
|
||||||
|
"katenary/utils"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/cli"
|
"github.com/compose-spec/compose-go/cli"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"katenary/generator"
|
|
||||||
"katenary/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const longHelp = `Katenary is a tool to convert compose files to Helm Charts.
|
const longHelp = `Katenary is a tool to convert compose files to Helm Charts.
|
||||||
@@ -133,6 +132,8 @@ func generateConvertCommand() *cobra.Command {
|
|||||||
var appVersion *string
|
var appVersion *string
|
||||||
givenAppVersion := ""
|
givenAppVersion := ""
|
||||||
chartVersion := "0.1.0"
|
chartVersion := "0.1.0"
|
||||||
|
icon := ""
|
||||||
|
envFiles := []string{}
|
||||||
|
|
||||||
convertCmd := &cobra.Command{
|
convertCmd := &cobra.Command{
|
||||||
Use: "convert",
|
Use: "convert",
|
||||||
@@ -148,17 +149,79 @@ func generateConvertCommand() *cobra.Command {
|
|||||||
HelmUpdate: helmdepUpdate,
|
HelmUpdate: helmdepUpdate,
|
||||||
AppVersion: appVersion,
|
AppVersion: appVersion,
|
||||||
ChartVersion: chartVersion,
|
ChartVersion: chartVersion,
|
||||||
|
Icon: icon,
|
||||||
|
EnvFiles: envFiles,
|
||||||
}, dockerComposeFile...)
|
}, dockerComposeFile...)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
convertCmd.Flags().BoolVarP(&force, "force", "f", force, "Force the overwrite of the chart directory")
|
convertCmd.Flags().BoolVarP(
|
||||||
convertCmd.Flags().BoolVarP(&helmdepUpdate, "helm-update", "u", helmdepUpdate, "Update helm dependencies if helm is installed")
|
&force,
|
||||||
convertCmd.Flags().StringSliceVarP(&profiles, "profile", "p", profiles, "Specify the profiles to use")
|
"force",
|
||||||
convertCmd.Flags().StringVarP(&outputDir, "output-dir", "o", outputDir, "Specify the output directory")
|
"f",
|
||||||
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.\nNote that overides files are also used whatever the files you specify here.\nThe overides files are:\n"+strings.Join(cli.DefaultOverrideFileNames, ", \n")+"\n")
|
force,
|
||||||
convertCmd.Flags().StringVarP(&givenAppVersion, "app-version", "a", "", "Specify the app version (in Chart.yaml)")
|
"Force the overwrite of the chart directory",
|
||||||
convertCmd.Flags().StringVarP(&chartVersion, "chart-version", "v", chartVersion, "Specify the chart version (in Chart.yaml)")
|
)
|
||||||
|
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
|
return convertCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,27 +2,16 @@ package generator
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"katenary/generator/labelStructs"
|
||||||
|
"katenary/utils"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
|
|
||||||
"katenary/generator/labelStructs"
|
|
||||||
"katenary/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConvertOptions are the options to convert a compose project to a helm chart.
|
|
||||||
type ConvertOptions struct {
|
|
||||||
AppVersion *string
|
|
||||||
OutputDir string
|
|
||||||
ChartVersion string
|
|
||||||
Profiles []string
|
|
||||||
Force bool
|
|
||||||
HelmUpdate bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChartTemplate is a template of a chart. It contains the content of the template and the name of the service.
|
// 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.
|
// This is used internally to generate the templates.
|
||||||
type ChartTemplate struct {
|
type ChartTemplate struct {
|
||||||
@@ -30,6 +19,18 @@ type ChartTemplate struct {
|
|||||||
Content []byte
|
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
|
||||||
|
Force bool
|
||||||
|
HelmUpdate bool
|
||||||
|
EnvFiles []string
|
||||||
|
}
|
||||||
|
|
||||||
// HelmChart is a Helm Chart representation. It contains all the
|
// HelmChart is a Helm Chart representation. It contains all the
|
||||||
// tempaltes, values, versions, helpers...
|
// tempaltes, values, versions, helpers...
|
||||||
type HelmChart struct {
|
type HelmChart struct {
|
||||||
@@ -38,6 +39,7 @@ type HelmChart struct {
|
|||||||
VolumeMounts map[string]any `yaml:"-"`
|
VolumeMounts map[string]any `yaml:"-"`
|
||||||
composeHash *string `yaml:"-"`
|
composeHash *string `yaml:"-"`
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
|
Icon string `yaml:"icon,omitempty"`
|
||||||
ApiVersion string `yaml:"apiVersion"`
|
ApiVersion string `yaml:"apiVersion"`
|
||||||
Version string `yaml:"version"`
|
Version string `yaml:"version"`
|
||||||
AppVersion string `yaml:"appVersion"`
|
AppVersion string `yaml:"appVersion"`
|
||||||
@@ -67,7 +69,7 @@ func (chart *HelmChart) SaveTemplates(templateDir string) {
|
|||||||
t := template.Content
|
t := template.Content
|
||||||
t = removeNewlinesInsideBrackets(t)
|
t = removeNewlinesInsideBrackets(t)
|
||||||
t = removeUnwantedLines(t)
|
t = removeUnwantedLines(t)
|
||||||
t = addModeline(t)
|
// t = addModeline(t)
|
||||||
|
|
||||||
kind := utils.GetKind(name)
|
kind := utils.GetKind(name)
|
||||||
var icon utils.Icon
|
var icon utils.Icon
|
||||||
@@ -168,7 +170,7 @@ func (chart *HelmChart) generateConfigMapsAndSecrets(project *types.Project) err
|
|||||||
delete(s.Environment, k)
|
delete(s.Environment, k)
|
||||||
}
|
}
|
||||||
if len(s.Environment) > 0 {
|
if len(s.Environment) > 0 {
|
||||||
cm := NewConfigMap(s, appName)
|
cm := NewConfigMap(s, appName, false)
|
||||||
y, _ := cm.Yaml()
|
y, _ := cm.Yaml()
|
||||||
name := cm.service.Name
|
name := cm.service.Name
|
||||||
chart.Templates[name+".configmap.yaml"] = &ChartTemplate{
|
chart.Templates[name+".configmap.yaml"] = &ChartTemplate{
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
package generator
|
package generator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"katenary/generator/labelStructs"
|
||||||
|
"katenary/utils"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -11,28 +13,8 @@ import (
|
|||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
"katenary/generator/labelStructs"
|
|
||||||
"katenary/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// only used to check interface implementation
|
|
||||||
var (
|
|
||||||
_ DataMap = (*ConfigMap)(nil)
|
|
||||||
_ Yaml = (*ConfigMap)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewFileMap creates a new DataMap from a compose service. The appName is the name of the application taken from the project name.
|
|
||||||
func NewFileMap(service types.ServiceConfig, appName, kind string) DataMap {
|
|
||||||
switch kind {
|
|
||||||
case "configmap":
|
|
||||||
return NewConfigMap(service, appName)
|
|
||||||
default:
|
|
||||||
log.Fatalf("Unknown filemap kind: %s", kind)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileMapUsage is the usage of the filemap.
|
// FileMapUsage is the usage of the filemap.
|
||||||
type FileMapUsage uint8
|
type FileMapUsage uint8
|
||||||
|
|
||||||
@@ -42,6 +24,23 @@ const (
|
|||||||
FileMapUsageFiles // files in a configmap.
|
FileMapUsageFiles // files in a configmap.
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// NewFileMap creates a new DataMap from a compose service. The appName is the name of the application taken from the project name.
|
||||||
|
func NewFileMap(service types.ServiceConfig, appName, kind string) DataMap {
|
||||||
|
switch kind {
|
||||||
|
case "configmap":
|
||||||
|
return NewConfigMap(service, appName, true)
|
||||||
|
default:
|
||||||
|
log.Fatalf("Unknown filemap kind: %s", kind)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// only used to check interface implementation
|
||||||
|
var (
|
||||||
|
_ DataMap = (*ConfigMap)(nil)
|
||||||
|
_ Yaml = (*ConfigMap)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
// ConfigMap is a kubernetes ConfigMap.
|
// ConfigMap is a kubernetes ConfigMap.
|
||||||
// Implements the DataMap interface.
|
// Implements the DataMap interface.
|
||||||
type ConfigMap struct {
|
type ConfigMap struct {
|
||||||
@@ -53,7 +52,7 @@ type ConfigMap struct {
|
|||||||
|
|
||||||
// NewConfigMap creates a new ConfigMap from a compose service. The appName is the name of the application taken from the project name.
|
// 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".
|
// The ConfigMap is filled by environment variables and labels "map-env".
|
||||||
func NewConfigMap(service types.ServiceConfig, appName string) *ConfigMap {
|
func NewConfigMap(service types.ServiceConfig, appName string, forFile bool) *ConfigMap {
|
||||||
done := map[string]bool{}
|
done := map[string]bool{}
|
||||||
drop := map[string]bool{}
|
drop := map[string]bool{}
|
||||||
labelValues := []string{}
|
labelValues := []string{}
|
||||||
@@ -99,6 +98,10 @@ func NewConfigMap(service types.ServiceConfig, appName string) *ConfigMap {
|
|||||||
service.Environment[value] = &val
|
service.Environment[value] = &val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if forFile {
|
||||||
|
// do not bind env variables to the configmap
|
||||||
|
return cm
|
||||||
|
}
|
||||||
// remove the variables that are already defined in the environment
|
// remove the variables that are already defined in the environment
|
||||||
if l, ok := service.Labels[LabelMapEnv]; ok {
|
if l, ok := service.Labels[LabelMapEnv]; ok {
|
||||||
envmap, err := labelStructs.MapEnvFrom(l)
|
envmap, err := labelStructs.MapEnvFrom(l)
|
||||||
@@ -155,11 +158,6 @@ func NewConfigMapFromDirectory(service types.ServiceConfig, appName, path string
|
|||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetData sets the data of the configmap. It replaces the entire data.
|
|
||||||
func (c *ConfigMap) SetData(data map[string]string) {
|
|
||||||
c.Data = data
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddData adds a key value pair to the configmap. Append or overwrite the value if the key already exists.
|
// 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) {
|
func (c *ConfigMap) AddData(key, value string) {
|
||||||
c.Data[key] = value
|
c.Data[key] = value
|
||||||
@@ -230,6 +228,11 @@ func (c *ConfigMap) Filename() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// Yaml returns the yaml representation of the configmap
|
||||||
func (c *ConfigMap) Yaml() ([]byte, error) {
|
func (c *ConfigMap) Yaml() ([]byte, error) {
|
||||||
return yaml.Marshal(c)
|
return yaml.Marshal(c)
|
||||||
|
@@ -4,6 +4,10 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"katenary/generator/extrafiles"
|
||||||
|
"katenary/generator/labelStructs"
|
||||||
|
"katenary/parser"
|
||||||
|
"katenary/utils"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -13,13 +17,22 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
|
|
||||||
"katenary/generator/extrafiles"
|
|
||||||
"katenary/generator/labelStructs"
|
|
||||||
"katenary/parser"
|
|
||||||
"katenary/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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
|
const headerHelp = `# This file is autogenerated by katenary
|
||||||
#
|
#
|
||||||
# DO NOT EDIT IT BY HAND UNLESS YOU KNOW WHAT YOU ARE DOING
|
# DO NOT EDIT IT BY HAND UNLESS YOU KNOW WHAT YOU ARE DOING
|
||||||
@@ -32,6 +45,47 @@ const headerHelp = `# This file is autogenerated by katenary
|
|||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
|
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, repsonsible 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:",
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyRegExp checks if the line starts by a #
|
||||||
|
var keyRegExp = regexp.MustCompile(`^\s*[^#]+:.*`)
|
||||||
|
|
||||||
// Convert a compose (docker, podman...) project to a helm chart.
|
// Convert a compose (docker, podman...) project to a helm chart.
|
||||||
// It calls Generate() to generate the chart and then write it to the disk.
|
// It calls Generate() to generate the chart and then write it to the disk.
|
||||||
func Convert(config ConvertOptions, dockerComposeFile ...string) {
|
func Convert(config ConvertOptions, dockerComposeFile ...string) {
|
||||||
@@ -59,7 +113,7 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse the compose files
|
// parse the compose files
|
||||||
project, err := parser.Parse(config.Profiles, dockerComposeFile...)
|
project, err := parser.Parse(config.Profiles, config.EnvFiles, dockerComposeFile...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -109,6 +163,11 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add icon from the command line
|
||||||
|
if config.Icon != "" {
|
||||||
|
chart.Icon = config.Icon
|
||||||
|
}
|
||||||
|
|
||||||
// write the templates to the disk
|
// write the templates to the disk
|
||||||
chart.SaveTemplates(templateDir)
|
chart.SaveTemplates(templateDir)
|
||||||
|
|
||||||
@@ -132,141 +191,6 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) {
|
|||||||
callHelmUpdate(config)
|
callHelmUpdate(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
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"
|
|
||||||
`
|
|
||||||
|
|
||||||
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"))
|
|
||||||
}
|
|
||||||
|
|
||||||
const storageClassHelp = `# Storage class to use for PVCs
|
|
||||||
# storageClass: "-" means use default
|
|
||||||
# storageClass: "" means do not specify
|
|
||||||
# storageClass: "foo" means use that storageClass
|
|
||||||
`
|
|
||||||
|
|
||||||
// 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"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// addModeline adds a modeline to the values.yaml file to make sure that vim
|
|
||||||
// will use the correct syntax highlighting.
|
|
||||||
func addModeline(values []byte) []byte {
|
|
||||||
modeline := "# vi" + "m: ft=helm.gotmpl.yaml"
|
|
||||||
|
|
||||||
// if the values ends by `{{- end }}` we need to add the modeline before
|
|
||||||
lines := strings.Split(string(values), "\n")
|
|
||||||
|
|
||||||
if lines[len(lines)-1] == "{{- end }}" || lines[len(lines)-1] == "{{- end -}}" {
|
|
||||||
lines = lines[:len(lines)-1]
|
|
||||||
lines = append(lines, modeline, "{{- end }}")
|
|
||||||
return []byte(strings.Join(lines, "\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(values, []byte(modeline)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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[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 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
|
|
||||||
}
|
|
||||||
|
|
||||||
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, repsonsible for creating the secret.
|
|
||||||
`
|
|
||||||
|
|
||||||
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 addChartDoc(values []byte, project *types.Project) []byte {
|
func addChartDoc(values []byte, project *types.Project) []byte {
|
||||||
chartDoc := fmt.Sprintf(`# This is the main values.yaml file for the %s chart.
|
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.
|
# More information can be found in the chart's README.md file.
|
||||||
@@ -303,67 +227,63 @@ func addChartDoc(values []byte, project *types.Project) []byte {
|
|||||||
return []byte(chartDoc + strings.Join(lines, "\n"))
|
return []byte(chartDoc + strings.Join(lines, "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
const imagePullPolicyHelp = `# imagePullPolicy allows you to specify a policy to cache or always pull an image.
|
func addCommentsToValues(values []byte) []byte {
|
||||||
# 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
|
|
||||||
`
|
|
||||||
|
|
||||||
func addImagePullPolicyHelp(values []byte) []byte {
|
|
||||||
// add imagePullPolicy help
|
|
||||||
lines := strings.Split(string(values), "\n")
|
lines := strings.Split(string(values), "\n")
|
||||||
for i, line := range lines {
|
for i, line := range lines {
|
||||||
if strings.Contains(line, "imagePullPolicy:") {
|
if strings.Contains(line, "ingress:") {
|
||||||
spaces := utils.CountStartingSpaces(line)
|
spaces := utils.CountStartingSpaces(line)
|
||||||
spacesString := strings.Repeat(" ", spaces)
|
spacesString := strings.Repeat(" ", spaces)
|
||||||
// indent imagePullPolicyHelp comment
|
// indent ingressClassHelper comment
|
||||||
imagePullPolicyHelp := strings.ReplaceAll(imagePullPolicyHelp, "\n", "\n"+spacesString)
|
ingressClassHelp := strings.ReplaceAll(ingressClassHelp, "\n", "\n"+spacesString)
|
||||||
imagePullPolicyHelp = strings.TrimRight(imagePullPolicyHelp, " ")
|
ingressClassHelp = strings.TrimRight(ingressClassHelp, " ")
|
||||||
imagePullPolicyHelp = spacesString + imagePullPolicyHelp
|
ingressClassHelp = spacesString + ingressClassHelp
|
||||||
lines[i] = imagePullPolicyHelp + line
|
lines[i] = ingressClassHelp + line
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []byte(strings.Join(lines, "\n"))
|
return []byte(strings.Join(lines, "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
const resourceHelp = `# Resources allows you to specify the resource requests and limits for a service.
|
func addDependencyDescription(values []byte, dependencies []labelStructs.Dependency) []byte {
|
||||||
# Resources are used to specify the amount of CPU and memory that
|
for _, d := range dependencies {
|
||||||
# a container needs.
|
name := d.Name
|
||||||
#
|
if d.Alias != "" {
|
||||||
# e.g.
|
name = d.Alias
|
||||||
# resources:
|
|
||||||
# requests:
|
|
||||||
# memory: "64Mi"
|
|
||||||
# cpu: "250m"
|
|
||||||
# limits:
|
|
||||||
# memory: "128Mi"
|
|
||||||
# cpu: "500m"
|
|
||||||
`
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
values = regexp.MustCompile(
|
||||||
|
`(?m)^`+name+`:$`,
|
||||||
|
).ReplaceAll(
|
||||||
|
values,
|
||||||
|
[]byte("\n# "+d.Name+" helm dependency configuration\n"+name+":"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return []byte(strings.Join(lines, "\n"))
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
func addVariablesDoc(values []byte, project *types.Project) []byte {
|
// addDescriptions adds the description from the label to the values.yaml file on top
|
||||||
lines := strings.Split(string(values), "\n")
|
// of the service definition.
|
||||||
|
func addDescriptions(values []byte, project types.Project) []byte {
|
||||||
for _, service := range project.Services {
|
for _, service := range project.Services {
|
||||||
lines = addDocToVariable(service, lines)
|
if description, ok := service.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 []byte(strings.Join(lines, "\n"))
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
func addDocToVariable(service types.ServiceConfig, lines []string) []string {
|
func addDocToVariable(service types.ServiceConfig, lines []string) []string {
|
||||||
@@ -394,25 +314,38 @@ func addDocToVariable(service types.ServiceConfig, lines []string) []string {
|
|||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainTagAppDoc = `This is the version of the main application.
|
func addImagePullPolicyHelp(values []byte) []byte {
|
||||||
Leave it to blank to use the Chart "AppVersion" value.`
|
// 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 addMainTagAppDoc(values []byte, project *types.Project) []byte {
|
func addImagePullSecretsHelp(values []byte) []byte {
|
||||||
|
// add imagePullSecrets help
|
||||||
lines := strings.Split(string(values), "\n")
|
lines := strings.Split(string(values), "\n")
|
||||||
|
|
||||||
for _, service := range project.Services {
|
for i, line := range lines {
|
||||||
// read the label LabelMainApp
|
if strings.Contains(line, "pullSecrets:") {
|
||||||
if v, ok := service.Labels[LabelMainApp]; !ok {
|
spaces := utils.CountStartingSpaces(line)
|
||||||
continue
|
spacesString := strings.Repeat(" ", spaces)
|
||||||
} else if v == "false" || v == "no" || v == "0" {
|
// indent imagePullSecretHelp comment
|
||||||
continue
|
imagePullSecretHelp := strings.ReplaceAll(imagePullSecretHelp, "\n", "\n"+spacesString)
|
||||||
} else {
|
imagePullSecretHelp = strings.TrimRight(imagePullSecretHelp, " ")
|
||||||
fmt.Printf("%s Adding main tag app doc %s\n", utils.IconConfig, service.Name)
|
imagePullSecretHelp = spacesString + imagePullSecretHelp
|
||||||
|
lines[i] = imagePullSecretHelp + line
|
||||||
}
|
}
|
||||||
|
|
||||||
lines = addMainAppDoc(lines, service)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return []byte(strings.Join(lines, "\n"))
|
return []byte(strings.Join(lines, "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,107 +373,84 @@ func addMainAppDoc(lines []string, service types.ServiceConfig) []string {
|
|||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeNewlinesInsideBrackets(values []byte) []byte {
|
func addMainTagAppDoc(values []byte, project *types.Project) []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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var unwantedLines = []string{
|
|
||||||
"creationTimestamp:",
|
|
||||||
"status:",
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeUnwantedLines(values []byte) []byte {
|
|
||||||
lines := strings.Split(string(values), "\n")
|
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"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 _, service := range project.Services {
|
||||||
for label := range service.Labels {
|
// read the label LabelMainApp
|
||||||
if strings.Contains(label, "katenary.") && !strings.Contains(label, katenaryLabelPrefix) {
|
if v, ok := service.Labels[LabelMainApp]; !ok {
|
||||||
badServices = append(badServices, fmt.Sprintf("- %s: %s", service.Name, label))
|
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"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// addModeline adds a modeline to the values.yaml file to make sure that vim
|
||||||
|
// will use the correct syntax highlighting.
|
||||||
|
func addModeline(values []byte) []byte {
|
||||||
|
modeline := "# vi" + "m: ft=helm.gotmpl.yaml"
|
||||||
|
|
||||||
|
// if the values ends by `{{- end }}` we need to add the modeline before
|
||||||
|
lines := strings.Split(string(values), "\n")
|
||||||
|
|
||||||
|
if lines[len(lines)-1] == "{{- end }}" || lines[len(lines)-1] == "{{- end -}}" {
|
||||||
|
lines = lines[:len(lines)-1]
|
||||||
|
lines = append(lines, modeline, "{{- end }}")
|
||||||
|
return []byte(strings.Join(lines, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(values, []byte(modeline)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(badServices) > 0 {
|
return []byte(strings.Join(lines, "\n"))
|
||||||
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,
|
|
||||||
katenaryLabelPrefix[0:len(katenaryLabelPrefix)-1],
|
|
||||||
strings.Join(badServices, "\n"),
|
|
||||||
)
|
|
||||||
|
|
||||||
return errors.New(utils.WordWrap(message, 80))
|
|
||||||
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// helmUpdate runs "helm dependency update" on the output directory.
|
// addStorageClassHelp adds a comment to the values.yaml file to explain how to
|
||||||
func helmUpdate(config ConvertOptions) error {
|
// use the storageClass option.
|
||||||
// lookup for "helm" binary
|
func addStorageClassHelp(values []byte) []byte {
|
||||||
fmt.Println(utils.IconInfo, "Updating helm dependencies...")
|
lines := strings.Split(string(values), "\n")
|
||||||
helm, err := exec.LookPath("helm")
|
for i, line := range lines {
|
||||||
if err != nil {
|
if strings.Contains(line, "storageClass:") {
|
||||||
fmt.Println(utils.IconFailure, err)
|
spaces := utils.CountStartingSpaces(line)
|
||||||
os.Exit(1)
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// run "helm dependency update"
|
return []byte(strings.Join(lines, "\n"))
|
||||||
cmd := exec.Command(helm, "dependency", "update", config.OutputDir)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// helmLint runs "helm lint" on the output directory.
|
func addVariablesDoc(values []byte, project *types.Project) []byte {
|
||||||
func helmLint(config ConvertOptions) error {
|
lines := strings.Split(string(values), "\n")
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
// keyRegExp checks if the line starts by a #
|
for _, service := range project.Services {
|
||||||
var keyRegExp = regexp.MustCompile(`^\s*[^#]+:.*`)
|
lines = addDocToVariable(service, lines)
|
||||||
|
}
|
||||||
|
return []byte(strings.Join(lines, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
// addYAMLSelectorPath adds a selector path to the yaml file for each key
|
// addYAMLSelectorPath adds a selector path to the yaml file for each key
|
||||||
// as comment. E.g. foo.ingress.host
|
// as comment. E.g. foo.ingress.host
|
||||||
@@ -587,14 +497,40 @@ func addYAMLSelectorPath(values []byte) []byte {
|
|||||||
return []byte(strings.Join(toReturn, "\n"))
|
return []byte(strings.Join(toReturn, "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeContent(path string, content []byte) {
|
func buildCharYamlFile(chart *HelmChart, project *types.Project, chartPath string) {
|
||||||
f, err := os.Create(path)
|
// calculate the sha1 hash of the services
|
||||||
|
yamlChart, err := utils.EncodeBasicYaml(chart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(utils.IconFailure, err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
// concat chart adding a comment with hash of services on top
|
||||||
f.Write(content)
|
yamlChart = append([]byte(fmt.Sprintf("# 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([]byte(fmt.Sprintf("# compose files: %s\n", strings.Join(files, ", "))), yamlChart...)
|
||||||
|
// add generated date
|
||||||
|
yamlChart = append([]byte(fmt.Sprintf("# 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) {
|
func buildValues(chart *HelmChart, project *types.Project, valuesPath string) {
|
||||||
@@ -622,42 +558,6 @@ func buildValues(chart *HelmChart, project *types.Project, valuesPath string) {
|
|||||||
writeContent(valuesPath, values)
|
writeContent(valuesPath, values)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 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([]byte(fmt.Sprintf("# 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([]byte(fmt.Sprintf("# compose files: %s\n", strings.Join(files, ", "))), yamlChart...)
|
|
||||||
// add generated date
|
|
||||||
yamlChart = append([]byte(fmt.Sprintf("# generated at: %s\n", time.Now().Format(time.RFC3339))), yamlChart...)
|
|
||||||
|
|
||||||
// document Chart.yaml file
|
|
||||||
yamlChart = addChartDoc(yamlChart, project)
|
|
||||||
|
|
||||||
writeContent(chartPath, yamlChart)
|
|
||||||
}
|
|
||||||
|
|
||||||
func callHelmUpdate(config ConvertOptions) {
|
func callHelmUpdate(config ConvertOptions) {
|
||||||
executeAndHandleError := func(fn func(ConvertOptions) error, config ConvertOptions, message string) {
|
executeAndHandleError := func(fn func(ConvertOptions) error, config ConvertOptions, message string) {
|
||||||
if err := fn(config); err != nil {
|
if err := fn(config); err != nil {
|
||||||
@@ -672,3 +572,107 @@ func callHelmUpdate(config ConvertOptions) {
|
|||||||
fmt.Println(utils.IconSuccess, "Helm chart created successfully")
|
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()
|
||||||
|
f.Write(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, 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,
|
||||||
|
katenaryLabelPrefix[0:len(katenaryLabelPrefix)-1],
|
||||||
|
strings.Join(badServices, "\n"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return errors.New(utils.WordWrap(message, 80))
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@@ -2,6 +2,8 @@ package generator
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"katenary/generator/labelStructs"
|
||||||
|
"katenary/utils"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -14,9 +16,6 @@ import (
|
|||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
"katenary/generator/labelStructs"
|
|
||||||
"katenary/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Yaml = (*Deployment)(nil)
|
var _ Yaml = (*Deployment)(nil)
|
||||||
@@ -106,32 +105,6 @@ func NewDeployment(service types.ServiceConfig, chart *HelmChart) *Deployment {
|
|||||||
return dep
|
return dep
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 "+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
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddContainer adds a container to the deployment.
|
// AddContainer adds a container to the deployment.
|
||||||
func (d *Deployment) AddContainer(service types.ServiceConfig) {
|
func (d *Deployment) AddContainer(service types.ServiceConfig) {
|
||||||
ports := []corev1.ContainerPort{}
|
ports := []corev1.ContainerPort{}
|
||||||
@@ -178,6 +151,34 @@ func (d *Deployment) AddContainer(service types.ServiceConfig) {
|
|||||||
d.Spec.Template.Spec.Containers = append(d.Spec.Template.Spec.Containers, 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[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.
|
// AddIngress adds an ingress to the deployment. It creates the ingress object.
|
||||||
func (d *Deployment) AddIngress(service types.ServiceConfig, appName string) *Ingress {
|
func (d *Deployment) AddIngress(service types.ServiceConfig, appName string) *Ingress {
|
||||||
return NewIngress(service, d.chart)
|
return NewIngress(service, d.chart)
|
||||||
@@ -209,124 +210,6 @@ func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Deployment) bindVolumes(volume types.ServiceVolumeConfig, isSamePod bool, tobind map[string]bool, service types.ServiceConfig, appName string) {
|
|
||||||
container, index := utils.GetContainerByName(service.Name, 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 _, ok := tobind[volume.Source]; !isSamePod && volume.Type == "bind" && !ok {
|
|
||||||
utils.Warn(
|
|
||||||
"Bind volumes are not supported yet, " +
|
|
||||||
"excepting for those declared as " +
|
|
||||||
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
|
|
||||||
container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{
|
|
||||||
Name: volume.Source,
|
|
||||||
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[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: volume.Source,
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
var cm *ConfigMap
|
|
||||||
if v, ok := d.configMaps[pathname]; !ok {
|
|
||||||
cm = NewConfigMap(*d.service, appName)
|
|
||||||
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
|
|
||||||
|
|
||||||
}
|
|
||||||
cm.AppendFile(volume.Source)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Deployment) BindFrom(service types.ServiceConfig, binded *Deployment) {
|
func (d *Deployment) BindFrom(service types.ServiceConfig, binded *Deployment) {
|
||||||
// find the volume in the binded deployment
|
// find the volume in the binded deployment
|
||||||
for _, bindedVolume := range binded.Spec.Template.Spec.Volumes {
|
for _, bindedVolume := range binded.Spec.Template.Spec.Volumes {
|
||||||
@@ -354,6 +237,37 @@ func (d *Deployment) BindFrom(service types.ServiceConfig, binded *Deployment) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 "+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.
|
// SetEnvFrom sets the environment variables to a configmap. The configmap is created.
|
||||||
func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string) {
|
func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string) {
|
||||||
if len(service.Environment) == 0 {
|
if len(service.Environment) == 0 {
|
||||||
@@ -447,34 +361,6 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string) {
|
|||||||
d.Spec.Template.Spec.Containers[index] = *container
|
d.Spec.Template.Spec.Containers[index] = *container
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Deployment) AddHealthCheck(service types.ServiceConfig, container *corev1.Container) {
|
|
||||||
// get the label for healthcheck
|
|
||||||
if v, ok := service.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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Yaml returns the yaml representation of the deployment.
|
// Yaml returns the yaml representation of the deployment.
|
||||||
func (d *Deployment) Yaml() ([]byte, error) {
|
func (d *Deployment) Yaml() ([]byte, error) {
|
||||||
serviceName := d.service.Name
|
serviceName := d.service.Name
|
||||||
@@ -489,11 +375,13 @@ func (d *Deployment) Yaml() ([]byte, error) {
|
|||||||
spaces := ""
|
spaces := ""
|
||||||
volumeName := ""
|
volumeName := ""
|
||||||
|
|
||||||
|
nameDirective := "name: "
|
||||||
|
|
||||||
// this loop add condition for each volume mount
|
// this loop add condition for each volume mount
|
||||||
for line, volume := range content {
|
for line, volume := range content {
|
||||||
// find the volume name
|
// find the volume name
|
||||||
for i := line; i < len(content); i++ {
|
for i := line; i < len(content); i++ {
|
||||||
if strings.Contains(content[i], "name: ") {
|
if strings.Contains(content[i], nameDirective) {
|
||||||
volumeName = strings.TrimSpace(strings.Replace(content[i], "name: ", "", 1))
|
volumeName = strings.TrimSpace(strings.Replace(content[i], "name: ", "", 1))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -511,7 +399,7 @@ func (d *Deployment) Yaml() ([]byte, error) {
|
|||||||
content[line] = spaces + `{{- if .Values.` + serviceName + `.persistence.` + volumeName + `.enabled }}` + "\n" + volume
|
content[line] = spaces + `{{- if .Values.` + serviceName + `.persistence.` + volumeName + `.enabled }}` + "\n" + volume
|
||||||
changing = true
|
changing = true
|
||||||
}
|
}
|
||||||
if strings.Contains(volume, "name: ") && changing {
|
if strings.Contains(volume, nameDirective) && changing {
|
||||||
content[line] = volume + "\n" + spaces + "{{- end }}"
|
content[line] = volume + "\n" + spaces + "{{- end }}"
|
||||||
changing = false
|
changing = false
|
||||||
}
|
}
|
||||||
@@ -624,7 +512,120 @@ func (d *Deployment) Yaml() ([]byte, error) {
|
|||||||
return []byte(strings.Join(content, "\n")), nil
|
return []byte(strings.Join(content, "\n")), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filename returns the filename of the deployment.
|
func (d *Deployment) appendDirectoryToConfigMap(service types.ServiceConfig, appName string, volume types.ServiceVolumeConfig) {
|
||||||
func (d *Deployment) Filename() string {
|
pathnme := utils.PathToName(volume.Source)
|
||||||
return d.service.Name + ".deployment.yaml"
|
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)
|
||||||
|
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
|
||||||
|
|
||||||
|
}
|
||||||
|
cm.AppendFile(volume.Source)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Deployment) bindVolumes(volume types.ServiceVolumeConfig, isSamePod bool, tobind map[string]bool, service types.ServiceConfig, appName string) {
|
||||||
|
container, index := utils.GetContainerByName(service.Name, 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 _, ok := tobind[volume.Source]; !isSamePod && volume.Type == "bind" && !ok {
|
||||||
|
utils.Warn(
|
||||||
|
"Bind volumes are not supported yet, " +
|
||||||
|
"excepting for those declared as " +
|
||||||
|
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
|
||||||
|
container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{
|
||||||
|
Name: volume.Source,
|
||||||
|
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[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: volume.Source,
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,14 +11,35 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed readme.tpl
|
||||||
|
var readmeTemplate string
|
||||||
|
|
||||||
type chart struct {
|
type chart struct {
|
||||||
Name string
|
Name string
|
||||||
Description string
|
Description string
|
||||||
Values []string
|
Values []string
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed readme.tpl
|
func parseValues(prefix string, values map[string]interface{}, result map[string]string) {
|
||||||
var readmeTemplate string
|
for key, value := range values {
|
||||||
|
path := key
|
||||||
|
if prefix != "" {
|
||||||
|
path = prefix + "." + key
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := value.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
for i, u := range v {
|
||||||
|
parseValues(fmt.Sprintf("%s[%d]", path, i), map[string]interface{}{"value": u}, result)
|
||||||
|
}
|
||||||
|
case map[string]interface{}:
|
||||||
|
parseValues(path, v, result)
|
||||||
|
default:
|
||||||
|
strValue := fmt.Sprintf("`%v`", value)
|
||||||
|
result["`"+path+"`"] = strValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ReadMeFile returns the content of the README.md file.
|
// ReadMeFile returns the content of the README.md file.
|
||||||
func ReadMeFile(charname, description string, values map[string]any) string {
|
func ReadMeFile(charname, description string, values map[string]any) string {
|
||||||
@@ -74,24 +95,3 @@ func ReadMeFile(charname, description string, values map[string]any) string {
|
|||||||
|
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseValues(prefix string, values map[string]interface{}, result map[string]string) {
|
|
||||||
for key, value := range values {
|
|
||||||
path := key
|
|
||||||
if prefix != "" {
|
|
||||||
path = prefix + "." + key
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v := value.(type) {
|
|
||||||
case []interface{}:
|
|
||||||
for i, u := range v {
|
|
||||||
parseValues(fmt.Sprintf("%s[%d]", path, i), map[string]interface{}{"value": u}, result)
|
|
||||||
}
|
|
||||||
case map[string]interface{}:
|
|
||||||
parseValues(path, v, result)
|
|
||||||
default:
|
|
||||||
strValue := fmt.Sprintf("`%v`", value)
|
|
||||||
result["`"+path+"`"] = strValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -162,49 +162,6 @@ func Generate(project *types.Project) (*HelmChart, error) {
|
|||||||
return chart, nil
|
return chart, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// computeNIndentm replace all __indent__ labels with the number of spaces before the label.
|
|
||||||
func computeNIndent(b []byte) []byte {
|
|
||||||
lines := bytes.Split(b, []byte("\n"))
|
|
||||||
for i, line := range lines {
|
|
||||||
if !bytes.Contains(line, []byte("__indent__")) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
startSpaces := ""
|
|
||||||
spaces := regexp.MustCompile(`^\s+`).FindAllString(string(line), -1)
|
|
||||||
if len(spaces) > 0 {
|
|
||||||
startSpaces = spaces[0]
|
|
||||||
}
|
|
||||||
line = []byte(startSpaces + strings.TrimLeft(string(line), " "))
|
|
||||||
line = bytes.ReplaceAll(line, []byte("__indent__"), []byte(fmt.Sprintf("%d", len(startSpaces))))
|
|
||||||
lines[i] = line
|
|
||||||
}
|
|
||||||
return bytes.Join(lines, []byte("\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeReplaceString replace all __replace_ labels with the value of the
|
|
||||||
// capture group and remove all new lines and repeated spaces.
|
|
||||||
//
|
|
||||||
// we created:
|
|
||||||
//
|
|
||||||
// __replace_bar: '{{ include "foo.labels" .
|
|
||||||
// }}'
|
|
||||||
//
|
|
||||||
// note the new line and spaces...
|
|
||||||
//
|
|
||||||
// we now want to replace it with {{ include "foo.labels" . }}, without the label name.
|
|
||||||
func removeReplaceString(b []byte) []byte {
|
|
||||||
// replace all matches with the value of the capture group
|
|
||||||
// and remove all new lines and repeated spaces
|
|
||||||
b = replaceLabelRegexp.ReplaceAllFunc(b, func(b []byte) []byte {
|
|
||||||
inc := replaceLabelRegexp.FindSubmatch(b)[1]
|
|
||||||
inc = bytes.ReplaceAll(inc, []byte("\n"), []byte(""))
|
|
||||||
inc = bytes.ReplaceAll(inc, []byte("\r"), []byte(""))
|
|
||||||
inc = regexp.MustCompile(`\s+`).ReplaceAll(inc, []byte(" "))
|
|
||||||
return inc
|
|
||||||
})
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// serviceIsMain returns true if the service is the main app.
|
// serviceIsMain returns true if the service is the main app.
|
||||||
func serviceIsMain(service types.ServiceConfig) bool {
|
func serviceIsMain(service types.ServiceConfig) bool {
|
||||||
if main, ok := service.Labels[LabelMainApp]; ok {
|
if main, ok := service.Labels[LabelMainApp]; ok {
|
||||||
@@ -213,37 +170,6 @@ func serviceIsMain(service types.ServiceConfig) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildVolumes creates the volumes for the service.
|
|
||||||
func buildVolumes(service types.ServiceConfig, chart *HelmChart, deployments map[string]*Deployment) error {
|
|
||||||
appName := chart.Name
|
|
||||||
for _, v := range service.Volumes {
|
|
||||||
// Do not add volumes if the pod is injected in a deployments
|
|
||||||
// via "same-pod" and the volume in destination deployment exists
|
|
||||||
if samePodVolume(service, v, deployments) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch v.Type {
|
|
||||||
case "volume":
|
|
||||||
pvc := NewVolumeClaim(service, v.Source, appName)
|
|
||||||
|
|
||||||
// if the service is integrated in another deployment, we need to add the volume
|
|
||||||
// to the target deployment
|
|
||||||
if override, ok := service.Labels[LabelSamePod]; ok {
|
|
||||||
pvc.nameOverride = override
|
|
||||||
pvc.Spec.StorageClassName = utils.StrPtr(`{{ .Values.` + override + `.persistence.` + v.Source + `.storageClass }}`)
|
|
||||||
chart.Values[override].(*Value).AddPersistence(v.Source)
|
|
||||||
}
|
|
||||||
y, _ := pvc.Yaml()
|
|
||||||
chart.Templates[pvc.Filename()] = &ChartTemplate{
|
|
||||||
Content: y,
|
|
||||||
Servicename: service.Name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addStaticVolumes(deployments map[string]*Deployment, service types.ServiceConfig) {
|
func addStaticVolumes(deployments map[string]*Deployment, service types.ServiceConfig) {
|
||||||
// add the bound configMaps files to the deployment containers
|
// add the bound configMaps files to the deployment containers
|
||||||
var d *Deployment
|
var d *Deployment
|
||||||
@@ -292,6 +218,80 @@ func addStaticVolumes(deployments map[string]*Deployment, service types.ServiceC
|
|||||||
d.Spec.Template.Spec.Containers[index] = *container
|
d.Spec.Template.Spec.Containers[index] = *container
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// computeNIndentm replace all __indent__ labels with the number of spaces before the label.
|
||||||
|
func computeNIndent(b []byte) []byte {
|
||||||
|
lines := bytes.Split(b, []byte("\n"))
|
||||||
|
for i, line := range lines {
|
||||||
|
if !bytes.Contains(line, []byte("__indent__")) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
startSpaces := ""
|
||||||
|
spaces := regexp.MustCompile(`^\s+`).FindAllString(string(line), -1)
|
||||||
|
if len(spaces) > 0 {
|
||||||
|
startSpaces = spaces[0]
|
||||||
|
}
|
||||||
|
line = []byte(startSpaces + strings.TrimLeft(string(line), " "))
|
||||||
|
line = bytes.ReplaceAll(line, []byte("__indent__"), []byte(fmt.Sprintf("%d", len(startSpaces))))
|
||||||
|
lines[i] = line
|
||||||
|
}
|
||||||
|
return bytes.Join(lines, []byte("\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeReplaceString replace all __replace_ labels with the value of the
|
||||||
|
// capture group and remove all new lines and repeated spaces.
|
||||||
|
//
|
||||||
|
// we created:
|
||||||
|
//
|
||||||
|
// __replace_bar: '{{ include "foo.labels" .
|
||||||
|
// }}'
|
||||||
|
//
|
||||||
|
// note the new line and spaces...
|
||||||
|
//
|
||||||
|
// we now want to replace it with {{ include "foo.labels" . }}, without the label name.
|
||||||
|
func removeReplaceString(b []byte) []byte {
|
||||||
|
// replace all matches with the value of the capture group
|
||||||
|
// and remove all new lines and repeated spaces
|
||||||
|
b = replaceLabelRegexp.ReplaceAllFunc(b, func(b []byte) []byte {
|
||||||
|
inc := replaceLabelRegexp.FindSubmatch(b)[1]
|
||||||
|
inc = bytes.ReplaceAll(inc, []byte("\n"), []byte(""))
|
||||||
|
inc = bytes.ReplaceAll(inc, []byte("\r"), []byte(""))
|
||||||
|
inc = regexp.MustCompile(`\s+`).ReplaceAll(inc, []byte(" "))
|
||||||
|
return inc
|
||||||
|
})
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildVolumes creates the volumes for the service.
|
||||||
|
func buildVolumes(service types.ServiceConfig, chart *HelmChart, deployments map[string]*Deployment) error {
|
||||||
|
appName := chart.Name
|
||||||
|
for _, v := range service.Volumes {
|
||||||
|
// Do not add volumes if the pod is injected in a deployments
|
||||||
|
// via "same-pod" and the volume in destination deployment exists
|
||||||
|
if samePodVolume(service, v, deployments) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch v.Type {
|
||||||
|
case "volume":
|
||||||
|
pvc := NewVolumeClaim(service, v.Source, appName)
|
||||||
|
|
||||||
|
// if the service is integrated in another deployment, we need to add the volume
|
||||||
|
// to the target deployment
|
||||||
|
if override, ok := service.Labels[LabelSamePod]; ok {
|
||||||
|
pvc.nameOverride = override
|
||||||
|
pvc.Spec.StorageClassName = utils.StrPtr(`{{ .Values.` + override + `.persistence.` + v.Source + `.storageClass }}`)
|
||||||
|
chart.Values[override].(*Value).AddPersistence(v.Source)
|
||||||
|
}
|
||||||
|
y, _ := pvc.Yaml()
|
||||||
|
chart.Templates[pvc.Filename()] = &ChartTemplate{
|
||||||
|
Content: y,
|
||||||
|
Servicename: service.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// samePodVolume returns true if the volume is already in the target deployment.
|
// samePodVolume returns true if the volume is already in the target deployment.
|
||||||
func samePodVolume(service types.ServiceConfig, v types.ServiceVolumeConfig, deployments map[string]*Deployment) bool {
|
func samePodVolume(service types.ServiceConfig, v types.ServiceVolumeConfig, deployments map[string]*Deployment) bool {
|
||||||
// if the service has volumes, and it has "same-pod" label
|
// if the service has volumes, and it has "same-pod" label
|
||||||
|
@@ -119,6 +119,10 @@ func NewIngress(service types.ServiceConfig, Chart *HelmChart) *Ingress {
|
|||||||
return ing
|
return ing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ingress *Ingress) Filename() string {
|
||||||
|
return ingress.service.Name + ".ingress.yaml"
|
||||||
|
}
|
||||||
|
|
||||||
func (ingress *Ingress) Yaml() ([]byte, error) {
|
func (ingress *Ingress) Yaml() ([]byte, error) {
|
||||||
serviceName := ingress.service.Name
|
serviceName := ingress.service.Name
|
||||||
ret, err := yaml.Marshal(ingress)
|
ret, err := yaml.Marshal(ingress)
|
||||||
@@ -159,7 +163,3 @@ func (ingress *Ingress) Yaml() ([]byte, error) {
|
|||||||
ret = []byte(strings.Join(out, "\n"))
|
ret = []byte(strings.Join(out, "\n"))
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ingress *Ingress) Filename() string {
|
|
||||||
return ingress.service.Name + ".ingress.yaml"
|
|
||||||
}
|
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"katenary/utils"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -11,37 +12,10 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
"katenary/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// Set the documentation of labels here
|
|
||||||
//
|
|
||||||
//go:embed katenaryLabelsDoc.yaml
|
|
||||||
labelFullHelpYAML []byte
|
|
||||||
|
|
||||||
// parsed yaml
|
|
||||||
labelFullHelp map[string]Help
|
|
||||||
)
|
|
||||||
|
|
||||||
// Label is a katenary label to find in compose files.
|
|
||||||
type Label = string
|
|
||||||
|
|
||||||
// Help is the documentation of a label.
|
|
||||||
type Help struct {
|
|
||||||
Short string `yaml:"short"`
|
|
||||||
Long string `yaml:"long"`
|
|
||||||
Example string `yaml:"example"`
|
|
||||||
Type string `yaml:"type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const katenaryLabelPrefix = "katenary.v3"
|
const katenaryLabelPrefix = "katenary.v3"
|
||||||
|
|
||||||
func Prefix() string {
|
|
||||||
return katenaryLabelPrefix
|
|
||||||
}
|
|
||||||
|
|
||||||
// Known labels.
|
// Known labels.
|
||||||
const (
|
const (
|
||||||
LabelMainApp Label = katenaryLabelPrefix + "/main-app"
|
LabelMainApp Label = katenaryLabelPrefix + "/main-app"
|
||||||
@@ -60,16 +34,47 @@ const (
|
|||||||
LabelEnvFrom Label = katenaryLabelPrefix + "/env-from"
|
LabelEnvFrom Label = katenaryLabelPrefix + "/env-from"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Set the documentation of labels here
|
||||||
|
//
|
||||||
|
//go:embed katenaryLabelsDoc.yaml
|
||||||
|
labelFullHelpYAML []byte
|
||||||
|
|
||||||
|
// parsed yaml
|
||||||
|
labelFullHelp map[string]Help
|
||||||
|
)
|
||||||
|
|
||||||
|
// Label is a katenary label to find in compose files.
|
||||||
|
type Label = string
|
||||||
|
|
||||||
|
func labelName(name string) Label {
|
||||||
|
return Label(katenaryLabelPrefix + "/" + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Help is the documentation of a label.
|
||||||
|
type Help struct {
|
||||||
|
Short string `yaml:"short"`
|
||||||
|
Long string `yaml:"long"`
|
||||||
|
Example string `yaml:"example"`
|
||||||
|
Type string `yaml:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLabelNames returns a sorted list of all katenary label names.
|
||||||
|
func GetLabelNames() []string {
|
||||||
|
var names []string
|
||||||
|
for name := range labelFullHelp {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if err := yaml.Unmarshal(labelFullHelpYAML, &labelFullHelp); err != nil {
|
if err := yaml.Unmarshal(labelFullHelpYAML, &labelFullHelp); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func labelName(name string) Label {
|
|
||||||
return Label(katenaryLabelPrefix + "/" + name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate the help for the labels.
|
// Generate the help for the labels.
|
||||||
func GetLabelHelp(asMarkdown bool) string {
|
func GetLabelHelp(asMarkdown bool) string {
|
||||||
names := GetLabelNames() // sorted
|
names := GetLabelNames() // sorted
|
||||||
@@ -79,73 +84,6 @@ func GetLabelHelp(asMarkdown bool) string {
|
|||||||
return generateMarkdownHelp(names)
|
return generateMarkdownHelp(names)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generatePlainHelp(names []string) string {
|
|
||||||
var builder strings.Builder
|
|
||||||
for _, name := range names {
|
|
||||||
help := labelFullHelp[name]
|
|
||||||
fmt.Fprintf(&builder, "%s:\t%s\t%s\n", labelName(name), help.Type, help.Short)
|
|
||||||
}
|
|
||||||
|
|
||||||
// use tabwriter to align the help text
|
|
||||||
buf := new(strings.Builder)
|
|
||||||
w := tabwriter.NewWriter(buf, 0, 8, 0, '\t', tabwriter.AlignRight)
|
|
||||||
fmt.Fprintln(w, builder.String())
|
|
||||||
w.Flush()
|
|
||||||
|
|
||||||
head := "To get more information about a label, use `katenary help-label <name_without_prefix>\ne.g. katenary help-label dependencies\n\n"
|
|
||||||
return head + buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateMarkdownHelp(names []string) string {
|
|
||||||
var builder strings.Builder
|
|
||||||
var maxNameLength, maxDescriptionLength, maxTypeLength int
|
|
||||||
|
|
||||||
max := func(a, b int) int {
|
|
||||||
if a > b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
for _, name := range names {
|
|
||||||
help := labelFullHelp[name]
|
|
||||||
maxNameLength = max(maxNameLength, len(name)+2+len(katenaryLabelPrefix))
|
|
||||||
maxDescriptionLength = max(maxDescriptionLength, len(help.Short))
|
|
||||||
maxTypeLength = max(maxTypeLength, len(help.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(&builder, "%s\n", generateTableHeader(maxNameLength, maxDescriptionLength, maxTypeLength))
|
|
||||||
fmt.Fprintf(&builder, "%s\n", generateTableHeaderSeparator(maxNameLength, maxDescriptionLength, maxTypeLength))
|
|
||||||
|
|
||||||
for _, name := range names {
|
|
||||||
help := labelFullHelp[name]
|
|
||||||
fmt.Fprintf(&builder, "| %-*s | %-*s | %-*s |\n",
|
|
||||||
maxNameLength, "`"+labelName(name)+"`", // enclose in backticks
|
|
||||||
maxDescriptionLength, help.Short,
|
|
||||||
maxTypeLength, help.Type,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateTableHeader(maxNameLength, maxDescriptionLength, maxTypeLength int) string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"| %-*s | %-*s | %-*s |",
|
|
||||||
maxNameLength, "Label name",
|
|
||||||
maxDescriptionLength, "Description",
|
|
||||||
maxTypeLength, "Type",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateTableHeaderSeparator(maxNameLength, maxDescriptionLength, maxTypeLength int) string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"| %s | %s | %s |",
|
|
||||||
strings.Repeat("-", maxNameLength),
|
|
||||||
strings.Repeat("-", maxDescriptionLength),
|
|
||||||
strings.Repeat("-", maxTypeLength),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLabelHelpFor returns the help for a specific label.
|
// GetLabelHelpFor returns the help for a specific label.
|
||||||
func GetLabelHelpFor(labelname string, asMarkdown bool) string {
|
func GetLabelHelpFor(labelname string, asMarkdown bool) string {
|
||||||
help, ok := labelFullHelp[labelname]
|
help, ok := labelFullHelp[labelname]
|
||||||
@@ -202,14 +140,71 @@ func GetLabelHelpFor(labelname string, asMarkdown bool) string {
|
|||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLabelNames returns a sorted list of all katenary label names.
|
func generateMarkdownHelp(names []string) string {
|
||||||
func GetLabelNames() []string {
|
var builder strings.Builder
|
||||||
var names []string
|
var maxNameLength, maxDescriptionLength, maxTypeLength int
|
||||||
for name := range labelFullHelp {
|
|
||||||
names = append(names, name)
|
max := func(a, b int) int {
|
||||||
|
if a > b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
sort.Strings(names)
|
for _, name := range names {
|
||||||
return names
|
help := labelFullHelp[name]
|
||||||
|
maxNameLength = max(maxNameLength, len(name)+2+len(katenaryLabelPrefix))
|
||||||
|
maxDescriptionLength = max(maxDescriptionLength, len(help.Short))
|
||||||
|
maxTypeLength = max(maxTypeLength, len(help.Type))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(&builder, "%s\n", generateTableHeader(maxNameLength, maxDescriptionLength, maxTypeLength))
|
||||||
|
fmt.Fprintf(&builder, "%s\n", generateTableHeaderSeparator(maxNameLength, maxDescriptionLength, maxTypeLength))
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
help := labelFullHelp[name]
|
||||||
|
fmt.Fprintf(&builder, "| %-*s | %-*s | %-*s |\n",
|
||||||
|
maxNameLength, "`"+labelName(name)+"`", // enclose in backticks
|
||||||
|
maxDescriptionLength, help.Short,
|
||||||
|
maxTypeLength, help.Type,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func generatePlainHelp(names []string) string {
|
||||||
|
var builder strings.Builder
|
||||||
|
for _, name := range names {
|
||||||
|
help := labelFullHelp[name]
|
||||||
|
fmt.Fprintf(&builder, "%s:\t%s\t%s\n", labelName(name), help.Type, help.Short)
|
||||||
|
}
|
||||||
|
|
||||||
|
// use tabwriter to align the help text
|
||||||
|
buf := new(strings.Builder)
|
||||||
|
w := tabwriter.NewWriter(buf, 0, 8, 0, '\t', tabwriter.AlignRight)
|
||||||
|
fmt.Fprintln(w, builder.String())
|
||||||
|
w.Flush()
|
||||||
|
|
||||||
|
head := "To get more information about a label, use `katenary help-label <name_without_prefix>\ne.g. katenary help-label dependencies\n\n"
|
||||||
|
return head + buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateTableHeader(maxNameLength, maxDescriptionLength, maxTypeLength int) string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"| %-*s | %-*s | %-*s |",
|
||||||
|
maxNameLength, "Label name",
|
||||||
|
maxDescriptionLength, "Description",
|
||||||
|
maxTypeLength, "Type",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateTableHeaderSeparator(maxNameLength, maxDescriptionLength, maxTypeLength int) string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"| %s | %s | %s |",
|
||||||
|
strings.Repeat("-", maxNameLength),
|
||||||
|
strings.Repeat("-", maxDescriptionLength),
|
||||||
|
strings.Repeat("-", maxTypeLength),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHelpTemplate(asMarkdown bool) string {
|
func getHelpTemplate(asMarkdown bool) string {
|
||||||
@@ -234,3 +229,7 @@ Example:
|
|||||||
{{ .Help.Example }}
|
{{ .Help.Example }}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Prefix() string {
|
||||||
|
return katenaryLabelPrefix
|
||||||
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
# Labels documentation.
|
# Labels documentation.
|
||||||
#
|
#
|
||||||
# To create a label documentation:
|
# To create a label documentation:
|
||||||
#
|
#
|
||||||
# "labelname":
|
# "labelname":
|
||||||
# type: the label type (bool, string, array, object...)
|
# type: the label type (bool, string, array, object...)
|
||||||
# short: a short description
|
# short: a short description
|
||||||
@@ -13,23 +13,23 @@
|
|||||||
# This file is embed in the Katenary binary and parsed in kanetaryLabels.go init() function.
|
# This file is embed in the Katenary binary and parsed in kanetaryLabels.go init() function.
|
||||||
#
|
#
|
||||||
# Note:
|
# Note:
|
||||||
# - The short and long texts are parsed with text/template, so you can use template syntax.
|
# - The short and long texts are parsed with text/template, so you can use template syntax.
|
||||||
# That means that if you want to display double brackets, you need to enclose them to
|
# That means that if you want to display double brackets, you need to enclose them to
|
||||||
# prevent template to try to expand the content, for example :
|
# prevent template to try to expand the content, for example :
|
||||||
# This is an {{ "{{ example }}" }}.
|
# This is an {{ "{{ example }}" }}.
|
||||||
#
|
#
|
||||||
# This will display "This is an {{ exemple }}" in the output.
|
# This will display "This is an {{ exemple }}" in the output.
|
||||||
# - Use {{ .KatenaryPrefix }} to let Katenary replace it with the label prefix (e.g. "katenary.v3")
|
# - Use {{ .KatenaryPrefix }} to let Katenary replace it with the label prefix (e.g. "katenary.v3")
|
||||||
|
|
||||||
"main-app":
|
"main-app":
|
||||||
short: "Mark the service as the main app."
|
short: "Mark the service as the main app."
|
||||||
long: |-
|
long: |-
|
||||||
This makes the service to be the main application. Its image tag is
|
This makes the service to be the main application. Its image tag is
|
||||||
considered to be the
|
considered to be the
|
||||||
|
|
||||||
Chart appVersion and to be the defaultvalue in Pod container
|
Chart appVersion and to be the defaultvalue in Pod container
|
||||||
image attribute.
|
image attribute.
|
||||||
|
|
||||||
!!! Warning
|
!!! Warning
|
||||||
This label cannot be repeated in others services. If this label is
|
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.
|
set in more than one service as true, Katenary will return an error.
|
||||||
@@ -43,17 +43,17 @@
|
|||||||
{{ .KatenaryPrefix }}/main-app: true
|
{{ .KatenaryPrefix }}/main-app: true
|
||||||
type: "bool"
|
type: "bool"
|
||||||
|
|
||||||
"values":
|
"values":
|
||||||
short: "Environment variables to be added to the values.yaml"
|
short: "Environment variables to be added to the values.yaml"
|
||||||
long: |-
|
long: |-
|
||||||
By default, all environment variables in the "env" and environment
|
By default, all environment variables in the "env" and environment
|
||||||
files are added to configmaps with the static values set. This label
|
files are added to configmaps with the static values set. This label
|
||||||
allows adding environment variables to the values.yaml file.
|
allows adding environment variables to the values.yaml file.
|
||||||
|
|
||||||
Note that the value inside the configmap is {{ "{{ tpl vaname . }}" }}, so
|
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
|
you can set the value to a template that will be rendered with the
|
||||||
values.yaml file.
|
values.yaml file.
|
||||||
|
|
||||||
The value can be set with a documentation. This may help to understand
|
The value can be set with a documentation. This may help to understand
|
||||||
the purpose of the variable.
|
the purpose of the variable.
|
||||||
example: |-
|
example: |-
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
|
|
||||||
"secrets":
|
"secrets":
|
||||||
short: "Env vars to be set as secrets."
|
short: "Env vars to be set as secrets."
|
||||||
long: |-
|
long: |-
|
||||||
This label allows setting the environment variables as secrets. The variable
|
This label allows setting the environment variables as secrets. The variable
|
||||||
is removed from the environment and added to a secret object.
|
is removed from the environment and added to a secret object.
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
- 8081
|
- 8081
|
||||||
type: "list of uint32"
|
type: "list of uint32"
|
||||||
|
|
||||||
"ingress":
|
"ingress":
|
||||||
short: "Ingress rules to be added to the service."
|
short: "Ingress rules to be added to the service."
|
||||||
long: |-
|
long: |-
|
||||||
Declare an ingress rule for the service. The port should be exposed or
|
Declare an ingress rule for the service. The port should be exposed or
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
hostname: mywebsite.com (optional)
|
hostname: mywebsite.com (optional)
|
||||||
type: "object"
|
type: "object"
|
||||||
|
|
||||||
"map-env":
|
"map-env":
|
||||||
short: "Map env vars from the service to the deployment."
|
short: "Map env vars from the service to the deployment."
|
||||||
long: |-
|
long: |-
|
||||||
Because you may need to change the variable for Kubernetes, this label
|
Because you may need to change the variable for Kubernetes, this label
|
||||||
@@ -136,8 +136,8 @@
|
|||||||
type: "object"
|
type: "object"
|
||||||
|
|
||||||
"health-check":
|
"health-check":
|
||||||
short: "Health check to be added to the deployment."
|
short: "Health check to be added to the deployment."
|
||||||
long: "Health check to be added to the deployment."
|
long: "Health check to be added to the deployment."
|
||||||
example: |-
|
example: |-
|
||||||
labels:
|
labels:
|
||||||
{{ .KatenaryPrefix }}/health-check: |-
|
{{ .KatenaryPrefix }}/health-check: |-
|
||||||
@@ -146,12 +146,12 @@
|
|||||||
port: 8080
|
port: 8080
|
||||||
type: "object"
|
type: "object"
|
||||||
|
|
||||||
"same-pod":
|
"same-pod":
|
||||||
short: "Move the same-pod deployment to the target deployment."
|
short: "Move the same-pod deployment to the target deployment."
|
||||||
long: |-
|
long: |-
|
||||||
This will make the service to be included in another service pod. Some services
|
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.
|
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
|
Note that volume and VolumeMount are copied from the source to the target
|
||||||
deployment.
|
deployment.
|
||||||
example: |-
|
example: |-
|
||||||
@@ -169,7 +169,7 @@
|
|||||||
long: |-
|
long: |-
|
||||||
This replaces the default comment in values.yaml file to the given description.
|
This replaces the default comment in values.yaml file to the given description.
|
||||||
It is useful to document the service and configuration.
|
It is useful to document the service and configuration.
|
||||||
|
|
||||||
The value can be set with a documentation in multiline format.
|
The value can be set with a documentation in multiline format.
|
||||||
example: |-
|
example: |-
|
||||||
labels:
|
labels:
|
||||||
@@ -179,12 +179,12 @@
|
|||||||
type: "string"
|
type: "string"
|
||||||
|
|
||||||
"ignore":
|
"ignore":
|
||||||
short: "Ignore the service"
|
short: "Ignore the service"
|
||||||
long: "Ingoring a service to not be exported in helm chart."
|
long: "Ingoring a service to not be exported in helm chart."
|
||||||
example: "labels:\n {{ .KatenaryPrefix }}/ignore: \"true\""
|
example: "labels:\n {{ .KatenaryPrefix }}/ignore: \"true\""
|
||||||
type: "bool"
|
type: "bool"
|
||||||
|
|
||||||
"dependencies":
|
"dependencies":
|
||||||
short: "Add Helm dependencies to the service."
|
short: "Add Helm dependencies to the service."
|
||||||
long: |-
|
long: |-
|
||||||
Set the service to be, actually, a Helm dependency. This means that the
|
Set the service to be, actually, a Helm dependency. This means that the
|
||||||
@@ -232,7 +232,7 @@
|
|||||||
service directory.
|
service directory.
|
||||||
|
|
||||||
If it is a directory, all files inside it are added to the ConfigMap.
|
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.
|
If the directory as subdirectories, so one configmap per subpath are created.
|
||||||
|
|
||||||
!!! Warning
|
!!! Warning
|
||||||
@@ -248,11 +248,11 @@
|
|||||||
- ./conf.d
|
- ./conf.d
|
||||||
type: "list of strings"
|
type: "list of strings"
|
||||||
|
|
||||||
"cronjob":
|
"cronjob":
|
||||||
short: "Create a cronjob from the service."
|
short: "Create a cronjob from the service."
|
||||||
long: |-
|
long: |-
|
||||||
This adds a cronjob to the chart.
|
This adds a cronjob to the chart.
|
||||||
|
|
||||||
The label value is a YAML object with the following attributes:
|
The label value is a YAML object with the following attributes:
|
||||||
- command: the command to be executed
|
- command: the command to be executed
|
||||||
- schedule: the cron schedule (cron format or @every where "every" is a
|
- schedule: the cron schedule (cron format or @every where "every" is a
|
||||||
@@ -284,4 +284,5 @@
|
|||||||
# defined inside this service too
|
# defined inside this service too
|
||||||
{{ .KatenaryPrefix }}/env-from: |-
|
{{ .KatenaryPrefix }}/env-from: |-
|
||||||
- myservice1
|
- myservice1
|
||||||
|
|
||||||
# vim: ft=gotmpl.yaml
|
# vim: ft=gotmpl.yaml
|
||||||
|
@@ -4,11 +4,11 @@ import "gopkg.in/yaml.v3"
|
|||||||
|
|
||||||
// Dependency is a dependency of a chart to other charts.
|
// Dependency is a dependency of a chart to other charts.
|
||||||
type Dependency struct {
|
type Dependency struct {
|
||||||
|
Values map[string]any `yaml:"-"`
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Version string `yaml:"version"`
|
Version string `yaml:"version"`
|
||||||
Repository string `yaml:"repository"`
|
Repository string `yaml:"repository"`
|
||||||
Alias string `yaml:"alias,omitempty"`
|
Alias string `yaml:"alias,omitempty"`
|
||||||
Values map[string]any `yaml:"-"` // do not export to Chart.yaml
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DependenciesFrom returns a slice of dependencies from the given string.
|
// DependenciesFrom returns a slice of dependencies from the given string.
|
||||||
|
@@ -3,18 +3,12 @@ package labelStructs
|
|||||||
import "gopkg.in/yaml.v3"
|
import "gopkg.in/yaml.v3"
|
||||||
|
|
||||||
type Ingress struct {
|
type Ingress struct {
|
||||||
// Hostname is the hostname to match against the request. It can contain wildcards.
|
Port *int32 `yaml:"port,omitempty"`
|
||||||
Hostname string `yaml:"hostname"`
|
|
||||||
// Path is the path to match against the request. It can contain wildcards.
|
|
||||||
Path string `yaml:"path"`
|
|
||||||
// Enabled is a flag to enable or disable the ingress.
|
|
||||||
Enabled bool `yaml:"enabled"`
|
|
||||||
// Class is the ingress class to use.
|
|
||||||
Class string `yaml:"class"`
|
|
||||||
// Port is the port to use.
|
|
||||||
Port *int32 `yaml:"port,omitempty"`
|
|
||||||
// Annotations is a list of key-value pairs to add to the ingress.
|
|
||||||
Annotations map[string]string `yaml:"annotations,omitempty"`
|
Annotations map[string]string `yaml:"annotations,omitempty"`
|
||||||
|
Hostname string `yaml:"hostname"`
|
||||||
|
Path string `yaml:"path"`
|
||||||
|
Class string `yaml:"class"`
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IngressFrom creates a new Ingress from a compose service.
|
// IngressFrom creates a new Ingress from a compose service.
|
||||||
|
@@ -102,38 +102,38 @@ type RoleBinding struct {
|
|||||||
service *types.ServiceConfig
|
service *types.ServiceConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RoleBinding) Yaml() ([]byte, error) {
|
|
||||||
return yaml.Marshal(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RoleBinding) Filename() string {
|
func (r *RoleBinding) Filename() string {
|
||||||
return r.service.Name + ".rolebinding.yaml"
|
return r.service.Name + ".rolebinding.yaml"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RoleBinding) Yaml() ([]byte, error) {
|
||||||
|
return yaml.Marshal(r)
|
||||||
|
}
|
||||||
|
|
||||||
// Role is a kubernetes Role.
|
// Role is a kubernetes Role.
|
||||||
type Role struct {
|
type Role struct {
|
||||||
*rbacv1.Role
|
*rbacv1.Role
|
||||||
service *types.ServiceConfig
|
service *types.ServiceConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Role) Yaml() ([]byte, error) {
|
|
||||||
return yaml.Marshal(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Role) Filename() string {
|
func (r *Role) Filename() string {
|
||||||
return r.service.Name + ".role.yaml"
|
return r.service.Name + ".role.yaml"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Role) Yaml() ([]byte, error) {
|
||||||
|
return yaml.Marshal(r)
|
||||||
|
}
|
||||||
|
|
||||||
// ServiceAccount is a kubernetes ServiceAccount.
|
// ServiceAccount is a kubernetes ServiceAccount.
|
||||||
type ServiceAccount struct {
|
type ServiceAccount struct {
|
||||||
*corev1.ServiceAccount
|
*corev1.ServiceAccount
|
||||||
service *types.ServiceConfig
|
service *types.ServiceConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ServiceAccount) Yaml() ([]byte, error) {
|
|
||||||
return yaml.Marshal(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ServiceAccount) Filename() string {
|
func (r *ServiceAccount) Filename() string {
|
||||||
return r.service.Name + ".serviceaccount.yaml"
|
return r.service.Name + ".serviceaccount.yaml"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ServiceAccount) Yaml() ([]byte, error) {
|
||||||
|
return yaml.Marshal(r)
|
||||||
|
}
|
||||||
|
@@ -76,13 +76,6 @@ func NewSecret(service types.ServiceConfig, appName string) *Secret {
|
|||||||
return secret
|
return secret
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetData sets the data of the secret.
|
|
||||||
func (s *Secret) SetData(data map[string]string) {
|
|
||||||
for key, value := range data {
|
|
||||||
s.AddData(key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddData adds a key value pair to the secret.
|
// AddData adds a key value pair to the secret.
|
||||||
func (s *Secret) AddData(key, value string) {
|
func (s *Secret) AddData(key, value string) {
|
||||||
if value == "" {
|
if value == "" {
|
||||||
@@ -91,6 +84,18 @@ func (s *Secret) AddData(key, value string) {
|
|||||||
s.Data[key] = []byte(`{{ tpl ` + value + ` $ | b64enc }}`)
|
s.Data[key] = []byte(`{{ tpl ` + value + ` $ | b64enc }}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filename returns the filename of the secret.
|
||||||
|
func (s *Secret) Filename() string {
|
||||||
|
return s.service.Name + ".secret.yaml"
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetData sets the data of the secret.
|
||||||
|
func (s *Secret) SetData(data map[string]string) {
|
||||||
|
for key, value := range data {
|
||||||
|
s.AddData(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Yaml returns the yaml representation of the secret.
|
// Yaml returns the yaml representation of the secret.
|
||||||
func (s *Secret) Yaml() ([]byte, error) {
|
func (s *Secret) Yaml() ([]byte, error) {
|
||||||
y, err := yaml.Marshal(s)
|
y, err := yaml.Marshal(s)
|
||||||
@@ -106,8 +111,3 @@ func (s *Secret) Yaml() ([]byte, error) {
|
|||||||
|
|
||||||
return y, nil
|
return y, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filename returns the filename of the secret.
|
|
||||||
func (s *Secret) Filename() string {
|
|
||||||
return s.service.Name + ".secret.yaml"
|
|
||||||
}
|
|
||||||
|
@@ -74,6 +74,11 @@ func (s *Service) AddPort(port types.ServicePortConfig, serviceName ...string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filename returns the filename of the service.
|
||||||
|
func (s *Service) Filename() string {
|
||||||
|
return s.service.Name + ".service.yaml"
|
||||||
|
}
|
||||||
|
|
||||||
// Yaml returns the yaml representation of the service.
|
// Yaml returns the yaml representation of the service.
|
||||||
func (s *Service) Yaml() ([]byte, error) {
|
func (s *Service) Yaml() ([]byte, error) {
|
||||||
y, err := yaml.Marshal(s)
|
y, err := yaml.Marshal(s)
|
||||||
@@ -88,8 +93,3 @@ func (s *Service) Yaml() ([]byte, error) {
|
|||||||
|
|
||||||
return y, err
|
return y, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filename returns the filename of the service.
|
|
||||||
func (s *Service) Filename() string {
|
|
||||||
return s.service.Name + ".service.yaml"
|
|
||||||
}
|
|
||||||
|
@@ -43,14 +43,6 @@ type Value struct {
|
|||||||
ServiceAccount string `yaml:"serviceAccount"`
|
ServiceAccount string `yaml:"serviceAccount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CronJobValue is a cronjob configuration that will be saved in values.yaml.
|
|
||||||
type CronJobValue struct {
|
|
||||||
Repository *RepositoryValue `yaml:"repository,omitempty"`
|
|
||||||
Environment map[string]any `yaml:"environment,omitempty"`
|
|
||||||
ImagePullPolicy string `yaml:"imagePullPolicy,omitempty"`
|
|
||||||
Schedule string `yaml:"schedule"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewValue creates a new Value from a compose service.
|
// NewValue creates a new Value from a compose service.
|
||||||
// The value contains the necessary information to deploy the service (image, tag, replicas, etc.).
|
// The value contains the necessary information to deploy the service (image, tag, replicas, etc.).
|
||||||
//
|
//
|
||||||
@@ -64,15 +56,22 @@ func NewValue(service types.ServiceConfig, main ...bool) *Value {
|
|||||||
|
|
||||||
// find the image tag
|
// find the image tag
|
||||||
tag := ""
|
tag := ""
|
||||||
|
|
||||||
split := strings.Split(service.Image, ":")
|
split := strings.Split(service.Image, ":")
|
||||||
v.Repository = &RepositoryValue{
|
if len(split) == 1 {
|
||||||
Image: split[0],
|
v.Repository = &RepositoryValue{
|
||||||
|
Image: service.Image,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v.Repository = &RepositoryValue{
|
||||||
|
Image: strings.Join(split[:len(split)-1], ":"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// for main service, the tag should the appVersion. So here we set it to empty.
|
// for main service, the tag should the appVersion. So here we set it to empty.
|
||||||
if len(main) > 0 && !main[0] {
|
if len(main) > 0 && !main[0] {
|
||||||
if len(split) > 1 {
|
if len(split) > 1 {
|
||||||
tag = split[1]
|
tag = split[len(split)-1]
|
||||||
}
|
}
|
||||||
v.Repository.Tag = tag
|
v.Repository.Tag = tag
|
||||||
} else {
|
} else {
|
||||||
@@ -82,6 +81,15 @@ func NewValue(service types.ServiceConfig, main ...bool) *Value {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Value) AddIngress(host, path string) {
|
||||||
|
v.Ingress = &IngressValue{
|
||||||
|
Enabled: true,
|
||||||
|
Host: host,
|
||||||
|
Path: path,
|
||||||
|
Class: "-",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// AddPersistence adds persistence configuration to the Value.
|
// AddPersistence adds persistence configuration to the Value.
|
||||||
func (v *Value) AddPersistence(volumeName string) {
|
func (v *Value) AddPersistence(volumeName string) {
|
||||||
if v.Persistence == nil {
|
if v.Persistence == nil {
|
||||||
@@ -95,11 +103,10 @@ func (v *Value) AddPersistence(volumeName string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Value) AddIngress(host, path string) {
|
// CronJobValue is a cronjob configuration that will be saved in values.yaml.
|
||||||
v.Ingress = &IngressValue{
|
type CronJobValue struct {
|
||||||
Enabled: true,
|
Repository *RepositoryValue `yaml:"repository,omitempty"`
|
||||||
Host: host,
|
Environment map[string]any `yaml:"environment,omitempty"`
|
||||||
Path: path,
|
ImagePullPolicy string `yaml:"imagePullPolicy,omitempty"`
|
||||||
Class: "-",
|
Schedule string `yaml:"schedule"`
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -12,10 +12,10 @@ import (
|
|||||||
"katenary/utils"
|
"katenary/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Yaml = (*VolumeClaim)(nil)
|
|
||||||
|
|
||||||
const persistenceKey = "persistence"
|
const persistenceKey = "persistence"
|
||||||
|
|
||||||
|
var _ Yaml = (*VolumeClaim)(nil)
|
||||||
|
|
||||||
// VolumeClaim is a kubernetes VolumeClaim. This is a PersistentVolumeClaim.
|
// VolumeClaim is a kubernetes VolumeClaim. This is a PersistentVolumeClaim.
|
||||||
type VolumeClaim struct {
|
type VolumeClaim struct {
|
||||||
*v1.PersistentVolumeClaim
|
*v1.PersistentVolumeClaim
|
||||||
@@ -59,6 +59,11 @@ func NewVolumeClaim(service types.ServiceConfig, volumeName, appName string) *Vo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filename returns the suggested filename for a VolumeClaim.
|
||||||
|
func (v *VolumeClaim) Filename() string {
|
||||||
|
return v.service.Name + "." + v.volumeName + ".volumeclaim.yaml"
|
||||||
|
}
|
||||||
|
|
||||||
// Yaml marshals a VolumeClaim into yaml.
|
// Yaml marshals a VolumeClaim into yaml.
|
||||||
func (v *VolumeClaim) Yaml() ([]byte, error) {
|
func (v *VolumeClaim) Yaml() ([]byte, error) {
|
||||||
serviceName := v.service.Name
|
serviceName := v.service.Name
|
||||||
@@ -122,8 +127,3 @@ func (v *VolumeClaim) Yaml() ([]byte, error) {
|
|||||||
|
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filename returns the suggested filename for a VolumeClaim.
|
|
||||||
func (v *VolumeClaim) Filename() string {
|
|
||||||
return v.service.Name + "." + v.volumeName + ".volumeclaim.yaml"
|
|
||||||
}
|
|
||||||
|
@@ -2,6 +2,9 @@
|
|||||||
package parser
|
package parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/cli"
|
"github.com/compose-spec/compose-go/cli"
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
)
|
)
|
||||||
@@ -12,6 +15,7 @@ func init() {
|
|||||||
"compose.katenary.yml",
|
"compose.katenary.yml",
|
||||||
"compose.katenary.yaml",
|
"compose.katenary.yaml",
|
||||||
}, cli.DefaultOverrideFileNames...)
|
}, cli.DefaultOverrideFileNames...)
|
||||||
|
// add podman-compose files
|
||||||
cli.DefaultOverrideFileNames = append(cli.DefaultOverrideFileNames,
|
cli.DefaultOverrideFileNames = append(cli.DefaultOverrideFileNames,
|
||||||
[]string{
|
[]string{
|
||||||
"podman-compose.katenary.yml",
|
"podman-compose.katenary.yml",
|
||||||
@@ -22,18 +26,31 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse compose files and return a project. The project is parsed with dotenv, osenv and profiles.
|
// Parse compose files and return a project. The project is parsed with dotenv, osenv and profiles.
|
||||||
func Parse(profiles []string, dockerComposeFile ...string) (*types.Project, error) {
|
func Parse(profiles []string, envFiles []string, dockerComposeFile ...string) (*types.Project, error) {
|
||||||
if len(dockerComposeFile) == 0 {
|
if len(dockerComposeFile) == 0 {
|
||||||
cli.DefaultOverrideFileNames = append(cli.DefaultOverrideFileNames, dockerComposeFile...)
|
cli.DefaultOverrideFileNames = append(cli.DefaultOverrideFileNames, dockerComposeFile...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Println("Loading compose files: ", cli.DefaultOverrideFileNames)
|
||||||
|
|
||||||
|
// resolve absolute paths of envFiles
|
||||||
|
for i := range envFiles {
|
||||||
|
var err error
|
||||||
|
envFiles[i], err = filepath.Abs(envFiles[i])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Println("Loading env files: ", envFiles)
|
||||||
|
|
||||||
options, err := cli.NewProjectOptions(nil,
|
options, err := cli.NewProjectOptions(nil,
|
||||||
cli.WithProfiles(profiles),
|
cli.WithProfiles(profiles),
|
||||||
|
cli.WithInterpolation(true),
|
||||||
cli.WithDefaultConfigPath,
|
cli.WithDefaultConfigPath,
|
||||||
|
cli.WithEnvFiles(envFiles...),
|
||||||
cli.WithOsEnv,
|
cli.WithOsEnv,
|
||||||
cli.WithDotEnv,
|
cli.WithDotEnv,
|
||||||
cli.WithNormalization(true),
|
cli.WithNormalization(true),
|
||||||
cli.WithInterpolation(true),
|
|
||||||
cli.WithResolvedPaths(false),
|
cli.WithResolvedPaths(false),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -20,6 +20,11 @@ func TplName(serviceName, appname string, suffix ...string) string {
|
|||||||
if len(suffix) > 0 {
|
if len(suffix) > 0 {
|
||||||
suffix[0] = "-" + suffix[0]
|
suffix[0] = "-" + suffix[0]
|
||||||
}
|
}
|
||||||
|
for i, s := range suffix {
|
||||||
|
// replae all "_" with "-"
|
||||||
|
suffix[i] = strings.ReplaceAll(s, "_", "-")
|
||||||
|
}
|
||||||
|
serviceName = strings.ReplaceAll(serviceName, "_", "-")
|
||||||
return `{{ include "` + appname + `.fullname" . }}-` + serviceName + strings.Join(suffix, "-")
|
return `{{ include "` + appname + `.fullname" . }}-` + serviceName + strings.Join(suffix, "-")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,8 +114,9 @@ func PathToName(path string) string {
|
|||||||
if path[0] == '/' || path[0] == '.' {
|
if path[0] == '/' || path[0] == '.' {
|
||||||
path = path[1:]
|
path = path[1:]
|
||||||
}
|
}
|
||||||
path = strings.ReplaceAll(path, "/", "_")
|
path = strings.ReplaceAll(path, "_", "-")
|
||||||
path = strings.ReplaceAll(path, ".", "_")
|
path = strings.ReplaceAll(path, "/", "-")
|
||||||
|
path = strings.ReplaceAll(path, ".", "-")
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user