Big refactorization

- reduce complexity
- use better tools to format the code
- add more tests
- and too many things to list here

We are rewriting for V3, so these commits are sometimes big and not
fully detailed. Of course, further work will be more documented.
This commit is contained in:
2024-05-06 21:11:36 +02:00
parent d98268f45b
commit 4367a01769
26 changed files with 582 additions and 513 deletions

View File

@@ -9,11 +9,11 @@ import (
"os" "os"
"strings" "strings"
"katenary/generator"
"katenary/utils"
"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.

View File

@@ -1,12 +0,0 @@
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
# 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.

View File

@@ -35,7 +35,7 @@ var Version = "master" // changed at compile time
``` ```
<a name="Convert"></a> <a name="Convert"></a>
## func [Convert](<https://github.com/metal3d/katenary/blob/develop/generator/converter.go#L38>) ## func [Convert](<https://github.com/metal3d/katenary/blob/develop/generator/converter.go#L37>)
```go ```go
func Convert(config ConvertOptions, dockerComposeFile ...string) func Convert(config ConvertOptions, dockerComposeFile ...string)
@@ -116,16 +116,14 @@ func Prefix() string
<a name="ChartTemplate"></a> <a name="ChartTemplate"></a>
## type [ChartTemplate](<https://github.com/metal3d/katenary/blob/develop/generator/chart.go#L9-L12>) ## type [ChartTemplate](<https://github.com/metal3d/katenary/blob/develop/generator/chart.go#L25-L28>)
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. 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 ```go
type ChartTemplate struct { type ChartTemplate struct {
Content []byte
Servicename string 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". NewConfigMap creates a new ConfigMap from a compose service. The appName is the name of the application taken from the project name. The ConfigMap is filled by environment variables and labels "map\-env".
<a name="NewConfigMapFromDirectory"></a> <a name="NewConfigMapFromDirectory"></a>
### func [NewConfigMapFromDirectory](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L130>) ### func [NewConfigMapFromDirectory](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L128>)
```go ```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. NewConfigMapFromDirectory creates a new ConfigMap from a compose service. This path is the path to the file or directory. If the path is a directory, all files in the directory are added to the ConfigMap. Each subdirectory are ignored. Note that the Generate\(\) function will create the subdirectories ConfigMaps.
<a name="ConfigMap.AddData"></a> <a name="ConfigMap.AddData"></a>
### func \(\*ConfigMap\) [AddData](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L166>) ### func \(\*ConfigMap\) [AddData](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L164>)
```go ```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. AddData adds a key value pair to the configmap. Append or overwrite the value if the key already exists.
<a name="ConfigMap.AppendDir"></a> <a name="ConfigMap.AppendDir"></a>
### func \(\*ConfigMap\) [AppendDir](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L172>) ### func \(\*ConfigMap\) [AppendDir](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L170>)
```go ```go
func (c *ConfigMap) AppendDir(path string) 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. 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.
<a name="ConfigMap.AppendFile"></a> <a name="ConfigMap.AppendFile"></a>
### func \(\*ConfigMap\) [AppendFile](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L208>) ### func \(\*ConfigMap\) [AppendFile](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L206>)
```go ```go
func (c *ConfigMap) AppendFile(path string) func (c *ConfigMap) AppendFile(path string)
@@ -187,7 +185,7 @@ func (c *ConfigMap) AppendFile(path string)
<a name="ConfigMap.Filename"></a> <a name="ConfigMap.Filename"></a>
### func \(\*ConfigMap\) [Filename](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L226>) ### func \(\*ConfigMap\) [Filename](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L224>)
```go ```go
func (c *ConfigMap) Filename() string 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. Filename returns the filename of the configmap. If the configmap is used for files, the filename contains the path.
<a name="ConfigMap.SetData"></a> <a name="ConfigMap.SetData"></a>
### func \(\*ConfigMap\) [SetData](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L161>) ### func \(\*ConfigMap\) [SetData](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L159>)
```go ```go
func (c *ConfigMap) SetData(data map[string]string) 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. SetData sets the data of the configmap. It replaces the entire data.
<a name="ConfigMap.Yaml"></a> <a name="ConfigMap.Yaml"></a>
### func \(\*ConfigMap\) [Yaml](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L236>) ### func \(\*ConfigMap\) [Yaml](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L234>)
```go ```go
func (c *ConfigMap) Yaml() ([]byte, error) func (c *ConfigMap) Yaml() ([]byte, error)
@@ -225,18 +223,18 @@ type ConfigMapMount struct {
``` ```
<a name="ConvertOptions"></a> <a name="ConvertOptions"></a>
## type [ConvertOptions](<https://github.com/metal3d/katenary/blob/develop/generator/chart.go#L46-L53>) ## type [ConvertOptions](<https://github.com/metal3d/katenary/blob/develop/generator/chart.go#L14-L21>)
ConvertOptions are the options to convert a compose project to a helm chart. ConvertOptions are the options to convert a compose project to a helm chart.
```go ```go
type ConvertOptions struct { type ConvertOptions struct {
Force bool // Force the chart directory deletion if it already exists. AppVersion *string
OutputDir string // The output directory of the chart. OutputDir string
Profiles []string // Profile to use for the conversion. ChartVersion string
HelmUpdate bool // If true, the "helm dep update" command will be run after the chart generation. Profiles []string
AppVersion *string // Set the chart "appVersion" field. If nil, the version will be set to 0.1.0. Force bool
ChartVersion string // Set the chart "version" field. HelmUpdate bool
} }
``` ```
@@ -275,7 +273,7 @@ Yaml returns the yaml representation of the cronjob.
Implements the Yaml interface. Implements the Yaml interface.
<a name="CronJobValue"></a> <a name="CronJobValue"></a>
## type [CronJobValue](<https://github.com/metal3d/katenary/blob/develop/generator/values.go#L50-L55>) ## type [CronJobValue](<https://github.com/metal3d/katenary/blob/develop/generator/values.go#L47-L52>)
CronJobValue is a cronjob configuration that will be saved in values.yaml. CronJobValue is a cronjob configuration that will be saved in values.yaml.
@@ -304,7 +302,7 @@ type DataMap interface {
### func [NewFileMap](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L26>) ### func [NewFileMap](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L26>)
```go ```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. 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. AddContainer adds a container to the deployment.
<a name="Deployment.AddHealthCheck"></a> <a name="Deployment.AddHealthCheck"></a>
### func \(\*Deployment\) [AddHealthCheck](<https://github.com/metal3d/katenary/blob/develop/generator/deployment.go#L440>) ### func \(\*Deployment\) [AddHealthCheck](<https://github.com/metal3d/katenary/blob/develop/generator/deployment.go#L450>)
```go ```go
func (d *Deployment) AddHealthCheck(service types.ServiceConfig, container *corev1.Container) 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. AddVolumes adds a volume to the deployment. It does not create the PVC, it only adds the volumes to the deployment. If the volume is a bind volume it will warn the user that it is not supported yet.
<a name="Deployment.BindFrom"></a> <a name="Deployment.BindFrom"></a>
### func \(\*Deployment\) [BindFrom](<https://github.com/metal3d/katenary/blob/develop/generator/deployment.go#L320>) ### func \(\*Deployment\) [BindFrom](<https://github.com/metal3d/katenary/blob/develop/generator/deployment.go#L330>)
```go ```go
func (d *Deployment) BindFrom(service types.ServiceConfig, binded *Deployment) 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. DependsOn adds a initContainer to the deployment that will wait for the service to be up.
<a name="Deployment.Filename"></a> <a name="Deployment.Filename"></a>
### func \(\*Deployment\) [Filename](<https://github.com/metal3d/katenary/blob/develop/generator/deployment.go#L618>) ### func \(\*Deployment\) [Filename](<https://github.com/metal3d/katenary/blob/develop/generator/deployment.go#L628>)
```go ```go
func (d *Deployment) Filename() string func (d *Deployment) Filename() string
@@ -394,7 +392,7 @@ func (d *Deployment) Filename() string
Filename returns the filename of the deployment. Filename returns the filename of the deployment.
<a name="Deployment.SetEnvFrom"></a> <a name="Deployment.SetEnvFrom"></a>
### func \(\*Deployment\) [SetEnvFrom](<https://github.com/metal3d/katenary/blob/develop/generator/deployment.go#L348>) ### func \(\*Deployment\) [SetEnvFrom](<https://github.com/metal3d/katenary/blob/develop/generator/deployment.go#L358>)
```go ```go
func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string) 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. SetEnvFrom sets the environment variables to a configmap. The configmap is created.
<a name="Deployment.Yaml"></a> <a name="Deployment.Yaml"></a>
### func \(\*Deployment\) [Yaml](<https://github.com/metal3d/katenary/blob/develop/generator/deployment.go#L469>) ### func \(\*Deployment\) [Yaml](<https://github.com/metal3d/katenary/blob/develop/generator/deployment.go#L479>)
```go ```go
func (d *Deployment) Yaml() ([]byte, error) func (d *Deployment) Yaml() ([]byte, error)
@@ -430,28 +428,29 @@ const (
``` ```
<a name="HelmChart"></a> <a name="HelmChart"></a>
## type [HelmChart](<https://github.com/metal3d/katenary/blob/develop/generator/chart.go#L16-L28>) ## type [HelmChart](<https://github.com/metal3d/katenary/blob/develop/generator/chart.go#L32-L44>)
HelmChart is a Helm Chart representation. It contains all the tempaltes, values, versions, helpers... HelmChart is a Helm Chart representation. It contains all the tempaltes, values, versions, helpers...
```go ```go
type HelmChart struct { type HelmChart struct {
Templates map[string]*ChartTemplate `yaml:"-"`
Values map[string]any `yaml:"-"`
VolumeMounts map[string]any `yaml:"-"`
Name string `yaml:"name"` Name string `yaml:"name"`
ApiVersion string `yaml:"apiVersion"` ApiVersion string `yaml:"apiVersion"`
Version string `yaml:"version"` Version string `yaml:"version"`
AppVersion string `yaml:"appVersion"` AppVersion string `yaml:"appVersion"`
Description string `yaml:"description"` Description string `yaml:"description"`
Helper string `yaml:"-"`
Dependencies []labelStructs.Dependency `yaml:"dependencies,omitempty"` 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 // contains filtered or unexported fields
} }
``` ```
<a name="Generate"></a> <a name="Generate"></a>
### func [Generate](<https://github.com/metal3d/katenary/blob/develop/generator/generator.go#L33>) ### func [Generate](<https://github.com/metal3d/katenary/blob/develop/generator/generator.go#L31>)
```go ```go
func Generate(project *types.Project) (*HelmChart, error) 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. - Merge the same\-pod services.
<a name="NewChart"></a> <a name="NewChart"></a>
### func [NewChart](<https://github.com/metal3d/katenary/blob/develop/generator/chart.go#L31>) ### func [NewChart](<https://github.com/metal3d/katenary/blob/develop/generator/chart.go#L47>)
```go ```go
func NewChart(name string) *HelmChart func NewChart(name string) *HelmChart
@@ -479,6 +478,15 @@ func NewChart(name string) *HelmChart
NewChart creates a new empty chart with the given name. NewChart creates a new empty chart with the given name.
<a name="HelmChart.SaveTemplates"></a>
### func \(\*HelmChart\) [SaveTemplates](<https://github.com/metal3d/katenary/blob/develop/generator/chart.go#L62>)
```go
func (chart *HelmChart) SaveTemplates(templateDir string)
```
SaveTemplates the templates of the chart to the given directory.
<a name="Help"></a> <a name="Help"></a>
## type [Help](<https://github.com/metal3d/katenary/blob/develop/generator/katenaryLabels.go#L32-L37>) ## type [Help](<https://github.com/metal3d/katenary/blob/develop/generator/katenaryLabels.go#L32-L37>)
@@ -533,17 +541,17 @@ func (ingress *Ingress) Yaml() ([]byte, error)
<a name="IngressValue"></a> <a name="IngressValue"></a>
## type [IngressValue](<https://github.com/metal3d/katenary/blob/develop/generator/values.go#L27-L33>) ## type [IngressValue](<https://github.com/metal3d/katenary/blob/develop/generator/values.go#L24-L30>)
IngressValue is a ingress configuration that will be saved in values.yaml. IngressValue is a ingress configuration that will be saved in values.yaml.
```go ```go
type IngressValue struct { type IngressValue struct {
Enabled bool `yaml:"enabled"` Annotations map[string]string `yaml:"annotations"`
Host string `yaml:"host"` Host string `yaml:"host"`
Path string `yaml:"path"` Path string `yaml:"path"`
Class string `yaml:"class"` Class string `yaml:"class"`
Annotations map[string]string `yaml:"annotations"` Enabled bool `yaml:"enabled"`
} }
``` ```
@@ -578,16 +586,16 @@ const (
``` ```
<a name="PersistenceValue"></a> <a name="PersistenceValue"></a>
## type [PersistenceValue](<https://github.com/metal3d/katenary/blob/develop/generator/values.go#L19-L24>) ## type [PersistenceValue](<https://github.com/metal3d/katenary/blob/develop/generator/values.go#L16-L21>)
PersistenceValue is a persistence configuration that will be saved in values.yaml. PersistenceValue is a persistence configuration that will be saved in values.yaml.
```go ```go
type PersistenceValue struct { type PersistenceValue struct {
Enabled bool `yaml:"enabled"`
StorageClass string `yaml:"storageClass"` StorageClass string `yaml:"storageClass"`
Size string `yaml:"size"` Size string `yaml:"size"`
AccessMode []string `yaml:"accessMode"` 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. NewRBAC creates a new RBAC from a compose service. The appName is the name of the application taken from the project name.
<a name="RepositoryValue"></a> <a name="RepositoryValue"></a>
## type [RepositoryValue](<https://github.com/metal3d/katenary/blob/develop/generator/values.go#L13-L16>) ## type [RepositoryValue](<https://github.com/metal3d/katenary/blob/develop/generator/values.go#L10-L13>)
RepositoryValue is a docker repository image and tag that will be saved in values.yaml. 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](<https://github.com/metal3d/katenary/blob/develop/generator/secret.go#L87>) ### func \(\*Secret\) [AddData](<https://github.com/metal3d/katenary/blob/develop/generator/secret.go#L87>)
```go ```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. AddData adds a key value pair to the secret.
@@ -823,7 +831,7 @@ func (r *ServiceAccount) Yaml() ([]byte, error)
<a name="Value"></a> <a name="Value"></a>
## type [Value](<https://github.com/metal3d/katenary/blob/develop/generator/values.go#L36-L47>) ## type [Value](<https://github.com/metal3d/katenary/blob/develop/generator/values.go#L33-L44>)
Value will be saved in values.yaml. It contains configuraiton for all deployment and services. 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"` Repository *RepositoryValue `yaml:"repository,omitempty"`
Persistence map[string]*PersistenceValue `yaml:"persistence,omitempty"` Persistence map[string]*PersistenceValue `yaml:"persistence,omitempty"`
Ingress *IngressValue `yaml:"ingress,omitempty"` Ingress *IngressValue `yaml:"ingress,omitempty"`
ImagePullPolicy string `yaml:"imagePullPolicy,omitempty"`
Environment map[string]any `yaml:"environment,omitempty"` Environment map[string]any `yaml:"environment,omitempty"`
Replicas *uint32 `yaml:"replicas,omitempty"` Replicas *uint32 `yaml:"replicas,omitempty"`
CronJob *CronJobValue `yaml:"cronjob,omitempty"` CronJob *CronJobValue `yaml:"cronjob,omitempty"`
NodeSelector map[string]string `yaml:"nodeSelector"` NodeSelector map[string]string `yaml:"nodeSelector"`
ServiceAccount string `yaml:"serviceAccount"`
Resources map[string]any `yaml:"resources"` Resources map[string]any `yaml:"resources"`
ImagePullPolicy string `yaml:"imagePullPolicy,omitempty"`
ServiceAccount string `yaml:"serviceAccount"`
} }
``` ```
<a name="NewValue"></a> <a name="NewValue"></a>
### func [NewValue](<https://github.com/metal3d/katenary/blob/develop/generator/values.go#L62>) ### func [NewValue](<https://github.com/metal3d/katenary/blob/develop/generator/values.go#L59>)
```go ```go
func NewValue(service types.ServiceConfig, main ...bool) *Value 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. If \`main\` is true, the tag will be empty because it will be set in the helm chart appVersion.
<a name="Value.AddIngress"></a> <a name="Value.AddIngress"></a>
### func \(\*Value\) [AddIngress](<https://github.com/metal3d/katenary/blob/develop/generator/values.go#L101>) ### func \(\*Value\) [AddIngress](<https://github.com/metal3d/katenary/blob/develop/generator/values.go#L98>)
```go ```go
func (v *Value) AddIngress(host, path string) func (v *Value) AddIngress(host, path string)
@@ -863,7 +871,7 @@ func (v *Value) AddIngress(host, path string)
<a name="Value.AddPersistence"></a> <a name="Value.AddPersistence"></a>
### func \(\*Value\) [AddPersistence](<https://github.com/metal3d/katenary/blob/develop/generator/values.go#L89>) ### func \(\*Value\) [AddPersistence](<https://github.com/metal3d/katenary/blob/develop/generator/values.go#L86>)
```go ```go
func (v *Value) AddPersistence(volumeName string) func (v *Value) AddPersistence(volumeName string)
@@ -872,7 +880,7 @@ func (v *Value) AddPersistence(volumeName string)
AddPersistence adds persistence configuration to the Value. AddPersistence adds persistence configuration to the Value.
<a name="VolumeClaim"></a> <a name="VolumeClaim"></a>
## type [VolumeClaim](<https://github.com/metal3d/katenary/blob/develop/generator/volume.go#L18-L23>) ## type [VolumeClaim](<https://github.com/metal3d/katenary/blob/develop/generator/volume.go#L20-L25>)
VolumeClaim is a kubernetes VolumeClaim. This is a PersistentVolumeClaim. VolumeClaim is a kubernetes VolumeClaim. This is a PersistentVolumeClaim.
@@ -884,7 +892,7 @@ type VolumeClaim struct {
``` ```
<a name="NewVolumeClaim"></a> <a name="NewVolumeClaim"></a>
### func [NewVolumeClaim](<https://github.com/metal3d/katenary/blob/develop/generator/volume.go#L26>) ### func [NewVolumeClaim](<https://github.com/metal3d/katenary/blob/develop/generator/volume.go#L28>)
```go ```go
func NewVolumeClaim(service types.ServiceConfig, volumeName, appName string) *VolumeClaim 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. NewVolumeClaim creates a new VolumeClaim from a compose service.
<a name="VolumeClaim.Filename"></a> <a name="VolumeClaim.Filename"></a>
### func \(\*VolumeClaim\) [Filename](<https://github.com/metal3d/katenary/blob/develop/generator/volume.go#L117>) ### func \(\*VolumeClaim\) [Filename](<https://github.com/metal3d/katenary/blob/develop/generator/volume.go#L127>)
```go ```go
func (v *VolumeClaim) Filename() string func (v *VolumeClaim) Filename() string
@@ -902,7 +910,7 @@ func (v *VolumeClaim) Filename() string
Filename returns the suggested filename for a VolumeClaim. Filename returns the suggested filename for a VolumeClaim.
<a name="VolumeClaim.Yaml"></a> <a name="VolumeClaim.Yaml"></a>
### func \(\*VolumeClaim\) [Yaml](<https://github.com/metal3d/katenary/blob/develop/generator/volume.go#L56>) ### func \(\*VolumeClaim\) [Yaml](<https://github.com/metal3d/katenary/blob/develop/generator/volume.go#L63>)
```go ```go
func (v *VolumeClaim) Yaml() ([]byte, error) func (v *VolumeClaim) Yaml() ([]byte, error)

View File

@@ -17,7 +17,7 @@ func NotesFile(services []string) string
NotesFile returns the content of the note.txt file. NotesFile returns the content of the note.txt file.
<a name="ReadMeFile"></a> <a name="ReadMeFile"></a>
## func [ReadMeFile](<https://github.com/metal3d/katenary/blob/develop/generator/extrafiles/readme.go#L25>) ## func [ReadMeFile](<https://github.com/metal3d/katenary/blob/develop/generator/extrafiles/readme.go#L24>)
```go ```go
func ReadMeFile(charname, description string, values map[string]any) string func ReadMeFile(charname, description string, values map[string]any) string

View File

@@ -158,7 +158,7 @@ func PortsFrom(data string) (Ports, error)
PortsFrom returns a Ports from the given string. PortsFrom returns a Ports from the given string.
<a name="Probe"></a> <a name="Probe"></a>
## type [Probe](<https://github.com/metal3d/katenary/blob/develop/generator/labelStructs/probes.go#L8-L11>) ## type [Probe](<https://github.com/metal3d/katenary/blob/develop/generator/labelStructs/probes.go#L11-L14>)
@@ -170,7 +170,7 @@ type Probe struct {
``` ```
<a name="ProbeFrom"></a> <a name="ProbeFrom"></a>
### func [ProbeFrom](<https://github.com/metal3d/katenary/blob/develop/generator/labelStructs/probes.go#L13>) ### func [ProbeFrom](<https://github.com/metal3d/katenary/blob/develop/generator/labelStructs/probes.go#L16>)
```go ```go
func ProbeFrom(data string) (*Probe, error) func ProbeFrom(data string) (*Probe, error)

View File

@@ -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. Utils package provides some utility functions used in katenary. It defines some constants and functions used in the whole project.
## func [CountStartingSpaces](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L31>) ## func [Confirm](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L167>)
```go
func Confirm(question string, icon ...Icon) bool
```
Confirm asks a question and returns true if the answer is y.
<a name="CountStartingSpaces"></a>
## func [CountStartingSpaces](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L33>)
```go ```go
func CountStartingSpaces(line string) int 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. CountStartingSpaces counts the number of spaces at the beginning of a string.
<a name="EncodeBasicYaml"></a>
## func [EncodeBasicYaml](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L179>)
```go
func EncodeBasicYaml(data any) ([]byte, error)
```
EncodeBasicYaml encodes a basic yaml from an interface.
<a name="GetContainerByName"></a> <a name="GetContainerByName"></a>
## func [GetContainerByName](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L82>) ## func [GetContainerByName](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L84>)
```go ```go
func GetContainerByName(name string, containers []corev1.Container) (*corev1.Container, int) 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. GetContainerByName returns a container by name and its index in the array. It returns nil, \-1 if not found.
<a name="GetKind"></a> <a name="GetKind"></a>
## func [GetKind](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L44>) ## func [GetKind](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L46>)
```go ```go
func GetKind(path string) (kind string) 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. GetKind returns the kind of the resource from the file path.
<a name="GetServiceNameByPort"></a> <a name="GetServiceNameByPort"></a>
## func [GetServiceNameByPort](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L72>) ## func [GetServiceNameByPort](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L74>)
```go ```go
func GetServiceNameByPort(port int) string 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. GetServiceNameByPort returns the service name for a port. It the service name is not found, it returns an empty string.
<a name="GetValuesFromLabel"></a> <a name="GetValuesFromLabel"></a>
## func [GetValuesFromLabel](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L122>) ## func [GetValuesFromLabel](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L124>)
```go ```go
func GetValuesFromLabel(service types.ServiceConfig, LabelValues string) map[string]*EnvConfig 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. HashComposefiles returns a hash of the compose files.
<a name="Int32Ptr"></a> <a name="Int32Ptr"></a>
## func [Int32Ptr](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L25>) ## func [Int32Ptr](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L27>)
```go ```go
func Int32Ptr(i int32) *int32 func Int32Ptr(i int32) *int32
@@ -71,7 +89,7 @@ func Int32Ptr(i int32) *int32
Int32Ptr returns a pointer to an int32. Int32Ptr returns a pointer to an int32.
<a name="MapKeys"></a> <a name="MapKeys"></a>
## func [MapKeys](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L156>) ## func [MapKeys](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L158>)
```go ```go
func MapKeys(m map[string]interface{}) []string func MapKeys(m map[string]interface{}) []string
@@ -80,7 +98,7 @@ func MapKeys(m map[string]interface{}) []string
<a name="PathToName"></a> <a name="PathToName"></a>
## func [PathToName](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L101>) ## func [PathToName](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L103>)
```go ```go
func PathToName(path string) string func PathToName(path string) string
@@ -89,7 +107,7 @@ func PathToName(path string) string
PathToName converts a path to a kubernetes complient name. PathToName converts a path to a kubernetes complient name.
<a name="StrPtr"></a> <a name="StrPtr"></a>
## func [StrPtr](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L28>) ## func [StrPtr](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L30>)
```go ```go
func StrPtr(s string) *string func StrPtr(s string) *string
@@ -98,7 +116,7 @@ func StrPtr(s string) *string
StrPtr returns a pointer to a string. StrPtr returns a pointer to a string.
<a name="TplName"></a> <a name="TplName"></a>
## func [TplName](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L17>) ## func [TplName](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L19>)
```go ```go
func TplName(serviceName, appname string, suffix ...string) string 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. TplName returns the name of the kubernetes resource as a template string. It is used in the templates and defined in \_helper.tpl file.
<a name="TplValue"></a> <a name="TplValue"></a>
## func [TplValue](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L92>) ## func [TplValue](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L94>)
```go ```go
func TplValue(serviceName, variable string, pipes ...string) string func TplValue(serviceName, variable string, pipes ...string) string
@@ -125,7 +143,7 @@ func Warn(msg ...interface{})
Warn prints a warning message Warn prints a warning message
<a name="WordWrap"></a> <a name="WordWrap"></a>
## func [WordWrap](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L152>) ## func [WordWrap](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L154>)
```go ```go
func WordWrap(text string, lineWidth int) string 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. WordWrap wraps a string to a given line width. Warning: it may break the string. You need to check the result.
<a name="Wrap"></a> <a name="Wrap"></a>
## func [Wrap](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L61>) ## func [Wrap](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L63>)
```go ```go
func Wrap(src, above, below string) string 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. Wrap wraps a string with a string above and below. It will respect the indentation of the src string.
<a name="WrapBytes"></a> <a name="WrapBytes"></a>
## func [WrapBytes](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L67>) ## func [WrapBytes](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L69>)
```go ```go
func WrapBytes(src, above, below []byte) []byte 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. WrapBytes wraps a byte array with a byte array above and below. It will respect the indentation of the src string.
<a name="EnvConfig"></a> <a name="EnvConfig"></a>
## type [EnvConfig](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L116-L119>) ## type [EnvConfig](<https://github.com/metal3d/katenary/blob/develop/utils/utils.go#L118-L121>)
EnvConfig is a struct to hold the description of an environment variable. EnvConfig is a struct to hold the description of an environment variable.
```go ```go
type EnvConfig struct { type EnvConfig struct {
Description string
Service types.ServiceConfig Service types.ServiceConfig
Description string
} }
``` ```

View File

@@ -77,11 +77,6 @@ h3[id*="katenaryio"] {
} }
/*Zoomable images*/ /*Zoomable images*/
/*[data-md-color-scheme="slate"] #logo {
background-image: url("logo-bright.svg");
}*/
.zoomable svg { .zoomable svg {
background-color: var(--md-default-bg-color); background-color: var(--md-default-bg-color);
padding: 1rem; padding: 1rem;

View File

@@ -1,30 +1,46 @@
package generator 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. // 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.
//
// TODO: maybe we can set it private.
type ChartTemplate struct { type ChartTemplate struct {
Content []byte
Servicename string Servicename string
Content []byte
} }
// 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 {
Templates map[string]*ChartTemplate `yaml:"-"`
Values map[string]any `yaml:"-"`
VolumeMounts map[string]any `yaml:"-"`
composeHash *string `yaml:"-"`
Name string `yaml:"name"` Name string `yaml:"name"`
ApiVersion string `yaml:"apiVersion"` ApiVersion string `yaml:"apiVersion"`
Version string `yaml:"version"` Version string `yaml:"version"`
AppVersion string `yaml:"appVersion"` AppVersion string `yaml:"appVersion"`
Description string `yaml:"description"` Description string `yaml:"description"`
Helper string `yaml:"-"`
Dependencies []labelStructs.Dependency `yaml:"dependencies,omitempty"` 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. // 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. // SaveTemplates the templates of the chart to the given directory.
type ConvertOptions struct { func (chart *HelmChart) SaveTemplates(templateDir string) {
Force bool // Force the chart directory deletion if it already exists. for name, template := range chart.Templates {
OutputDir string // The output directory of the chart. t := template.Content
Profiles []string // Profile to use for the conversion. t = removeNewlinesInsideBrackets(t)
HelmUpdate bool // If true, the "helm dep update" command will be run after the chart generation. t = removeUnwantedLines(t)
AppVersion *string // Set the chart "appVersion" field. If nil, the version will be set to 0.1.0. t = addModeline(t)
ChartVersion string // Set the chart "version" field.
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()
}
} }

View File

@@ -7,13 +7,13 @@ import (
"regexp" "regexp"
"strings" "strings"
"katenary/generator/labelStructs"
"katenary/utils"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
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 // 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. // 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 { switch kind {
case "configmap": case "configmap":
return NewConfigMap(service, appName) return NewConfigMap(service, appName)
@@ -47,8 +47,8 @@ const (
type ConfigMap struct { type ConfigMap struct {
*corev1.ConfigMap *corev1.ConfigMap
service *types.ServiceConfig service *types.ServiceConfig
usage FileMapUsage
path string 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. // 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 // 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) log.Fatal(err)
} else { }
// drop the secrets from the environment // drop the secrets from the environment
for _, secret := range secrets { for _, secret := range secrets {
drop[secret] = true drop[secret] = true
}
} }
// get the label values from the labels // get the label values from the labels
varDescriptons := utils.GetValuesFromLabel(service, LabelValues) varDescriptons := utils.GetValuesFromLabel(service, LabelValues)
@@ -95,7 +95,6 @@ func NewConfigMap(service types.ServiceConfig, appName string) *ConfigMap {
done[value] = true done[value] = true
continue continue
} }
// val := `{{ tpl .Values.` + service.Name + `.environment.` + value + ` $ }}`
val := utils.TplValue(service.Name, "environment."+value) val := utils.TplValue(service.Name, "environment."+value)
service.Environment[value] = &val service.Environment[value] = &val
} }
@@ -112,10 +111,9 @@ func NewConfigMap(service types.ServiceConfig, appName string) *ConfigMap {
} }
} }
for key, env := range service.Environment { for key, env := range service.Environment {
if _, ok := done[key]; ok { _, isDropped := drop[key]
continue _, isDone := done[key]
} if isDropped || isDone {
if _, ok := drop[key]; ok {
continue continue
} }
cm.AddData(key, *env) 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 // 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. // 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. // 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 := path
normalized = strings.TrimLeft(normalized, ".") normalized = strings.TrimLeft(normalized, ".")
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. // 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 c.Data[key] = value
} }

View File

@@ -12,13 +12,12 @@ import (
"strings" "strings"
"time" "time"
"github.com/compose-spec/compose-go/types"
"katenary/generator/extrafiles" "katenary/generator/extrafiles"
"katenary/generator/labelStructs" "katenary/generator/labelStructs"
"katenary/parser" "katenary/parser"
"katenary/utils" "katenary/utils"
"github.com/compose-spec/compose-go/types"
goyaml "gopkg.in/yaml.v3"
) )
const headerHelp = `# This file is autogenerated by katenary const headerHelp = `# This file is autogenerated by katenary
@@ -76,10 +75,11 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) {
// check if the chart directory exists // check if the chart directory exists
// if yes, prevent the user from overwriting it and ask for confirmation // if yes, prevent the user from overwriting it and ask for confirmation
if _, err := os.Stat(config.OutputDir); err == nil { 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] ") overwrite := utils.Confirm(
var answer string "The chart directory "+config.OutputDir+" already exists, do you want to overwrite it?",
fmt.Scanln(&answer) utils.IconWarning,
if strings.ToLower(answer) != "y" { )
if !overwrite {
fmt.Println("Aborting") fmt.Println("Aborting")
os.Exit(126) // 126 is the exit code for "Command invoked cannot execute" 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) os.Exit(1)
} }
for name, template := range chart.Templates { // write the templates to the disk
t := template.Content chart.SaveTemplates(templateDir)
t = removeNewlinesInsideBrackets(t)
t = removeUnwantedLines(t)
t = addModeline(t)
kind := utils.GetKind(name) // write the Chart.yaml file
var icon utils.Icon buildCharYamlFile(chart, project, chartPath)
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 // build and write the values.yaml file
if err := os.MkdirAll(filepath.Join(templateDir, servicename), 0o755); err != nil { buildValues(chart, project, valuesPath)
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) // write the _helpers.tpl to the disk
f.Close() writeContent(helpersPath, []byte(chart.Helper))
}
// 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 readme to the disk
readme := extrafiles.ReadMeFile(chart.Name, chart.Description, chart.Values) readme := extrafiles.ReadMeFile(chart.Name, chart.Description, chart.Values)
f, err = os.Create(readmePath) writeContent(readmePath, []byte(readme))
if err != nil {
fmt.Println(utils.IconFailure, err)
os.Exit(1)
}
f.Write([]byte(readme))
f.Close()
services := make([]string, 0) // get the list of services to write in the notes
for _, service := range project.Services { buildNotesFile(project, notesPath)
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()
executeAndHandleError := func(fn func(ConvertOptions) error, config ConvertOptions, message string) { // call helm update if needed
if err := fn(config); err != nil { callHelmUpdate(config)
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")
}
} }
const ingressClassHelp = `# Default value for ingress.class annotation 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 { func addVariablesDoc(values []byte, project *types.Project) []byte {
lines := strings.Split(string(values), "\n") lines := strings.Split(string(values), "\n")
currentService := ""
for _, service := range project.Services { for _, service := range project.Services {
variables := utils.GetValuesFromLabel(service, LabelValues) lines = addDocToVariable(service, lines)
for i, line := range lines { }
if regexp.MustCompile(`(?m)^` + service.Name + `:`).MatchString(line) { return []byte(strings.Join(lines, "\n"))
currentService = service.Name }
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 { spaces := utils.CountStartingSpaces(line)
if variable == nil { if regexp.MustCompile(`(?m)\s*`+varname+`:`).MatchString(line) && currentService == service.Name {
continue
}
spaces := utils.CountStartingSpaces(line)
if regexp.MustCompile(`(?m)\s*`+varname+`:`).MatchString(line) && currentService == service.Name {
// add # to the beginning of the Description // add # to the beginning of the Description
doc := strings.ReplaceAll("\n"+variable.Description, "\n", "\n"+strings.Repeat(" ", spaces)+"# ") doc := strings.ReplaceAll("\n"+variable.Description, "\n", "\n"+strings.Repeat(" ", spaces)+"# ")
doc = strings.TrimRight(doc, " ") doc = strings.TrimRight(doc, " ")
doc += "\n" + line 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. 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") lines := strings.Split(string(values), "\n")
for _, service := range project.Services { for _, service := range project.Services {
inService := false
inRegistry := false
// read the label LabelMainApp // read the label LabelMainApp
if v, ok := service.Labels[LabelMainApp]; !ok { if v, ok := service.Labels[LabelMainApp]; !ok {
continue 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) fmt.Printf("%s Adding main tag app doc %s\n", utils.IconConfig, service.Name)
} }
for i, line := range lines { lines = addMainAppDoc(lines, service)
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 []byte(strings.Join(lines, "\n")) 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 { func removeNewlinesInsideBrackets(values []byte) []byte {
re, err := regexp.Compile(`(?s)\{\{(.*?)\}\}`) re, err := regexp.Compile(`(?s)\{\{(.*?)\}\}`)
if err != nil { if err != nil {
@@ -715,3 +586,89 @@ func addYAMLSelectorPath(values []byte) []byte {
} }
return []byte(strings.Join(toReturn, "\n")) 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")
}
}

View File

@@ -4,14 +4,14 @@ import (
"log" "log"
"strings" "strings"
"katenary/generator/labelStructs"
"katenary/utils"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
batchv1 "k8s.io/api/batch/v1" batchv1 "k8s.io/api/batch/v1"
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 // only used to check interface implementation

View File

@@ -12,7 +12,7 @@ import (
) )
func TestBasicCronJob(t *testing.T) { func TestBasicCronJob(t *testing.T) {
compose_file := ` composeFile := `
services: services:
cron: cron:
image: fedora image: fedora
@@ -23,7 +23,7 @@ services:
schedule: "*/1 * * * *" schedule: "*/1 * * * *"
rbac: false rbac: false
` `
tmpDir := setup(compose_file) tmpDir := setup(composeFile)
defer teardown(tmpDir) defer teardown(tmpDir)
currentDir, _ := os.Getwd() currentDir, _ := os.Getwd()
@@ -64,7 +64,7 @@ services:
} }
func TestCronJobbWithRBAC(t *testing.T) { func TestCronJobbWithRBAC(t *testing.T) {
compose_file := ` composeFile := `
services: services:
cron: cron:
image: fedora image: fedora
@@ -76,7 +76,7 @@ services:
rbac: true rbac: true
` `
tmpDir := setup(compose_file) tmpDir := setup(composeFile)
defer teardown(tmpDir) defer teardown(tmpDir)
currentDir, _ := os.Getwd() currentDir, _ := os.Getwd()

View File

@@ -9,14 +9,14 @@ import (
"strings" "strings"
"time" "time"
"katenary/generator/labelStructs"
"katenary/utils"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
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)
@@ -204,117 +204,127 @@ func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string) {
isSamePod = v != "" 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) container, index := utils.GetContainerByName(service.Name, d.Spec.Template.Spec.Containers)
defer func(d *Deployment, container *corev1.Container, index int) { defer func(d *Deployment, container *corev1.Container, index int) {
d.Spec.Template.Spec.Containers[index] = *container d.Spec.Template.Spec.Containers[index] = *container
}(d, container, index) }(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 { if container == nil {
// not declared as a bind volume, skip utils.Warn("Container not found for volume", volume.Source)
if _, ok := tobind[volume.Source]; !isSamePod && volume.Type == "bind" && !ok { return
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 { // ensure that the volume is not already present in the container
utils.Warn("Container not found for volume", volume.Source) for _, vm := range container.VolumeMounts {
continue if vm.Name == volume.Source {
} return
// 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)
}
} }
} }
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) {

View File

@@ -10,20 +10,22 @@ import (
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
) )
const webTemplateOutput = `templates/web/deployment.yaml`
func TestGenerate(t *testing.T) { func TestGenerate(t *testing.T) {
compose_file := ` composeFile := `
services: services:
web: web:
image: nginx:1.29 image: nginx:1.29
` `
tmpDir := setup(compose_file) tmpDir := setup(composeFile)
defer teardown(tmpDir) defer teardown(tmpDir)
currentDir, _ := os.Getwd() currentDir, _ := os.Getwd()
os.Chdir(tmpDir) os.Chdir(tmpDir)
defer os.Chdir(currentDir) defer os.Chdir(currentDir)
output := _compile_test(t, "-s", "templates/web/deployment.yaml") output := _compile_test(t, "-s", webTemplateOutput)
// dt := DeploymentTest{} // dt := DeploymentTest{}
dt := v1.Deployment{} dt := v1.Deployment{}
@@ -42,7 +44,7 @@ services:
} }
func TestGenerateOneDeploymentWithSamePod(t *testing.T) { func TestGenerateOneDeploymentWithSamePod(t *testing.T) {
compose_file := ` composeFile := `
services: services:
web: web:
image: nginx:1.29 image: nginx:1.29
@@ -57,14 +59,15 @@ services:
katenary.v3/same-pod: web katenary.v3/same-pod: web
` `
tmpDir := setup(compose_file) outDir := "./chart"
tmpDir := setup(composeFile)
defer teardown(tmpDir) defer teardown(tmpDir)
currentDir, _ := os.Getwd() currentDir, _ := os.Getwd()
os.Chdir(tmpDir) os.Chdir(tmpDir)
defer os.Chdir(currentDir) defer os.Chdir(currentDir)
output := _compile_test(t, "-s", "templates/web/deployment.yaml") output := _compile_test(t, "-s", webTemplateOutput)
dt := v1.Deployment{} dt := v1.Deployment{}
if err := yaml.Unmarshal([]byte(output), &dt); err != nil { if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
t.Errorf(unmarshalError, err) t.Errorf(unmarshalError, err)
@@ -76,8 +79,8 @@ services:
// endsure that the fpm service is not created // endsure that the fpm service is not created
var err error var err error
output, err = helmTemplate(ConvertOptions{ _, err = helmTemplate(ConvertOptions{
OutputDir: "./chart", OutputDir: outDir,
}, "-s", "templates/fpm/deployment.yaml") }, "-s", "templates/fpm/deployment.yaml")
if err == nil { if err == nil {
t.Errorf("Expected error, got nil") t.Errorf("Expected error, got nil")
@@ -85,7 +88,7 @@ services:
// ensure that the web service is created and has got 2 ports // ensure that the web service is created and has got 2 ports
output, err = helmTemplate(ConvertOptions{ output, err = helmTemplate(ConvertOptions{
OutputDir: "./chart", OutputDir: outDir,
}, "-s", "templates/web/service.yaml") }, "-s", "templates/web/service.yaml")
if err != nil { if err != nil {
t.Errorf("Error: %s", err) t.Errorf("Error: %s", err)
@@ -101,7 +104,7 @@ services:
} }
func TestDependsOn(t *testing.T) { func TestDependsOn(t *testing.T) {
compose_file := ` composeFile := `
services: services:
web: web:
image: nginx:1.29 image: nginx:1.29
@@ -115,14 +118,14 @@ services:
ports: ports:
- 3306:3306 - 3306:3306
` `
tmpDir := setup(compose_file) tmpDir := setup(composeFile)
defer teardown(tmpDir) defer teardown(tmpDir)
currentDir, _ := os.Getwd() currentDir, _ := os.Getwd()
os.Chdir(tmpDir) os.Chdir(tmpDir)
defer os.Chdir(currentDir) defer os.Chdir(currentDir)
output := _compile_test(t, "-s", "templates/web/deployment.yaml") output := _compile_test(t, "-s", webTemplateOutput)
dt := v1.Deployment{} dt := v1.Deployment{}
if err := yaml.Unmarshal([]byte(output), &dt); err != nil { if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
t.Errorf(unmarshalError, err) t.Errorf(unmarshalError, err)
@@ -138,7 +141,7 @@ services:
} }
func TestHelmDependencies(t *testing.T) { func TestHelmDependencies(t *testing.T) {
compose_file := ` composeFile := `
services: services:
web: web:
image: nginx:1.29 image: nginx:1.29
@@ -156,15 +159,15 @@ services:
version: 18.x.X version: 18.x.X
` `
compose_file = fmt.Sprintf(compose_file, Prefix()) composeFile = fmt.Sprintf(composeFile, Prefix())
tmpDir := setup(compose_file) tmpDir := setup(composeFile)
defer teardown(tmpDir) defer teardown(tmpDir)
currentDir, _ := os.Getwd() currentDir, _ := os.Getwd()
os.Chdir(tmpDir) os.Chdir(tmpDir)
defer os.Chdir(currentDir) defer os.Chdir(currentDir)
output := _compile_test(t, "-s", "templates/web/deployment.yaml") output := _compile_test(t, "-s", webTemplateOutput)
dt := v1.Deployment{} dt := v1.Deployment{}
if err := yaml.Unmarshal([]byte(output), &dt); err != nil { if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
t.Errorf(unmarshalError, err) t.Errorf(unmarshalError, err)
@@ -198,7 +201,7 @@ services:
} }
func TestLivenessProbesFromHealthCheck(t *testing.T) { func TestLivenessProbesFromHealthCheck(t *testing.T) {
compose_file := ` composeFile := `
services: services:
web: web:
image: nginx:1.29 image: nginx:1.29
@@ -210,14 +213,14 @@ services:
timeout: 3s timeout: 3s
retries: 3 retries: 3
` `
tmpDir := setup(compose_file) tmpDir := setup(composeFile)
defer teardown(tmpDir) defer teardown(tmpDir)
currentDir, _ := os.Getwd() currentDir, _ := os.Getwd()
os.Chdir(tmpDir) os.Chdir(tmpDir)
defer os.Chdir(currentDir) defer os.Chdir(currentDir)
output := _compile_test(t, "-s", "templates/web/deployment.yaml") output := _compile_test(t, "-s", webTemplateOutput)
dt := v1.Deployment{} dt := v1.Deployment{}
if err := yaml.Unmarshal([]byte(output), &dt); err != nil { if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
t.Errorf(unmarshalError, err) t.Errorf(unmarshalError, err)
@@ -229,7 +232,7 @@ services:
} }
func TestProbesFromLabels(t *testing.T) { func TestProbesFromLabels(t *testing.T) {
compose_file := ` composeFile := `
services: services:
web: web:
image: nginx:1.29 image: nginx:1.29
@@ -246,15 +249,15 @@ services:
path: /ready path: /ready
port: 80 port: 80
` `
compose_file = fmt.Sprintf(compose_file, Prefix()) composeFile = fmt.Sprintf(composeFile, Prefix())
tmpDir := setup(compose_file) tmpDir := setup(composeFile)
defer teardown(tmpDir) defer teardown(tmpDir)
currentDir, _ := os.Getwd() currentDir, _ := os.Getwd()
os.Chdir(tmpDir) os.Chdir(tmpDir)
defer os.Chdir(currentDir) defer os.Chdir(currentDir)
output := _compile_test(t, "-s", "templates/web/deployment.yaml") output := _compile_test(t, "-s", webTemplateOutput)
dt := v1.Deployment{} dt := v1.Deployment{}
if err := yaml.Unmarshal([]byte(output), &dt); err != nil { if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
t.Errorf(unmarshalError, err) t.Errorf(unmarshalError, err)
@@ -280,7 +283,7 @@ services:
} }
func TestSetValues(t *testing.T) { func TestSetValues(t *testing.T) {
compose_file := ` composeFile := `
services: services:
web: web:
image: nginx:1.29 image: nginx:1.29
@@ -292,15 +295,15 @@ services:
- FOO - FOO
` `
compose_file = fmt.Sprintf(compose_file, Prefix()) composeFile = fmt.Sprintf(composeFile, Prefix())
tmpDir := setup(compose_file) tmpDir := setup(composeFile)
defer teardown(tmpDir) defer teardown(tmpDir)
currentDir, _ := os.Getwd() currentDir, _ := os.Getwd()
os.Chdir(tmpDir) os.Chdir(tmpDir)
defer os.Chdir(currentDir) defer os.Chdir(currentDir)
output := _compile_test(t, "-s", "templates/web/deployment.yaml") output := _compile_test(t, "-s", webTemplateOutput)
dt := v1.Deployment{} dt := v1.Deployment{}
if err := yaml.Unmarshal([]byte(output), &dt); err != nil { if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
t.Errorf(unmarshalError, err) t.Errorf(unmarshalError, err)

View File

@@ -2,13 +2,12 @@ package extrafiles
import ( import (
"bytes" "bytes"
_ "embed"
"fmt" "fmt"
"sort" "sort"
"strings" "strings"
"text/template" "text/template"
_ "embed"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )

View File

@@ -1,7 +1,5 @@
package generator package generator
// TODO: configmap from files 20%
import ( import (
"bytes" "bytes"
"fmt" "fmt"
@@ -10,11 +8,11 @@ import (
"strconv" "strconv"
"strings" "strings"
"katenary/generator/labelStructs"
"katenary/utils"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"katenary/generator/labelStructs"
"katenary/utils"
) )
// Generate a chart from a compose project. // Generate a chart from a compose project.
@@ -388,7 +386,7 @@ func buildVolumes(service types.ServiceConfig, chart *HelmChart, deployments map
y, _ := pvc.Yaml() y, _ := pvc.Yaml()
chart.Templates[pvc.Filename()] = &ChartTemplate{ chart.Templates[pvc.Filename()] = &ChartTemplate{
Content: y, Content: y,
Servicename: service.Name, // TODO, use name Servicename: service.Name,
} }
} }
} }

View File

@@ -4,13 +4,13 @@ import (
"log" "log"
"strings" "strings"
"katenary/generator/labelStructs"
"katenary/utils"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
networkv1 "k8s.io/api/networking/v1" networkv1 "k8s.io/api/networking/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 = (*Ingress)(nil) var _ Yaml = (*Ingress)(nil)

View File

@@ -10,9 +10,9 @@ import (
"text/tabwriter" "text/tabwriter"
"text/template" "text/template"
"katenary/utils"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"katenary/utils"
) )
var ( var (

View File

@@ -8,6 +8,8 @@ import (
var testingKatenaryPrefix = Prefix() var testingKatenaryPrefix = Prefix()
const mainAppLabel = "main-app"
func TestPrefix(t *testing.T) { func TestPrefix(t *testing.T) {
tests := []struct { tests := []struct {
name string 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 { type args struct {
name string name string
} }
@@ -39,9 +41,9 @@ func Test_labelName(t *testing.T) {
{ {
name: "Test_labelName", name: "Test_labelName",
args: args{ args: args{
name: "main-app", name: mainAppLabel,
}, },
want: testingKatenaryPrefix + "/main-app", want: testingKatenaryPrefix + "/" + mainAppLabel,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@@ -65,7 +67,7 @@ func TestGetLabelHelp(t *testing.T) {
} }
func TestGetLabelHelpFor(t *testing.T) { func TestGetLabelHelpFor(t *testing.T) {
help := GetLabelHelpFor("main-app", false) help := GetLabelHelpFor(mainAppLabel, false)
if help == "" { if help == "" {
t.Errorf("GetLabelHelpFor() = %v, want %v", help, "Help") t.Errorf("GetLabelHelpFor() = %v, want %v", help, "Help")
} }

View File

@@ -1,13 +1,13 @@
package generator package generator
import ( import (
"katenary/utils"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/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/utils"
) )
var ( var (

View File

@@ -5,12 +5,12 @@ import (
"fmt" "fmt"
"strings" "strings"
"katenary/utils"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
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/utils"
) )
var ( var (
@@ -84,7 +84,7 @@ func (s *Secret) SetData(data map[string]string) {
} }
// AddData adds a key value pair to the secret. // 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 == "" { if value == "" {
return return
} }

View File

@@ -4,13 +4,13 @@ import (
"regexp" "regexp"
"strings" "strings"
"katenary/utils"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"katenary/utils"
) )
var _ Yaml = (*Service)(nil) var _ Yaml = (*Service)(nil)

View File

@@ -6,9 +6,6 @@ import (
"github.com/compose-spec/compose-go/types" "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. // RepositoryValue is a docker repository image and tag that will be saved in values.yaml.
type RepositoryValue struct { type RepositoryValue struct {
Image string `yaml:"image"` Image string `yaml:"image"`
@@ -17,19 +14,19 @@ type RepositoryValue struct {
// PersistenceValue is a persistence configuration that will be saved in values.yaml. // PersistenceValue is a persistence configuration that will be saved in values.yaml.
type PersistenceValue struct { type PersistenceValue struct {
Enabled bool `yaml:"enabled"`
StorageClass string `yaml:"storageClass"` StorageClass string `yaml:"storageClass"`
Size string `yaml:"size"` Size string `yaml:"size"`
AccessMode []string `yaml:"accessMode"` AccessMode []string `yaml:"accessMode"`
Enabled bool `yaml:"enabled"`
} }
// IngressValue is a ingress configuration that will be saved in values.yaml. // IngressValue is a ingress configuration that will be saved in values.yaml.
type IngressValue struct { type IngressValue struct {
Enabled bool `yaml:"enabled"` Annotations map[string]string `yaml:"annotations"`
Host string `yaml:"host"` Host string `yaml:"host"`
Path string `yaml:"path"` Path string `yaml:"path"`
Class string `yaml:"class"` 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. // 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"` Repository *RepositoryValue `yaml:"repository,omitempty"`
Persistence map[string]*PersistenceValue `yaml:"persistence,omitempty"` Persistence map[string]*PersistenceValue `yaml:"persistence,omitempty"`
Ingress *IngressValue `yaml:"ingress,omitempty"` Ingress *IngressValue `yaml:"ingress,omitempty"`
ImagePullPolicy string `yaml:"imagePullPolicy,omitempty"`
Environment map[string]any `yaml:"environment,omitempty"` Environment map[string]any `yaml:"environment,omitempty"`
Replicas *uint32 `yaml:"replicas,omitempty"` Replicas *uint32 `yaml:"replicas,omitempty"`
CronJob *CronJobValue `yaml:"cronjob,omitempty"` CronJob *CronJobValue `yaml:"cronjob,omitempty"`
NodeSelector map[string]string `yaml:"nodeSelector"` NodeSelector map[string]string `yaml:"nodeSelector"`
ServiceAccount string `yaml:"serviceAccount"`
Resources map[string]any `yaml:"resources"` 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. // CronJobValue is a cronjob configuration that will be saved in values.yaml.

