diff --git a/cmd/katenary/main.go b/cmd/katenary/main.go
index 3f84d90..3243a6f 100644
--- a/cmd/katenary/main.go
+++ b/cmd/katenary/main.go
@@ -9,11 +9,11 @@ import (
"os"
"strings"
- "katenary/generator"
- "katenary/utils"
-
"github.com/compose-spec/compose-go/cli"
"github.com/spf13/cobra"
+
+ "katenary/generator"
+ "katenary/utils"
)
const longHelp = `Katenary is a tool to convert compose files to Helm Charts.
diff --git a/doc/docs/packages/cmd/katenary.md b/doc/docs/packages/cmd/katenary.md
deleted file mode 100644
index 9261889..0000000
--- a/doc/docs/packages/cmd/katenary.md
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-# katenary
-
-```go
-import "katenary/cmd/katenary"
-```
-
-Katenary CLI, main package.
-
-This package is not intended to be imported. It contains the main function that build the command line with \`cobra\` package.
-
diff --git a/doc/docs/packages/generator.md b/doc/docs/packages/generator.md
index 7e9b482..924a942 100644
--- a/doc/docs/packages/generator.md
+++ b/doc/docs/packages/generator.md
@@ -35,7 +35,7 @@ var Version = "master" // changed at compile time
```
-## func [Convert]()
+## func [Convert]()
```go
func Convert(config ConvertOptions, dockerComposeFile ...string)
@@ -116,16 +116,14 @@ func Prefix() string
-## type [ChartTemplate]()
+## type [ChartTemplate]()
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.
-TODO: maybe we can set it private.
-
```go
type ChartTemplate struct {
- Content []byte
Servicename string
+ Content []byte
}
```
@@ -151,25 +149,25 @@ func NewConfigMap(service types.ServiceConfig, appName string) *ConfigMap
NewConfigMap creates a new ConfigMap from a compose service. The appName is the name of the application taken from the project name. The ConfigMap is filled by environment variables and labels "map\-env".
-### func [NewConfigMapFromDirectory]()
+### func [NewConfigMapFromDirectory]()
```go
-func NewConfigMapFromDirectory(service types.ServiceConfig, appName string, path string) *ConfigMap
+func NewConfigMapFromDirectory(service types.ServiceConfig, appName, path string) *ConfigMap
```
NewConfigMapFromDirectory creates a new ConfigMap from a compose service. This path is the path to the file or directory. If the path is a directory, all files in the directory are added to the ConfigMap. Each subdirectory are ignored. Note that the Generate\(\) function will create the subdirectories ConfigMaps.
-### func \(\*ConfigMap\) [AddData]()
+### func \(\*ConfigMap\) [AddData]()
```go
-func (c *ConfigMap) AddData(key string, value string)
+func (c *ConfigMap) AddData(key, value string)
```
AddData adds a key value pair to the configmap. Append or overwrite the value if the key already exists.
-### func \(\*ConfigMap\) [AppendDir]()
+### func \(\*ConfigMap\) [AppendDir]()
```go
func (c *ConfigMap) AppendDir(path string)
@@ -178,7 +176,7 @@ func (c *ConfigMap) AppendDir(path string)
AddFile adds files from given path to the configmap. It is not recursive, to add all files in a directory, you need to call this function for each subdirectory.
-### func \(\*ConfigMap\) [AppendFile]()
+### func \(\*ConfigMap\) [AppendFile]()
```go
func (c *ConfigMap) AppendFile(path string)
@@ -187,7 +185,7 @@ func (c *ConfigMap) AppendFile(path string)
-### func \(\*ConfigMap\) [Filename]()
+### func \(\*ConfigMap\) [Filename]()
```go
func (c *ConfigMap) Filename() string
@@ -196,7 +194,7 @@ func (c *ConfigMap) Filename() string
Filename returns the filename of the configmap. If the configmap is used for files, the filename contains the path.
-### func \(\*ConfigMap\) [SetData]()
+### func \(\*ConfigMap\) [SetData]()
```go
func (c *ConfigMap) SetData(data map[string]string)
@@ -205,7 +203,7 @@ func (c *ConfigMap) SetData(data map[string]string)
SetData sets the data of the configmap. It replaces the entire data.
-### func \(\*ConfigMap\) [Yaml]()
+### func \(\*ConfigMap\) [Yaml]()
```go
func (c *ConfigMap) Yaml() ([]byte, error)
@@ -225,18 +223,18 @@ type ConfigMapMount struct {
```
-## type [ConvertOptions]()
+## type [ConvertOptions]()
ConvertOptions are the options to convert a compose project to a helm chart.
```go
type ConvertOptions struct {
- Force bool // Force the chart directory deletion if it already exists.
- OutputDir string // The output directory of the chart.
- Profiles []string // Profile to use for the conversion.
- HelmUpdate bool // If true, the "helm dep update" command will be run after the chart generation.
- AppVersion *string // Set the chart "appVersion" field. If nil, the version will be set to 0.1.0.
- ChartVersion string // Set the chart "version" field.
+ AppVersion *string
+ OutputDir string
+ ChartVersion string
+ Profiles []string
+ Force bool
+ HelmUpdate bool
}
```
@@ -275,7 +273,7 @@ Yaml returns the yaml representation of the cronjob.
Implements the Yaml interface.
-## type [CronJobValue]()
+## type [CronJobValue]()
CronJobValue is a cronjob configuration that will be saved in values.yaml.
@@ -304,7 +302,7 @@ type DataMap interface {
### func [NewFileMap]()
```go
-func NewFileMap(service types.ServiceConfig, appName string, kind string) DataMap
+func NewFileMap(service types.ServiceConfig, appName, kind string) DataMap
```
NewFileMap creates a new DataMap from a compose service. The appName is the name of the application taken from the project name.
@@ -340,7 +338,7 @@ func (d *Deployment) AddContainer(service types.ServiceConfig)
AddContainer adds a container to the deployment.
-### func \(\*Deployment\) [AddHealthCheck]()
+### func \(\*Deployment\) [AddHealthCheck]()
```go
func (d *Deployment) AddHealthCheck(service types.ServiceConfig, container *corev1.Container)
@@ -367,7 +365,7 @@ func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string)
AddVolumes adds a volume to the deployment. It does not create the PVC, it only adds the volumes to the deployment. If the volume is a bind volume it will warn the user that it is not supported yet.
-### func \(\*Deployment\) [BindFrom]()
+### func \(\*Deployment\) [BindFrom]()
```go
func (d *Deployment) BindFrom(service types.ServiceConfig, binded *Deployment)
@@ -385,7 +383,7 @@ func (d *Deployment) DependsOn(to *Deployment, servicename string) error
DependsOn adds a initContainer to the deployment that will wait for the service to be up.
-### func \(\*Deployment\) [Filename]()
+### func \(\*Deployment\) [Filename]()
```go
func (d *Deployment) Filename() string
@@ -394,7 +392,7 @@ func (d *Deployment) Filename() string
Filename returns the filename of the deployment.
-### func \(\*Deployment\) [SetEnvFrom]()
+### func \(\*Deployment\) [SetEnvFrom]()
```go
func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string)
@@ -403,7 +401,7 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string)
SetEnvFrom sets the environment variables to a configmap. The configmap is created.
-### func \(\*Deployment\) [Yaml]()
+### func \(\*Deployment\) [Yaml]()
```go
func (d *Deployment) Yaml() ([]byte, error)
@@ -430,28 +428,29 @@ const (
```
-## type [HelmChart]()
+## type [HelmChart]()
HelmChart is a Helm Chart representation. It contains all the tempaltes, values, versions, helpers...
```go
type HelmChart struct {
+ Templates map[string]*ChartTemplate `yaml:"-"`
+ Values map[string]any `yaml:"-"`
+ VolumeMounts map[string]any `yaml:"-"`
+
Name string `yaml:"name"`
ApiVersion string `yaml:"apiVersion"`
Version string `yaml:"version"`
AppVersion string `yaml:"appVersion"`
Description string `yaml:"description"`
+ Helper string `yaml:"-"`
Dependencies []labelStructs.Dependency `yaml:"dependencies,omitempty"`
- Templates map[string]*ChartTemplate `yaml:"-"` // do not export to yaml
- Helper string `yaml:"-"` // do not export to yaml
- Values map[string]any `yaml:"-"` // do not export to yaml
- VolumeMounts map[string]any `yaml:"-"` // do not export to yaml
// contains filtered or unexported fields
}
```
-### func [Generate]()
+### func [Generate]()
```go
func Generate(project *types.Project) (*HelmChart, error)
@@ -471,7 +470,7 @@ The Generate function will create the HelmChart object this way:
- Merge the same\-pod services.
-### func [NewChart]()
+### func [NewChart]()
```go
func NewChart(name string) *HelmChart
@@ -479,6 +478,15 @@ func NewChart(name string) *HelmChart
NewChart creates a new empty chart with the given name.
+
+### func \(\*HelmChart\) [SaveTemplates]()
+
+```go
+func (chart *HelmChart) SaveTemplates(templateDir string)
+```
+
+SaveTemplates the templates of the chart to the given directory.
+
## type [Help]()
@@ -533,17 +541,17 @@ func (ingress *Ingress) Yaml() ([]byte, error)
-## type [IngressValue]()
+## type [IngressValue]()
IngressValue is a ingress configuration that will be saved in values.yaml.
```go
type IngressValue struct {
- Enabled bool `yaml:"enabled"`
+ Annotations map[string]string `yaml:"annotations"`
Host string `yaml:"host"`
Path string `yaml:"path"`
Class string `yaml:"class"`
- Annotations map[string]string `yaml:"annotations"`
+ Enabled bool `yaml:"enabled"`
}
```
@@ -578,16 +586,16 @@ const (
```
-## type [PersistenceValue]()
+## type [PersistenceValue]()
PersistenceValue is a persistence configuration that will be saved in values.yaml.
```go
type PersistenceValue struct {
- Enabled bool `yaml:"enabled"`
StorageClass string `yaml:"storageClass"`
Size string `yaml:"size"`
AccessMode []string `yaml:"accessMode"`
+ Enabled bool `yaml:"enabled"`
}
```
@@ -614,7 +622,7 @@ func NewRBAC(service types.ServiceConfig, appName string) *RBAC
NewRBAC creates a new RBAC from a compose service. The appName is the name of the application taken from the project name.
-## type [RepositoryValue]()
+## type [RepositoryValue]()
RepositoryValue is a docker repository image and tag that will be saved in values.yaml.
@@ -712,7 +720,7 @@ NewSecret creates a new Secret from a compose service
### func \(\*Secret\) [AddData]()
```go
-func (s *Secret) AddData(key string, value string)
+func (s *Secret) AddData(key, value string)
```
AddData adds a key value pair to the secret.
@@ -823,7 +831,7 @@ func (r *ServiceAccount) Yaml() ([]byte, error)
-## type [Value]()
+## type [Value]()
Value will be saved in values.yaml. It contains configuraiton for all deployment and services.
@@ -832,18 +840,18 @@ type Value struct {
Repository *RepositoryValue `yaml:"repository,omitempty"`
Persistence map[string]*PersistenceValue `yaml:"persistence,omitempty"`
Ingress *IngressValue `yaml:"ingress,omitempty"`
- ImagePullPolicy string `yaml:"imagePullPolicy,omitempty"`
Environment map[string]any `yaml:"environment,omitempty"`
Replicas *uint32 `yaml:"replicas,omitempty"`
CronJob *CronJobValue `yaml:"cronjob,omitempty"`
NodeSelector map[string]string `yaml:"nodeSelector"`
- ServiceAccount string `yaml:"serviceAccount"`
Resources map[string]any `yaml:"resources"`
+ ImagePullPolicy string `yaml:"imagePullPolicy,omitempty"`
+ ServiceAccount string `yaml:"serviceAccount"`
}
```
-### func [NewValue]()
+### func [NewValue]()
```go
func NewValue(service types.ServiceConfig, main ...bool) *Value
@@ -854,7 +862,7 @@ NewValue creates a new Value from a compose service. The value contains the nece
If \`main\` is true, the tag will be empty because it will be set in the helm chart appVersion.
-### func \(\*Value\) [AddIngress]()
+### func \(\*Value\) [AddIngress]()
```go
func (v *Value) AddIngress(host, path string)
@@ -863,7 +871,7 @@ func (v *Value) AddIngress(host, path string)
-### func \(\*Value\) [AddPersistence]()
+### func \(\*Value\) [AddPersistence]()
```go
func (v *Value) AddPersistence(volumeName string)
@@ -872,7 +880,7 @@ func (v *Value) AddPersistence(volumeName string)
AddPersistence adds persistence configuration to the Value.
-## type [VolumeClaim]()
+## type [VolumeClaim]()
VolumeClaim is a kubernetes VolumeClaim. This is a PersistentVolumeClaim.
@@ -884,7 +892,7 @@ type VolumeClaim struct {
```
-### func [NewVolumeClaim]()
+### func [NewVolumeClaim]()
```go
func NewVolumeClaim(service types.ServiceConfig, volumeName, appName string) *VolumeClaim
@@ -893,7 +901,7 @@ func NewVolumeClaim(service types.ServiceConfig, volumeName, appName string) *Vo
NewVolumeClaim creates a new VolumeClaim from a compose service.
-### func \(\*VolumeClaim\) [Filename]()
+### func \(\*VolumeClaim\) [Filename]()
```go
func (v *VolumeClaim) Filename() string
@@ -902,7 +910,7 @@ func (v *VolumeClaim) Filename() string
Filename returns the suggested filename for a VolumeClaim.
-### func \(\*VolumeClaim\) [Yaml]()
+### func \(\*VolumeClaim\) [Yaml]()
```go
func (v *VolumeClaim) Yaml() ([]byte, error)
diff --git a/doc/docs/packages/generator/extrafiles.md b/doc/docs/packages/generator/extrafiles.md
index aa76efe..123cd95 100644
--- a/doc/docs/packages/generator/extrafiles.md
+++ b/doc/docs/packages/generator/extrafiles.md
@@ -17,7 +17,7 @@ func NotesFile(services []string) string
NotesFile returns the content of the note.txt file.
-## func [ReadMeFile]()
+## func [ReadMeFile]()
```go
func ReadMeFile(charname, description string, values map[string]any) string
diff --git a/doc/docs/packages/generator/labelStructs.md b/doc/docs/packages/generator/labelStructs.md
index 8a2d547..4b86499 100644
--- a/doc/docs/packages/generator/labelStructs.md
+++ b/doc/docs/packages/generator/labelStructs.md
@@ -158,7 +158,7 @@ func PortsFrom(data string) (Ports, error)
PortsFrom returns a Ports from the given string.
-## type [Probe]()
+## type [Probe]()
@@ -170,7 +170,7 @@ type Probe struct {
```
-### func [ProbeFrom]()
+### func [ProbeFrom]()
```go
func ProbeFrom(data string) (*Probe, error)
diff --git a/doc/docs/packages/utils.md b/doc/docs/packages/utils.md
index 35f5e0c..bb31f13 100644
--- a/doc/docs/packages/utils.md
+++ b/doc/docs/packages/utils.md
@@ -8,7 +8,16 @@ import "katenary/utils"
Utils package provides some utility functions used in katenary. It defines some constants and functions used in the whole project.
-## func [CountStartingSpaces]()
+## func [Confirm]()
+
+```go
+func Confirm(question string, icon ...Icon) bool
+```
+
+Confirm asks a question and returns true if the answer is y.
+
+
+## func [CountStartingSpaces]()
```go
func CountStartingSpaces(line string) int
@@ -16,8 +25,17 @@ func CountStartingSpaces(line string) int
CountStartingSpaces counts the number of spaces at the beginning of a string.
+
+## func [EncodeBasicYaml]()
+
+```go
+func EncodeBasicYaml(data any) ([]byte, error)
+```
+
+EncodeBasicYaml encodes a basic yaml from an interface.
+
-## func [GetContainerByName]()
+## func [GetContainerByName]()
```go
func GetContainerByName(name string, containers []corev1.Container) (*corev1.Container, int)
@@ -26,7 +44,7 @@ func GetContainerByName(name string, containers []corev1.Container) (*corev1.Con
GetContainerByName returns a container by name and its index in the array. It returns nil, \-1 if not found.
-## func [GetKind]()
+## func [GetKind]()
```go
func GetKind(path string) (kind string)
@@ -35,7 +53,7 @@ func GetKind(path string) (kind string)
GetKind returns the kind of the resource from the file path.
-## func [GetServiceNameByPort]()
+## func [GetServiceNameByPort]()
```go
func GetServiceNameByPort(port int) string
@@ -44,7 +62,7 @@ func GetServiceNameByPort(port int) string
GetServiceNameByPort returns the service name for a port. It the service name is not found, it returns an empty string.
-## func [GetValuesFromLabel]()
+## func [GetValuesFromLabel]()
```go
func GetValuesFromLabel(service types.ServiceConfig, LabelValues string) map[string]*EnvConfig
@@ -62,7 +80,7 @@ func HashComposefiles(files []string) (string, error)
HashComposefiles returns a hash of the compose files.
-## func [Int32Ptr]()
+## func [Int32Ptr]()
```go
func Int32Ptr(i int32) *int32
@@ -71,7 +89,7 @@ func Int32Ptr(i int32) *int32
Int32Ptr returns a pointer to an int32.
-## func [MapKeys]()
+## func [MapKeys]()
```go
func MapKeys(m map[string]interface{}) []string
@@ -80,7 +98,7 @@ func MapKeys(m map[string]interface{}) []string
-## func [PathToName]()
+## func [PathToName]()
```go
func PathToName(path string) string
@@ -89,7 +107,7 @@ func PathToName(path string) string
PathToName converts a path to a kubernetes complient name.
-## func [StrPtr]()
+## func [StrPtr]()
```go
func StrPtr(s string) *string
@@ -98,7 +116,7 @@ func StrPtr(s string) *string
StrPtr returns a pointer to a string.
-## func [TplName]()
+## func [TplName]()
```go
func TplName(serviceName, appname string, suffix ...string) string
@@ -107,7 +125,7 @@ func TplName(serviceName, appname string, suffix ...string) string
TplName returns the name of the kubernetes resource as a template string. It is used in the templates and defined in \_helper.tpl file.
-## func [TplValue]()
+## func [TplValue]()
```go
func TplValue(serviceName, variable string, pipes ...string) string
@@ -125,7 +143,7 @@ func Warn(msg ...interface{})
Warn prints a warning message
-## func [WordWrap]()
+## func [WordWrap]()
```go
func WordWrap(text string, lineWidth int) string
@@ -134,7 +152,7 @@ func WordWrap(text string, lineWidth int) string
WordWrap wraps a string to a given line width. Warning: it may break the string. You need to check the result.
-## func [Wrap]()
+## func [Wrap]()
```go
func Wrap(src, above, below string) string
@@ -143,7 +161,7 @@ func Wrap(src, above, below string) string
Wrap wraps a string with a string above and below. It will respect the indentation of the src string.
-## func [WrapBytes]()
+## func [WrapBytes]()
```go
func WrapBytes(src, above, below []byte) []byte
@@ -152,14 +170,14 @@ func WrapBytes(src, above, below []byte) []byte
WrapBytes wraps a byte array with a byte array above and below. It will respect the indentation of the src string.
-## type [EnvConfig]()
+## type [EnvConfig]()
EnvConfig is a struct to hold the description of an environment variable.
```go
type EnvConfig struct {
- Description string
Service types.ServiceConfig
+ Description string
}
```
diff --git a/doc/docs/statics/main.css b/doc/docs/statics/main.css
index 2394786..36cfaca 100644
--- a/doc/docs/statics/main.css
+++ b/doc/docs/statics/main.css
@@ -77,11 +77,6 @@ h3[id*="katenaryio"] {
}
/*Zoomable images*/
-
-/*[data-md-color-scheme="slate"] #logo {
- background-image: url("logo-bright.svg");
-}*/
-
.zoomable svg {
background-color: var(--md-default-bg-color);
padding: 1rem;
diff --git a/generator/chart.go b/generator/chart.go
index 3e5904f..e9a13fa 100644
--- a/generator/chart.go
+++ b/generator/chart.go
@@ -1,30 +1,46 @@
package generator
-import "katenary/generator/labelStructs"
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "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.
// This is used internally to generate the templates.
-//
-// TODO: maybe we can set it private.
type ChartTemplate struct {
- Content []byte
Servicename string
+ Content []byte
}
// HelmChart is a Helm Chart representation. It contains all the
// tempaltes, values, versions, helpers...
type HelmChart struct {
+ Templates map[string]*ChartTemplate `yaml:"-"`
+ Values map[string]any `yaml:"-"`
+ VolumeMounts map[string]any `yaml:"-"`
+ composeHash *string `yaml:"-"`
Name string `yaml:"name"`
ApiVersion string `yaml:"apiVersion"`
Version string `yaml:"version"`
AppVersion string `yaml:"appVersion"`
Description string `yaml:"description"`
+ Helper string `yaml:"-"`
Dependencies []labelStructs.Dependency `yaml:"dependencies,omitempty"`
- Templates map[string]*ChartTemplate `yaml:"-"` // do not export to yaml
- Helper string `yaml:"-"` // do not export to yaml
- Values map[string]any `yaml:"-"` // do not export to yaml
- VolumeMounts map[string]any `yaml:"-"` // do not export to yaml
- composeHash *string `yaml:"-"` // do not export to yaml
}
// NewChart creates a new empty chart with the given name.
@@ -42,12 +58,59 @@ func NewChart(name string) *HelmChart {
}
}
-// ConvertOptions are the options to convert a compose project to a helm chart.
-type ConvertOptions struct {
- Force bool // Force the chart directory deletion if it already exists.
- OutputDir string // The output directory of the chart.
- Profiles []string // Profile to use for the conversion.
- HelmUpdate bool // If true, the "helm dep update" command will be run after the chart generation.
- AppVersion *string // Set the chart "appVersion" field. If nil, the version will be set to 0.1.0.
- ChartVersion string // Set the chart "version" field.
+// SaveTemplates the templates of the chart to the given directory.
+func (chart *HelmChart) SaveTemplates(templateDir string) {
+ for name, template := range chart.Templates {
+ t := template.Content
+ t = removeNewlinesInsideBrackets(t)
+ t = removeUnwantedLines(t)
+ t = addModeline(t)
+
+ kind := utils.GetKind(name)
+ var icon utils.Icon
+ switch kind {
+ case "deployment":
+ icon = utils.IconPackage
+ case "service":
+ icon = utils.IconPlug
+ case "ingress":
+ icon = utils.IconWorld
+ case "volumeclaim":
+ icon = utils.IconCabinet
+ case "configmap":
+ icon = utils.IconConfig
+ case "secret":
+ icon = utils.IconSecret
+ default:
+ icon = utils.IconInfo
+ }
+
+ servicename := template.Servicename
+ if err := os.MkdirAll(filepath.Join(templateDir, servicename), 0o755); err != nil {
+ fmt.Println(utils.IconFailure, err)
+ os.Exit(1)
+ }
+ fmt.Println(icon, "Creating", kind, servicename)
+ // if the name is a path, create the directory
+ if strings.Contains(name, string(filepath.Separator)) {
+ name = filepath.Join(templateDir, name)
+ err := os.MkdirAll(filepath.Dir(name), 0o755)
+ if err != nil {
+ fmt.Println(utils.IconFailure, err)
+ os.Exit(1)
+ }
+ } else {
+ // remove the serivce name from the template name
+ name = strings.Replace(name, servicename+".", "", 1)
+ name = filepath.Join(templateDir, servicename, name)
+ }
+ f, err := os.Create(name)
+ if err != nil {
+ fmt.Println(utils.IconFailure, err)
+ os.Exit(1)
+ }
+
+ f.Write(t)
+ f.Close()
+ }
}
diff --git a/generator/configMap.go b/generator/configMap.go
index 66dca6a..5b726d4 100644
--- a/generator/configMap.go
+++ b/generator/configMap.go
@@ -7,13 +7,13 @@ import (
"regexp"
"strings"
- "katenary/generator/labelStructs"
- "katenary/utils"
-
"github.com/compose-spec/compose-go/types"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
+
+ "katenary/generator/labelStructs"
+ "katenary/utils"
)
// only used to check interface implementation
@@ -23,7 +23,7 @@ var (
)
// 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 string, kind string) DataMap {
+func NewFileMap(service types.ServiceConfig, appName, kind string) DataMap {
switch kind {
case "configmap":
return NewConfigMap(service, appName)
@@ -47,8 +47,8 @@ const (
type ConfigMap struct {
*corev1.ConfigMap
service *types.ServiceConfig
- usage FileMapUsage
path string
+ usage FileMapUsage
}
// NewConfigMap creates a new ConfigMap from a compose service. The appName is the name of the application taken from the project name.
@@ -75,13 +75,13 @@ func NewConfigMap(service types.ServiceConfig, appName string) *ConfigMap {
}
// get the secrets from the labels
- if secrets, err := labelStructs.SecretsFrom(service.Labels[LabelSecrets]); err != nil {
+ secrets, err := labelStructs.SecretsFrom(service.Labels[LabelSecrets])
+ if err != nil {
log.Fatal(err)
- } else {
- // drop the secrets from the environment
- for _, secret := range secrets {
- drop[secret] = true
- }
+ }
+ // drop the secrets from the environment
+ for _, secret := range secrets {
+ drop[secret] = true
}
// get the label values from the labels
varDescriptons := utils.GetValuesFromLabel(service, LabelValues)
@@ -95,7 +95,6 @@ func NewConfigMap(service types.ServiceConfig, appName string) *ConfigMap {
done[value] = true
continue
}
- // val := `{{ tpl .Values.` + service.Name + `.environment.` + value + ` $ }}`
val := utils.TplValue(service.Name, "environment."+value)
service.Environment[value] = &val
}
@@ -112,10 +111,9 @@ func NewConfigMap(service types.ServiceConfig, appName string) *ConfigMap {
}
}
for key, env := range service.Environment {
- if _, ok := done[key]; ok {
- continue
- }
- if _, ok := drop[key]; ok {
+ _, isDropped := drop[key]
+ _, isDone := done[key]
+ if isDropped || isDone {
continue
}
cm.AddData(key, *env)
@@ -127,7 +125,7 @@ func NewConfigMap(service types.ServiceConfig, appName string) *ConfigMap {
// NewConfigMapFromDirectory creates a new ConfigMap from a compose service. This path is the path to the
// file or directory. If the path is a directory, all files in the directory are added to the ConfigMap.
// Each subdirectory are ignored. Note that the Generate() function will create the subdirectories ConfigMaps.
-func NewConfigMapFromDirectory(service types.ServiceConfig, appName string, path string) *ConfigMap {
+func NewConfigMapFromDirectory(service types.ServiceConfig, appName, path string) *ConfigMap {
normalized := path
normalized = strings.TrimLeft(normalized, ".")
normalized = strings.TrimLeft(normalized, "/")
@@ -163,7 +161,7 @@ func (c *ConfigMap) SetData(data map[string]string) {
}
// AddData adds a key value pair to the configmap. Append or overwrite the value if the key already exists.
-func (c *ConfigMap) AddData(key string, value string) {
+func (c *ConfigMap) AddData(key, value string) {
c.Data[key] = value
}
diff --git a/generator/converter.go b/generator/converter.go
index 4a9a224..684301e 100644
--- a/generator/converter.go
+++ b/generator/converter.go
@@ -12,13 +12,12 @@ import (
"strings"
"time"
+ "github.com/compose-spec/compose-go/types"
+
"katenary/generator/extrafiles"
"katenary/generator/labelStructs"
"katenary/parser"
"katenary/utils"
-
- "github.com/compose-spec/compose-go/types"
- goyaml "gopkg.in/yaml.v3"
)
const headerHelp = `# This file is autogenerated by katenary
@@ -76,10 +75,11 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) {
// check if the chart directory exists
// if yes, prevent the user from overwriting it and ask for confirmation
if _, err := os.Stat(config.OutputDir); err == nil {
- fmt.Print(utils.IconWarning, " The chart directory "+config.OutputDir+" already exists, do you want to overwrite it? [y/N] ")
- var answer string
- fmt.Scanln(&answer)
- if strings.ToLower(answer) != "y" {
+ overwrite := utils.Confirm(
+ "The chart directory "+config.OutputDir+" already exists, do you want to overwrite it?",
+ utils.IconWarning,
+ )
+ if !overwrite {
fmt.Println("Aborting")
os.Exit(126) // 126 is the exit code for "Command invoked cannot execute"
}
@@ -109,168 +109,27 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) {
os.Exit(1)
}
- for name, template := range chart.Templates {
- t := template.Content
- t = removeNewlinesInsideBrackets(t)
- t = removeUnwantedLines(t)
- t = addModeline(t)
+ // write the templates to the disk
+ chart.SaveTemplates(templateDir)
- kind := utils.GetKind(name)
- var icon utils.Icon
- switch kind {
- case "deployment":
- icon = utils.IconPackage
- case "service":
- icon = utils.IconPlug
- case "ingress":
- icon = utils.IconWorld
- case "volumeclaim":
- icon = utils.IconCabinet
- case "configmap":
- icon = utils.IconConfig
- case "secret":
- icon = utils.IconSecret
- default:
- icon = utils.IconInfo
- }
+ // write the Chart.yaml file
+ buildCharYamlFile(chart, project, chartPath)
- servicename := template.Servicename
- if err := os.MkdirAll(filepath.Join(templateDir, servicename), 0o755); err != nil {
- fmt.Println(utils.IconFailure, err)
- os.Exit(1)
- }
- fmt.Println(icon, "Creating", kind, servicename)
- // if the name is a path, create the directory
- if strings.Contains(name, string(filepath.Separator)) {
- name = filepath.Join(templateDir, name)
- err := os.MkdirAll(filepath.Dir(name), 0o755)
- if err != nil {
- fmt.Println(utils.IconFailure, err)
- os.Exit(1)
- }
- } else {
- // remove the serivce name from the template name
- name = strings.Replace(name, servicename+".", "", 1)
- name = filepath.Join(templateDir, servicename, name)
- }
- f, err := os.Create(name)
- if err != nil {
- fmt.Println(utils.IconFailure, err)
- os.Exit(1)
- }
+ // build and write the values.yaml file
+ buildValues(chart, project, valuesPath)
- f.Write(t)
- f.Close()
- }
-
- // calculate the sha1 hash of the services
- buf := bytes.NewBuffer(nil)
- encoder := goyaml.NewEncoder(buf)
- encoder.SetIndent(2)
- if err := encoder.Encode(chart); err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
-
- yamlChart := buf.Bytes()
- 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)
-
- f, err := os.Create(chartPath)
- if err != nil {
- fmt.Println(utils.IconFailure, err)
- os.Exit(1)
- }
- f.Write(yamlChart)
- f.Close()
-
- buf.Reset()
- encoder = goyaml.NewEncoder(buf)
- encoder.SetIndent(2)
- if err = encoder.Encode(&chart.Values); err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
- values := buf.Bytes()
- values = addDescriptions(values, *project)
- values = addDependencyDescription(values, chart.Dependencies)
- values = addCommentsToValues(values)
- values = addStorageClassHelp(values)
- values = addImagePullSecretsHelp(values)
- values = addImagePullPolicyHelp(values)
- values = addVariablesDoc(values, project)
- values = addMainTagAppDoc(values, project)
- values = addResourceHelp(values)
- values = addYAMLSelectorPath(values)
- values = append([]byte(headerHelp), values...)
-
- f, err = os.Create(valuesPath)
- if err != nil {
- fmt.Println(utils.IconFailure, err)
- os.Exit(1)
- }
- f.Write(values)
- f.Close()
-
- f, err = os.Create(helpersPath)
- if err != nil {
- fmt.Println(utils.IconFailure, err)
- os.Exit(1)
- }
- f.Write([]byte(chart.Helper))
- f.Close()
+ // write the _helpers.tpl to the disk
+ writeContent(helpersPath, []byte(chart.Helper))
+ // write the readme to the disk
readme := extrafiles.ReadMeFile(chart.Name, chart.Description, chart.Values)
- f, err = os.Create(readmePath)
- if err != nil {
- fmt.Println(utils.IconFailure, err)
- os.Exit(1)
- }
- f.Write([]byte(readme))
- f.Close()
+ writeContent(readmePath, []byte(readme))
- services := make([]string, 0)
- for _, service := range project.Services {
- services = append(services, service.Name)
- }
- notes := extrafiles.NotesFile(services)
- f, err = os.Create(notesPath)
- if err != nil {
- fmt.Println(utils.IconFailure, err)
- os.Exit(1)
- }
- f.Write([]byte(notes))
- f.Close()
+ // get the list of services to write in the notes
+ buildNotesFile(project, notesPath)
- executeAndHandleError := func(fn func(ConvertOptions) error, config ConvertOptions, message string) {
- if err := fn(config); err != nil {
- fmt.Println(utils.IconFailure, err)
- os.Exit(1)
- }
- fmt.Println(utils.IconSuccess, message)
- }
-
- if config.HelmUpdate {
- executeAndHandleError(helmUpdate, config, "Helm dependencies updated")
- executeAndHandleError(helmLint, config, "Helm chart linted")
- fmt.Println(utils.IconSuccess, "Helm chart created successfully")
- }
+ // call helm update if needed
+ callHelmUpdate(config)
}
const ingressClassHelp = `# Default value for ingress.class annotation
@@ -501,31 +360,38 @@ func addResourceHelp(values []byte) []byte {
func addVariablesDoc(values []byte, project *types.Project) []byte {
lines := strings.Split(string(values), "\n")
- currentService := ""
for _, service := range project.Services {
- variables := utils.GetValuesFromLabel(service, LabelValues)
- for i, line := range lines {
- if regexp.MustCompile(`(?m)^` + service.Name + `:`).MatchString(line) {
- currentService = service.Name
+ lines = addDocToVariable(service, lines)
+ }
+ return []byte(strings.Join(lines, "\n"))
+}
+
+func addDocToVariable(service types.ServiceConfig, lines []string) []string {
+ currentService := ""
+ variables := utils.GetValuesFromLabel(service, LabelValues)
+ for i, line := range lines {
+ // if the line is a service, it is a name followed by a colon
+ if regexp.MustCompile(`(?m)^` + service.Name + `:`).MatchString(line) {
+ currentService = service.Name
+ }
+ // for each variable in the service, add the description
+ for varname, variable := range variables {
+ if variable == nil {
+ continue
}
- for varname, variable := range variables {
- if variable == nil {
- continue
- }
- spaces := utils.CountStartingSpaces(line)
- if regexp.MustCompile(`(?m)\s*`+varname+`:`).MatchString(line) && currentService == service.Name {
+ spaces := utils.CountStartingSpaces(line)
+ if regexp.MustCompile(`(?m)\s*`+varname+`:`).MatchString(line) && currentService == service.Name {
- // add # to the beginning of the Description
- doc := strings.ReplaceAll("\n"+variable.Description, "\n", "\n"+strings.Repeat(" ", spaces)+"# ")
- doc = strings.TrimRight(doc, " ")
- doc += "\n" + line
+ // add # to the beginning of the Description
+ doc := strings.ReplaceAll("\n"+variable.Description, "\n", "\n"+strings.Repeat(" ", spaces)+"# ")
+ doc = strings.TrimRight(doc, " ")
+ doc += "\n" + line
- lines[i] = doc
- }
+ lines[i] = doc
}
}
}
- return []byte(strings.Join(lines, "\n"))
+ return lines
}
const mainTagAppDoc = `This is the version of the main application.
@@ -535,8 +401,6 @@ func addMainTagAppDoc(values []byte, project *types.Project) []byte {
lines := strings.Split(string(values), "\n")
for _, service := range project.Services {
- inService := false
- inRegistry := false
// read the label LabelMainApp
if v, ok := service.Labels[LabelMainApp]; !ok {
continue
@@ -546,29 +410,36 @@ func addMainTagAppDoc(values []byte, project *types.Project) []byte {
fmt.Printf("%s Adding main tag app doc %s\n", utils.IconConfig, service.Name)
}
- for i, line := range lines {
- if regexp.MustCompile(`^` + service.Name + `:`).MatchString(line) {
- inService = true
- }
- if inService && regexp.MustCompile(`^\s*repository:.*`).MatchString(line) {
- inRegistry = true
- }
- if inService && inRegistry {
- if regexp.MustCompile(`^\s*tag: .*`).MatchString(line) {
- spaces := utils.CountStartingSpaces(line)
- doc := strings.ReplaceAll(mainTagAppDoc, "\n", "\n"+strings.Repeat(" ", spaces)+"# ")
- doc = strings.Repeat(" ", spaces) + "# " + doc
-
- lines[i] = doc + "\n" + line + "\n"
- break
- }
- }
- }
+ lines = addMainAppDoc(lines, service)
}
return []byte(strings.Join(lines, "\n"))
}
+func addMainAppDoc(lines []string, service types.ServiceConfig) []string {
+ inService := false
+ inRegistry := false
+ for i, line := range lines {
+ if regexp.MustCompile(`^` + service.Name + `:`).MatchString(line) {
+ inService = true
+ }
+ if inService && regexp.MustCompile(`^\s*repository:.*`).MatchString(line) {
+ inRegistry = true
+ }
+ if inService && inRegistry {
+ if regexp.MustCompile(`^\s*tag: .*`).MatchString(line) {
+ spaces := utils.CountStartingSpaces(line)
+ doc := strings.ReplaceAll(mainTagAppDoc, "\n", "\n"+strings.Repeat(" ", spaces)+"# ")
+ doc = strings.Repeat(" ", spaces) + "# " + doc
+
+ lines[i] = doc + "\n" + line + "\n"
+ break
+ }
+ }
+ }
+ return lines
+}
+
func removeNewlinesInsideBrackets(values []byte) []byte {
re, err := regexp.Compile(`(?s)\{\{(.*?)\}\}`)
if err != nil {
@@ -715,3 +586,89 @@ func addYAMLSelectorPath(values []byte) []byte {
}
return []byte(strings.Join(toReturn, "\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)
+}
+
+func buildValues(chart *HelmChart, project *types.Project, valuesPath string) {
+ values, err := utils.EncodeBasicYaml(&chart.Values)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ values = addDescriptions(values, *project)
+ values = addDependencyDescription(values, chart.Dependencies)
+ values = addCommentsToValues(values)
+ values = addStorageClassHelp(values)
+ values = addImagePullSecretsHelp(values)
+ values = addImagePullPolicyHelp(values)
+ values = addVariablesDoc(values, project)
+ values = addMainTagAppDoc(values, project)
+ values = addResourceHelp(values)
+ values = addYAMLSelectorPath(values)
+ values = append([]byte(headerHelp), values...)
+
+ // add vim modeline
+ values = append(values, []byte("\n# vim: ft=yaml\n")...)
+
+ // write the values to the disk
+ writeContent(valuesPath, values)
+}
+
+func 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) {
+ executeAndHandleError := func(fn func(ConvertOptions) error, config ConvertOptions, message string) {
+ if err := fn(config); err != nil {
+ fmt.Println(utils.IconFailure, err)
+ os.Exit(1)
+ }
+ fmt.Println(utils.IconSuccess, message)
+ }
+ if config.HelmUpdate {
+ executeAndHandleError(helmUpdate, config, "Helm dependencies updated")
+ executeAndHandleError(helmLint, config, "Helm chart linted")
+ fmt.Println(utils.IconSuccess, "Helm chart created successfully")
+ }
+}
diff --git a/generator/cronJob.go b/generator/cronJob.go
index 55552d9..69411ea 100644
--- a/generator/cronJob.go
+++ b/generator/cronJob.go
@@ -4,14 +4,14 @@ import (
"log"
"strings"
- "katenary/generator/labelStructs"
- "katenary/utils"
-
"github.com/compose-spec/compose-go/types"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
+
+ "katenary/generator/labelStructs"
+ "katenary/utils"
)
// only used to check interface implementation
diff --git a/generator/cronJob_test.go b/generator/cronJob_test.go
index cb7ee0c..b892726 100644
--- a/generator/cronJob_test.go
+++ b/generator/cronJob_test.go
@@ -12,7 +12,7 @@ import (
)
func TestBasicCronJob(t *testing.T) {
- compose_file := `
+ composeFile := `
services:
cron:
image: fedora
@@ -23,7 +23,7 @@ services:
schedule: "*/1 * * * *"
rbac: false
`
- tmpDir := setup(compose_file)
+ tmpDir := setup(composeFile)
defer teardown(tmpDir)
currentDir, _ := os.Getwd()
@@ -64,7 +64,7 @@ services:
}
func TestCronJobbWithRBAC(t *testing.T) {
- compose_file := `
+ composeFile := `
services:
cron:
image: fedora
@@ -76,7 +76,7 @@ services:
rbac: true
`
- tmpDir := setup(compose_file)
+ tmpDir := setup(composeFile)
defer teardown(tmpDir)
currentDir, _ := os.Getwd()
diff --git a/generator/deployment.go b/generator/deployment.go
index fb593c3..5e2c637 100644
--- a/generator/deployment.go
+++ b/generator/deployment.go
@@ -9,14 +9,14 @@ import (
"strings"
"time"
- "katenary/generator/labelStructs"
- "katenary/utils"
-
"github.com/compose-spec/compose-go/types"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
+
+ "katenary/generator/labelStructs"
+ "katenary/utils"
)
var _ Yaml = (*Deployment)(nil)
@@ -204,117 +204,127 @@ func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string) {
isSamePod = v != ""
}
+ for _, volume := range service.Volumes {
+ d.bindVolumes(volume, isSamePod, tobind, service, appName)
+ }
+}
+
+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
+ }
- for _, volume := range service.Volumes {
- // not declared as a bind volume, skip
- 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,
- )
- continue
- }
+ if container == nil {
+ utils.Warn("Container not found for volume", volume.Source)
+ return
+ }
- if container == nil {
- utils.Warn("Container not found for volume", volume.Source)
- continue
- }
-
- // ensure that the volume is not already present in the container
- for _, vm := range container.VolumeMounts {
- if vm.Name == volume.Source {
- continue
- }
- }
-
- 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() {
- 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,
- }),
- }
- } else {
- // 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)
- }
+ // 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) {
diff --git a/generator/deployment_test.go b/generator/deployment_test.go
index 4f3ce05..5c716c2 100644
--- a/generator/deployment_test.go
+++ b/generator/deployment_test.go
@@ -10,20 +10,22 @@ import (
"sigs.k8s.io/yaml"
)
+const webTemplateOutput = `templates/web/deployment.yaml`
+
func TestGenerate(t *testing.T) {
- compose_file := `
+ composeFile := `
services:
web:
image: nginx:1.29
`
- tmpDir := setup(compose_file)
+ tmpDir := setup(composeFile)
defer teardown(tmpDir)
currentDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(currentDir)
- output := _compile_test(t, "-s", "templates/web/deployment.yaml")
+ output := _compile_test(t, "-s", webTemplateOutput)
// dt := DeploymentTest{}
dt := v1.Deployment{}
@@ -42,7 +44,7 @@ services:
}
func TestGenerateOneDeploymentWithSamePod(t *testing.T) {
- compose_file := `
+ composeFile := `
services:
web:
image: nginx:1.29
@@ -57,14 +59,15 @@ services:
katenary.v3/same-pod: web
`
- tmpDir := setup(compose_file)
+ outDir := "./chart"
+ tmpDir := setup(composeFile)
defer teardown(tmpDir)
currentDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(currentDir)
- output := _compile_test(t, "-s", "templates/web/deployment.yaml")
+ output := _compile_test(t, "-s", webTemplateOutput)
dt := v1.Deployment{}
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
t.Errorf(unmarshalError, err)
@@ -76,8 +79,8 @@ services:
// endsure that the fpm service is not created
var err error
- output, err = helmTemplate(ConvertOptions{
- OutputDir: "./chart",
+ _, err = helmTemplate(ConvertOptions{
+ OutputDir: outDir,
}, "-s", "templates/fpm/deployment.yaml")
if err == nil {
t.Errorf("Expected error, got nil")
@@ -85,7 +88,7 @@ services:
// ensure that the web service is created and has got 2 ports
output, err = helmTemplate(ConvertOptions{
- OutputDir: "./chart",
+ OutputDir: outDir,
}, "-s", "templates/web/service.yaml")
if err != nil {
t.Errorf("Error: %s", err)
@@ -101,7 +104,7 @@ services:
}
func TestDependsOn(t *testing.T) {
- compose_file := `
+ composeFile := `
services:
web:
image: nginx:1.29
@@ -115,14 +118,14 @@ services:
ports:
- 3306:3306
`
- tmpDir := setup(compose_file)
+ tmpDir := setup(composeFile)
defer teardown(tmpDir)
currentDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(currentDir)
- output := _compile_test(t, "-s", "templates/web/deployment.yaml")
+ output := _compile_test(t, "-s", webTemplateOutput)
dt := v1.Deployment{}
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
t.Errorf(unmarshalError, err)
@@ -138,7 +141,7 @@ services:
}
func TestHelmDependencies(t *testing.T) {
- compose_file := `
+ composeFile := `
services:
web:
image: nginx:1.29
@@ -156,15 +159,15 @@ services:
version: 18.x.X
`
- compose_file = fmt.Sprintf(compose_file, Prefix())
- tmpDir := setup(compose_file)
+ composeFile = fmt.Sprintf(composeFile, Prefix())
+ tmpDir := setup(composeFile)
defer teardown(tmpDir)
currentDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(currentDir)
- output := _compile_test(t, "-s", "templates/web/deployment.yaml")
+ output := _compile_test(t, "-s", webTemplateOutput)
dt := v1.Deployment{}
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
t.Errorf(unmarshalError, err)
@@ -198,7 +201,7 @@ services:
}
func TestLivenessProbesFromHealthCheck(t *testing.T) {
- compose_file := `
+ composeFile := `
services:
web:
image: nginx:1.29
@@ -210,14 +213,14 @@ services:
timeout: 3s
retries: 3
`
- tmpDir := setup(compose_file)
+ tmpDir := setup(composeFile)
defer teardown(tmpDir)
currentDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(currentDir)
- output := _compile_test(t, "-s", "templates/web/deployment.yaml")
+ output := _compile_test(t, "-s", webTemplateOutput)
dt := v1.Deployment{}
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
t.Errorf(unmarshalError, err)
@@ -229,7 +232,7 @@ services:
}
func TestProbesFromLabels(t *testing.T) {
- compose_file := `
+ composeFile := `
services:
web:
image: nginx:1.29
@@ -246,15 +249,15 @@ services:
path: /ready
port: 80
`
- compose_file = fmt.Sprintf(compose_file, Prefix())
- tmpDir := setup(compose_file)
+ composeFile = fmt.Sprintf(composeFile, Prefix())
+ tmpDir := setup(composeFile)
defer teardown(tmpDir)
currentDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(currentDir)
- output := _compile_test(t, "-s", "templates/web/deployment.yaml")
+ output := _compile_test(t, "-s", webTemplateOutput)
dt := v1.Deployment{}
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
t.Errorf(unmarshalError, err)
@@ -280,7 +283,7 @@ services:
}
func TestSetValues(t *testing.T) {
- compose_file := `
+ composeFile := `
services:
web:
image: nginx:1.29
@@ -292,15 +295,15 @@ services:
- FOO
`
- compose_file = fmt.Sprintf(compose_file, Prefix())
- tmpDir := setup(compose_file)
+ composeFile = fmt.Sprintf(composeFile, Prefix())
+ tmpDir := setup(composeFile)
defer teardown(tmpDir)
currentDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(currentDir)
- output := _compile_test(t, "-s", "templates/web/deployment.yaml")
+ output := _compile_test(t, "-s", webTemplateOutput)
dt := v1.Deployment{}
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
t.Errorf(unmarshalError, err)
diff --git a/generator/extrafiles/readme.go b/generator/extrafiles/readme.go
index 5d6c9db..01c54a2 100644
--- a/generator/extrafiles/readme.go
+++ b/generator/extrafiles/readme.go
@@ -2,13 +2,12 @@ package extrafiles
import (
"bytes"
+ _ "embed"
"fmt"
"sort"
"strings"
"text/template"
- _ "embed"
-
"gopkg.in/yaml.v3"
)
diff --git a/generator/generator.go b/generator/generator.go
index 185e0ef..d6c479c 100644
--- a/generator/generator.go
+++ b/generator/generator.go
@@ -1,7 +1,5 @@
package generator
-// TODO: configmap from files 20%
-
import (
"bytes"
"fmt"
@@ -10,11 +8,11 @@ import (
"strconv"
"strings"
- "katenary/generator/labelStructs"
- "katenary/utils"
-
"github.com/compose-spec/compose-go/types"
corev1 "k8s.io/api/core/v1"
+
+ "katenary/generator/labelStructs"
+ "katenary/utils"
)
// Generate a chart from a compose project.
@@ -388,7 +386,7 @@ func buildVolumes(service types.ServiceConfig, chart *HelmChart, deployments map
y, _ := pvc.Yaml()
chart.Templates[pvc.Filename()] = &ChartTemplate{
Content: y,
- Servicename: service.Name, // TODO, use name
+ Servicename: service.Name,
}
}
}
diff --git a/generator/ingress.go b/generator/ingress.go
index 02f0aae..669593a 100644
--- a/generator/ingress.go
+++ b/generator/ingress.go
@@ -4,13 +4,13 @@ import (
"log"
"strings"
- "katenary/generator/labelStructs"
- "katenary/utils"
-
"github.com/compose-spec/compose-go/types"
networkv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
+
+ "katenary/generator/labelStructs"
+ "katenary/utils"
)
var _ Yaml = (*Ingress)(nil)
diff --git a/generator/katenaryLabels.go b/generator/katenaryLabels.go
index d26d08d..c1f3448 100644
--- a/generator/katenaryLabels.go
+++ b/generator/katenaryLabels.go
@@ -10,9 +10,9 @@ import (
"text/tabwriter"
"text/template"
- "katenary/utils"
-
"sigs.k8s.io/yaml"
+
+ "katenary/utils"
)
var (
diff --git a/generator/katenaryLabels_test.go b/generator/katenaryLabels_test.go
index 4cfbcc3..f6aa73c 100644
--- a/generator/katenaryLabels_test.go
+++ b/generator/katenaryLabels_test.go
@@ -8,6 +8,8 @@ import (
var testingKatenaryPrefix = Prefix()
+const mainAppLabel = "main-app"
+
func TestPrefix(t *testing.T) {
tests := []struct {
name string
@@ -27,7 +29,7 @@ func TestPrefix(t *testing.T) {
}
}
-func Test_labelName(t *testing.T) {
+func TestLabelName(t *testing.T) {
type args struct {
name string
}
@@ -39,9 +41,9 @@ func Test_labelName(t *testing.T) {
{
name: "Test_labelName",
args: args{
- name: "main-app",
+ name: mainAppLabel,
},
- want: testingKatenaryPrefix + "/main-app",
+ want: testingKatenaryPrefix + "/" + mainAppLabel,
},
}
for _, tt := range tests {
@@ -65,7 +67,7 @@ func TestGetLabelHelp(t *testing.T) {
}
func TestGetLabelHelpFor(t *testing.T) {
- help := GetLabelHelpFor("main-app", false)
+ help := GetLabelHelpFor(mainAppLabel, false)
if help == "" {
t.Errorf("GetLabelHelpFor() = %v, want %v", help, "Help")
}
diff --git a/generator/rbac.go b/generator/rbac.go
index 8d0df76..f8295ab 100644
--- a/generator/rbac.go
+++ b/generator/rbac.go
@@ -1,13 +1,13 @@
package generator
import (
- "katenary/utils"
-
"github.com/compose-spec/compose-go/types"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
+
+ "katenary/utils"
)
var (
diff --git a/generator/secret.go b/generator/secret.go
index 9a6122f..e26869b 100644
--- a/generator/secret.go
+++ b/generator/secret.go
@@ -5,12 +5,12 @@ import (
"fmt"
"strings"
- "katenary/utils"
-
"github.com/compose-spec/compose-go/types"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
+
+ "katenary/utils"
)
var (
@@ -84,7 +84,7 @@ func (s *Secret) SetData(data map[string]string) {
}
// AddData adds a key value pair to the secret.
-func (s *Secret) AddData(key string, value string) {
+func (s *Secret) AddData(key, value string) {
if value == "" {
return
}
diff --git a/generator/service.go b/generator/service.go
index a573f4d..1951a33 100644
--- a/generator/service.go
+++ b/generator/service.go
@@ -4,13 +4,13 @@ import (
"regexp"
"strings"
- "katenary/utils"
-
"github.com/compose-spec/compose-go/types"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/yaml"
+
+ "katenary/utils"
)
var _ Yaml = (*Service)(nil)
diff --git a/generator/values.go b/generator/values.go
index 8b26b39..59b4ca5 100644
--- a/generator/values.go
+++ b/generator/values.go
@@ -6,9 +6,6 @@ import (
"github.com/compose-spec/compose-go/types"
)
-// Values is a map of all values for all services. Written to values.yaml.
-// var Values = map[string]any{}
-
// RepositoryValue is a docker repository image and tag that will be saved in values.yaml.
type RepositoryValue struct {
Image string `yaml:"image"`
@@ -17,19 +14,19 @@ type RepositoryValue struct {
// PersistenceValue is a persistence configuration that will be saved in values.yaml.
type PersistenceValue struct {
- Enabled bool `yaml:"enabled"`
StorageClass string `yaml:"storageClass"`
Size string `yaml:"size"`
AccessMode []string `yaml:"accessMode"`
+ Enabled bool `yaml:"enabled"`
}
// IngressValue is a ingress configuration that will be saved in values.yaml.
type IngressValue struct {
- Enabled bool `yaml:"enabled"`
+ Annotations map[string]string `yaml:"annotations"`
Host string `yaml:"host"`
Path string `yaml:"path"`
Class string `yaml:"class"`
- Annotations map[string]string `yaml:"annotations"`
+ Enabled bool `yaml:"enabled"`
}
// Value will be saved in values.yaml. It contains configuraiton for all deployment and services.
@@ -37,13 +34,13 @@ type Value struct {
Repository *RepositoryValue `yaml:"repository,omitempty"`
Persistence map[string]*PersistenceValue `yaml:"persistence,omitempty"`
Ingress *IngressValue `yaml:"ingress,omitempty"`
- ImagePullPolicy string `yaml:"imagePullPolicy,omitempty"`
Environment map[string]any `yaml:"environment,omitempty"`
Replicas *uint32 `yaml:"replicas,omitempty"`
CronJob *CronJobValue `yaml:"cronjob,omitempty"`
NodeSelector map[string]string `yaml:"nodeSelector"`
- ServiceAccount string `yaml:"serviceAccount"`
Resources map[string]any `yaml:"resources"`
+ ImagePullPolicy string `yaml:"imagePullPolicy,omitempty"`
+ ServiceAccount string `yaml:"serviceAccount"`
}
// CronJobValue is a cronjob configuration that will be saved in values.yaml.
diff --git a/generator/volume.go b/generator/volume.go
index 133f32d..39a02eb 100644
--- a/generator/volume.go
+++ b/generator/volume.go
@@ -3,17 +3,19 @@ package generator
import (
"strings"
- "katenary/utils"
-
"github.com/compose-spec/compose-go/types"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
+
+ "katenary/utils"
)
var _ Yaml = (*VolumeClaim)(nil)
+const persistenceKey = "persistence"
+
// VolumeClaim is a kubernetes VolumeClaim. This is a PersistentVolumeClaim.
type VolumeClaim struct {
*v1.PersistentVolumeClaim
@@ -41,7 +43,12 @@ func NewVolumeClaim(service types.ServiceConfig, volumeName, appName string) *Vo
AccessModes: []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce,
},
- StorageClassName: utils.StrPtr(`{{ .Values.` + service.Name + `.persistence.` + volumeName + `.storageClass }}`),
+ StorageClassName: utils.StrPtr(
+ `{{ .Values.` +
+ service.Name +
+ "." + persistenceKey +
+ "." + volumeName + `.storageClass }}`,
+ ),
Resources: v1.VolumeResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceStorage: resource.MustParse("1Gi"),
@@ -69,7 +76,7 @@ func (v *VolumeClaim) Yaml() ([]byte, error) {
strings.Replace(
string(out),
"1Gi",
- utils.TplValue(serviceName, "persistence."+volumeName+".size"),
+ utils.TplValue(serviceName, persistenceKey+"."+volumeName+".size"),
1,
),
)
@@ -80,8 +87,8 @@ func (v *VolumeClaim) Yaml() ([]byte, error) {
"- ReadWriteOnce",
"{{- .Values."+
serviceName+
- ".persistence."+
- volumeName+
+ "."+persistenceKey+
+ "."+volumeName+
".accessMode | toYaml | nindent __indent__ }}",
1,
),
@@ -92,7 +99,10 @@ func (v *VolumeClaim) Yaml() ([]byte, error) {
if strings.Contains(line, "storageClass") {
lines[i] = utils.Wrap(
line,
- "{{- if ne .Values."+serviceName+".persistence."+volumeName+".storageClass \"-\" }}",
+ "{{- if ne .Values."+
+ serviceName+
+ "."+persistenceKey+
+ "."+volumeName+".storageClass \"-\" }}",
"{{- end }}",
)
}
@@ -103,8 +113,8 @@ func (v *VolumeClaim) Yaml() ([]byte, error) {
out = []byte(
"{{- if .Values." +
serviceName +
- ".persistence." +
- volumeName +
+ "." + persistenceKey +
+ "." + volumeName +
".enabled }}\n" +
string(out) +
"\n{{- end }}",
diff --git a/update/update_test.go b/update/update_test.go
index 0627958..4ec18c5 100644
--- a/update/update_test.go
+++ b/update/update_test.go
@@ -7,7 +7,6 @@ import (
)
func TestDownloadLatestRelease(t *testing.T) {
-
// Reset the version to test the latest release
Version = "0.0.0"
@@ -17,15 +16,14 @@ func TestDownloadLatestRelease(t *testing.T) {
// Now call the CheckLatestVersion function
version, assets, err := CheckLatestVersion()
-
if err != nil {
- t.Errorf("Error: %s", err)
+ t.Errorf("Error getting latest version: %s", err)
}
fmt.Println("Version found", version)
// Touch exe binary
- f, _ := os.OpenFile(exe, os.O_RDONLY|os.O_CREATE, 0755)
+ f, _ := os.OpenFile(exe, os.O_RDONLY|os.O_CREATE, 0o755)
f.Write(nil)
f.Close()
@@ -48,5 +46,4 @@ func TestAlreadyUpToDate(t *testing.T) {
}
t.Log("Version is already the most recent", version)
-
}
diff --git a/utils/utils.go b/utils/utils.go
index 220d296..29081b7 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -1,6 +1,8 @@
package utils
import (
+ "bytes"
+ "fmt"
"log"
"path/filepath"
"strings"
@@ -114,8 +116,8 @@ func PathToName(path string) string {
// EnvConfig is a struct to hold the description of an environment variable.
type EnvConfig struct {
- Description string
Service types.ServiceConfig
+ Description string
}
// GetValuesFromLabel returns a map of values from a label.
@@ -160,3 +162,27 @@ func MapKeys(m map[string]interface{}) []string {
}
return keys
}
+
+// Confirm asks a question and returns true if the answer is y.
+func Confirm(question string, icon ...Icon) bool {
+ if len(icon) > 0 {
+ fmt.Printf("%s %s [y/N] ", icon[0], question)
+ } else {
+ fmt.Print(question + " [y/N] ")
+ }
+ var response string
+ fmt.Scanln(&response)
+ return strings.ToLower(response) == "y"
+}
+
+// EncodeBasicYaml encodes a basic yaml from an interface.
+func EncodeBasicYaml(data any) ([]byte, error) {
+ buf := bytes.NewBuffer(nil)
+ enc := yaml.NewEncoder(buf)
+ enc.SetIndent(2)
+ err := enc.Encode(data)
+ if err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
+}