Merge pull request #87 from metal3d/develop
Add schema to the root, fix test coverage
This commit is contained in:
3
Makefile
3
Makefile
@@ -7,7 +7,8 @@ PREFIX=~/.local
|
||||
GOVERSION=1.23
|
||||
GO=container
|
||||
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
|
||||
GOARCH=amd64
|
||||
SIGNER=metal3d@gmail.com
|
||||
|
83
README.md
83
README.md
@@ -19,7 +19,7 @@ and Helm Chart creation.
|
||||
💡 Effortless Efficiency: You only need to add labels when it's necessary to precise things.
|
||||
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.
|
||||
|
||||
@@ -33,7 +33,7 @@ share it with the community.
|
||||
|
||||
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
|
||||
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)
|
||||
```
|
||||
|
||||
# Or, build yourself
|
||||
## Or, build yourself
|
||||
|
||||
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.
|
||||
|
||||
# Tips
|
||||
## Tips
|
||||
|
||||
We strongly recommend adding the completion call to you SHELL using the common `bashrc`, or whatever the profile file
|
||||
you use.
|
||||
@@ -102,7 +102,7 @@ katenary completion fish | source
|
||||
# powershell (as we don't provide any support on Windows yet, please avoid this...)
|
||||
```
|
||||
|
||||
# Usage
|
||||
## Usage
|
||||
|
||||
```text
|
||||
Katenary is a tool to convert compose files to Helm Charts.
|
||||
@@ -121,6 +121,7 @@ convert Converts a docker-compose file to a Helm Chart
|
||||
hash-composefiles Print the hash of the composefiles
|
||||
help Help about any command
|
||||
help-labels Print the labels help for all or a specific label
|
||||
schema Print the schema of the katenary file
|
||||
version Print the version number of Katenary
|
||||
|
||||
Flags:
|
||||
@@ -185,7 +186,7 @@ services:
|
||||
- MARIADB_PASSWORD
|
||||
```
|
||||
|
||||
# Labels
|
||||
## Labels
|
||||
|
||||
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/description: string Description of the 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/ignore: bool Ignore 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/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-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.
|
||||
|
||||
|
@@ -35,7 +35,7 @@ func buildRootCmd() *cobra.Command {
|
||||
}
|
||||
rootCmd.Example = ` katenary convert -c docker-compose.yml -o ./charts`
|
||||
|
||||
rootCmd.Version = generator.Version
|
||||
rootCmd.Version = generator.GetVersion()
|
||||
rootCmd.CompletionOptions.DisableDescriptions = false
|
||||
rootCmd.CompletionOptions.DisableNoDescFlag = false
|
||||
|
||||
@@ -233,7 +233,7 @@ func generateVersionCommand() *cobra.Command {
|
||||
Use: "version",
|
||||
Short: "Print the version number of Katenary",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
println(generator.Version)
|
||||
fmt.Println(generator.GetVersion())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,13 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildCommand(t *testing.T) {
|
||||
rootCmd := buildRootCmd()
|
||||
@@ -15,3 +22,49 @@ func TestBuildCommand(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,24 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Version is the version of katenary. It is set 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
21
generator/version_test.go
Normal 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
405
katenary.json
Normal 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"
|
||||
}
|
Reference in New Issue
Block a user