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"
"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.

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>
## 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
func Convert(config ConvertOptions, dockerComposeFile ...string)
@@ -116,16 +116,14 @@ func Prefix() string
<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.
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".
<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
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.
<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
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.
<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
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.
<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
func (c *ConfigMap) AppendFile(path string)
@@ -187,7 +185,7 @@ func (c *ConfigMap) AppendFile(path string)
<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
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.
<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
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.
<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
func (c *ConfigMap) Yaml() ([]byte, error)
@@ -225,18 +223,18 @@ type ConfigMapMount struct {
```
<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.
```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.
<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.
@@ -304,7 +302,7 @@ type DataMap interface {
### func [NewFileMap](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L26>)
```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.
<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
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.
<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
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.
<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
func (d *Deployment) Filename() string
@@ -394,7 +392,7 @@ func (d *Deployment) Filename() string
Filename returns the filename of the deployment.
<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
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.
<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
func (d *Deployment) Yaml() ([]byte, error)
@@ -430,28 +428,29 @@ const (
```
<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...
```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
}
```
<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
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.
<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
func NewChart(name string) *HelmChart
@@ -479,6 +478,15 @@ func NewChart(name string) *HelmChart
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>
## 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>
## 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.
```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 (
```
<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.
```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.
<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.
@@ -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>)
```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)
<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.
@@ -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"`
}
```
<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
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.
<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
func (v *Value) AddIngress(host, path string)
@@ -863,7 +871,7 @@ func (v *Value) AddIngress(host, path string)
<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
func (v *Value) AddPersistence(volumeName string)
@@ -872,7 +880,7 @@ func (v *Value) AddPersistence(volumeName string)
AddPersistence adds persistence configuration to the Value.
<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.
@@ -884,7 +892,7 @@ type VolumeClaim struct {
```
<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
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.
<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
func (v *VolumeClaim) Filename() string
@@ -902,7 +910,7 @@ func (v *VolumeClaim) Filename() string
Filename returns the suggested filename for a VolumeClaim.
<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
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.
<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
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.
<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>
### 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
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.
## 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
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.
<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>
## 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
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.
<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
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.
<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
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.
<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
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.
<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
func Int32Ptr(i int32) *int32
@@ -71,7 +89,7 @@ func Int32Ptr(i int32) *int32
Int32Ptr returns a pointer to an int32.
<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
func MapKeys(m map[string]interface{}) []string
@@ -80,7 +98,7 @@ func MapKeys(m map[string]interface{}) []string
<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
func PathToName(path string) string
@@ -89,7 +107,7 @@ func PathToName(path string) string
PathToName converts a path to a kubernetes complient name.
<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
func StrPtr(s string) *string
@@ -98,7 +116,7 @@ func StrPtr(s string) *string
StrPtr returns a pointer to a string.
<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
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.
<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
func TplValue(serviceName, variable string, pipes ...string) string
@@ -125,7 +143,7 @@ func Warn(msg ...interface{})
Warn prints a warning message
<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
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.
<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
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.
<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
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.
<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.
```go
type EnvConfig struct {
Description string
Service types.ServiceConfig
Description string
}
```

View File

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

View File

@@ -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()
}
}

View File

@@ -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,14 +75,14 @@ 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
}
}
// get the label values from the labels
varDescriptons := utils.GetValuesFromLabel(service, LabelValues)
for value := range varDescriptons {
@@ -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
}

View File

@@ -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,13 +360,21 @@ 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 {
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
@@ -524,8 +391,7 @@ func addVariablesDoc(values []byte, project *types.Project) []byte {
}
}
}
}
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,6 +410,15 @@ func addMainTagAppDoc(values []byte, project *types.Project) []byte {
fmt.Printf("%s Adding main tag app doc %s\n", utils.IconConfig, service.Name)
}
lines = addMainAppDoc(lines, service)
}
return []byte(strings.Join(lines, "\n"))
}
func 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
@@ -564,9 +437,7 @@ func addMainTagAppDoc(values []byte, project *types.Project) []byte {
}
}
}
}
return []byte(strings.Join(lines, "\n"))
return lines
}
func removeNewlinesInsideBrackets(values []byte) []byte {
@@ -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")
}
}

View File

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

View File

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

View File

@@ -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,13 +204,16 @@ 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)
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, " +
@@ -219,18 +222,18 @@ func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string) {
", skipping volume " + volume.Source +
" from service " + service.Name,
)
continue
return
}
if container == nil {
utils.Warn("Container not found for volume", volume.Source)
continue
return
}
// ensure that the volume is not already present in the container
for _, vm := range container.VolumeMounts {
if vm.Name == volume.Source {
continue
return
}
}
@@ -263,6 +266,14 @@ func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string) {
}
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{
@@ -282,7 +293,9 @@ func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string) {
mountPath: volume.Target,
}),
}
} else {
}
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
@@ -313,9 +326,6 @@ func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string) {
}
cm.AppendFile(volume.Source)
}
}
}
}
func (d *Deployment) BindFrom(service types.ServiceConfig, binded *Deployment) {
// find the volume in the binded deployment

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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")
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 }}",

View File

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

View File

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