View File

@@ -3,17 +3,19 @@ package generator
import ( import (
"strings" "strings"
"katenary/utils"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
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/utils"
) )
var _ Yaml = (*VolumeClaim)(nil) var _ Yaml = (*VolumeClaim)(nil)
const persistenceKey = "persistence"
// 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
@@ -41,7 +43,12 @@ func NewVolumeClaim(service types.ServiceConfig, volumeName, appName string) *Vo
AccessModes: []v1.PersistentVolumeAccessMode{ AccessModes: []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce, v1.ReadWriteOnce,
}, },
StorageClassName: utils.StrPtr(`{{ .Values.` + service.Name + `.persistence.` + volumeName + `.storageClass }}`), StorageClassName: utils.StrPtr(
`{{ .Values.` +
service.Name +
"." + persistenceKey +
"." + volumeName + `.storageClass }}`,
),
Resources: v1.VolumeResourceRequirements{ Resources: v1.VolumeResourceRequirements{
Requests: v1.ResourceList{ Requests: v1.ResourceList{
v1.ResourceStorage: resource.MustParse("1Gi"), v1.ResourceStorage: resource.MustParse("1Gi"),
@@ -69,7 +76,7 @@ func (v *VolumeClaim) Yaml() ([]byte, error) {
strings.Replace( strings.Replace(
string(out), string(out),
"1Gi", "1Gi",
utils.TplValue(serviceName, "persistence."+volumeName+".size"), utils.TplValue(serviceName, persistenceKey+"."+volumeName+".size"),
1, 1,
), ),
) )
@@ -80,8 +87,8 @@ func (v *VolumeClaim) Yaml() ([]byte, error) {
"- ReadWriteOnce", "- ReadWriteOnce",
"{{- .Values."+ "{{- .Values."+
serviceName+ serviceName+
".persistence."+ "."+persistenceKey+
volumeName+ "."+volumeName+
".accessMode | toYaml | nindent __indent__ }}", ".accessMode | toYaml | nindent __indent__ }}",
1, 1,
), ),
@@ -92,7 +99,10 @@ func (v *VolumeClaim) Yaml() ([]byte, error) {
if strings.Contains(line, "storageClass") { if strings.Contains(line, "storageClass") {
lines[i] = utils.Wrap( lines[i] = utils.Wrap(
line, line,
"{{- if ne .Values."+serviceName+".persistence."+volumeName+".storageClass \"-\" }}", "{{- if ne .Values."+
serviceName+
"."+persistenceKey+
"."+volumeName+".storageClass \"-\" }}",
"{{- end }}", "{{- end }}",
) )
} }
@@ -103,8 +113,8 @@ func (v *VolumeClaim) Yaml() ([]byte, error) {
out = []byte( out = []byte(
"{{- if .Values." + "{{- if .Values." +
serviceName + serviceName +
".persistence." + "." + persistenceKey +
volumeName + "." + volumeName +
".enabled }}\n" + ".enabled }}\n" +
string(out) + string(out) +
"\n{{- end }}", "\n{{- end }}",

View File

@@ -7,7 +7,6 @@ import (
) )
func TestDownloadLatestRelease(t *testing.T) { func TestDownloadLatestRelease(t *testing.T) {
// Reset the version to test the latest release // Reset the version to test the latest release
Version = "0.0.0" Version = "0.0.0"
@@ -17,15 +16,14 @@ func TestDownloadLatestRelease(t *testing.T) {
// Now call the CheckLatestVersion function // Now call the CheckLatestVersion function
version, assets, err := CheckLatestVersion() version, assets, err := CheckLatestVersion()
if err != nil { if err != nil {
t.Errorf("Error: %s", err) t.Errorf("Error getting latest version: %s", err)
} }
fmt.Println("Version found", version) fmt.Println("Version found", version)
// Touch exe binary // 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.Write(nil)
f.Close() f.Close()
@@ -48,5 +46,4 @@ func TestAlreadyUpToDate(t *testing.T) {
} }
t.Log("Version is already the most recent", version) t.Log("Version is already the most recent", version)
} }

View File

@@ -1,6 +1,8 @@
package utils package utils
import ( import (
"bytes"
"fmt"
"log" "log"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -114,8 +116,8 @@ func PathToName(path string) string {
// EnvConfig is a struct to hold the description of an environment variable. // EnvConfig is a struct to hold the description of an environment variable.
type EnvConfig struct { type EnvConfig struct {
Description string
Service types.ServiceConfig Service types.ServiceConfig
Description string
} }
// GetValuesFromLabel returns a map of values from a label. // GetValuesFromLabel returns a map of values from a label.
@@ -160,3 +162,27 @@ func MapKeys(m map[string]interface{}) []string {
} }
return keys 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
}