Merge pull request #87 from metal3d/develop

Add schema to the root, fix test coverage
This commit is contained in:
2024-11-26 17:55:46 +01:00
committed by GitHub
7 changed files with 590 additions and 21 deletions

View File

@@ -7,7 +7,8 @@ PREFIX=~/.local
GOVERSION=1.23 GOVERSION=1.23
GO=container GO=container
OUT=katenary OUT=katenary
BLD_CMD=go build -ldflags="-X 'katenary/generator.Version=$(VERSION)'" -o $(OUT) ./cmd/katenary RELEASE=""
BLD_CMD=go build -ldflags="-X 'katenary/generator.Version=$(RELEASE)$(VERSION)'" -o $(OUT) ./cmd/katenary
GOOS=linux GOOS=linux
GOARCH=amd64 GOARCH=amd64
SIGNER=metal3d@gmail.com SIGNER=metal3d@gmail.com

103
README.md
View File

@@ -19,7 +19,7 @@ and Helm Chart creation.
💡 Effortless Efficiency: You only need to add labels when it's necessary to precise things. 💡 Effortless Efficiency: You only need to add labels when it's necessary to precise things.
Then call `katenary convert` and let the magic happen. Then call `katenary convert` and let the magic happen.
# What ? ## What ?
Katenary is a tool to help to transform `docker-compose` files to a working Helm Chart for Kubernetes. Katenary is a tool to help to transform `docker-compose` files to a working Helm Chart for Kubernetes.
@@ -33,7 +33,7 @@ share it with the community.
The main developer is [Patrice FERLET](https://github.com/metal3d). The main developer is [Patrice FERLET](https://github.com/metal3d).
# Install ## Install
You can download the binaries from the [Release](https://github.com/metal3d/katenary/releases) section. Copy the binary You can download the binaries from the [Release](https://github.com/metal3d/katenary/releases) section. Copy the binary
and rename it to `katenary`. Place the binary inside your `PATH`. You should now be able to call the `katenary` command. and rename it to `katenary`. Place the binary inside your `PATH`. You should now be able to call the `katenary` command.
@@ -47,7 +47,7 @@ You can use this commands on Linux:
sh <(curl -sSL https://raw.githubusercontent.com/metal3d/katenary/master/install.sh) sh <(curl -sSL https://raw.githubusercontent.com/metal3d/katenary/master/install.sh)
``` ```
# Or, build yourself ## Or, build yourself
If you've got `podman` or `docker`, you can build `katenary` by using: If you've got `podman` or `docker`, you can build `katenary` by using:
@@ -79,7 +79,7 @@ make build GO=local GOOS=linux GOARCH=arm64
Then place the `katenary` binary file inside your PATH. Then place the `katenary` binary file inside your PATH.
# Tips ## Tips
We strongly recommend adding the completion call to you SHELL using the common `bashrc`, or whatever the profile file We strongly recommend adding the completion call to you SHELL using the common `bashrc`, or whatever the profile file
you use. you use.
@@ -102,7 +102,7 @@ katenary completion fish | source
# powershell (as we don't provide any support on Windows yet, please avoid this...) # powershell (as we don't provide any support on Windows yet, please avoid this...)
``` ```
# Usage ## Usage
```text ```text
Katenary is a tool to convert compose files to Helm Charts. Katenary is a tool to convert compose files to Helm Charts.
@@ -110,22 +110,23 @@ Katenary is a tool to convert compose files to Helm Charts.
Each [command] and subcommand has got an "help" and "--help" flag to show more information. Each [command] and subcommand has got an "help" and "--help" flag to show more information.
Usage: Usage:
katenary [command] katenary [command]
Examples: Examples:
katenary convert -c docker-compose.yml -o ./charts katenary convert -c docker-compose.yml -o ./charts
Available Commands: Available Commands:
completion Generates completion scripts completion Generates completion scripts
convert Converts a docker-compose file to a Helm Chart convert Converts a docker-compose file to a Helm Chart
hash-composefiles Print the hash of the composefiles hash-composefiles Print the hash of the composefiles
help Help about any command help Help about any command
help-labels Print the labels help for all or a specific label help-labels Print the labels help for all or a specific label
version Print the version number of Katenary schema Print the schema of the katenary file
version Print the version number of Katenary
Flags: Flags:
-h, --help help for katenary -h, --help help for katenary
-v, --version version for katenary -v, --version version for katenary
Use "katenary [command] --help" for more information about a command. Use "katenary [command] --help" for more information about a command.
``` ```
@@ -185,7 +186,7 @@ services:
- MARIADB_PASSWORD - MARIADB_PASSWORD
``` ```
# Labels ## Labels
These labels could be found by `katenary help-labels`, and can be placed as labels inside your docker-compose file: These labels could be found by `katenary help-labels`, and can be placed as labels inside your docker-compose file:
@@ -198,6 +199,7 @@ katenary.v3/cronjob: object Create a cronjob from the service.
katenary.v3/dependencies: list of objects Add Helm dependencies to the service. katenary.v3/dependencies: list of objects Add Helm dependencies to the service.
katenary.v3/description: string Description of the service katenary.v3/description: string Description of the service
katenary.v3/env-from: list of strings Add environment variables from antoher service. katenary.v3/env-from: list of strings Add environment variables from antoher service.
katenary.v3/exchange-volumes: list of objects Add exchange volumes (empty directory on the node) to share data
katenary.v3/health-check: object Health check to be added to the deployment. katenary.v3/health-check: object Health check to be added to the deployment.
katenary.v3/ignore: bool Ignore the service katenary.v3/ignore: bool Ignore the service
katenary.v3/ingress: object Ingress rules to be added to the service. katenary.v3/ingress: object Ingress rules to be added to the service.
@@ -207,9 +209,76 @@ katenary.v3/ports: list of uint32 Ports to be added to the service.
katenary.v3/same-pod: string Move the same-pod deployment to the target deployment. katenary.v3/same-pod: string Move the same-pod deployment to the target deployment.
katenary.v3/secrets: list of string Env vars to be set as secrets. katenary.v3/secrets: list of string Env vars to be set as secrets.
katenary.v3/values: list of string or map Environment variables to be added to the values.yaml katenary.v3/values: list of string or map Environment variables to be added to the values.yaml
katenary.v3/values-from: map[string]string Add values from another service.
``` ```
# What a name… ## Katenary.yaml file and schema validation
Instead of using labels inside the docker-compose file, you can use a `katenary.yaml` file to define the labels. This
file is simpler to read and maintain, but you need to keep it up-to-date with the docker-compose file.
For example, instead of using this:
```yaml
services:
web:
image: nginx:latest
katenary.v3/ingress: |-
hostname: myapp.example.com
port: 80
```
You can remove the labels, and use a kanetary.yaml file:
```yaml
web:
ingress:
hostname: myapp.example.com
port: 80
```
To validate the `katenary.yaml` file, you can use the JSON schema using the "master" raw content:
`https://raw.githubusercontent.com/metal3d/katenary/refs/heads/master/katenary.json`
It's easy to configure in LazyVim, create a Lua file in your plugins directory:
```lua
-- yaml.lua
return {
{
"neovim/nvim-lspconfig",
opts = {
servers = {
yamlls = {
settings = {
yaml = {
schemas = {
["https://raw.githubusercontent.com/metal3d/katenary/refs/heads/master/katenary.json"] = "katenary.yaml",
},
},
},
},
},
},
},
}
```
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"
}
}
```
You can, of course, replace the `refs/heads/master` with a specific tag or branch.
## What a name…
Katenary is the stylized name of the project that comes from the "catenary" word. Katenary is the stylized name of the project that comes from the "catenary" word.

View File

@@ -35,7 +35,7 @@ func buildRootCmd() *cobra.Command {
} }
rootCmd.Example = ` katenary convert -c docker-compose.yml -o ./charts` rootCmd.Example = ` katenary convert -c docker-compose.yml -o ./charts`
rootCmd.Version = generator.Version rootCmd.Version = generator.GetVersion()
rootCmd.CompletionOptions.DisableDescriptions = false rootCmd.CompletionOptions.DisableDescriptions = false
rootCmd.CompletionOptions.DisableNoDescFlag = false rootCmd.CompletionOptions.DisableNoDescFlag = false
@@ -233,7 +233,7 @@ func generateVersionCommand() *cobra.Command {
Use: "version", Use: "version",
Short: "Print the version number of Katenary", Short: "Print the version number of Katenary",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
println(generator.Version) fmt.Println(generator.GetVersion())
}, },
} }
} }

View File

@@ -1,6 +1,13 @@
package main package main
import "testing" import (
"bytes"
"encoding/json"
"io"
"os"
"strings"
"testing"
)
func TestBuildCommand(t *testing.T) { func TestBuildCommand(t *testing.T) {
rootCmd := buildRootCmd() rootCmd := buildRootCmd()
@@ -15,3 +22,49 @@ func TestBuildCommand(t *testing.T) {
t.Errorf("Expected %d command, got %d", numCommands, len(rootCmd.Commands())) t.Errorf("Expected %d command, got %d", numCommands, len(rootCmd.Commands()))
} }
} }
func TestGetVersion(t *testing.T) {
cmd := buildRootCmd()
if cmd == nil {
t.Errorf("Expected cmd to be defined")
}
version := generateVersionCommand()
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
version.Run(cmd, nil)
w.Close()
os.Stdout = old
var buf bytes.Buffer
io.Copy(&buf, r)
output := buf.String()
if !strings.Contains(output, "(devel)") {
t.Errorf("Expected output to contain '(devel)', got %s", output)
}
}
func TestSchemaCommand(t *testing.T) {
cmd := buildRootCmd()
if cmd == nil {
t.Errorf("Expected cmd to be defined")
}
schema := generateSchemaCommand()
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
schema.Run(cmd, nil)
w.Close()
os.Stdout = old
var buf bytes.Buffer
io.Copy(&buf, r)
output := buf.String()
// try to parse json
schemaContent := make(map[string]interface{})
if err := json.Unmarshal([]byte(output), &schemaContent); err != nil {
t.Errorf("Expected valid json, got %s", output)
}
}

View File

@@ -1,4 +1,24 @@
package generator package generator
import (
"runtime/debug"
"strings"
)
// Version is the version of katenary. It is set at compile time. // Version is the version of katenary. It is set at compile time.
var Version = "master" // changed at compile time var Version = "master" // changed at compile time
// GetVersion return the version of katneary. It's important to understand that
// the version is set at compile time for the github release. But, it the user get
// katneary using `go install`, the version should be different.
func GetVersion() string {
if strings.HasPrefix(Version, "release-") {
return Version
}
// get the version from the build info
v, ok := debug.ReadBuildInfo()
if ok {
return v.Main.Version + "-" + v.GoVersion
}
return Version
}

21
generator/version_test.go Normal file
View File

@@ -0,0 +1,21 @@
package generator
import (
"strings"
"testing"
)
func TestVersion(t *testing.T) {
// we build on "devel" branch
v := GetVersion()
if strings.Contains(v, "(devel)") {
t.Errorf("Expected version to be set, got %s", v)
}
// now, imagine we are on a release branch
Version = "release-1.0.0"
v = GetVersion()
if !strings.Contains(v, "release-1.0.0") {
t.Errorf("Expected version to be set, got %s", v)
}
}

405
katenary.json Normal file
View File

@@ -0,0 +1,405 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"ConfigMapFile": {
"items": {
"type": "string"
},
"type": "array"
},
"CronJob": {
"properties": {
"image": {
"type": "string"
},
"command": {
"type": "string"
},
"schedule": {
"type": "string"
},
"rbac": {
"type": "boolean"
}
},
"additionalProperties": false,
"type": "object"
},
"Dependency": {
"properties": {
"values": {
"type": "object"
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"repository": {
"type": "string"
},
"alias": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"name",
"version",
"repository"
]
},
"EnvFrom": {
"items": {
"type": "string"
},
"type": "array"
},
"ExchangeVolume": {
"properties": {
"name": {
"type": "string"
},
"mountPath": {
"type": "string"
},
"type": {
"type": "string"
},
"init": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"name",
"mountPath"
]
},
"ExecAction": {
"properties": {
"command": {
"items": {
"type": "string"
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object"
},
"GRPCAction": {
"properties": {
"port": {
"type": "integer"
},
"service": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"port",
"service"
]
},
"HTTPGetAction": {
"properties": {
"path": {
"type": "string"
},
"port": {
"$ref": "#/$defs/IntOrString"
},
"host": {
"type": "string"
},
"scheme": {
"type": "string"
},
"httpHeaders": {
"items": {
"$ref": "#/$defs/HTTPHeader"
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"port"
]
},
"HTTPHeader": {
"properties": {
"name": {
"type": "string"
},
"value": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"name",
"value"
]
},
"HealthCheck": {
"properties": {
"livenessProbe": {
"$ref": "#/$defs/Probe"
},
"readinessProbe": {
"$ref": "#/$defs/Probe"
}
},
"additionalProperties": false,
"type": "object"
},
"Ingress": {
"properties": {
"port": {
"type": "integer"
},
"annotations": {
"oneOf": [
{
"additionalProperties": {
"type": "string"
},
"type": "object"
},
{
"type": "null"
}
]
},
"hostname": {
"type": "string"
},
"path": {
"type": "string"
},
"class": {
"type": "string"
},
"enabled": {
"type": "boolean"
},
"tls": {
"$ref": "#/$defs/TLS"
}
},
"additionalProperties": false,
"type": "object"
},
"IntOrString": {
"oneOf": [
{
"type": "integer"
},
{
"type": "string"
}
]
},
"MapEnv": {
"additionalProperties": {
"type": "string"
},
"type": "object"
},
"Ports": {
"items": {
"type": "integer"
},
"type": "array"
},
"Probe": {
"properties": {
"exec": {
"$ref": "#/$defs/ExecAction"
},
"httpGet": {
"$ref": "#/$defs/HTTPGetAction"
},
"tcpSocket": {
"$ref": "#/$defs/TCPSocketAction"
},
"grpc": {
"$ref": "#/$defs/GRPCAction"
},
"initialDelaySeconds": {
"type": "integer"
},
"timeoutSeconds": {
"type": "integer"
},
"periodSeconds": {
"type": "integer"
},
"successThreshold": {
"type": "integer"
},
"failureThreshold": {
"type": "integer"
},
"terminationGracePeriodSeconds": {
"type": "integer"
}
},
"additionalProperties": false,
"type": "object"
},
"Secrets": {
"items": {
"type": "string"
},
"type": "array"
},
"Service": {
"properties": {
"main-app": {
"type": "boolean",
"title": "Is this service the main application"
},
"values": {
"items": true,
"type": "array",
"description": "Environment variables to be set in values.yaml with or without a description"
},
"secrets": {
"$ref": "#/$defs/Secrets",
"title": "Secrets",
"description": "Environment variables to be set as secrets"
},
"ports": {
"$ref": "#/$defs/Ports",
"title": "Ports",
"description": "Ports to be exposed in services"
},
"ingress": {
"$ref": "#/$defs/Ingress",
"title": "Ingress",
"description": "Ingress configuration"
},
"health-check": {
"$ref": "#/$defs/HealthCheck",
"title": "Health Check",
"description": "Health check configuration that respects the kubernetes api"
},
"same-pod": {
"type": "string",
"title": "Same Pod",
"description": "Service that should be in the same pod"
},
"description": {
"type": "string",
"title": "Description",
"description": "Description of the service that will be injected in the values.yaml file"
},
"ignore": {
"type": "boolean",
"title": "Ignore",
"description": "Ignore the service in the conversion"
},
"dependencies": {
"items": {
"$ref": "#/$defs/Dependency"
},
"type": "array",
"title": "Dependencies",
"description": "Services that should be injected in the Chart.yaml file"
},
"configmap-files": {
"$ref": "#/$defs/ConfigMapFile",
"title": "ConfigMap Files",
"description": "Files that should be injected as ConfigMap"
},
"map-env": {
"$ref": "#/$defs/MapEnv",
"title": "Map Env",
"description": "Map environment variables to another value"
},
"cron-job": {
"$ref": "#/$defs/CronJob",
"title": "Cron Job",
"description": "Cron Job configuration"
},
"env-from": {
"$ref": "#/$defs/EnvFrom",
"title": "Env From",
"description": "Inject environment variables from another service"
},
"exchange-volumes": {
"items": {
"$ref": "#/$defs/ExchangeVolume"
},
"type": "array",
"title": "Exchange Volumes",
"description": "Exchange volumes between services"
},
"values-from": {
"$ref": "#/$defs/ValueFrom",
"title": "Values From",
"description": "Inject values from another service (secret or configmap environment variables)"
}
},
"additionalProperties": false,
"type": "object"
},
"StringOrMap": {
"oneOf": [
{
"type": "string"
},
{
"additionalProperties": {
"type": "string"
},
"type": "object"
}
]
},
"TCPSocketAction": {
"properties": {
"port": {
"$ref": "#/$defs/IntOrString"
},
"host": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"port"
]
},
"TLS": {
"properties": {
"enabled": {
"type": "boolean"
}
},
"additionalProperties": false,
"type": "object"
},
"ValueFrom": {
"additionalProperties": {
"type": "string"
},
"type": "object"
}
},
"additionalProperties": {
"$ref": "#/$defs/Service"
},
"type": "object"
}