Merge pull request #91 from metal3d/develop

Fix binary data, Add tests, Error management
This commit is contained in:
2024-12-03 14:44:19 +01:00
committed by GitHub
10 changed files with 241 additions and 47 deletions

View File

@@ -1,7 +1,5 @@
<div style="text-align:center; margin: auto 0 4em 0" align="center">
<img src="./doc/docs/statics/logo-vertical.svg" alt="Katenary Logo" style="max-width: 90%" align="center"/>
</div>
[![Documentation Status](https://readthedocs.org/projects/katenary/badge/?version=latest)](https://katenary.readthedocs.io/en/latest/?badge=latest)
@@ -262,7 +260,7 @@ return {
settings = {
yaml = {
schemas = {
["https://raw.githubusercontent.com/metal3d/katenary/refs/heads/master/katenary.json"] = "katenary.yaml",
["https://raw.githubusercontent.com/metal3d/katenary/master/katenary.json"] = "katenary.yaml",
},
},
},
@@ -278,12 +276,12 @@ Use this address to validate the `katenary.yaml` file in VSCode:
```json
{
"yaml.schemas": {
"https://raw.githubusercontent.com/metal3d/katenary/refs/heads/master/katenary.json": "katenary.yaml"
"https://raw.githubusercontent.com/metal3d/katenary/master/katenary.json": "katenary.yaml"
}
}
```
> You can, of course, replace the `refs/heads/master` with a specific tag or branch.
> You can, of course, replace the `master` with a specific tag or branch.
## What a name…

View File

@@ -141,11 +141,11 @@ func generateConvertCommand() *cobra.Command {
convertCmd := &cobra.Command{
Use: "convert",
Short: "Converts a docker-compose file to a Helm Chart",
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
if givenAppVersion != "" {
appVersion = &givenAppVersion
}
generator.Convert(generator.ConvertOptions{
return generator.Convert(generator.ConvertOptions{
Force: force,
OutputDir: outputDir,
Profiles: profiles,

View File

@@ -119,7 +119,7 @@ type ChartTemplate struct {
```
<a name="ConfigMap"></a>
## type [ConfigMap](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L35-L40>)
## type [ConfigMap](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L36-L41>)
ConfigMap is a kubernetes ConfigMap. Implements the DataMap interface.
@@ -131,7 +131,7 @@ type ConfigMap struct {
```
<a name="NewConfigMap"></a>
### func [NewConfigMap](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L44>)
### func [NewConfigMap](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L45>)
```go
func NewConfigMap(service types.ServiceConfig, appName string, forFile bool) *ConfigMap
@@ -140,7 +140,7 @@ func NewConfigMap(service types.ServiceConfig, appName string, forFile bool) *Co
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#L117>)
### func [NewConfigMapFromDirectory](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L118>)
```go
func NewConfigMapFromDirectory(service types.ServiceConfig, appName, path string) *ConfigMap
@@ -148,8 +148,17 @@ func NewConfigMapFromDirectory(service types.ServiceConfig, appName, path string
NewConfigMapFromDirectory creates a new ConfigMap from a compose service. This path is the path to the file or directory. If the path is a directory, all files in the directory are added to the ConfigMap. Each subdirectory are ignored. Note that the Generate\(\) function will create the subdirectories ConfigMaps.
<a name="ConfigMap.AddBinaryData"></a>
### func \(\*ConfigMap\) [AddBinaryData](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L154>)
```go
func (c *ConfigMap) AddBinaryData(key string, value []byte)
```
AddBinaryData adds binary data to the configmap. Append or overwrite the value if the key already exists.
<a name="ConfigMap.AddData"></a>
### func \(\*ConfigMap\) [AddData](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L148>)
### func \(\*ConfigMap\) [AddData](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L149>)
```go
func (c *ConfigMap) AddData(key, value string)
@@ -157,17 +166,8 @@ 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#L154>)
```go
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#L189>)
### func \(\*ConfigMap\) [AppendFile](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L209>)
```go
func (c *ConfigMap) AppendFile(path string)
@@ -175,8 +175,17 @@ func (c *ConfigMap) AppendFile(path string)
<a name="ConfigMap.AppenddDir"></a>
### func \(\*ConfigMap\) [AppenddDir](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L163>)
```go
func (c *ConfigMap) AppenddDir(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.Filename"></a>
### func \(\*ConfigMap\) [Filename](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L207>)
### func \(\*ConfigMap\) [Filename](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L232>)
```go
func (c *ConfigMap) Filename() string
@@ -185,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#L217>)
### func \(\*ConfigMap\) [SetData](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L242>)
```go
func (c *ConfigMap) SetData(data map[string]string)
@@ -194,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#L222>)
### func \(\*ConfigMap\) [Yaml](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L247>)
```go
func (c *ConfigMap) Yaml() ([]byte, error)
@@ -421,7 +430,7 @@ func (d *Deployment) Yaml() ([]byte, error)
Yaml returns the yaml representation of the deployment.
<a name="FileMapUsage"></a>
## type [FileMapUsage](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L19>)
## type [FileMapUsage](<https://github.com/metal3d/katenary/blob/develop/generator/configMap.go#L20>)
FileMapUsage is the usage of the filemap.

View File

@@ -196,13 +196,13 @@ type Icon string
const (
IconSuccess Icon = "✅"
IconFailure Icon = "❌"
IconWarning Icon = "⚠️'"
IconWarning Icon = ""
IconNote Icon = "📝"
IconWorld Icon = "🌐"
IconPlug Icon = "🔌"
IconPackage Icon = "📦"
IconCabinet Icon = "🗄️"
IconInfo Icon = ""
IconInfo Icon = "🔵"
IconSecret Icon = "🔒"
IconConfig Icon = "🔧"
IconDependency Icon = "🔗"

View File

@@ -1,6 +1,7 @@
package generator
import (
"fmt"
"katenary/generator/labels"
"katenary/generator/labels/labelStructs"
"katenary/utils"
@@ -9,6 +10,7 @@ import (
"path/filepath"
"regexp"
"strings"
"unicode/utf8"
"github.com/compose-spec/compose-go/types"
corev1 "k8s.io/api/core/v1"
@@ -149,58 +151,84 @@ func (c *ConfigMap) AddData(key, value string) {
c.Data[key] = value
}
// AddBinaryData adds binary data to the configmap. Append or overwrite the value if the key already exists.
func (c *ConfigMap) AddBinaryData(key string, value []byte) {
if c.BinaryData == nil {
c.BinaryData = make(map[string][]byte)
}
c.BinaryData[key] = value
}
// AddFile adds files from given path to the configmap. It is not recursive, to add all files in a directory,
// you need to call this function for each subdirectory.
func (c *ConfigMap) AppendDir(path string) {
func (c *ConfigMap) AppendDir(path string) error {
// read all files in the path and add them to the configmap
stat, err := os.Stat(path)
if err != nil {
log.Fatalf("Path %s does not exist\n", path)
return fmt.Errorf("Path %s does not exist, %w\n", path, err)
}
// recursively read all files in the path and add them to the configmap
if stat.IsDir() {
files, err := os.ReadDir(path)
if err != nil {
log.Fatal(err)
return err
}
for _, file := range files {
if file.IsDir() {
utils.Warn("Subdirectories are ignored for the moment, skipping", filepath.Join(path, file.Name()))
continue
}
path := filepath.Join(path, file.Name())
content, err := os.ReadFile(path)
if err != nil {
log.Fatal(err)
return err
}
// remove the path from the file
filename := filepath.Base(path)
c.AddData(filename, string(content))
if utf8.Valid(content) {
c.AddData(filename, string(content))
} else {
c.AddBinaryData(filename, content)
}
}
} else {
// add the file to the configmap
content, err := os.ReadFile(path)
if err != nil {
log.Fatal(err)
return err
}
filename := filepath.Base(path)
if utf8.Valid(content) {
c.AddData(filename, string(content))
} else {
c.AddBinaryData(filename, content)
}
c.AddData(filepath.Base(path), string(content))
}
return nil
}
func (c *ConfigMap) AppendFile(path string) {
func (c *ConfigMap) AppendFile(path string) error {
// read all files in the path and add them to the configmap
stat, err := os.Stat(path)
if err != nil {
log.Fatalf("Path %s does not exist\n", path)
return fmt.Errorf("Path %s doesn not exists, %w", path, err)
}
// recursively read all files in the path and add them to the configmap
if !stat.IsDir() {
// add the file to the configmap
content, err := os.ReadFile(path)
if err != nil {
log.Fatal(err)
return err
}
c.AddData(filepath.Base(path), string(content))
if utf8.Valid(content) {
c.AddData(filepath.Base(path), string(content))
} else {
c.AddBinaryData(filepath.Base(path), content)
}
}
return nil
}
// Filename returns the filename of the configmap. If the configmap is used for files, the filename contains the path.

View File

@@ -6,6 +6,7 @@ import (
"os"
"testing"
"github.com/compose-spec/compose-go/types"
v1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml"
)
@@ -73,3 +74,19 @@ services:
t.Errorf("Expected FOO to be baz, got %s", v)
}
}
func TestAppendBadFile(t *testing.T) {
cm := NewConfigMap(types.ServiceConfig{}, "app", true)
err := cm.AppendFile("foo")
if err == nil {
t.Errorf("Expected error, got nil")
}
}
func TestAppendBadDir(t *testing.T) {
cm := NewConfigMap(types.ServiceConfig{}, "app", true)
err := cm.AppendDir("foo")
if err == nil {
t.Errorf("Expected error, got nil")
}
}

View File

@@ -90,7 +90,7 @@ var keyRegExp = regexp.MustCompile(`^\s*[^#]+:.*`)
// Convert a compose (docker, podman...) project to a helm chart.
// It calls Generate() to generate the chart and then write it to the disk.
func Convert(config ConvertOptions, dockerComposeFile ...string) {
func Convert(config ConvertOptions, dockerComposeFile ...string) error {
var (
templateDir = filepath.Join(config.OutputDir, "templates")
helpersPath = filepath.Join(config.OutputDir, "templates", "_helpers.tpl")
@@ -105,7 +105,7 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) {
// go to the root of the project
if err := os.Chdir(filepath.Dir(dockerComposeFile[0])); err != nil {
fmt.Println(utils.IconFailure, err)
os.Exit(1)
return err
}
defer os.Chdir(currentDir) // after the generation, go back to the original directory
@@ -118,13 +118,13 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) {
project, err := parser.Parse(config.Profiles, config.EnvFiles, dockerComposeFile...)
if err != nil {
fmt.Println(err)
os.Exit(1)
return err
}
// check older version of labels
if err := checkOldLabels(project); err != nil {
fmt.Println(utils.IconFailure, err)
os.Exit(1)
return err
}
// TODO: use katenary.yaml file here to set the labels
@@ -140,7 +140,7 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) {
)
if !overwrite {
fmt.Println("Aborting")
os.Exit(126) // 126 is the exit code for "Command invoked cannot execute"
return nil
}
}
fmt.Println() // clean line
@@ -150,7 +150,7 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) {
chart, err := Generate(project)
if err != nil {
fmt.Println(err)
os.Exit(1)
return err
}
// if the app version is set from the command line, use it
@@ -194,6 +194,7 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) {
// call helm update if needed
callHelmUpdate(config)
return nil
}
func addChartDoc(values []byte, project *types.Project) []byte {

View File

@@ -48,7 +48,9 @@ func internalCompileTest(t *testing.T, options ...string) string {
AppVersion: appVersion,
ChartVersion: chartVersion,
}
Convert(convertOptions, "compose.yml")
if err := Convert(convertOptions, "compose.yml"); err != nil {
return err.Error()
}
// launch helm lint to check the generated chart
if helmLint(convertOptions) != nil {

View File

@@ -2,8 +2,13 @@ package generator
import (
"fmt"
"image"
"image/color"
"image/png"
"katenary/generator/labels"
"log"
"os"
"path/filepath"
"testing"
v1 "k8s.io/api/apps/v1"
@@ -149,6 +154,140 @@ services:
}
}
func TestBinaryMount(t *testing.T) {
composeFile := `
services:
web:
image: nginx
volumes:
- ./images/foo.png:/var/www/foo
labels:
%[1]s/configmap-files: |-
- ./images/foo.png
`
composeFile = fmt.Sprintf(composeFile, labels.KatenaryLabelPrefix)
tmpDir := setup(composeFile)
log.Println(tmpDir)
defer teardown(tmpDir)
os.Mkdir(filepath.Join(tmpDir, "images"), 0o755)
// create a png image
pngFile := tmpDir + "/images/foo.png"
w, h := 100, 100
img := image.NewRGBA(image.Rect(0, 0, w, h))
red := color.RGBA{255, 0, 0, 255}
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
img.Set(x, y, red)
}
}
blue := color.RGBA{0, 0, 255, 255}
for y := 30; y < 70; y++ {
for x := 30; x < 70; x++ {
img.Set(x, y, blue)
}
}
currentDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(currentDir)
f, err := os.Create(pngFile)
if err != nil {
t.Fatal(err)
}
png.Encode(f, img)
f.Close()
output := internalCompileTest(t, "-s", "templates/web/deployment.yaml")
d := v1.Deployment{}
yaml.Unmarshal([]byte(output), &d)
volumes := d.Spec.Template.Spec.Volumes
if len(volumes) != 1 {
t.Errorf("Expected 1 volume, got %d", len(volumes))
}
cm := corev1.ConfigMap{}
cmContent, err := helmTemplate(ConvertOptions{
OutputDir: "chart",
}, "-s", "templates/web/statics/images/configmap.yaml")
yaml.Unmarshal([]byte(cmContent), &cm)
if im, ok := cm.BinaryData["foo.png"]; !ok {
t.Errorf("Expected foo.png to be in the configmap")
} else {
if len(im) == 0 {
t.Errorf("Expected image to be non-empty")
}
}
}
func TestGloballyBinaryMount(t *testing.T) {
composeFile := `
services:
web:
image: nginx
volumes:
- ./images:/var/www/foo
labels:
%[1]s/configmap-files: |-
- ./images
`
composeFile = fmt.Sprintf(composeFile, labels.KatenaryLabelPrefix)
tmpDir := setup(composeFile)
log.Println(tmpDir)
defer teardown(tmpDir)
os.Mkdir(filepath.Join(tmpDir, "images"), 0o755)
// create a png image
pngFile := tmpDir + "/images/foo.png"
w, h := 100, 100
img := image.NewRGBA(image.Rect(0, 0, w, h))
red := color.RGBA{255, 0, 0, 255}
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
img.Set(x, y, red)
}
}
blue := color.RGBA{0, 0, 255, 255}
for y := 30; y < 70; y++ {
for x := 30; x < 70; x++ {
img.Set(x, y, blue)
}
}
currentDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(currentDir)
f, err := os.Create(pngFile)
if err != nil {
t.Fatal(err)
}
png.Encode(f, img)
f.Close()
output := internalCompileTest(t, "-s", "templates/web/deployment.yaml")
d := v1.Deployment{}
yaml.Unmarshal([]byte(output), &d)
volumes := d.Spec.Template.Spec.Volumes
if len(volumes) != 1 {
t.Errorf("Expected 1 volume, got %d", len(volumes))
}
cm := corev1.ConfigMap{}
cmContent, err := helmTemplate(ConvertOptions{
OutputDir: "chart",
}, "-s", "templates/web/statics/images/configmap.yaml")
yaml.Unmarshal([]byte(cmContent), &cm)
if im, ok := cm.BinaryData["foo.png"]; !ok {
t.Errorf("Expected foo.png to be in the configmap")
} else {
if len(im) == 0 {
t.Errorf("Expected image to be non-empty")
}
}
}
func TestBindFrom(t *testing.T) {
composeFile := `
services:

View File

@@ -9,13 +9,13 @@ type Icon string
const (
IconSuccess Icon = "✅"
IconFailure Icon = "❌"
IconWarning Icon = "⚠️'"
IconWarning Icon = ""
IconNote Icon = "📝"
IconWorld Icon = "🌐"
IconPlug Icon = "🔌"
IconPackage Icon = "📦"
IconCabinet Icon = "🗄️"
IconInfo Icon = ""
IconInfo Icon = "🔵"
IconSecret Icon = "🔒"
IconConfig Icon = "🔧"
IconDependency Icon = "🔗"