31 Commits

Author SHA1 Message Date
75dd6701a9 Fixes command expansion
All checks were successful
Go-Tests / tests (pull_request) Successful in 1m50s
Go-Tests / sonar (pull_request) Successful in 37s
2026-05-03 22:20:49 +02:00
dc7a8bc422 Fixes the test for cronjob 2026-05-03 21:33:38 +02:00
b9e81c7e49 Update docs 2026-05-03 21:21:38 +02:00
99c8e12f40 Use standard labels 2026-05-03 21:21:14 +02:00
ca9e3da208 Improve cronjobs 2026-05-03 21:20:59 +02:00
99d8173c15 Fixes same-pod problems 2026-05-03 21:20:38 +02:00
a1e6726763 Now, use traefik
Ingress-nginx is now deprecated, so we changed ingress management:
- traefik is now the default ingress class to use
- we add traefik ingressroute management too
2026-05-03 21:19:59 +02:00
9924ede999 Merge pull request 'feature/better-depends-on' (#184) from feature/better-depends-on into master
All checks were successful
Go-Tests / tests (push) Successful in 1m32s
Go-Tests / sonar (push) Successful in 49s
Reviewed-on: #184
2026-05-02 12:43:01 +00:00
7ee6b74b10 feat(depends): fixes the tests 2026-03-17 10:46:54 +01:00
b40378ec23 feat(quality): enhance logger
Warnings are flushed after the generation to help the user to find
issues.
Colors are better defined.
2026-03-17 10:39:48 +01:00
0b1a45319f feat(depends): fixes the API point 2026-03-17 10:39:04 +01:00
e879c3f10f feat(depends): Warn user that the service is not created 2026-03-16 22:20:24 +01:00
985418ae51 feat(quality): fixes some bad patterns
All checks were successful
Go-Tests / tests (pull_request) Successful in 2m8s
Go-Tests / sonar (pull_request) Successful in 40s
2026-03-16 21:37:50 +01:00
0e133ae6db fix(path): fixing the default compose file check
All checks were successful
Go-Tests / tests (pull_request) Successful in 1m54s
Go-Tests / sonar (pull_request) Successful in 1m15s
2026-03-15 21:50:26 +01:00
5d839035b9 feat(depends): add suffix on RBAC and SA 2026-03-15 10:15:47 +01:00
7e1bbdc9b3 feat(quality): remove unused modules 2026-03-15 09:43:58 +01:00
f175416ac2 feat(quality): fix duplicates and modernize 2026-03-15 09:43:16 +01:00
613baaf229 feat(depends): add RBAC 2026-03-15 08:55:24 +01:00
8fc9cb31c4 feat(depends): Check call to kubernetes API 2026-03-08 23:50:29 +01:00
78b5af747e feat(depends): Use kubernetes API for depends_on management
We were using netcat to port to check if a service is up, but actually
we can do like Docker / Podman compose and check the status. For now,
I'm using the endpoint status, but maybe we can just check if the object
is "up".
2026-03-08 23:47:13 +01:00
269717eb1c fix(err): No port with depends_on
All checks were successful
Go-Tests / tests (push) Successful in 3m25s
Go-Tests / sonar (push) Successful in 42s
As #182 were not clear, the `depends_on` from a compose file needs, at
this time, to check the port of the dependent service. If the port is
not declared (ports or with label), we need to "fail", not to "warn.

Fixes #182
2026-03-08 22:52:24 +01:00
61896baad8 feat(logger) Change others logs 2026-03-08 22:46:26 +01:00
feff997aba feat(logger): Add a Fatal logger 2026-03-08 22:38:03 +01:00
89e331069e Merge pull request 'Typo: replace "skpped" with "skipped"' (#183) from macier-pro/katenary:fix-typos into master
All checks were successful
Go-Tests / tests (push) Successful in 2m33s
Go-Tests / sonar (push) Successful in 42s
Reviewed-on: #183
2026-01-28 20:34:08 +00:00
88ce6d4579 Typo: Replace "skpped" with "skipped"
Some checks failed
Go-Tests / tests (pull_request) Successful in 1m39s
Go-Tests / sonar (pull_request) Failing after 35s
2026-01-19 09:11:45 +00:00
3e80221641 Merge pull request 'fix: convent error' (#178) from kanrin/katenary:master into master
All checks were successful
Go-Tests / tests (push) Successful in 1m39s
Go-Tests / sonar (push) Successful in 37s
Reviewed-on: #178
2025-12-07 10:08:33 +00:00
Orz
990eda74eb fix: convent error
Some checks failed
Go-Tests / tests (pull_request) Successful in 2m54s
Go-Tests / sonar (pull_request) Failing after 24s
2025-10-28 18:42:06 +08:00
7230081401 fix(install): bad release substitution 2025-10-20 00:02:53 +00:00
f0fc694d50 Fix typo
not important but...
2025-10-18 13:22:36 +00:00
d92cc8a01c Fixup comments remove hard coded tagname 2025-10-18 13:22:36 +00:00
3abfaf591c feat(install)
Installation should now be taken from katenary.io
2025-10-18 13:22:36 +00:00
40 changed files with 1917 additions and 327 deletions

View File

@@ -6,13 +6,13 @@ package main
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"strings" "strings"
"katenary.io/internal/generator" "katenary.io/internal/generator"
"katenary.io/internal/generator/katenaryfile" "katenary.io/internal/generator/katenaryfile"
"katenary.io/internal/generator/labels" "katenary.io/internal/generator/labels"
"katenary.io/internal/logger"
"katenary.io/internal/utils" "katenary.io/internal/utils"
"github.com/compose-spec/compose-go/v2/cli" "github.com/compose-spec/compose-go/v2/cli"
@@ -28,7 +28,7 @@ func main() {
rootCmd := buildRootCmd() rootCmd := buildRootCmd()
if err := rootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
log.Fatal(err) logger.Fatal(err)
} }
} }

View File

@@ -21,6 +21,7 @@ Katenary will try to _Unmarshal_ these labels.
| `katenary.v3/configmap-files` | Inject files as Configmap. | `[]string` | | `katenary.v3/configmap-files` | Inject files as Configmap. | `[]string` |
| `katenary.v3/cronjob` | Create a cronjob from the service. | `object` | | `katenary.v3/cronjob` | Create a cronjob from the service. | `object` |
| `katenary.v3/dependencies` | Add Helm dependencies to the service. | `[]object` | | `katenary.v3/dependencies` | Add Helm dependencies to the service. | `[]object` |
| `katenary.v3/depends-on` | Method to check if a service is ready (for depends_on). | `string` |
| `katenary.v3/description` | Description of the service | `string` | | `katenary.v3/description` | Description of the service | `string` |
| `katenary.v3/env-from` | Add environment variables from another service. | `[]string` | | `katenary.v3/env-from` | Add environment variables from another service. | `[]string` |
| `katenary.v3/exchange-volumes` | Add exchange volumes (empty directory on the node) to share data | `[]object` | | `katenary.v3/exchange-volumes` | Add exchange volumes (empty directory on the node) to share data | `[]object` |
@@ -144,6 +145,45 @@ labels:
``` ```
### katenary.v3/depends-on
Method to check if a service is ready (for depends_on).
**Type**: `string`
When a service uses `depends_on`, Katenary creates an initContainer to wait
for the dependent service to be ready.
By default, Katenary uses the Kubernetes API to check if the deployment's
`readyReplicas` status is greater than 0. This method does not require the
service to expose a port and does not create a Kubernetes Service automatically.
If you need to create a Kubernetes Service for external access, use the
`katenary.v3/ports` label instead.
Set this label to `legacy` to use the old netcat method that requires a port
to be defined for the dependent service.
**Example:**
```yaml
web:
image: nginx
depends_on:
- database
labels:
# Use legacy netcat method (requires port)
katenary.v3/depends-on: legacy
database:
image: mysql
labels:
# Create a Kubernetes Service for external access
katenary.v3/ports:
- 3306
```
### katenary.v3/description ### katenary.v3/description
Description of the service Description of the service
@@ -280,6 +320,17 @@ Ingress rules to be added to the service.
Declare an ingress rule for the service. The port should be exposed or Declare an ingress rule for the service. The port should be exposed or
declared with `katenary.v3/ports`. declared with `katenary.v3/ports`.
The default ingress class is "traefik".
**Files generated:** Both `ingress.yaml` (standard Kubernetes Ingress) and
`ingressroute.yaml` (Traefik IngressRoute CRD) are generated. You can
control which one is installed via values.yaml:
- `ingress.enabled` - controls standard Ingress installation
- `ingress.ingressRouteEnabled` - controls IngressRoute installation
Setting `type: ingressroute` automatically sets `ingressRouteEnabled: true`.
**Example:** **Example:**
```yaml ```yaml
@@ -287,6 +338,9 @@ labels:
katenary.v3/ingress: |- katenary.v3/ingress: |-
port: 80 port: 80
hostname: mywebsite.com (optional) hostname: mywebsite.com (optional)
# Use Traefik IngressRoute instead of standard Ingress
type: ingressroute
enabled: true
``` ```
@@ -352,7 +406,12 @@ Ports to be added to the service.
**Type**: `[]uint32` **Type**: `[]uint32`
Only useful for services without exposed port. It is mandatory if the Only useful for services without exposed port. It is mandatory if the
service is a dependency of another service. service is a dependency of another service AND you want to create a
Kubernetes Service for external access.
If you only need to check if the service is ready (using depends_on),
you don't need to declare ports. The service will not be created automatically
unless you add this label.
**Example:** **Example:**

View File

@@ -35,7 +35,7 @@ var Version = "master" // changed at compile time
``` ```
<a name="Convert"></a> <a name="Convert"></a>
## func [Convert](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/converter.go#L101>) ## func Convert
```go ```go
func Convert(config ConvertOptions, dockerComposeFile ...string) error func Convert(config ConvertOptions, dockerComposeFile ...string) error
@@ -44,7 +44,7 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) error
Convert a compose \(docker, podman...\) project to a helm chart. It calls Generate\(\) to generate the chart and then write it to the disk. Convert a compose \(docker, podman...\) project to a helm chart. It calls Generate\(\) to generate the chart and then write it to the disk.
<a name="GetLabels"></a> <a name="GetLabels"></a>
## func [GetLabels](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels.go#L13>) ## func GetLabels
```go ```go
func GetLabels(serviceName, appName string) map[string]string func GetLabels(serviceName, appName string) map[string]string
@@ -53,7 +53,7 @@ func GetLabels(serviceName, appName string) map[string]string
GetLabels returns the labels for a service. It uses the appName to replace the \_\_replace\_\_ in the labels. This is used to generate the labels in the templates. GetLabels returns the labels for a service. It uses the appName to replace the \_\_replace\_\_ in the labels. This is used to generate the labels in the templates.
<a name="GetMatchLabels"></a> <a name="GetMatchLabels"></a>
## func [GetMatchLabels](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels.go#L26>) ## func GetMatchLabels
```go ```go
func GetMatchLabels(serviceName, appName string) map[string]string func GetMatchLabels(serviceName, appName string) map[string]string
@@ -62,7 +62,7 @@ func GetMatchLabels(serviceName, appName string) map[string]string
GetMatchLabels returns the matchLabels for a service. It uses the appName to replace the \_\_replace\_\_ in the labels. This is used to generate the matchLabels in the templates. GetMatchLabels returns the matchLabels for a service. It uses the appName to replace the \_\_replace\_\_ in the labels. This is used to generate the matchLabels in the templates.
<a name="GetVersion"></a> <a name="GetVersion"></a>
## func [GetVersion](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/version.go#L15>) ## func GetVersion
```go ```go
func GetVersion() string func GetVersion() string
@@ -71,7 +71,7 @@ func GetVersion() string
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. 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.
<a name="Helper"></a> <a name="Helper"></a>
## func [Helper](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/helper.go#L16>) ## func Helper
```go ```go
func Helper(name string) string func Helper(name string) string
@@ -80,7 +80,7 @@ func Helper(name string) string
Helper returns the \_helpers.tpl file for a chart. Helper returns the \_helpers.tpl file for a chart.
<a name="NewCronJob"></a> <a name="NewCronJob"></a>
## func [NewCronJob](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/cronJob.go#L29>) ## func NewCronJob
```go ```go
func NewCronJob(service types.ServiceConfig, chart *HelmChart, appName string) (*CronJob, *RBAC) func NewCronJob(service types.ServiceConfig, chart *HelmChart, appName string) (*CronJob, *RBAC)
@@ -89,7 +89,7 @@ func NewCronJob(service types.ServiceConfig, chart *HelmChart, appName string) (
NewCronJob creates a new CronJob from a compose service. The appName is the name of the application taken from the project name. NewCronJob creates a new CronJob from a compose service. The appName is the name of the application taken from the project name.
<a name="ToK8SYaml"></a> <a name="ToK8SYaml"></a>
## func [ToK8SYaml](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/utils.go#L91>) ## func ToK8SYaml
```go ```go
func ToK8SYaml(obj any) ([]byte, error) func ToK8SYaml(obj any) ([]byte, error)
@@ -98,7 +98,7 @@ func ToK8SYaml(obj any) ([]byte, error)
<a name="UnWrapTPL"></a> <a name="UnWrapTPL"></a>
## func [UnWrapTPL](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/utils.go#L87>) ## func UnWrapTPL
```go ```go
func UnWrapTPL(in []byte) []byte func UnWrapTPL(in []byte) []byte
@@ -107,7 +107,7 @@ func UnWrapTPL(in []byte) []byte
UnWrapTPL removes the line wrapping from a template. UnWrapTPL removes the line wrapping from a template.
<a name="ChartTemplate"></a> <a name="ChartTemplate"></a>
## type [ChartTemplate](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/chart.go#L23-L26>) ## type ChartTemplate
ChartTemplate is a template of a chart. It contains the content of the template and the name of the service. This is used internally to generate the templates. ChartTemplate is a template of a chart. It contains the content of the template and the name of the service. This is used internally to generate the templates.
@@ -119,7 +119,7 @@ type ChartTemplate struct {
``` ```
<a name="ConfigMap"></a> <a name="ConfigMap"></a>
## type [ConfigMap](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/configMap.go#L39-L44>) ## type ConfigMap
ConfigMap is a kubernetes ConfigMap. Implements the DataMap interface. ConfigMap is a kubernetes ConfigMap. Implements the DataMap interface.
@@ -131,7 +131,7 @@ type ConfigMap struct {
``` ```
<a name="NewConfigMap"></a> <a name="NewConfigMap"></a>
### func [NewConfigMap](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/configMap.go#L48>) ### func NewConfigMap
```go ```go
func NewConfigMap(service types.ServiceConfig, appName string, forFile bool) *ConfigMap 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". NewConfigMap creates a new ConfigMap from a compose service. The appName is the name of the application taken from the project name. The ConfigMap is filled by environment variables and labels "map\-env".
<a name="NewConfigMapFromDirectory"></a> <a name="NewConfigMapFromDirectory"></a>
### func [NewConfigMapFromDirectory](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/configMap.go#L121>) ### func NewConfigMapFromDirectory
```go ```go
func NewConfigMapFromDirectory(service types.ServiceConfig, appName, path string) *ConfigMap func NewConfigMapFromDirectory(service types.ServiceConfig, appName, path string) *ConfigMap
@@ -149,7 +149,7 @@ 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. 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> <a name="ConfigMap.AddBinaryData"></a>
### func \(\*ConfigMap\) [AddBinaryData](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/configMap.go#L159>) ### func \(\*ConfigMap\) AddBinaryData
```go ```go
func (c *ConfigMap) AddBinaryData(key string, value []byte) func (c *ConfigMap) AddBinaryData(key string, value []byte)
@@ -158,7 +158,7 @@ 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. AddBinaryData adds binary data to the configmap. Append or overwrite the value if the key already exists.
<a name="ConfigMap.AddData"></a> <a name="ConfigMap.AddData"></a>
### func \(\*ConfigMap\) [AddData](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/configMap.go#L154>) ### func \(\*ConfigMap\) AddData
```go ```go
func (c *ConfigMap) AddData(key, value string) func (c *ConfigMap) AddData(key, value string)
@@ -167,7 +167,7 @@ func (c *ConfigMap) AddData(key, value string)
AddData adds a key value pair to the configmap. Append or overwrite the value if the key already exists. AddData adds a key value pair to the configmap. Append or overwrite the value if the key already exists.
<a name="ConfigMap.AppendDir"></a> <a name="ConfigMap.AppendDir"></a>
### func \(\*ConfigMap\) [AppendDir](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/configMap.go#L168>) ### func \(\*ConfigMap\) AppendDir
```go ```go
func (c *ConfigMap) AppendDir(path string) error func (c *ConfigMap) AppendDir(path string) error
@@ -176,7 +176,7 @@ func (c *ConfigMap) AppendDir(path string) error
AppendDir 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. AppendDir adds files from given path to the configmap. It is not recursive, to add all files in a directory, you need to call this function for each subdirectory.
<a name="ConfigMap.AppendFile"></a> <a name="ConfigMap.AppendFile"></a>
### func \(\*ConfigMap\) [AppendFile](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/configMap.go#L215>) ### func \(\*ConfigMap\) AppendFile
```go ```go
func (c *ConfigMap) AppendFile(path string) error func (c *ConfigMap) AppendFile(path string) error
@@ -185,7 +185,7 @@ func (c *ConfigMap) AppendFile(path string) error
<a name="ConfigMap.Filename"></a> <a name="ConfigMap.Filename"></a>
### func \(\*ConfigMap\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/configMap.go#L239>) ### func \(\*ConfigMap\) Filename
```go ```go
func (c *ConfigMap) Filename() string func (c *ConfigMap) Filename() string
@@ -194,7 +194,7 @@ func (c *ConfigMap) Filename() string
Filename returns the filename of the configmap. If the configmap is used for files, the filename contains the path. Filename returns the filename of the configmap. If the configmap is used for files, the filename contains the path.
<a name="ConfigMap.SetData"></a> <a name="ConfigMap.SetData"></a>
### func \(\*ConfigMap\) [SetData](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/configMap.go#L249>) ### func \(\*ConfigMap\) SetData
```go ```go
func (c *ConfigMap) SetData(data map[string]string) func (c *ConfigMap) SetData(data map[string]string)
@@ -203,7 +203,7 @@ func (c *ConfigMap) SetData(data map[string]string)
SetData sets the data of the configmap. It replaces the entire data. SetData sets the data of the configmap. It replaces the entire data.
<a name="ConfigMap.Yaml"></a> <a name="ConfigMap.Yaml"></a>
### func \(\*ConfigMap\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/configMap.go#L254>) ### func \(\*ConfigMap\) Yaml
```go ```go
func (c *ConfigMap) Yaml() ([]byte, error) func (c *ConfigMap) Yaml() ([]byte, error)
@@ -212,7 +212,7 @@ func (c *ConfigMap) Yaml() ([]byte, error)
Yaml returns the yaml representation of the configmap Yaml returns the yaml representation of the configmap
<a name="ConfigMapMount"></a> <a name="ConfigMapMount"></a>
## type [ConfigMapMount](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L30-L33>) ## type ConfigMapMount
@@ -223,7 +223,7 @@ type ConfigMapMount struct {
``` ```
<a name="ConvertOptions"></a> <a name="ConvertOptions"></a>
## type [ConvertOptions](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/chart.go#L29-L38>) ## type ConvertOptions
ConvertOptions are the options to convert a compose project to a helm chart. ConvertOptions are the options to convert a compose project to a helm chart.
@@ -241,7 +241,7 @@ type ConvertOptions struct {
``` ```
<a name="CronJob"></a> <a name="CronJob"></a>
## type [CronJob](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/cronJob.go#L23-L26>) ## type CronJob
CronJob is a kubernetes CronJob. CronJob is a kubernetes CronJob.
@@ -253,7 +253,7 @@ type CronJob struct {
``` ```
<a name="CronJob.Filename"></a> <a name="CronJob.Filename"></a>
### func \(\*CronJob\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/cronJob.go#L113>) ### func \(\*CronJob\) Filename
```go ```go
func (c *CronJob) Filename() string func (c *CronJob) Filename() string
@@ -264,7 +264,7 @@ Filename returns the filename of the cronjob.
Implements the Yaml interface. Implements the Yaml interface.
<a name="CronJob.Yaml"></a> <a name="CronJob.Yaml"></a>
### func \(\*CronJob\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/cronJob.go#L120>) ### func \(\*CronJob\) Yaml
```go ```go
func (c *CronJob) Yaml() ([]byte, error) func (c *CronJob) Yaml() ([]byte, error)
@@ -275,7 +275,7 @@ Yaml returns the yaml representation of the cronjob.
Implements the Yaml interface. Implements the Yaml interface.
<a name="CronJobValue"></a> <a name="CronJobValue"></a>
## type [CronJobValue](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/values.go#L118-L123>) ## type CronJobValue
CronJobValue is a cronjob configuration that will be saved in values.yaml. CronJobValue is a cronjob configuration that will be saved in values.yaml.
@@ -289,7 +289,7 @@ type CronJobValue struct {
``` ```
<a name="DataMap"></a> <a name="DataMap"></a>
## type [DataMap](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/types.go#L4-L7>) ## type DataMap
DataMap is a kubernetes ConfigMap or Secret. It can be used to add data to the ConfigMap or Secret. DataMap is a kubernetes ConfigMap or Secret. It can be used to add data to the ConfigMap or Secret.
@@ -301,7 +301,7 @@ type DataMap interface {
``` ```
<a name="Deployment"></a> <a name="Deployment"></a>
## type [Deployment](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L36-L46>) ## type Deployment
Deployment is a kubernetes Deployment. Deployment is a kubernetes Deployment.
@@ -313,7 +313,7 @@ type Deployment struct {
``` ```
<a name="NewDeployment"></a> <a name="NewDeployment"></a>
### func [NewDeployment](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L50>) ### func NewDeployment
```go ```go
func NewDeployment(service types.ServiceConfig, chart *HelmChart) *Deployment func NewDeployment(service types.ServiceConfig, chart *HelmChart) *Deployment
@@ -322,7 +322,7 @@ func NewDeployment(service types.ServiceConfig, chart *HelmChart) *Deployment
NewDeployment creates a new Deployment from a compose service. The appName is the name of the application taken from the project name. It also creates the Values map that will be used to create the values.yaml file. NewDeployment creates a new Deployment from a compose service. The appName is the name of the application taken from the project name. It also creates the Values map that will be used to create the values.yaml file.
<a name="Deployment.AddContainer"></a> <a name="Deployment.AddContainer"></a>
### func \(\*Deployment\) [AddContainer](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L117>) ### func \(\*Deployment\) AddContainer
```go ```go
func (d *Deployment) AddContainer(service types.ServiceConfig) func (d *Deployment) AddContainer(service types.ServiceConfig)
@@ -331,7 +331,7 @@ func (d *Deployment) AddContainer(service types.ServiceConfig)
AddContainer adds a container to the deployment. AddContainer adds a container to the deployment.
<a name="Deployment.AddHealthCheck"></a> <a name="Deployment.AddHealthCheck"></a>
### func \(\*Deployment\) [AddHealthCheck](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L164>) ### func \(\*Deployment\) AddHealthCheck
```go ```go
func (d *Deployment) AddHealthCheck(service types.ServiceConfig, container *corev1.Container) func (d *Deployment) AddHealthCheck(service types.ServiceConfig, container *corev1.Container)
@@ -340,7 +340,7 @@ func (d *Deployment) AddHealthCheck(service types.ServiceConfig, container *core
<a name="Deployment.AddIngress"></a> <a name="Deployment.AddIngress"></a>
### func \(\*Deployment\) [AddIngress](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L193>) ### func \(\*Deployment\) AddIngress
```go ```go
func (d *Deployment) AddIngress(service types.ServiceConfig, appName string) *Ingress func (d *Deployment) AddIngress(service types.ServiceConfig, appName string) *Ingress
@@ -348,8 +348,17 @@ func (d *Deployment) AddIngress(service types.ServiceConfig, appName string) *In
AddIngress adds an ingress to the deployment. It creates the ingress object. AddIngress adds an ingress to the deployment. It creates the ingress object.
<a name="Deployment.AddIngressRoute"></a>
### func \(\*Deployment\) AddIngressRoute
```go
func (d *Deployment) AddIngressRoute(service types.ServiceConfig, appName string) Yaml
```
AddIngressRoute adds an IngressRoute to the deployment if type is "ingressroute".
<a name="Deployment.AddLegacyVolume"></a> <a name="Deployment.AddLegacyVolume"></a>
### func \(\*Deployment\) [AddLegacyVolume](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L216>) ### func \(\*Deployment\) AddLegacyVolume
```go ```go
func (d *Deployment) AddLegacyVolume(name, kind string) func (d *Deployment) AddLegacyVolume(name, kind string)
@@ -358,7 +367,7 @@ func (d *Deployment) AddLegacyVolume(name, kind string)
<a name="Deployment.AddVolumes"></a> <a name="Deployment.AddVolumes"></a>
### func \(\*Deployment\) [AddVolumes](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L199>) ### func \(\*Deployment\) AddVolumes
```go ```go
func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string) func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string)
@@ -367,7 +376,7 @@ func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string)
AddVolumes adds a volume to the deployment. It does not create the PVC, it only adds the volumes to the deployment. If the volume is a bind volume it will warn the user that it is not supported yet. AddVolumes adds a volume to the deployment. It does not create the PVC, it only adds the volumes to the deployment. If the volume is a bind volume it will warn the user that it is not supported yet.
<a name="Deployment.BindFrom"></a> <a name="Deployment.BindFrom"></a>
### func \(\*Deployment\) [BindFrom](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L237>) ### func \(\*Deployment\) BindFrom
```go ```go
func (d *Deployment) BindFrom(service types.ServiceConfig, binded *Deployment) func (d *Deployment) BindFrom(service types.ServiceConfig, binded *Deployment)
@@ -376,7 +385,7 @@ func (d *Deployment) BindFrom(service types.ServiceConfig, binded *Deployment)
<a name="Deployment.BindMapFilesToContainer"></a> <a name="Deployment.BindMapFilesToContainer"></a>
### func \(\*Deployment\) [BindMapFilesToContainer](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L372>) ### func \(\*Deployment\) BindMapFilesToContainer
```go ```go
func (d *Deployment) BindMapFilesToContainer(service types.ServiceConfig, secrets []string, appName string) (*corev1.Container, int) func (d *Deployment) BindMapFilesToContainer(service types.ServiceConfig, secrets []string, appName string) (*corev1.Container, int)
@@ -385,7 +394,7 @@ func (d *Deployment) BindMapFilesToContainer(service types.ServiceConfig, secret
<a name="Deployment.DependsOn"></a> <a name="Deployment.DependsOn"></a>
### func \(\*Deployment\) [DependsOn](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L265>) ### func \(\*Deployment\) DependsOn
```go ```go
func (d *Deployment) DependsOn(to *Deployment, servicename string) error func (d *Deployment) DependsOn(to *Deployment, servicename string) error
@@ -394,7 +403,7 @@ func (d *Deployment) DependsOn(to *Deployment, servicename string) error
DependsOn adds a initContainer to the deployment that will wait for the service to be up. DependsOn adds a initContainer to the deployment that will wait for the service to be up.
<a name="Deployment.Filename"></a> <a name="Deployment.Filename"></a>
### func \(\*Deployment\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L297>) ### func \(\*Deployment\) Filename
```go ```go
func (d *Deployment) Filename() string func (d *Deployment) Filename() string
@@ -403,7 +412,7 @@ func (d *Deployment) Filename() string
Filename returns the filename of the deployment. Filename returns the filename of the deployment.
<a name="Deployment.MountExchangeVolumes"></a> <a name="Deployment.MountExchangeVolumes"></a>
### func \(\*Deployment\) [MountExchangeVolumes](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L423>) ### func \(\*Deployment\) MountExchangeVolumes
```go ```go
func (d *Deployment) MountExchangeVolumes() func (d *Deployment) MountExchangeVolumes()
@@ -412,7 +421,7 @@ func (d *Deployment) MountExchangeVolumes()
<a name="Deployment.SetEnvFrom"></a> <a name="Deployment.SetEnvFrom"></a>
### func \(\*Deployment\) [SetEnvFrom](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L302>) ### func \(\*Deployment\) SetEnvFrom
```go ```go
func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string, samePod ...bool) func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string, samePod ...bool)
@@ -420,8 +429,17 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string, sam
SetEnvFrom sets the environment variables to a configmap. The configmap is created. SetEnvFrom sets the environment variables to a configmap. The configmap is created.
<a name="Deployment.SetServiceAccountName"></a>
### func \(\*Deployment\) SetServiceAccountName
```go
func (d *Deployment) SetServiceAccountName()
```
<a name="Deployment.Yaml"></a> <a name="Deployment.Yaml"></a>
### func \(\*Deployment\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L447>) ### func \(\*Deployment\) Yaml
```go ```go
func (d *Deployment) Yaml() ([]byte, error) func (d *Deployment) Yaml() ([]byte, error)
@@ -430,7 +448,7 @@ func (d *Deployment) Yaml() ([]byte, error)
Yaml returns the yaml representation of the deployment. Yaml returns the yaml representation of the deployment.
<a name="FileMapUsage"></a> <a name="FileMapUsage"></a>
## type [FileMapUsage](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/configMap.go#L23>) ## type FileMapUsage
FileMapUsage is the usage of the filemap. FileMapUsage is the usage of the filemap.
@@ -448,7 +466,7 @@ const (
``` ```
<a name="HelmChart"></a> <a name="HelmChart"></a>
## type [HelmChart](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/chart.go#L42-L55>) ## type HelmChart
HelmChart is a Helm Chart representation. It contains all the templates, values, versions, helpers... HelmChart is a Helm Chart representation. It contains all the templates, values, versions, helpers...
@@ -471,7 +489,7 @@ type HelmChart struct {
``` ```
<a name="Generate"></a> <a name="Generate"></a>
### func [Generate](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/generator.go#L33>) ### func Generate
```go ```go
func Generate(project *types.Project) (*HelmChart, error) func Generate(project *types.Project) (*HelmChart, error)
@@ -482,7 +500,7 @@ Generate a chart from a compose project. This does not write files to disk, it o
The Generate function will create the HelmChart object this way: The Generate function will create the HelmChart object this way:
- Detect the service port name or leave the port number if not found. - Detect the service port name or leave the port number if not found.
- Create a deployment for each service that are not ingnore. - Create a deployment for each service that are not ingore.
- Create a service and ingresses for each service that has ports and/or declared ingresses. - Create a service and ingresses for each service that has ports and/or declared ingresses.
- Create a PVC or Configmap volumes for each volume. - Create a PVC or Configmap volumes for each volume.
- Create init containers for each service which has dependencies to other services. - Create init containers for each service which has dependencies to other services.
@@ -491,7 +509,7 @@ The Generate function will create the HelmChart object this way:
- Merge the same\-pod services. - Merge the same\-pod services.
<a name="NewChart"></a> <a name="NewChart"></a>
### func [NewChart](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/chart.go#L58>) ### func NewChart
```go ```go
func NewChart(name string) *HelmChart func NewChart(name string) *HelmChart
@@ -500,7 +518,7 @@ func NewChart(name string) *HelmChart
NewChart creates a new empty chart with the given name. NewChart creates a new empty chart with the given name.
<a name="HelmChart.SaveTemplates"></a> <a name="HelmChart.SaveTemplates"></a>
### func \(\*HelmChart\) [SaveTemplates](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/chart.go#L73>) ### func \(\*HelmChart\) SaveTemplates
```go ```go
func (chart *HelmChart) SaveTemplates(templateDir string) func (chart *HelmChart) SaveTemplates(templateDir string)
@@ -509,7 +527,7 @@ func (chart *HelmChart) SaveTemplates(templateDir string)
SaveTemplates the templates of the chart to the given directory. SaveTemplates the templates of the chart to the given directory.
<a name="Ingress"></a> <a name="Ingress"></a>
## type [Ingress](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/ingress.go#L18-L22>) ## type Ingress
@@ -521,16 +539,16 @@ type Ingress struct {
``` ```
<a name="NewIngress"></a> <a name="NewIngress"></a>
### func [NewIngress](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/ingress.go#L25>) ### func NewIngress
```go ```go
func NewIngress(service types.ServiceConfig, Chart *HelmChart) *Ingress func NewIngress(service types.ServiceConfig, Chart *HelmChart) *Ingress
``` ```
NewIngress creates a new Ingress from a compose service. NewIngress creates a new standard Kubernetes Ingress from a compose service.
<a name="Ingress.Filename"></a> <a name="Ingress.Filename"></a>
### func \(\*Ingress\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/ingress.go#L129>) ### func \(\*Ingress\) Filename
```go ```go
func (ingress *Ingress) Filename() string func (ingress *Ingress) Filename() string
@@ -539,7 +557,7 @@ func (ingress *Ingress) Filename() string
<a name="Ingress.Yaml"></a> <a name="Ingress.Yaml"></a>
### func \(\*Ingress\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/ingress.go#L133>) ### func \(\*Ingress\) Yaml
```go ```go
func (ingress *Ingress) Yaml() ([]byte, error) func (ingress *Ingress) Yaml() ([]byte, error)
@@ -547,8 +565,110 @@ func (ingress *Ingress) Yaml() ([]byte, error)
<a name="IngressRoute"></a>
## type IngressRoute
IngressRoute represents a Traefik IngressRoute CRD
```go
type IngressRoute struct {
metav1.TypeMeta `yaml:",inline"`
metav1.ObjectMeta `yaml:"metadata"`
Spec IngressRouteSpec `yaml:"spec"`
// contains filtered or unexported fields
}
```
<a name="NewIngressRoute"></a>
### func NewIngressRoute
```go
func NewIngressRoute(service types.ServiceConfig, Chart *HelmChart, mapping *labelstructs.Ingress, serviceName, appName string) *IngressRoute
```
NewIngressRoute creates a new Traefik IngressRoute from a compose service.
<a name="IngressRoute.Filename"></a>
### func \(\*IngressRoute\) Filename
```go
func (ir *IngressRoute) Filename() string
```
<a name="IngressRoute.Yaml"></a>
### func \(\*IngressRoute\) Yaml
```go
func (ir *IngressRoute) Yaml() ([]byte, error)
```
<a name="IngressRouteRoute"></a>
## type IngressRouteRoute
IngressRouteRoute defines a route in the IngressRoute
```go
type IngressRouteRoute struct {
Match string `json:"match" yaml:"match"`
Kind string `json:"kind" yaml:"kind"`
Services []IngressRouteService `json:"services" yaml:"services"`
}
```
<a name="IngressRouteService"></a>
## type IngressRouteService
IngressRouteService defines a service backend in IngressRoute
```go
type IngressRouteService struct {
Name string `json:"name" yaml:"name"`
Port int `json:"port" yaml:"port"`
}
```
<a name="IngressRouteSpec"></a>
## type IngressRouteSpec
IngressRouteSpec defines the spec for Traefik IngressRoute
```go
type IngressRouteSpec struct {
EntryPoints []string `json:"entryPoints,omitempty" yaml:"entryPoints,omitempty"`
Routes []IngressRouteRoute `json:"routes" yaml:"routes"`
TLS *IngressRouteTLS `json:"tls,omitempty" yaml:"tls,omitempty"`
}
```
<a name="IngressRouteTLS"></a>
## type IngressRouteTLS
IngressRouteTLS defines TLS configuration for IngressRoute
```go
type IngressRouteTLS struct {
SecretName string `json:"secretName,omitempty" yaml:"secretName,omitempty"`
Domains []IngressRouteTLSDomain `json:"domains,omitempty" yaml:"domains,omitempty"`
}
```
<a name="IngressRouteTLSDomain"></a>
## type IngressRouteTLSDomain
IngressRouteTLSDomain defines a domain for TLS
```go
type IngressRouteTLSDomain struct {
Main string `json:"main" yaml:"main"`
}
```
<a name="IngressValue"></a> <a name="IngressValue"></a>
## type [IngressValue](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/values.go#L29-L36>) ## type IngressValue
IngressValue is a ingress configuration that will be saved in values.yaml. IngressValue is a ingress configuration that will be saved in values.yaml.
@@ -558,13 +678,15 @@ type IngressValue struct {
Host string `yaml:"host"` Host string `yaml:"host"`
Path string `yaml:"path"` Path string `yaml:"path"`
Class string `yaml:"class"` Class string `yaml:"class"`
Type string `yaml:"type"`
Enabled bool `yaml:"enabled"` Enabled bool `yaml:"enabled"`
IngressRouteEnabled bool `yaml:"ingressRouteEnabled"`
TLS TLS `yaml:"tls"` TLS TLS `yaml:"tls"`
} }
``` ```
<a name="PersistenceValue"></a> <a name="PersistenceValue"></a>
## type [PersistenceValue](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/values.go#L16-L21>) ## type PersistenceValue
PersistenceValue is a persistence configuration that will be saved in values.yaml. PersistenceValue is a persistence configuration that will be saved in values.yaml.
@@ -578,7 +700,7 @@ type PersistenceValue struct {
``` ```
<a name="RBAC"></a> <a name="RBAC"></a>
## type [RBAC](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/rbac.go#L20-L24>) ## type RBAC
RBAC is a kubernetes RBAC containing a role, a rolebinding and an associated serviceaccount. RBAC is a kubernetes RBAC containing a role, a rolebinding and an associated serviceaccount.
@@ -591,7 +713,7 @@ type RBAC struct {
``` ```
<a name="NewRBAC"></a> <a name="NewRBAC"></a>
### func [NewRBAC](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/rbac.go#L27>) ### func NewRBAC
```go ```go
func NewRBAC(service types.ServiceConfig, appName string) *RBAC func NewRBAC(service types.ServiceConfig, appName string) *RBAC
@@ -600,7 +722,7 @@ func NewRBAC(service types.ServiceConfig, appName string) *RBAC
NewRBAC creates a new RBAC from a compose service. The appName is the name of the application taken from the project name. NewRBAC creates a new RBAC from a compose service. The appName is the name of the application taken from the project name.
<a name="RepositoryValue"></a> <a name="RepositoryValue"></a>
## type [RepositoryValue](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/values.go#L10-L13>) ## type RepositoryValue
RepositoryValue is a docker repository image and tag that will be saved in values.yaml. RepositoryValue is a docker repository image and tag that will be saved in values.yaml.
@@ -612,7 +734,7 @@ type RepositoryValue struct {
``` ```
<a name="Role"></a> <a name="Role"></a>
## type [Role](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/rbac.go#L114-L117>) ## type Role
Role is a kubernetes Role. Role is a kubernetes Role.
@@ -623,8 +745,17 @@ type Role struct {
} }
``` ```
<a name="NewRestrictedRole"></a>
### func NewRestrictedRole
```go
func NewRestrictedRole(service types.ServiceConfig, appName string) *Role
```
NewRestrictedRole creates a Role with minimal permissions for init containers.
<a name="Role.Filename"></a> <a name="Role.Filename"></a>
### func \(\*Role\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/rbac.go#L119>) ### func \(\*Role\) Filename
```go ```go
func (r *Role) Filename() string func (r *Role) Filename() string
@@ -633,7 +764,7 @@ func (r *Role) Filename() string
<a name="Role.Yaml"></a> <a name="Role.Yaml"></a>
### func \(\*Role\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/rbac.go#L123>) ### func \(\*Role\) Yaml
```go ```go
func (r *Role) Yaml() ([]byte, error) func (r *Role) Yaml() ([]byte, error)
@@ -642,7 +773,7 @@ func (r *Role) Yaml() ([]byte, error)
<a name="RoleBinding"></a> <a name="RoleBinding"></a>
## type [RoleBinding](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/rbac.go#L100-L103>) ## type RoleBinding
RoleBinding is a kubernetes RoleBinding. RoleBinding is a kubernetes RoleBinding.
@@ -653,8 +784,17 @@ type RoleBinding struct {
} }
``` ```
<a name="NewRestrictedRoleBinding"></a>
### func NewRestrictedRoleBinding
```go
func NewRestrictedRoleBinding(service types.ServiceConfig, appName string) *RoleBinding
```
NewRestrictedRoleBinding creates a RoleBinding that binds the restricted role to the ServiceAccount.
<a name="RoleBinding.Filename"></a> <a name="RoleBinding.Filename"></a>
### func \(\*RoleBinding\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/rbac.go#L105>) ### func \(\*RoleBinding\) Filename
```go ```go
func (r *RoleBinding) Filename() string func (r *RoleBinding) Filename() string
@@ -663,7 +803,7 @@ func (r *RoleBinding) Filename() string
<a name="RoleBinding.Yaml"></a> <a name="RoleBinding.Yaml"></a>
### func \(\*RoleBinding\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/rbac.go#L109>) ### func \(\*RoleBinding\) Yaml
```go ```go
func (r *RoleBinding) Yaml() ([]byte, error) func (r *RoleBinding) Yaml() ([]byte, error)
@@ -672,7 +812,7 @@ func (r *RoleBinding) Yaml() ([]byte, error)
<a name="Secret"></a> <a name="Secret"></a>
## type [Secret](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/secret.go#L23-L26>) ## type Secret
Secret is a kubernetes Secret. Secret is a kubernetes Secret.
@@ -686,7 +826,7 @@ type Secret struct {
``` ```
<a name="NewSecret"></a> <a name="NewSecret"></a>
### func [NewSecret](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/secret.go#L29>) ### func NewSecret
```go ```go
func NewSecret(service types.ServiceConfig, appName string) *Secret func NewSecret(service types.ServiceConfig, appName string) *Secret
@@ -695,7 +835,7 @@ func NewSecret(service types.ServiceConfig, appName string) *Secret
NewSecret creates a new Secret from a compose service NewSecret creates a new Secret from a compose service
<a name="Secret.AddData"></a> <a name="Secret.AddData"></a>
### func \(\*Secret\) [AddData](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/secret.go#L71>) ### func \(\*Secret\) AddData
```go ```go
func (s *Secret) AddData(key, value string) func (s *Secret) AddData(key, value string)
@@ -704,7 +844,7 @@ func (s *Secret) AddData(key, value string)
AddData adds a key value pair to the secret. AddData adds a key value pair to the secret.
<a name="Secret.Filename"></a> <a name="Secret.Filename"></a>
### func \(\*Secret\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/secret.go#L87>) ### func \(\*Secret\) Filename
```go ```go
func (s *Secret) Filename() string func (s *Secret) Filename() string
@@ -713,7 +853,7 @@ func (s *Secret) Filename() string
Filename returns the filename of the secret. Filename returns the filename of the secret.
<a name="Secret.SetData"></a> <a name="Secret.SetData"></a>
### func \(\*Secret\) [SetData](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/secret.go#L92>) ### func \(\*Secret\) SetData
```go ```go
func (s *Secret) SetData(data map[string]string) func (s *Secret) SetData(data map[string]string)
@@ -722,7 +862,7 @@ func (s *Secret) SetData(data map[string]string)
SetData sets the data of the secret. SetData sets the data of the secret.
<a name="Secret.Yaml"></a> <a name="Secret.Yaml"></a>
### func \(\*Secret\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/secret.go#L99>) ### func \(\*Secret\) Yaml
```go ```go
func (s *Secret) Yaml() ([]byte, error) func (s *Secret) Yaml() ([]byte, error)
@@ -731,7 +871,7 @@ func (s *Secret) Yaml() ([]byte, error)
Yaml returns the yaml representation of the secret. Yaml returns the yaml representation of the secret.
<a name="Service"></a> <a name="Service"></a>
## type [Service](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/service.go#L19-L22>) ## type Service
Service is a kubernetes Service. Service is a kubernetes Service.
@@ -743,7 +883,7 @@ type Service struct {
``` ```
<a name="NewService"></a> <a name="NewService"></a>
### func [NewService](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/service.go#L25>) ### func NewService
```go ```go
func NewService(service types.ServiceConfig, appName string) *Service func NewService(service types.ServiceConfig, appName string) *Service
@@ -752,7 +892,7 @@ func NewService(service types.ServiceConfig, appName string) *Service
NewService creates a new Service from a compose service. NewService creates a new Service from a compose service.
<a name="Service.AddPort"></a> <a name="Service.AddPort"></a>
### func \(\*Service\) [AddPort](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/service.go#L54>) ### func \(\*Service\) AddPort
```go ```go
func (s *Service) AddPort(port types.ServicePortConfig, serviceName ...string) func (s *Service) AddPort(port types.ServicePortConfig, serviceName ...string)
@@ -761,7 +901,7 @@ func (s *Service) AddPort(port types.ServicePortConfig, serviceName ...string)
AddPort adds a port to the service. AddPort adds a port to the service.
<a name="Service.Filename"></a> <a name="Service.Filename"></a>
### func \(\*Service\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/service.go#L75>) ### func \(\*Service\) Filename
```go ```go
func (s *Service) Filename() string func (s *Service) Filename() string
@@ -770,7 +910,7 @@ func (s *Service) Filename() string
Filename returns the filename of the service. Filename returns the filename of the service.
<a name="Service.Yaml"></a> <a name="Service.Yaml"></a>
### func \(\*Service\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/service.go#L80>) ### func \(\*Service\) Yaml
```go ```go
func (s *Service) Yaml() ([]byte, error) func (s *Service) Yaml() ([]byte, error)
@@ -779,7 +919,7 @@ func (s *Service) Yaml() ([]byte, error)
Yaml returns the yaml representation of the service. Yaml returns the yaml representation of the service.
<a name="ServiceAccount"></a> <a name="ServiceAccount"></a>
## type [ServiceAccount](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/rbac.go#L132-L135>) ## type ServiceAccount
ServiceAccount is a kubernetes ServiceAccount. ServiceAccount is a kubernetes ServiceAccount.
@@ -790,8 +930,17 @@ type ServiceAccount struct {
} }
``` ```
<a name="NewServiceAccount"></a>
### func NewServiceAccount
```go
func NewServiceAccount(service types.ServiceConfig, appName string) *ServiceAccount
```
NewServiceAccount creates a new ServiceAccount from a compose service.
<a name="ServiceAccount.Filename"></a> <a name="ServiceAccount.Filename"></a>
### func \(\*ServiceAccount\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/rbac.go#L137>) ### func \(\*ServiceAccount\) Filename
```go ```go
func (r *ServiceAccount) Filename() string func (r *ServiceAccount) Filename() string
@@ -800,7 +949,7 @@ func (r *ServiceAccount) Filename() string
<a name="ServiceAccount.Yaml"></a> <a name="ServiceAccount.Yaml"></a>
### func \(\*ServiceAccount\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/rbac.go#L141>) ### func \(\*ServiceAccount\) Yaml
```go ```go
func (r *ServiceAccount) Yaml() ([]byte, error) func (r *ServiceAccount) Yaml() ([]byte, error)
@@ -809,7 +958,7 @@ func (r *ServiceAccount) Yaml() ([]byte, error)
<a name="TLS"></a> <a name="TLS"></a>
## type [TLS](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/values.go#L23-L26>) ## type TLS
@@ -821,7 +970,7 @@ type TLS struct {
``` ```
<a name="Value"></a> <a name="Value"></a>
## type [Value](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/values.go#L39-L50>) ## type Value
Value will be saved in values.yaml. It contains configuration for all deployment and services. Value will be saved in values.yaml. It contains configuration for all deployment and services.
@@ -841,7 +990,7 @@ type Value struct {
``` ```
<a name="NewValue"></a> <a name="NewValue"></a>
### func [NewValue](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/values.go#L57>) ### func NewValue
```go ```go
func NewValue(service types.ServiceConfig, main ...bool) *Value func NewValue(service types.ServiceConfig, main ...bool) *Value
@@ -852,7 +1001,7 @@ NewValue creates a new Value from a compose service. The value contains the nece
If \`main\` is true, the tag will be empty because it will be set in the helm chart appVersion. If \`main\` is true, the tag will be empty because it will be set in the helm chart appVersion.
<a name="Value.AddIngress"></a> <a name="Value.AddIngress"></a>
### func \(\*Value\) [AddIngress](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/values.go#L90>) ### func \(\*Value\) AddIngress
```go ```go
func (v *Value) AddIngress(host, path string) func (v *Value) AddIngress(host, path string)
@@ -861,7 +1010,7 @@ func (v *Value) AddIngress(host, path string)
<a name="Value.AddPersistence"></a> <a name="Value.AddPersistence"></a>
### func \(\*Value\) [AddPersistence](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/values.go#L104>) ### func \(\*Value\) AddPersistence
```go ```go
func (v *Value) AddPersistence(volumeName string) func (v *Value) AddPersistence(volumeName string)
@@ -870,7 +1019,7 @@ func (v *Value) AddPersistence(volumeName string)
AddPersistence adds persistence configuration to the Value. AddPersistence adds persistence configuration to the Value.
<a name="VolumeClaim"></a> <a name="VolumeClaim"></a>
## type [VolumeClaim](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/volume.go#L19-L24>) ## type VolumeClaim
VolumeClaim is a kubernetes VolumeClaim. This is a PersistentVolumeClaim. VolumeClaim is a kubernetes VolumeClaim. This is a PersistentVolumeClaim.
@@ -882,7 +1031,7 @@ type VolumeClaim struct {
``` ```
<a name="NewVolumeClaim"></a> <a name="NewVolumeClaim"></a>
### func [NewVolumeClaim](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/volume.go#L27>) ### func NewVolumeClaim
```go ```go
func NewVolumeClaim(service types.ServiceConfig, volumeName, appName string) *VolumeClaim func NewVolumeClaim(service types.ServiceConfig, volumeName, appName string) *VolumeClaim
@@ -891,7 +1040,7 @@ func NewVolumeClaim(service types.ServiceConfig, volumeName, appName string) *Vo
NewVolumeClaim creates a new VolumeClaim from a compose service. NewVolumeClaim creates a new VolumeClaim from a compose service.
<a name="VolumeClaim.Filename"></a> <a name="VolumeClaim.Filename"></a>
### func \(\*VolumeClaim\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/volume.go#L63>) ### func \(\*VolumeClaim\) Filename
```go ```go
func (v *VolumeClaim) Filename() string func (v *VolumeClaim) Filename() string
@@ -900,7 +1049,7 @@ func (v *VolumeClaim) Filename() string
Filename returns the suggested filename for a VolumeClaim. Filename returns the suggested filename for a VolumeClaim.
<a name="VolumeClaim.Yaml"></a> <a name="VolumeClaim.Yaml"></a>
### func \(\*VolumeClaim\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/volume.go#L68>) ### func \(\*VolumeClaim\) Yaml
```go ```go
func (v *VolumeClaim) Yaml() ([]byte, error) func (v *VolumeClaim) Yaml() ([]byte, error)
@@ -909,7 +1058,7 @@ func (v *VolumeClaim) Yaml() ([]byte, error)
Yaml marshals a VolumeClaim into yaml. Yaml marshals a VolumeClaim into yaml.
<a name="Yaml"></a> <a name="Yaml"></a>
## type [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/types.go#L10-L13>) ## type Yaml
Yaml is a kubernetes object that can be converted to yaml. Yaml is a kubernetes object that can be converted to yaml.
@@ -920,4 +1069,13 @@ type Yaml interface {
} }
``` ```
<a name="NewIngressRouteFromService"></a>
### func NewIngressRouteFromService
```go
func NewIngressRouteFromService(service types.ServiceConfig, Chart *HelmChart) Yaml
```
NewIngressRouteFromService creates a Traefik IngressRoute from the same service config. This is called separately to generate the IngressRoute file in addition to Ingress.
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>) Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)

View File

@@ -8,7 +8,7 @@ import "katenary.io/internal/generator/extrafiles"
Package extrafiles provides function to generate the Chart files that are not objects. Like README.md and notes.txt... Package extrafiles provides function to generate the Chart files that are not objects. Like README.md and notes.txt...
## func [NotesFile](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/extrafiles/notes.go#L13>) ## func NotesFile
```go ```go
func NotesFile(services []string) string func NotesFile(services []string) string
@@ -17,7 +17,7 @@ func NotesFile(services []string) string
NotesFile returns the content of the note.txt file. NotesFile returns the content of the note.txt file.
<a name="ReadMeFile"></a> <a name="ReadMeFile"></a>
## func [ReadMeFile](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/extrafiles/readme.go#L46>) ## func ReadMeFile
```go ```go
func ReadMeFile(charname, description string, values map[string]any) string func ReadMeFile(charname, description string, values map[string]any) string

View File

@@ -12,7 +12,7 @@ A katenary file, named "katenary.yml" or "katenary.yaml", is a file where you ca
Formely, the file describe the same structure as in labels, and so that can be validated and completed by LSP. It also ease the use of katenary. Formely, the file describe the same structure as in labels, and so that can be validated and completed by LSP. It also ease the use of katenary.
## func [GenerateSchema](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/katenaryfile/main.go#L137>) ## func GenerateSchema
```go ```go
func GenerateSchema() string func GenerateSchema() string
@@ -21,7 +21,7 @@ func GenerateSchema() string
GenerateSchema generates the schema for the katenary.yaml file. GenerateSchema generates the schema for the katenary.yaml file.
<a name="OverrideWithConfig"></a> <a name="OverrideWithConfig"></a>
## func [OverrideWithConfig](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/katenaryfile/main.go#L49>) ## func OverrideWithConfig
```go ```go
func OverrideWithConfig(project *types.Project) func OverrideWithConfig(project *types.Project)
@@ -30,7 +30,7 @@ func OverrideWithConfig(project *types.Project)
OverrideWithConfig overrides the project with the katenary.yaml file. It will set the labels of the services with the values from the katenary.yaml file. It work in memory, so it will not modify the original project. OverrideWithConfig overrides the project with the katenary.yaml file. It will set the labels of the services with the values from the katenary.yaml file. It work in memory, so it will not modify the original project.
<a name="Service"></a> <a name="Service"></a>
## type [Service](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/katenaryfile/main.go#L27-L44>) ## type Service
Service is a struct that contains the service configuration for katenary Service is a struct that contains the service configuration for katenary
@@ -56,7 +56,7 @@ type Service struct {
``` ```
<a name="StringOrMap"></a> <a name="StringOrMap"></a>
## type [StringOrMap](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/katenaryfile/main.go#L24>) ## type StringOrMap
StringOrMap is a struct that can be either a string or a map of strings. It's a helper struct to unmarshal the katenary.yaml file and produce the schema StringOrMap is a struct that can be either a string or a map of strings. It's a helper struct to unmarshal the katenary.yaml file and produce the schema

View File

@@ -17,7 +17,7 @@ const KatenaryLabelPrefix = "katenary.v3"
``` ```
<a name="GetLabelHelp"></a> <a name="GetLabelHelp"></a>
## func [GetLabelHelp](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/katenaryLabels.go#L89>) ## func GetLabelHelp
```go ```go
func GetLabelHelp(asMarkdown bool) string func GetLabelHelp(asMarkdown bool) string
@@ -26,7 +26,7 @@ func GetLabelHelp(asMarkdown bool) string
GetLabelHelp return the help for the labels. GetLabelHelp return the help for the labels.
<a name="GetLabelHelpFor"></a> <a name="GetLabelHelpFor"></a>
## func [GetLabelHelpFor](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/katenaryLabels.go#L98>) ## func GetLabelHelpFor
```go ```go
func GetLabelHelpFor(labelname string, asMarkdown bool) string func GetLabelHelpFor(labelname string, asMarkdown bool) string
@@ -35,7 +35,7 @@ func GetLabelHelpFor(labelname string, asMarkdown bool) string
GetLabelHelpFor returns the help for a specific label. GetLabelHelpFor returns the help for a specific label.
<a name="GetLabelNames"></a> <a name="GetLabelNames"></a>
## func [GetLabelNames](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/katenaryLabels.go#L73>) ## func GetLabelNames
```go ```go
func GetLabelNames() []string func GetLabelNames() []string
@@ -44,7 +44,7 @@ func GetLabelNames() []string
GetLabelNames returns a sorted list of all katenary label names. GetLabelNames returns a sorted list of all katenary label names.
<a name="Prefix"></a> <a name="Prefix"></a>
## func [Prefix](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/katenaryLabels.go#L236>) ## func Prefix
```go ```go
func Prefix() string func Prefix() string
@@ -53,7 +53,7 @@ func Prefix() string
<a name="Help"></a> <a name="Help"></a>
## type [Help](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/katenaryLabels.go#L65-L70>) ## type Help
Help is the documentation of a label. Help is the documentation of a label.
@@ -67,7 +67,7 @@ type Help struct {
``` ```
<a name="Label"></a> <a name="Label"></a>
## type [Label](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/katenaryLabels.go#L58>) ## type Label
Label is a katenary label to find in compose files. Label is a katenary label to find in compose files.
@@ -95,11 +95,12 @@ const (
LabelEnvFrom Label = KatenaryLabelPrefix + "/env-from" LabelEnvFrom Label = KatenaryLabelPrefix + "/env-from"
LabelExchangeVolume Label = KatenaryLabelPrefix + "/exchange-volumes" LabelExchangeVolume Label = KatenaryLabelPrefix + "/exchange-volumes"
LabelValuesFrom Label = KatenaryLabelPrefix + "/values-from" LabelValuesFrom Label = KatenaryLabelPrefix + "/values-from"
LabelDependsOn Label = KatenaryLabelPrefix + "/depends-on"
) )
``` ```
<a name="LabelName"></a> <a name="LabelName"></a>
### func [LabelName](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/katenaryLabels.go#L60>) ### func LabelName
```go ```go
func LabelName(name string) Label func LabelName(name string) Label

View File

@@ -8,7 +8,7 @@ import "katenary.io/internal/generator/labels/labelstructs"
Package labelstructs is a package that contains the structs used to represent the labels in the yaml files. Package labelstructs is a package that contains the structs used to represent the labels in the yaml files.
## type [ConfigMapFiles](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/configMap.go#L5>) ## type ConfigMapFiles
@@ -17,7 +17,7 @@ type ConfigMapFiles []string
``` ```
<a name="ConfigMapFileFrom"></a> <a name="ConfigMapFileFrom"></a>
### func [ConfigMapFileFrom](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/configMap.go#L7>) ### func ConfigMapFileFrom
```go ```go
func ConfigMapFileFrom(data string) (ConfigMapFiles, error) func ConfigMapFileFrom(data string) (ConfigMapFiles, error)
@@ -26,7 +26,7 @@ func ConfigMapFileFrom(data string) (ConfigMapFiles, error)
<a name="CronJob"></a> <a name="CronJob"></a>
## type [CronJob](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/cronJob.go#L5-L10>) ## type CronJob
@@ -40,7 +40,7 @@ type CronJob struct {
``` ```
<a name="CronJobFrom"></a> <a name="CronJobFrom"></a>
### func [CronJobFrom](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/cronJob.go#L12>) ### func CronJobFrom
```go ```go
func CronJobFrom(data string) (*CronJob, error) func CronJobFrom(data string) (*CronJob, error)
@@ -49,7 +49,7 @@ func CronJobFrom(data string) (*CronJob, error)
<a name="Dependency"></a> <a name="Dependency"></a>
## type [Dependency](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/dependencies.go#L6-L12>) ## type Dependency
Dependency is a dependency of a chart to other charts. Dependency is a dependency of a chart to other charts.
@@ -64,7 +64,7 @@ type Dependency struct {
``` ```
<a name="DependenciesFrom"></a> <a name="DependenciesFrom"></a>
### func [DependenciesFrom](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/dependencies.go#L15>) ### func DependenciesFrom
```go ```go
func DependenciesFrom(data string) ([]Dependency, error) func DependenciesFrom(data string) ([]Dependency, error)
@@ -73,7 +73,7 @@ func DependenciesFrom(data string) ([]Dependency, error)
DependenciesFrom returns a slice of dependencies from the given string. DependenciesFrom returns a slice of dependencies from the given string.
<a name="EnvFrom"></a> <a name="EnvFrom"></a>
## type [EnvFrom](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/envFrom.go#L5>) ## type EnvFrom
@@ -82,7 +82,7 @@ type EnvFrom []string
``` ```
<a name="EnvFromFrom"></a> <a name="EnvFromFrom"></a>
### func [EnvFromFrom](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/envFrom.go#L8>) ### func EnvFromFrom
```go ```go
func EnvFromFrom(data string) (EnvFrom, error) func EnvFromFrom(data string) (EnvFrom, error)
@@ -91,7 +91,7 @@ func EnvFromFrom(data string) (EnvFrom, error)
EnvFromFrom returns a EnvFrom from the given string. EnvFromFrom returns a EnvFrom from the given string.
<a name="ExchangeVolume"></a> <a name="ExchangeVolume"></a>
## type [ExchangeVolume](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/exchangeVolume.go#L5-L10>) ## type ExchangeVolume
@@ -105,7 +105,7 @@ type ExchangeVolume struct {
``` ```
<a name="NewExchangeVolumes"></a> <a name="NewExchangeVolumes"></a>
### func [NewExchangeVolumes](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/exchangeVolume.go#L12>) ### func NewExchangeVolumes
```go ```go
func NewExchangeVolumes(data string) ([]*ExchangeVolume, error) func NewExchangeVolumes(data string) ([]*ExchangeVolume, error)
@@ -114,7 +114,7 @@ func NewExchangeVolumes(data string) ([]*ExchangeVolume, error)
<a name="HealthCheck"></a> <a name="HealthCheck"></a>
## type [HealthCheck](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/probes.go#L11-L14>) ## type HealthCheck
@@ -126,7 +126,7 @@ type HealthCheck struct {
``` ```
<a name="ProbeFrom"></a> <a name="ProbeFrom"></a>
### func [ProbeFrom](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/probes.go#L16>) ### func ProbeFrom
```go ```go
func ProbeFrom(data string) (*HealthCheck, error) func ProbeFrom(data string) (*HealthCheck, error)
@@ -135,7 +135,7 @@ func ProbeFrom(data string) (*HealthCheck, error)
<a name="Ingress"></a> <a name="Ingress"></a>
## type [Ingress](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/ingress.go#L15-L23>) ## type Ingress
@@ -145,14 +145,15 @@ type Ingress struct {
Annotations map[string]string `yaml:"annotations,omitempty" jsonschema:"nullable" json:"annotations,omitempty"` Annotations map[string]string `yaml:"annotations,omitempty" jsonschema:"nullable" json:"annotations,omitempty"`
Hostname string `yaml:"hostname,omitempty" json:"hostname,omitempty"` Hostname string `yaml:"hostname,omitempty" json:"hostname,omitempty"`
Path *string `yaml:"path,omitempty" json:"path,omitempty"` Path *string `yaml:"path,omitempty" json:"path,omitempty"`
Class *string `yaml:"class,omitempty" json:"class,omitempty" jsonschema:"default:-"` Class *string `yaml:"class,omitempty" json:"class,omitempty" jsonschema:"default:traefik"`
Type string `yaml:"type,omitempty" json:"type,omitempty"`
Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"` Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
TLS *TLS `yaml:"tls,omitempty" json:"tls,omitempty"` TLS *TLS `yaml:"tls,omitempty" json:"tls,omitempty"`
} }
``` ```
<a name="IngressFrom"></a> <a name="IngressFrom"></a>
### func [IngressFrom](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/ingress.go#L26>) ### func IngressFrom
```go ```go
func IngressFrom(data string) (*Ingress, error) func IngressFrom(data string) (*Ingress, error)
@@ -161,7 +162,7 @@ func IngressFrom(data string) (*Ingress, error)
IngressFrom creates a new Ingress from a compose service. IngressFrom creates a new Ingress from a compose service.
<a name="MapEnv"></a> <a name="MapEnv"></a>
## type [MapEnv](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/mapenv.go#L5>) ## type MapEnv
@@ -170,7 +171,7 @@ type MapEnv map[string]string
``` ```
<a name="MapEnvFrom"></a> <a name="MapEnvFrom"></a>
### func [MapEnvFrom](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/mapenv.go#L8>) ### func MapEnvFrom
```go ```go
func MapEnvFrom(data string) (MapEnv, error) func MapEnvFrom(data string) (MapEnv, error)
@@ -179,7 +180,7 @@ func MapEnvFrom(data string) (MapEnv, error)
MapEnvFrom returns a MapEnv from the given string. MapEnvFrom returns a MapEnv from the given string.
<a name="Ports"></a> <a name="Ports"></a>
## type [Ports](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/ports.go#L5>) ## type Ports
@@ -188,7 +189,7 @@ type Ports []uint32
``` ```
<a name="PortsFrom"></a> <a name="PortsFrom"></a>
### func [PortsFrom](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/ports.go#L8>) ### func PortsFrom
```go ```go
func PortsFrom(data string) (Ports, error) func PortsFrom(data string) (Ports, error)
@@ -197,7 +198,7 @@ func PortsFrom(data string) (Ports, error)
PortsFrom returns a Ports from the given string. PortsFrom returns a Ports from the given string.
<a name="Secrets"></a> <a name="Secrets"></a>
## type [Secrets](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/secrets.go#L5>) ## type Secrets
@@ -206,7 +207,7 @@ type Secrets []string
``` ```
<a name="SecretsFrom"></a> <a name="SecretsFrom"></a>
### func [SecretsFrom](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/secrets.go#L7>) ### func SecretsFrom
```go ```go
func SecretsFrom(data string) (Secrets, error) func SecretsFrom(data string) (Secrets, error)
@@ -215,7 +216,7 @@ func SecretsFrom(data string) (Secrets, error)
<a name="TLS"></a> <a name="TLS"></a>
## type [TLS](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/ingress.go#L11-L13>) ## type TLS
@@ -226,7 +227,7 @@ type TLS struct {
``` ```
<a name="ValueFrom"></a> <a name="ValueFrom"></a>
## type [ValueFrom](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/valueFrom.go#L5>) ## type ValueFrom
@@ -235,7 +236,7 @@ type ValueFrom map[string]string
``` ```
<a name="GetValueFrom"></a> <a name="GetValueFrom"></a>
### func [GetValueFrom](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/labels/labelstructs/valueFrom.go#L7>) ### func GetValueFrom
```go ```go
func GetValueFrom(data string) (*ValueFrom, error) func GetValueFrom(data string) (*ValueFrom, error)

View File

@@ -8,7 +8,21 @@ import "katenary.io/internal/logger"
Package logger provides simple logging functions with icons and colors. Package logger provides simple logging functions with icons and colors.
## func [Failure](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/logger/logger.go#L43>) ## Constants
<a name="ColorGreen"></a>
```go
const (
ColorGreen = "\033[38;5;34m"
ColorRed = "\033[38;5;196m"
ColorOrange = "\033[38;5;214m"
ColorWarning = "\033[38;5;214m"
)
```
<a name="Failure"></a>
## func Failure
```go ```go
func Failure(msg ...any) func Failure(msg ...any)
@@ -16,8 +30,44 @@ func Failure(msg ...any)
Failure prints a failure message. Failure prints a failure message.
<a name="Failuref"></a>
## func Failuref
```go
func Failuref(format string, msg ...any)
```
Failuref prints a formatted failure message.
<a name="Fatal"></a>
## func Fatal
```go
func Fatal(msg ...any)
```
Fatal prints a fatal error message and exits with code 1.
<a name="Fatalf"></a>
## func Fatalf
```go
func Fatalf(format string, msg ...any)
```
Fatalf prints a fatal error message with formatting and exits with code 1.
<a name="FlushWarnings"></a>
## func FlushWarnings
```go
func FlushWarnings()
```
FlushWarnings prints all collected warnings at the end of the conversion.
<a name="Info"></a> <a name="Info"></a>
## func [Info](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/logger/logger.go#L26>) ## func Info
```go ```go
func Info(msg ...any) func Info(msg ...any)
@@ -25,8 +75,17 @@ func Info(msg ...any)
Info prints an informational message. Info prints an informational message.
<a name="Infof"></a>
## func Infof
```go
func Infof(format string, msg ...any)
```
Infof prints a formatted informational message.
<a name="Log"></a> <a name="Log"></a>
## func [Log](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/logger/logger.go#L49>) ## func Log
```go ```go
func Log(icon Icon, msg ...any) func Log(icon Icon, msg ...any)
@@ -34,8 +93,35 @@ func Log(icon Icon, msg ...any)
Log prints a message with a custom icon. Log prints a message with a custom icon.
<a name="Logf"></a>
## func Logf
```go
func Logf(icon Icon, format string, msg ...any)
```
Logf prints a formatted message with a custom icon.
<a name="Print"></a>
## func Print
```go
func Print(msg ...any)
```
Print prints a message without icon.
<a name="Printf"></a>
## func Printf
```go
func Printf(format string, msg ...any)
```
Printf prints a formatted message without icon.
<a name="Success"></a> <a name="Success"></a>
## func [Success](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/logger/logger.go#L37>) ## func Success
```go ```go
func Success(msg ...any) func Success(msg ...any)
@@ -43,8 +129,17 @@ func Success(msg ...any)
Success prints a success message. Success prints a success message.
<a name="Successf"></a>
## func Successf
```go
func Successf(format string, msg ...any)
```
Successf prints a formatted success message.
<a name="Warn"></a> <a name="Warn"></a>
## func [Warn](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/logger/logger.go#L31>) ## func Warn
```go ```go
func Warn(msg ...any) func Warn(msg ...any)
@@ -52,8 +147,17 @@ func Warn(msg ...any)
Warn prints a warning message. Warn prints a warning message.
<a name="Warnf"></a>
## func Warnf
```go
func Warnf(format string, msg ...any)
```
Warnf prints a formatted warning message.
<a name="Icon"></a> <a name="Icon"></a>
## type [Icon](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/logger/logger.go#L5>) ## type Icon
Icon is a unicode icon Icon is a unicode icon

View File

@@ -8,7 +8,7 @@ import "katenary.io/internal/parser"
Package parser is a wrapper around compose\-go to parse compose files. Package parser is a wrapper around compose\-go to parse compose files.
## func [Parse](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/parser/main.go#L29>) ## func Parse
```go ```go
func Parse(profiles []string, envFiles []string, dockerComposeFile ...string) (*types.Project, error) func Parse(profiles []string, envFiles []string, dockerComposeFile ...string) (*types.Project, error)

View File

@@ -17,7 +17,7 @@ const DirectoryPermission = 0o755
``` ```
<a name="AsResourceName"></a> <a name="AsResourceName"></a>
## func [AsResourceName](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/utils/utils.go#L198>) ## func AsResourceName
```go ```go
func AsResourceName(name string) string func AsResourceName(name string) string
@@ -26,7 +26,7 @@ func AsResourceName(name string) string
AsResourceName returns a resource name with underscores to respect the kubernetes naming convention. It's the opposite of FixedResourceName. AsResourceName returns a resource name with underscores to respect the kubernetes naming convention. It's the opposite of FixedResourceName.
<a name="Confirm"></a> <a name="Confirm"></a>
## func [Confirm](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/utils/utils.go#L166>) ## func Confirm
```go ```go
func Confirm(question string, icon ...logger.Icon) bool func Confirm(question string, icon ...logger.Icon) bool
@@ -35,7 +35,7 @@ func Confirm(question string, icon ...logger.Icon) bool
Confirm asks a question and returns true if the answer is y. Confirm asks a question and returns true if the answer is y.
<a name="CountStartingSpaces"></a> <a name="CountStartingSpaces"></a>
## func [CountStartingSpaces](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/utils/utils.go#L42>) ## func CountStartingSpaces
```go ```go
func CountStartingSpaces(line string) int func CountStartingSpaces(line string) int
@@ -44,7 +44,7 @@ func CountStartingSpaces(line string) int
CountStartingSpaces counts the number of spaces at the beginning of a string. CountStartingSpaces counts the number of spaces at the beginning of a string.
<a name="EncodeBasicYaml"></a> <a name="EncodeBasicYaml"></a>
## func [EncodeBasicYaml](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/utils/utils.go#L180>) ## func EncodeBasicYaml
```go ```go
func EncodeBasicYaml(data any) ([]byte, error) func EncodeBasicYaml(data any) ([]byte, error)
@@ -53,7 +53,7 @@ func EncodeBasicYaml(data any) ([]byte, error)
EncodeBasicYaml encodes a basic yaml from an interface. EncodeBasicYaml encodes a basic yaml from an interface.
<a name="FixedResourceName"></a> <a name="FixedResourceName"></a>
## func [FixedResourceName](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/utils/utils.go#L192>) ## func FixedResourceName
```go ```go
func FixedResourceName(name string) string func FixedResourceName(name string) string
@@ -62,7 +62,7 @@ func FixedResourceName(name string) string
FixedResourceName returns a resource name without underscores to respect the kubernetes naming convention. FixedResourceName returns a resource name without underscores to respect the kubernetes naming convention.
<a name="GetContainerByName"></a> <a name="GetContainerByName"></a>
## func [GetContainerByName](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/utils/utils.go#L88>) ## func GetContainerByName
```go ```go
func GetContainerByName(name string, containers []corev1.Container) (*corev1.Container, int) func GetContainerByName(name string, containers []corev1.Container) (*corev1.Container, int)
@@ -71,7 +71,7 @@ func GetContainerByName(name string, containers []corev1.Container) (*corev1.Con
GetContainerByName returns a container by name and its index in the array. It returns nil, \-1 if not found. GetContainerByName returns a container by name and its index in the array. It returns nil, \-1 if not found.
<a name="GetKind"></a> <a name="GetKind"></a>
## func [GetKind](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/utils/utils.go#L55>) ## func GetKind
```go ```go
func GetKind(path string) (kind string) func GetKind(path string) (kind string)
@@ -80,7 +80,7 @@ func GetKind(path string) (kind string)
GetKind returns the kind of the resource from the file path. GetKind returns the kind of the resource from the file path.
<a name="GetServiceNameByPort"></a> <a name="GetServiceNameByPort"></a>
## func [GetServiceNameByPort](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/utils/utils.go#L78>) ## func GetServiceNameByPort
```go ```go
func GetServiceNameByPort(port int) string func GetServiceNameByPort(port int) string
@@ -89,7 +89,7 @@ func GetServiceNameByPort(port int) string
GetServiceNameByPort returns the service name for a port. It the service name is not found, it returns an empty string. GetServiceNameByPort returns the service name for a port. It the service name is not found, it returns an empty string.
<a name="GetValuesFromLabel"></a> <a name="GetValuesFromLabel"></a>
## func [GetValuesFromLabel](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/utils/utils.go#L130>) ## func GetValuesFromLabel
```go ```go
func GetValuesFromLabel(service types.ServiceConfig, LabelValues string) map[string]*EnvConfig func GetValuesFromLabel(service types.ServiceConfig, LabelValues string) map[string]*EnvConfig
@@ -98,7 +98,7 @@ func GetValuesFromLabel(service types.ServiceConfig, LabelValues string) map[str
GetValuesFromLabel returns a map of values from a label. GetValuesFromLabel returns a map of values from a label.
<a name="HashComposefiles"></a> <a name="HashComposefiles"></a>
## func [HashComposefiles](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/utils/hash.go#L12>) ## func HashComposefiles
```go ```go
func HashComposefiles(files []string) (string, error) func HashComposefiles(files []string) (string, error)
@@ -107,7 +107,7 @@ func HashComposefiles(files []string) (string, error)
HashComposefiles returns a hash of the compose files. HashComposefiles returns a hash of the compose files.
<a name="Int32Ptr"></a> <a name="Int32Ptr"></a>
## func [Int32Ptr](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/utils/utils.go#L36>) ## func Int32Ptr
```go ```go
func Int32Ptr(i int32) *int32 func Int32Ptr(i int32) *int32
@@ -116,7 +116,7 @@ func Int32Ptr(i int32) *int32
Int32Ptr returns a pointer to an int32. Int32Ptr returns a pointer to an int32.
<a name="PathToName"></a> <a name="PathToName"></a>
## func [PathToName](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/utils/utils.go#L107>) ## func PathToName
```go ```go
func PathToName(path string) string func PathToName(path string) string
@@ -125,7 +125,7 @@ func PathToName(path string) string
PathToName converts a path to a kubernetes complient name. PathToName converts a path to a kubernetes complient name.
<a name="StrPtr"></a> <a name="StrPtr"></a>
## func [StrPtr](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/utils/utils.go#L39>) ## func StrPtr
```go ```go
func StrPtr(s string) *string func StrPtr(s string) *string
@@ -134,7 +134,7 @@ func StrPtr(s string) *string
StrPtr returns a pointer to a string. StrPtr returns a pointer to a string.
<a name="TplName"></a> <a name="TplName"></a>
## func [TplName](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/utils/utils.go#L23>) ## func TplName
```go ```go
func TplName(serviceName, appname string, suffix ...string) string func TplName(serviceName, appname string, suffix ...string) string
@@ -143,7 +143,7 @@ func TplName(serviceName, appname string, suffix ...string) string
TplName returns the name of the kubernetes resource as a template string. It is used in the templates and defined in \_helper.tpl file. TplName returns the name of the kubernetes resource as a template string. It is used in the templates and defined in \_helper.tpl file.
<a name="TplValue"></a> <a name="TplValue"></a>
## func [TplValue](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/utils/utils.go#L98>) ## func TplValue
```go ```go
func TplValue(serviceName, variable string, pipes ...string) string func TplValue(serviceName, variable string, pipes ...string) string
@@ -152,7 +152,7 @@ func TplValue(serviceName, variable string, pipes ...string) string
TplValue returns a string that can be used in a template to access a value from the values file. TplValue returns a string that can be used in a template to access a value from the values file.
<a name="WordWrap"></a> <a name="WordWrap"></a>
## func [WordWrap](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/utils/utils.go#L161>) ## func WordWrap
```go ```go
func WordWrap(text string, lineWidth int) string func WordWrap(text string, lineWidth int) string
@@ -161,7 +161,7 @@ func WordWrap(text string, lineWidth int) string
WordWrap wraps a string to a given line width. Warning: it may break the string. You need to check the result. WordWrap wraps a string to a given line width. Warning: it may break the string. You need to check the result.
<a name="Wrap"></a> <a name="Wrap"></a>
## func [Wrap](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/utils/utils.go#L72>) ## func Wrap
```go ```go
func Wrap(src, above, below string) string func Wrap(src, above, below string) string
@@ -170,7 +170,7 @@ func Wrap(src, above, below string) string
Wrap wraps a string with a string above and below. It will respect the indentation of the src string. Wrap wraps a string with a string above and below. It will respect the indentation of the src string.
<a name="EnvConfig"></a> <a name="EnvConfig"></a>
## type [EnvConfig](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/utils/utils.go#L124-L127>) ## type EnvConfig
EnvConfig is a struct to hold the description of an environment variable. EnvConfig is a struct to hold the description of an environment variable.

View File

@@ -97,7 +97,9 @@ Katenary transforms compose services this way:
- environment variables will be stored inside a `ConfigMap` - environment variables will be stored inside a `ConfigMap`
- image, tags, and ingresses configuration are also stored in `values.yaml` file - image, tags, and ingresses configuration are also stored in `values.yaml` file
- if named volumes are declared, Katenary create `PersistentVolumeClaims` - not enabled in values file - if named volumes are declared, Katenary create `PersistentVolumeClaims` - not enabled in values file
- `depends_on` needs that the pointed service declared a port. If not, you can use labels to inform Katenary - `depends_on` uses Kubernetes API by default to check if the service endpoint is ready. No port required.
- If you need to create a Kubernetes Service for external access, add the `katenary.v3/ports` label.
Use label `katenary.v3/depends-on: legacy` to use the old netcat method (requires port).
For any other specific configuration, like binding local files as `ConfigMap`, bind variables, add values with For any other specific configuration, like binding local files as `ConfigMap`, bind variables, add values with
documentation, etc. You'll need to use labels. documentation, etc. You'll need to use labels.
@@ -147,10 +149,8 @@ Katenary proposes a lot of labels to configure the helm chart generation, but so
### Work with Depends On? ### Work with Depends On?
Kubernetes does not provide service or pod starting detection from others pods. But Katenary will create `initContainer` Katenary creates `initContainer` to wait for dependent services to be ready. By default, it uses the Kubernetes API
to make you able to wait for a service to respond. But you'll probably need to adapt a bit the compose file. to check if the service endpoint has ready addresses - no port required.
See this compose file:
```yaml ```yaml
version: "3" version: "3"
@@ -167,9 +167,28 @@ services:
MYSQL_ROOT_PASSWORD: foobar MYSQL_ROOT_PASSWORD: foobar
``` ```
In this case, `webapp` needs to know the `database` port because the `depends_on` points on it and Kubernetes has not If you need the old netcat-based method (requires port), add the `katenary.v3/depends-on: legacy` label to the dependent service:
(yet) solution to check the database startup. Katenary wants to create a `initContainer` to hit on the related service.
So, instead of exposing the port in the compose definition, let's declare this to Katenary with labels: ```yaml
version: "3"
services:
webapp:
image: php:8-apache
depends_on:
- database
labels:
katenary.v3/depends-on: legacy
database:
image: mariadb
environment:
MYSQL_ROOT_PASSWORD: foobar
ports:
- 3306:3306
```
If you want to create a Kubernetes Service for external access, add the `katenary.v3/ports` label to the service:
```yaml ```yaml
version: "3" version: "3"
@@ -185,7 +204,7 @@ services:
environment: environment:
MYSQL_ROOT_PASSWORD: foobar MYSQL_ROOT_PASSWORD: foobar
labels: labels:
katenary.v3/ports: |- katenary.v3/ports:
- 3306 - 3306
``` ```

View File

@@ -107,8 +107,8 @@ image, tags, and ingresses configuration are also stored in \f[CR]values.yaml\f[
.IP \[bu] 2 .IP \[bu] 2
if named volumes are declared, Katenary create \f[CR]PersistentVolumeClaims\f[R] \- not enabled in values file if named volumes are declared, Katenary create \f[CR]PersistentVolumeClaims\f[R] \- not enabled in values file
.IP \[bu] 2 .IP \[bu] 2
\f[CR]depends_on\f[R] needs that the pointed service declared a port. \f[CR]depends_on\f[R] uses Kubernetes API to check if the service endpoint is ready. No port declaration is required.
If not, you can use labels to inform Katenary If you need to create a Kubernetes Service for external access, use the \f[CR]katenary.v3/ports\f[R] label.
.PP .PP
For any other specific configuration, like binding local files as \f[CR]ConfigMap\f[R], bind variables, add values with documentation, etc. For any other specific configuration, like binding local files as \f[CR]ConfigMap\f[R], bind variables, add values with documentation, etc.
You\[aq]ll need to use labels. You\[aq]ll need to use labels.

View File

@@ -4,20 +4,25 @@
# Can be launched with the following command: # Can be launched with the following command:
# sh <(curl -sSL https://raw.githubusercontent.com/Katenary/katenary/master/install.sh) # sh <(curl -sSL https://raw.githubusercontent.com/Katenary/katenary/master/install.sh)
set -e
# Detect the OS and architecture # Detect the OS and architecture
OS=$(uname) OS=$(uname)
ARCH=$(uname -m) ARCH=$(uname -m)
for c in curl grep cut tr; do
if ! command -v $c >/dev/null 2>&1; then
echo "Error: $c is not installed"
exit 1
fi
done
# Detect the home directory "bin" directory, it is commonly: # Detect the home directory "bin" directory, it is commonly:
# - $HOME/.local/bin # - $HOME/.local/bin
# - $HOME/.bin # - $HOME/.bin
# - $HOME/bin # - $HOME/bin
COMON_INSTALL_PATHS="$HOME/.local/bin $HOME/.bin $HOME/bin" COMMON_INSTALL_PATHS="$HOME/.local/bin $HOME/.bin $HOME/bin"
INSTALL_PATH="" INSTALL_PATH=""
for p in $COMON_INSTALL_PATHS; do for p in $COMMON_INSTALL_PATHS; do
if [ -d $p ]; then if [ -d $p ]; then
INSTALL_PATH=$p INSTALL_PATH=$p
break break
@@ -43,20 +48,22 @@ if ! echo "$PATH" | grep -q "$INSTALL_PATH"; then
fi fi
# Where to download the binary # Where to download the binary
BASE="https://github.com/Katenary/katenary/releases/latest/download/" TAG=$(curl -sLf https://repo.katenary.io/api/v1/repos/katenary/katenary/releases/latest 2>/dev/null | grep -Po '"tag_name":\s*"[^"]*"' | cut -d ":" -f2 | tr -d '"')
TAG=${TAG#releases/}
# for compatibility with older ARM versions # use the right names for the OS and architecture
if [ $ARCH = "x86_64" ]; then if [ $ARCH = "x86_64" ]; then
ARCH="amd64" ARCH="amd64"
fi fi
BIN_URL="$BASE/katenary-$OS-$ARCH" BIN_URL="https://repo.katenary.io/api/packages/Katenary/generic/katenary/$TAG/katenary-$OS-$ARCH"
echo echo
echo "Downloading $BIN_URL" echo "Downloading $BIN_URL"
T=$(mktemp -u) T=$(mktemp -u)
curl -SL -# $BIN_URL -o $T || (echo "Failed to download katenary" && rm -f $T && exit 1) curl -sLf -# $BIN_URL -o $T 2>/dev/null || (echo -e "Failed to download katenary version $TAG.\n\nPlease open an issue and explain the problem, following the link:\nhttps://repo.katenary.io/Katenary/katenary/issues/new?title=[install.sh]%20Install%20$TAG%20failed" && rm -f $T && exit 1)
mv "$T" "${INSTALL_PATH}/katenary" mv "$T" "${INSTALL_PATH}/katenary"
chmod +x "${INSTALL_PATH}/katenary" chmod +x "${INSTALL_PATH}/katenary"

View File

@@ -2,7 +2,6 @@ package generator
import ( import (
"fmt" "fmt"
"log"
"maps" "maps"
"os" "os"
"path/filepath" "path/filepath"
@@ -230,10 +229,11 @@ func (chart *HelmChart) generateDeployment(service types.ServiceConfig, deployme
// get the same-pod label if exists, add it to the list. // get the same-pod label if exists, add it to the list.
// We later will copy some parts to the target deployment and remove this one. // We later will copy some parts to the target deployment and remove this one.
if samePod, ok := service.Labels[labels.LabelSamePod]; ok && samePod != "" { if samePod, ok := service.Labels[labels.LabelSamePod]; ok && samePod != "" {
podToMerge[samePod] = &service podToMerge[service.Name] = &service
} }
// create the needed service for the container port // create the needed service for the container port
// NOTE: ports from same-pod services are now added in generator.go after all services are processed
if len(service.Ports) > 0 { if len(service.Ports) > 0 {
s := NewService(service, appName) s := NewService(service, appName)
services[service.Name] = s services[service.Name] = s
@@ -248,6 +248,15 @@ func (chart *HelmChart) generateDeployment(service types.ServiceConfig, deployme
} }
} }
// create IngressRoute (Traefik CRD) if specified
if ingressRoute := d.AddIngressRoute(service, appName); ingressRoute != nil {
y, _ := ingressRoute.Yaml()
chart.Templates[ingressRoute.Filename()] = &ChartTemplate{
Content: y,
Servicename: service.Name,
}
}
return nil return nil
} }
@@ -331,12 +340,12 @@ func (chart *HelmChart) setSharedConf(service types.ServiceConfig, deployments m
} }
fromservices, err := labelstructs.EnvFromFrom(service.Labels[labels.LabelEnvFrom]) fromservices, err := labelstructs.EnvFromFrom(service.Labels[labels.LabelEnvFrom])
if err != nil { if err != nil {
log.Fatal("error unmarshaling env-from label:", err) logger.Fatal("error unmarshaling env-from label:", err)
} }
// find the configmap in the chart templates // find the configmap in the chart templates
for _, fromservice := range fromservices { for _, fromservice := range fromservices {
if _, ok := chart.Templates[fromservice+".configmap.yaml"]; !ok { if _, ok := chart.Templates[fromservice+".configmap.yaml"]; !ok {
log.Printf("configmap %s not found in chart templates", fromservice) logger.Warnf("configmap %s not found in chart templates", fromservice)
continue continue
} }
// find the corresponding target deployment // find the corresponding target deployment
@@ -356,7 +365,7 @@ func (chart *HelmChart) setEnvironmentValuesFrom(service types.ServiceConfig, de
} }
mapping, err := labelstructs.GetValueFrom(service.Labels[labels.LabelValuesFrom]) mapping, err := labelstructs.GetValueFrom(service.Labels[labels.LabelValuesFrom])
if err != nil { if err != nil {
log.Fatal("error unmarshaling values-from label:", err) logger.Fatal("error unmarshaling values-from label:", err)
} }
findDeployment := func(name string) *Deployment { findDeployment := func(name string) *Deployment {
@@ -375,11 +384,11 @@ func (chart *HelmChart) setEnvironmentValuesFrom(service types.ServiceConfig, de
dep := findDeployment(depName[0]) dep := findDeployment(depName[0])
target := findDeployment(service.Name) target := findDeployment(service.Name)
if dep == nil || target == nil { if dep == nil || target == nil {
log.Fatalf("deployment %s or %s not found", depName[0], service.Name) logger.Fatalf("deployment %s or %s not found", depName[0], service.Name)
} }
container, index := utils.GetContainerByName(target.service.ContainerName, target.Spec.Template.Spec.Containers) container, index := utils.GetContainerByName(target.service.ContainerName, target.Spec.Template.Spec.Containers)
if container == nil { if container == nil {
log.Fatalf("Container %s not found", target.GetName()) logger.Fatalf("Container %s not found", target.GetName())
} }
reourceName := fmt.Sprintf(`{{ include "%s.fullname" . }}-%s`, chart.Name, depName[0]) reourceName := fmt.Sprintf(`{{ include "%s.fullname" . }}-%s`, chart.Name, depName[0])
// add environment with from // add environment with from

View File

@@ -2,7 +2,6 @@ package generator
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@@ -69,7 +68,7 @@ func NewConfigMap(service types.ServiceConfig, appName string, forFile bool) *Co
// get the secrets from the labels // get the secrets from the labels
secrets, err := labelstructs.SecretsFrom(service.Labels[labels.LabelSecrets]) secrets, err := labelstructs.SecretsFrom(service.Labels[labels.LabelSecrets])
if err != nil { if err != nil {
log.Fatal(err) logger.Fatal(err)
} }
// drop the secrets from the environment // drop the secrets from the environment
for _, secret := range secrets { for _, secret := range secrets {
@@ -95,7 +94,7 @@ func NewConfigMap(service types.ServiceConfig, appName string, forFile bool) *Co
if l, ok := service.Labels[labels.LabelMapEnv]; ok { if l, ok := service.Labels[labels.LabelMapEnv]; ok {
envmap, err := labelstructs.MapEnvFrom(l) envmap, err := labelstructs.MapEnvFrom(l)
if err != nil { if err != nil {
log.Fatal("Error parsing map-env", err) logger.Fatal("Error parsing map-env", err)
} }
for key, value := range envmap { for key, value := range envmap {
cm.AddData(key, strings.ReplaceAll(value, "__APP__", appName)) cm.AddData(key, strings.ReplaceAll(value, "__APP__", appName))
@@ -145,7 +144,7 @@ func NewConfigMapFromDirectory(service types.ServiceConfig, appName, path string
path = filepath.Join(service.WorkingDir, path) path = filepath.Join(service.WorkingDir, path)
path = filepath.Clean(path) path = filepath.Clean(path)
if err := cm.AppendDir(path); err != nil { if err := cm.AppendDir(path); err != nil {
log.Fatal("Error adding files to configmap:", err) logger.Fatal("Error adding files to configmap:", err)
} }
return cm return cm
} }

View File

@@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"log"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@@ -23,12 +22,15 @@ import (
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
) )
const ingressClassHelp = `# Default value for ingress.class annotation const ingressClassHelp = `# Default value for ingress.class
# class: "-" # class: "traefik"
# If the value is "-", controller will not set ingressClassName # If the value is "-", controller will not set ingressClassName
# If the value is "", Ingress will be set to an empty string, so # If the value is "", Ingress will be set to an empty string, so
# controller will use the default value for ingressClass # controller will use the default value for ingressClass
# If the value is specified, controller will set the named class e.g. "nginx" # If the value is specified, controller will set the named class e.g. "traefik"
#
# Ingress type: "ingress" (default) or "ingressroute" (Traefik CRD)
# type: "ingress"
` `
const storageClassHelp = `# Storage class to use for PVCs const storageClassHelp = `# Storage class to use for PVCs
@@ -110,8 +112,19 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) error {
// the current working directory is the directory // the current working directory is the directory
currentDir, _ := os.Getwd() currentDir, _ := os.Getwd()
// Filter to only existing files before chdir
var existingFiles []string
for _, f := range dockerComposeFile {
if _, err := os.Stat(f); err == nil {
existingFiles = append(existingFiles, f)
}
}
if len(existingFiles) == 0 && len(dockerComposeFile) > 0 {
return fmt.Errorf("no compose file found: %v", dockerComposeFile)
}
// go to the root of the project // go to the root of the project
if err := os.Chdir(filepath.Dir(dockerComposeFile[0])); err != nil { if err := os.Chdir(filepath.Dir(existingFiles[0])); err != nil {
logger.Failure(err.Error()) logger.Failure(err.Error())
return err return err
} }
@@ -123,12 +136,12 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) error {
}() }()
// repove the directory part of the docker-compose files // repove the directory part of the docker-compose files
for i, f := range dockerComposeFile { for i, f := range existingFiles {
dockerComposeFile[i] = filepath.Base(f) existingFiles[i] = filepath.Base(f)
} }
// parse the compose files // parse the compose files
project, err := parser.Parse(config.Profiles, config.EnvFiles, dockerComposeFile...) project, err := parser.Parse(config.Profiles, config.EnvFiles, existingFiles...)
if err != nil { if err != nil {
logger.Failure("Cannot parse compose files", err.Error()) logger.Failure("Cannot parse compose files", err.Error())
return err return err
@@ -206,6 +219,10 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) error {
// call helm update if needed // call helm update if needed
callHelmUpdate(config) callHelmUpdate(config)
// flush warnings after all conversion is complete
logger.FlushWarnings()
return nil return nil
} }
@@ -596,7 +613,7 @@ func callHelmUpdate(config ConvertOptions) {
func removeNewlinesInsideBrackets(values []byte) []byte { func removeNewlinesInsideBrackets(values []byte) []byte {
re, err := regexp.Compile(`(?s)\{\{(.*?)\}\}`) re, err := regexp.Compile(`(?s)\{\{(.*?)\}\}`)
if err != nil { if err != nil {
log.Fatal(err) logger.Fatal(err)
} }
return re.ReplaceAllFunc(values, func(b []byte) []byte { return re.ReplaceAllFunc(values, func(b []byte) []byte {
// get the first match // get the first match
@@ -635,7 +652,7 @@ func writeContent(path string, content []byte) {
defer f.Close() defer f.Close()
defer func() { defer func() {
if _, err := f.Write(content); err != nil { if _, err := f.Write(content); err != nil {
log.Fatal(err) logger.Fatal(err)
} }
}() }()
} }

View File

@@ -1,13 +1,14 @@
package generator package generator
import ( import (
"log"
"strings" "strings"
"katenary.io/internal/generator/labels" "katenary.io/internal/generator/labels"
"katenary.io/internal/generator/labels/labelstructs" "katenary.io/internal/generator/labels/labelstructs"
"katenary.io/internal/logger"
"katenary.io/internal/utils" "katenary.io/internal/utils"
"github.com/mattn/go-shellwords"
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
batchv1 "k8s.io/api/batch/v1" batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@@ -33,7 +34,7 @@ func NewCronJob(service types.ServiceConfig, chart *HelmChart, appName string) (
} }
mapping, err := labelstructs.CronJobFrom(labels) mapping, err := labelstructs.CronJobFrom(labels)
if err != nil { if err != nil {
log.Fatalf("Error parsing cronjob labels: %s", err) logger.Fatalf("Error parsing cronjob labels: %s", err)
return nil, nil return nil, nil
} }
@@ -53,8 +54,9 @@ func NewCronJob(service types.ServiceConfig, chart *HelmChart, appName string) (
} }
if strings.Contains(image, ":") { if strings.Contains(image, ":") {
image = strings.Split(service.Image, ":")[0] parts := strings.Split(image, ":")
tag = strings.Split(service.Image, ":")[1] image = parts[0]
tag = parts[1]
} }
chart.Values[service.Name].(*Value).CronJob.Repository = &RepositoryValue{ chart.Values[service.Name].(*Value).CronJob.Repository = &RepositoryValue{
@@ -62,6 +64,19 @@ func NewCronJob(service types.ServiceConfig, chart *HelmChart, appName string) (
Tag: tag, Tag: tag,
} }
command := mapping.Command
var commandParts []string
if !strings.HasPrefix(command, "sh -c") && !strings.HasPrefix(command, "/bin/sh") {
commandParts = []string{"sh", "-c", command}
} else {
parts, err := shellwords.Parse(command)
if err != nil {
commandParts = []string{"sh", "-c", command}
} else {
commandParts = parts
}
}
cronjob := &CronJob{ cronjob := &CronJob{
CronJob: &batchv1.CronJob{ CronJob: &batchv1.CronJob{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
@@ -79,13 +94,12 @@ func NewCronJob(service types.ServiceConfig, chart *HelmChart, appName string) (
Spec: batchv1.JobSpec{ Spec: batchv1.JobSpec{
Template: corev1.PodTemplateSpec{ Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyOnFailure,
Containers: []corev1.Container{ Containers: []corev1.Container{
{ {
Name: "cronjob", Name: "cronjob",
Image: "{{ .Values." + service.Name + ".cronjob.repository.image }}:{{ default .Values." + service.Name + ".cronjob.repository.tag \"latest\" }}", Image: "{{ .Values." + service.Name + ".cronjob.repository.image }}:{{ default .Values." + service.Name + ".cronjob.repository.tag \"latest\" }}",
Args: []string{ Args: commandParts,
mapping.Command,
},
}, },
}, },
}, },

View File

@@ -2,7 +2,6 @@ package generator
import ( import (
"os" "os"
"strings"
"testing" "testing"
v1 "k8s.io/api/apps/v1" v1 "k8s.io/api/apps/v1"
@@ -38,9 +37,13 @@ services:
if cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Image != "alpine:latest" { if cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Image != "alpine:latest" {
t.Errorf("Expected image to be alpine, got %s", cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Image) t.Errorf("Expected image to be alpine, got %s", cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Image)
} }
combinedCommand := strings.Join(cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Args, " ") if len(cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Args) != 3 {
if combinedCommand != "echo hello" { t.Errorf("Expected 3 args, got %d", len(cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Args))
t.Errorf("Expected command to be sh -c echo hello, got %s", combinedCommand) }
if cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Args[0] != "sh" ||
cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Args[1] != "-c" ||
cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Args[2] != "echo hello" {
t.Errorf("Expected args [sh -c echo hello], got %v", cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Args)
} }
if cronJob.Spec.Schedule != "*/1 * * * *" { if cronJob.Spec.Schedule != "*/1 * * * *" {
t.Errorf("Expected schedule to be */1 * * * *, got %s", cronJob.Spec.Schedule) t.Errorf("Expected schedule to be */1 * * * *, got %s", cronJob.Spec.Schedule)

View File

@@ -2,7 +2,6 @@ package generator
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@@ -20,6 +19,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
const shCommand = "/bin/sh"
var _ Yaml = (*Deployment)(nil) var _ Yaml = (*Deployment)(nil)
type mountPathConfig struct { type mountPathConfig struct {
@@ -43,6 +44,7 @@ type Deployment struct {
isMainApp bool `yaml:"-"` isMainApp bool `yaml:"-"`
exchangesVolumes map[string]*labelstructs.ExchangeVolume `yaml:"-"` exchangesVolumes map[string]*labelstructs.ExchangeVolume `yaml:"-"`
boundEnvVar []string `yaml:"-"` // environement to remove boundEnvVar []string `yaml:"-"` // environement to remove
needsServiceAccount bool `yaml:"-"`
} }
// NewDeployment creates a new Deployment from a compose service. The appName is the name of the application taken from the project name. // NewDeployment creates a new Deployment from a compose service. The appName is the name of the application taken from the project name.
@@ -166,7 +168,7 @@ func (d *Deployment) AddHealthCheck(service types.ServiceConfig, container *core
if v, ok := service.Labels[labels.LabelHealthCheck]; ok { if v, ok := service.Labels[labels.LabelHealthCheck]; ok {
probes, err := labelstructs.ProbeFrom(v) probes, err := labelstructs.ProbeFrom(v)
if err != nil { if err != nil {
log.Fatal(err) logger.Fatal(err)
} }
container.LivenessProbe = probes.LivenessProbe container.LivenessProbe = probes.LivenessProbe
container.ReadinessProbe = probes.ReadinessProbe container.ReadinessProbe = probes.ReadinessProbe
@@ -194,6 +196,11 @@ func (d *Deployment) AddIngress(service types.ServiceConfig, appName string) *In
return NewIngress(service, d.chart) return NewIngress(service, d.chart)
} }
// AddIngressRoute adds an IngressRoute to the deployment if type is "ingressroute".
func (d *Deployment) AddIngressRoute(service types.ServiceConfig, appName string) Yaml {
return NewIngressRouteFromService(service, d.chart)
}
// AddVolumes adds a volume to the deployment. It does not create the PVC, it only adds the volumes to the deployment. // 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. // If the volume is a bind volume it will warn the user that it is not supported yet.
func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string) { func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string) {
@@ -201,7 +208,7 @@ func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string) {
if v, ok := service.Labels[labels.LabelConfigMapFiles]; ok { if v, ok := service.Labels[labels.LabelConfigMapFiles]; ok {
binds, err := labelstructs.ConfigMapFileFrom(v) binds, err := labelstructs.ConfigMapFileFrom(v)
if err != nil { if err != nil {
log.Fatal(err) logger.Fatal(err)
} }
for _, bind := range binds { for _, bind := range binds {
tobind[bind] = true tobind[bind] = true
@@ -263,26 +270,38 @@ func (d *Deployment) BindFrom(service types.ServiceConfig, binded *Deployment) {
// DependsOn adds a initContainer to the deployment that will wait for the service to be up. // DependsOn adds a initContainer to the deployment that will wait for the service to be up.
func (d *Deployment) DependsOn(to *Deployment, servicename string) error { func (d *Deployment) DependsOn(to *Deployment, servicename string) error {
// Add a initContainer with busybox:latest using netcat to check if the service is up
// it will wait until the service responds to all ports
logger.Info("Adding dependency from ", d.service.Name, " to ", to.service.Name) logger.Info("Adding dependency from ", d.service.Name, " to ", to.service.Name)
useLegacy := false
if label, ok := d.service.Labels[labels.LabelDependsOn]; ok {
useLegacy = strings.ToLower(label) == "legacy"
}
if useLegacy {
return d.dependsOnLegacy(to, servicename)
}
d.needsServiceAccount = true
return d.dependsOnK8sAPI(to)
}
func (d *Deployment) dependsOnLegacy(to *Deployment, servicename string) error {
for _, container := range to.Spec.Template.Spec.Containers { for _, container := range to.Spec.Template.Spec.Containers {
commands := []string{} commands := []string{}
if len(container.Ports) == 0 { if len(container.Ports) == 0 {
logger.Warn("No ports found for service ", logger.Fatal("No ports found for service ",
servicename, servicename,
". You should declare a port in the service or use "+ ". You should declare a port in the service or use "+
labels.LabelPorts+ labels.LabelPorts+
" label.", " label.",
) )
os.Exit(1)
} }
for _, port := range container.Ports { for _, port := range container.Ports {
command := fmt.Sprintf("until nc -z %s %d; do\n sleep 1;\ndone", to.Name, port.ContainerPort) command := fmt.Sprintf("until nc -z %s %d; do\n sleep 1;\ndone", to.Name, port.ContainerPort)
commands = append(commands, command) commands = append(commands, command)
} }
command := []string{"/bin/sh", "-c", strings.Join(commands, "\n")} command := []string{shCommand, "-c", strings.Join(commands, "\n")}
d.Spec.Template.Spec.InitContainers = append(d.Spec.Template.Spec.InitContainers, corev1.Container{ d.Spec.Template.Spec.InitContainers = append(d.Spec.Template.Spec.InitContainers, corev1.Container{
Name: "wait-for-" + to.service.Name, Name: "wait-for-" + to.service.Name,
Image: "busybox:latest", Image: "busybox:latest",
@@ -293,6 +312,39 @@ func (d *Deployment) DependsOn(to *Deployment, servicename string) error {
return nil return nil
} }
func (d *Deployment) dependsOnK8sAPI(to *Deployment) error {
script := `NAMESPACE=${NAMESPACE:-default}
DEPLOYMENT_NAME=%s
KUBERNETES_SERVICE_HOST=${KUBERNETES_SERVICE_HOST:-kubernetes.default.svc}
KUBERNETES_SERVICE_PORT=${KUBERNETES_SERVICE_PORT:-443}
until curl -s -o- --header="Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
--cacert=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
"https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}/apis/apps/v1/namespaces/${NAMESPACE}/deployments/${DEPLOYMENT_NAME}" \
| grep -q '"readyReplicas":\s*[1-9][0-9]*'; do
sleep 2
done`
command := []string{shCommand, "-c", fmt.Sprintf(script, to.Name)}
d.Spec.Template.Spec.InitContainers = append(d.Spec.Template.Spec.InitContainers, corev1.Container{
Name: "wait-for-" + to.service.Name,
Image: "quay.io/curl/curl:latest",
Command: command,
Env: []corev1.EnvVar{
{
Name: "NAMESPACE",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "metadata.namespace",
},
},
},
},
})
return nil
}
// Filename returns the filename of the deployment. // Filename returns the filename of the deployment.
func (d *Deployment) Filename() string { func (d *Deployment) Filename() string {
return d.service.Name + ".deployment.yaml" return d.service.Name + ".deployment.yaml"
@@ -311,7 +363,7 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string, sam
defer func() { defer func() {
c, index := d.BindMapFilesToContainer(service, secrets, appName) c, index := d.BindMapFilesToContainer(service, secrets, appName)
if c == nil || index == -1 { if c == nil || index == -1 {
log.Println("Container not found for service ", service.Name) logger.Warn("Container not found for service ", service.Name)
return return
} }
d.Spec.Template.Spec.Containers[index] = *c d.Spec.Template.Spec.Containers[index] = *c
@@ -320,7 +372,7 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string, sam
// secrets from label // secrets from label
labelSecrets, err := labelstructs.SecretsFrom(service.Labels[labels.LabelSecrets]) labelSecrets, err := labelstructs.SecretsFrom(service.Labels[labels.LabelSecrets])
if err != nil { if err != nil {
log.Fatal(err) logger.Fatal(err)
} }
// values from label // values from label
@@ -335,7 +387,7 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string, sam
_, ok := service.Environment[secret] _, ok := service.Environment[secret]
if !ok { if !ok {
drop = append(drop, secret) drop = append(drop, secret)
logger.Warn("Secret " + secret + " not found in service " + service.Name + " - skpped") logger.Warn("Secret " + secret + " not found in service " + service.Name + " - skipped")
continue continue
} }
secrets = append(secrets, secret) secrets = append(secrets, secret)
@@ -352,7 +404,7 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string, sam
val, ok := service.Environment[value] val, ok := service.Environment[value]
if !ok { if !ok {
drop = append(drop, value) drop = append(drop, value)
logger.Warn("Environment variable " + value + " not found in service " + service.Name + " - skpped") logger.Warn("Environment variable " + value + " not found in service " + service.Name + " - skipped")
continue continue
} }
if d.chart.Values[service.Name].(*Value).Environment == nil { if d.chart.Values[service.Name].(*Value).Environment == nil {
@@ -384,8 +436,8 @@ func (d *Deployment) BindMapFilesToContainer(service types.ServiceConfig, secret
if envSize > 0 { if envSize > 0 {
if service.Name == "db" { if service.Name == "db" {
log.Println("Service ", service.Name, " has environment variables") logger.Info("Service ", service.Name, " has environment variables")
log.Println(service.Environment) logger.Info(service.Environment)
} }
fromSources = append(fromSources, corev1.EnvFromSource{ fromSources = append(fromSources, corev1.EnvFromSource{
ConfigMapRef: &corev1.ConfigMapEnvSource{ ConfigMapRef: &corev1.ConfigMapEnvSource{
@@ -568,7 +620,7 @@ func (d *Deployment) Yaml() ([]byte, error) {
} }
// manage serviceAccount, add condition to use the serviceAccount from values.yaml // manage serviceAccount, add condition to use the serviceAccount from values.yaml
if strings.Contains(line, "serviceAccountName:") { if strings.Contains(line, "serviceAccountName:") && !d.needsServiceAccount {
spaces = strings.Repeat(" ", utils.CountStartingSpaces(line)) spaces = strings.Repeat(" ", utils.CountStartingSpaces(line))
pre := spaces + `{{- if ne .Values.` + serviceName + `.serviceAccount "" }}` pre := spaces + `{{- if ne .Values.` + serviceName + `.serviceAccount "" }}`
post := spaces + "{{- end }}" post := spaces + "{{- end }}"
@@ -604,6 +656,12 @@ func (d *Deployment) Yaml() ([]byte, error) {
return []byte(strings.Join(content, "\n")), nil return []byte(strings.Join(content, "\n")), nil
} }
func (d *Deployment) SetServiceAccountName() {
if d.needsServiceAccount {
d.Spec.Template.Spec.ServiceAccountName = utils.TplName(d.service.Name, d.chart.Name, "dependency")
}
}
func (d *Deployment) appendDirectoryToConfigMap(service types.ServiceConfig, appName string, volume types.ServiceVolumeConfig) { func (d *Deployment) appendDirectoryToConfigMap(service types.ServiceConfig, appName string, volume types.ServiceVolumeConfig) {
pathnme := utils.PathToName(volume.Source) pathnme := utils.PathToName(volume.Source)
if _, ok := d.configMaps[pathnme]; !ok { if _, ok := d.configMaps[pathnme]; !ok {
@@ -615,7 +673,7 @@ func (d *Deployment) appendDirectoryToConfigMap(service types.ServiceConfig, app
// TODO: make it recursive to add all files in the directory and subdirectories // TODO: make it recursive to add all files in the directory and subdirectories
_, err := os.ReadDir(volume.Source) _, err := os.ReadDir(volume.Source)
if err != nil { if err != nil {
log.Fatal(err) logger.Fatal(err)
} }
cm := NewConfigMapFromDirectory(service, appName, volume.Source) cm := NewConfigMapFromDirectory(service, appName, volume.Source)
d.configMaps[pathnme] = &ConfigMapMount{ d.configMaps[pathnme] = &ConfigMapMount{
@@ -660,7 +718,7 @@ func (d *Deployment) appendFileToConfigMap(service types.ServiceConfig, appName
} }
if err := cm.AppendFile(volume.Source); err != nil { if err := cm.AppendFile(volume.Source); err != nil {
log.Fatal("Error adding file to configmap:", err) logger.Fatal("Error adding file to configmap:", err)
} }
} }
@@ -721,7 +779,7 @@ func (d *Deployment) bindVolumes(volume types.ServiceVolumeConfig, tobind map[st
// Add volume to container // Add volume to container
stat, err := os.Stat(volume.Source) stat, err := os.Stat(volume.Source)
if err != nil { if err != nil {
log.Fatal(err) logger.Fatal(err)
} }
if stat.IsDir() { if stat.IsDir() {

View File

@@ -3,6 +3,7 @@ package generator
import ( import (
"fmt" "fmt"
"os" "os"
"slices"
"strings" "strings"
"testing" "testing"
@@ -11,6 +12,7 @@ import (
yaml3 "gopkg.in/yaml.v3" yaml3 "gopkg.in/yaml.v3"
v1 "k8s.io/api/apps/v1" v1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
) )
@@ -142,6 +144,90 @@ services:
if len(dt.Spec.Template.Spec.InitContainers) != 1 { if len(dt.Spec.Template.Spec.InitContainers) != 1 {
t.Errorf("Expected 1 init container, got %d", len(dt.Spec.Template.Spec.InitContainers)) t.Errorf("Expected 1 init container, got %d", len(dt.Spec.Template.Spec.InitContainers))
} }
initContainer := dt.Spec.Template.Spec.InitContainers[0]
if !strings.Contains(initContainer.Image, "quay.io/curl/curl") {
t.Errorf("Expected quay.io/curl/curl image, got %s", initContainer.Image)
}
fullCommand := strings.Join(initContainer.Command, " ")
if !strings.Contains(fullCommand, "curl") {
t.Errorf("Expected curl command (K8s API method), got %s", fullCommand)
}
if !strings.Contains(fullCommand, "/apis/apps/v1/namespaces/") {
t.Errorf("Expected Kubernetes API call to /apis/apps/v1/namespaces/, got %s", fullCommand)
}
if !strings.Contains(fullCommand, "/deployments/") {
t.Errorf("Expected Kubernetes API call to /deployments/, got %s", fullCommand)
}
if !strings.Contains(fullCommand, "readyReplicas") {
t.Errorf("Expected readyReplicas check, got %s", fullCommand)
}
if len(initContainer.Env) == 0 {
t.Errorf("Expected environment variables to be set for namespace")
}
hasNamespace := false
for _, env := range initContainer.Env {
if env.Name == "NAMESPACE" && env.ValueFrom != nil && env.ValueFrom.FieldRef != nil {
if env.ValueFrom.FieldRef.FieldPath == "metadata.namespace" {
hasNamespace = true
break
}
}
}
if !hasNamespace {
t.Errorf("Expected NAMESPACE env var with metadata.namespace fieldRef")
}
}
func TestDependsOnLegacy(t *testing.T) {
composeFile := `
services:
web:
image: nginx:1.29
ports:
- 80:80
depends_on:
- database
labels:
katenary.v3/depends-on: legacy
database:
image: mariadb:10.5
ports:
- 3306:3306
`
tmpDir := setup(composeFile)
defer teardown(tmpDir)
currentDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(currentDir)
output := internalCompileTest(t, "-s", webTemplateOutput)
dt := v1.Deployment{}
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
t.Errorf(unmarshalError, err)
}
if len(dt.Spec.Template.Spec.InitContainers) != 1 {
t.Errorf("Expected 1 init container, got %d", len(dt.Spec.Template.Spec.InitContainers))
}
initContainer := dt.Spec.Template.Spec.InitContainers[0]
if !strings.Contains(initContainer.Image, "busybox") {
t.Errorf("Expected busybox image, got %s", initContainer.Image)
}
fullCommand := strings.Join(initContainer.Command, " ")
if !strings.Contains(fullCommand, "nc") {
t.Errorf("Expected nc (netcat) command for legacy method, got %s", fullCommand)
}
} }
func TestHelmDependencies(t *testing.T) { func TestHelmDependencies(t *testing.T) {
@@ -563,3 +649,195 @@ services:
t.Errorf("Expected command to be 'bar baz', got %s", strings.Join(command, " ")) t.Errorf("Expected command to be 'bar baz', got %s", strings.Join(command, " "))
} }
} }
func TestRestrictedRBACGeneration(t *testing.T) {
composeFile := `
services:
web:
image: nginx:1.29
ports:
- 80:80
depends_on:
- database
database:
image: mariadb:10.5
ports:
- 3306:3306
`
tmpDir := setup(composeFile)
defer teardown(tmpDir)
currentDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(currentDir)
rbacOutput := internalCompileTest(t, "-s", "templates/web/depends-on.rbac.yaml")
docs := strings.Split(rbacOutput, "---\n")
// Filter out empty documents and strip helm template comments
var filteredDocs []string
for _, doc := range docs {
if strings.TrimSpace(doc) != "" {
// Remove '# Source:' comment lines that helm template adds
lines := strings.Split(doc, "\n")
var contentLines []string
for _, line := range lines {
if !strings.HasPrefix(strings.TrimSpace(line), "# Source:") {
contentLines = append(contentLines, line)
}
}
filteredDocs = append(filteredDocs, strings.Join(contentLines, "\n"))
}
}
if len(filteredDocs) != 3 {
t.Fatalf("Expected 3 YAML documents in RBAC file, got %d (filtered from %d)", len(filteredDocs), len(docs))
}
var sa corev1.ServiceAccount
if err := yaml.Unmarshal([]byte(strings.TrimSpace(filteredDocs[0])), &sa); err != nil {
t.Errorf("Failed to unmarshal ServiceAccount: %v", err)
}
if sa.Kind != "ServiceAccount" {
t.Errorf("Expected Kind=ServiceAccount, got %s", sa.Kind)
}
if !strings.Contains(sa.Name, "web") {
t.Errorf("Expected ServiceAccount name to contain 'web', got %s", sa.Name)
}
var role rbacv1.Role
if err := yaml.Unmarshal([]byte(strings.TrimSpace(filteredDocs[1])), &role); err != nil {
t.Errorf("Failed to unmarshal Role: %v", err)
}
if role.Kind != "Role" {
t.Errorf("Expected Kind=Role, got %s", role.Kind)
}
if len(role.Rules) != 1 {
t.Errorf("Expected 1 rule in Role, got %d", len(role.Rules))
}
rule := role.Rules[0]
if !contains(rule.APIGroups, "apps") {
t.Error("Expected APIGroup to include 'apps'")
}
if !contains(rule.Resources, "deployments") {
t.Errorf("Expected Resource to include 'deployments', got %v", rule.Resources)
}
for _, res := range rule.Resources {
if res == "*" {
t.Error("Role should not have wildcard (*) resource permissions")
}
}
for _, verb := range rule.Verbs {
if verb == "*" {
t.Error("Role should not have wildcard (*) verb permissions")
}
}
var rb rbacv1.RoleBinding
if err := yaml.Unmarshal([]byte(strings.TrimSpace(filteredDocs[2])), &rb); err != nil {
t.Errorf("Failed to unmarshal RoleBinding: %v", err)
}
if rb.Kind != "RoleBinding" {
t.Errorf("Expected Kind=RoleBinding, got %s", rb.Kind)
}
if len(rb.Subjects) != 1 {
t.Errorf("Expected 1 subject in RoleBinding, got %d", len(rb.Subjects))
}
if rb.Subjects[0].Kind != "ServiceAccount" {
t.Errorf("Expected Subject Kind=ServiceAccount, got %s", rb.Subjects[0].Kind)
}
// Helm template renders the name, so check if it contains "web"
if !strings.Contains(rb.RoleRef.Name, "web") {
t.Errorf("Expected RoleRef Name to contain 'web', got %s", rb.RoleRef.Name)
}
if rb.RoleRef.Kind != "Role" {
t.Errorf("Expected RoleRef Kind=Role, got %s", rb.RoleRef.Kind)
}
}
func TestDeploymentReferencesServiceAccount(t *testing.T) {
composeFile := `
services:
web:
image: nginx:1.29
ports:
- 80:80
depends_on:
- database
database:
image: mariadb:10.5
ports:
- 3306:3306
`
tmpDir := setup(composeFile)
defer teardown(tmpDir)
currentDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(currentDir)
output := internalCompileTest(t, "-s", "templates/web/deployment.yaml")
var dt v1.Deployment
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
t.Errorf("Failed to unmarshal Deployment: %v", err)
}
serviceAccountName := dt.Spec.Template.Spec.ServiceAccountName
if !strings.Contains(serviceAccountName, "web") {
t.Errorf("Expected ServiceAccountName to contain 'web', got %s", serviceAccountName)
}
if len(dt.Spec.Template.Spec.InitContainers) == 0 {
t.Fatal("Expected at least one init container for depends_on")
}
initContainer := dt.Spec.Template.Spec.InitContainers[0]
if initContainer.Name != "wait-for-database" {
t.Errorf("Expected init container name 'wait-for-database', got %s", initContainer.Name)
}
fullCommand := strings.Join(initContainer.Command, " ")
if !strings.Contains(fullCommand, "curl") {
t.Error("Expected init container to use curl for K8s API calls")
}
if !strings.Contains(fullCommand, "/apis/apps/v1/namespaces/") {
t.Error("Expected init container to call /apis/apps/v1/namespaces/ endpoint")
}
if !strings.Contains(fullCommand, "/deployments/") {
t.Error("Expected init container to access /deployments/ resource")
}
if !strings.Contains(fullCommand, "readyReplicas") {
t.Error("Expected init container to check readyReplicas")
}
hasNamespace := false
for _, env := range initContainer.Env {
if env.Name == "NAMESPACE" && env.ValueFrom != nil && env.ValueFrom.FieldRef != nil {
if env.ValueFrom.FieldRef.FieldPath == "metadata.namespace" {
hasNamespace = true
break
}
}
}
if !hasNamespace {
t.Error("Expected NAMESPACE env var with metadata.namespace fieldRef")
}
_, err := os.Stat("./chart/templates/web/depends-on.rbac.yaml")
if os.IsNotExist(err) {
t.Error("RBAC file depends-on.rbac.yaml should exist for service using depends_on with K8s API")
} else if err != nil {
t.Errorf("Unexpected error checking RBAC file: %v", err)
}
}
func contains(slice []string, item string) bool {
return slices.Contains(slice, item)
}

View File

@@ -4,12 +4,12 @@ import (
"bytes" "bytes"
_ "embed" _ "embed"
"fmt" "fmt"
"log"
"sort" "sort"
"strings" "strings"
"text/template" "text/template"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"katenary.io/internal/logger"
) )
//go:embed readme.tpl //go:embed readme.tpl
@@ -50,7 +50,7 @@ func ReadMeFile(charname, description string, values map[string]any) string {
vv := map[string]any{} vv := map[string]any{}
out, _ := yaml.Marshal(values) out, _ := yaml.Marshal(values)
if err := yaml.Unmarshal(out, &vv); err != nil { if err := yaml.Unmarshal(out, &vv); err != nil {
log.Printf("Error parsing values: %s", err) logger.Warnf("Error parsing values: %s", err)
} }
result := make(map[string]string) result := make(map[string]string)

View File

@@ -3,7 +3,6 @@ package generator
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"log"
"regexp" "regexp"
"strings" "strings"
@@ -23,7 +22,7 @@ import (
// The Generate function will create the HelmChart object this way: // The Generate function will create the HelmChart object this way:
// //
// - Detect the service port name or leave the port number if not found. // - Detect the service port name or leave the port number if not found.
// - Create a deployment for each service that are not ingnore. // - Create a deployment for each service that are not ingore.
// - Create a service and ingresses for each service that has ports and/or declared ingresses. // - Create a service and ingresses for each service that has ports and/or declared ingresses.
// - Create a PVC or Configmap volumes for each volume. // - Create a PVC or Configmap volumes for each volume.
// - Create init containers for each service which has dependencies to other services. // - Create init containers for each service which has dependencies to other services.
@@ -80,6 +79,22 @@ func Generate(project *types.Project) (*HelmChart, error) {
} }
} }
// second pass, create services for services with ports (now podToMerge is fully populated)
for _, service := range project.Services {
if len(service.Ports) > 0 {
s := NewService(service, appName)
// add ports from same-pod services that target this service
for _, mergedSvc := range podToMerge {
if mergedSvc.Labels[labels.LabelSamePod] == service.Name {
for _, p := range mergedSvc.Ports {
s.AddPort(p)
}
}
}
services[service.Name] = s
}
}
// now we have all deployments, we can create PVC if needed (it's separated from // now we have all deployments, we can create PVC if needed (it's separated from
// the above loop because we need all deployments to not duplicate PVC for "same-pod" services) // the above loop because we need all deployments to not duplicate PVC for "same-pod" services)
// bind static volumes // bind static volumes
@@ -135,6 +150,23 @@ func Generate(project *types.Project) (*HelmChart, error) {
} }
} }
} }
// warn users if dependent service has no ports
for _, s := range project.Services {
for _, d := range s.GetDependencies() {
if dep, ok := deployments[d]; ok {
if len(dep.service.Ports) == 0 {
logger.Warnf("Service %s is used in depends_on but has no ports declared. No Kubernetes Service will be created for it. Add katenary.v3/ports label if you need to create a Service.", d)
}
}
}
}
// set ServiceAccountName for deployments that need it
for _, d := range deployments {
d.SetServiceAccountName()
}
for _, name := range drops { for _, name := range drops {
delete(deployments, name) delete(deployments, name)
} }
@@ -143,9 +175,14 @@ func Generate(project *types.Project) (*HelmChart, error) {
chart.setEnvironmentValuesFrom(s, deployments) chart.setEnvironmentValuesFrom(s, deployments)
} }
// generate RBAC resources for services that need K8s API access (non-legacy depends_on)
if err := chart.generateRBAC(deployments); err != nil {
logger.Fatalf("error generating RBAC: %s", err)
}
// generate configmaps with environment variables // generate configmaps with environment variables
if err := chart.generateConfigMapsAndSecrets(project); err != nil { if err := chart.generateConfigMapsAndSecrets(project); err != nil {
log.Fatalf("error generating configmaps and secrets: %s", err) logger.Fatalf("error generating configmaps and secrets: %s", err)
} }
// if the env-from label is set, we need to add the env vars from the configmap // if the env-from label is set, we need to add the env vars from the configmap
@@ -178,12 +215,9 @@ func Generate(project *types.Project) (*HelmChart, error) {
// generate all services // generate all services
for _, s := range services { for _, s := range services {
// add the service ports to the target service if it's a "same-pod" service // skip same-pod services - they are merged into target deployments
if samePod, ok := podToMerge[s.service.Name]; ok { if _, isSamePod := podToMerge[s.service.Name]; isSamePod {
// get the target service continue
target := services[samePod.Name]
// merge the services
s.Spec.Ports = append(s.Spec.Ports, target.Spec.Ports...)
} }
y, _ := s.Yaml() y, _ := s.Yaml()
chart.Templates[s.Filename()] = &ChartTemplate{ chart.Templates[s.Filename()] = &ChartTemplate{
@@ -192,12 +226,11 @@ func Generate(project *types.Project) (*HelmChart, error) {
} }
} }
// drop all "same-pod" services // drop all "same-pod" service templates (they are merged into target deployments)
for _, s := range podToMerge { for name := range podToMerge {
// get the target service // find the service for this same-pod service
target := services[s.Name] if svc, ok := services[name]; ok {
if target != nil { delete(chart.Templates, svc.Filename())
delete(chart.Templates, target.Filename())
} }
} }
@@ -280,7 +313,7 @@ func addStaticVolumes(deployments map[string]*Deployment, service types.ServiceC
var d *Deployment var d *Deployment
var ok bool var ok bool
if d, ok = deployments[service.Name]; !ok { if d, ok = deployments[service.Name]; !ok {
log.Printf("service %s not found in deployments", service.Name) logger.Warnf("service %s not found in deployments", service.Name)
return return
} }
@@ -292,7 +325,7 @@ func addStaticVolumes(deployments map[string]*Deployment, service types.ServiceC
var y []byte var y []byte
var err error var err error
if y, err = config.configMap.Yaml(); err != nil { if y, err = config.configMap.Yaml(); err != nil {
log.Fatal(err) logger.Fatal(err)
} }
// add the configmap to the chart // add the configmap to the chart
@@ -434,13 +467,65 @@ func samePodVolume(service types.ServiceConfig, v types.ServiceVolumeConfig, dep
// check if it has the same volume // check if it has the same volume
for _, tv := range target.Spec.Template.Spec.Volumes { for _, tv := range target.Spec.Template.Spec.Volumes {
if tv.Name == v.Source { if tv.Name == v.Source {
log.Printf("found same pod volume %s in deployment %s and %s", tv.Name, service.Name, targetDeployment) logger.Warnf("found same pod volume %s in deployment %s and %s", tv.Name, service.Name, targetDeployment)
return true return true
} }
} }
return false return false
} }
// generateRBAC creates RBAC resources (ServiceAccount, Role, RoleBinding) for services that need K8s API access.
// A service needs RBAC if it has non-legacy depends_on relationships.
func (chart *HelmChart) generateRBAC(deployments map[string]*Deployment) error {
serviceMap := make(map[string]bool)
for _, d := range deployments {
if !d.needsServiceAccount {
continue
}
sa := NewServiceAccount(*d.service, chart.Name)
role := NewRestrictedRole(*d.service, chart.Name)
rb := NewRestrictedRoleBinding(*d.service, chart.Name)
var buf bytes.Buffer
saYaml, err := yaml.Marshal(sa.ServiceAccount)
if err != nil {
return fmt.Errorf("error marshaling ServiceAccount for %s: %w", d.service.Name, err)
}
buf.Write(saYaml)
buf.WriteString("---\n")
roleYaml, err := yaml.Marshal(role.Role)
if err != nil {
return fmt.Errorf("error marshaling Role for %s: %w", d.service.Name, err)
}
buf.Write(roleYaml)
buf.WriteString("---\n")
rbYaml, err := yaml.Marshal(rb.RoleBinding)
if err != nil {
return fmt.Errorf("error marshaling RoleBinding for %s: %w", d.service.Name, err)
}
buf.Write(rbYaml)
filename := d.service.Name + "/depends-on.rbac.yaml"
chart.Templates[filename] = &ChartTemplate{
Content: buf.Bytes(),
Servicename: d.service.Name,
}
serviceMap[d.service.Name] = true
}
for svcName := range serviceMap {
logger.Log(logger.IconPackage, "Creating RBAC", svcName)
}
return nil
}
func fixContainerNames(project *types.Project) { func fixContainerNames(project *types.Project) {
// fix container names to be unique // fix container names to be unique
for i, service := range project.Services { for i, service := range project.Services {

View File

@@ -31,6 +31,6 @@
{{- define "__APP__.selectorLabels" -}} {{- define "__APP__.selectorLabels" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}} {{- $name := default .Chart.Name .Values.nameOverride -}}
{{ printf "__PREFIX__/name: %s" $name }} {{ printf "app.kubernetes.io/name: %s" $name }}
{{ printf "__PREFIX__/instance: %s" .Release.Name }} {{ printf "app.kubernetes.io/instance: %s" .Release.Name }}
{{- end -}} {{- end -}}

View File

@@ -1,11 +1,11 @@
package generator package generator
import ( import (
"log"
"strings" "strings"
"katenary.io/internal/generator/labels" "katenary.io/internal/generator/labels"
"katenary.io/internal/generator/labels/labelstructs" "katenary.io/internal/generator/labels/labelstructs"
"katenary.io/internal/logger"
"katenary.io/internal/utils" "katenary.io/internal/utils"
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
@@ -21,7 +21,7 @@ type Ingress struct {
appName string `yaml:"-"` appName string `yaml:"-"`
} }
// NewIngress creates a new Ingress from a compose service. // NewIngress creates a new standard Kubernetes Ingress from a compose service.
func NewIngress(service types.ServiceConfig, Chart *HelmChart) *Ingress { func NewIngress(service types.ServiceConfig, Chart *HelmChart) *Ingress {
appName := Chart.Name appName := Chart.Name
@@ -36,19 +36,13 @@ func NewIngress(service types.ServiceConfig, Chart *HelmChart) *Ingress {
mapping, err := labelstructs.IngressFrom(label) mapping, err := labelstructs.IngressFrom(label)
if err != nil { if err != nil {
log.Fatalf("Failed to parse ingress label: %s\n", err) logger.Fatalf("Failed to parse ingress label: %s\n", err)
} }
if mapping.Hostname == "" { if mapping.Hostname == "" {
mapping.Hostname = service.Name + ".tld" mapping.Hostname = service.Name + ".tld"
} }
// create the ingress
pathType := networkv1.PathTypeImplementationSpecific
// fix the service name, and create the full name from variable name
// which is injected in the YAML() method
serviceName := strings.ReplaceAll(service.Name, "_", "-") serviceName := strings.ReplaceAll(service.Name, "_", "-")
fullName := `{{ $fullname }}-` + serviceName
// Add the ingress host to the values.yaml // Add the ingress host to the values.yaml
if Chart.Values[service.Name] == nil { if Chart.Values[service.Name] == nil {
@@ -60,11 +54,44 @@ func NewIngress(service types.ServiceConfig, Chart *HelmChart) *Ingress {
Path: *mapping.Path, Path: *mapping.Path,
Host: mapping.Hostname, Host: mapping.Hostname,
Class: *mapping.Class, Class: *mapping.Class,
Type: mapping.Type,
IngressRouteEnabled: mapping.Type == "ingressroute" && mapping.Enabled,
Annotations: mapping.Annotations, Annotations: mapping.Annotations,
TLS: TLS{Enabled: mapping.TLS.Enabled}, TLS: TLS{Enabled: mapping.TLS.Enabled},
} }
// ingressClassName := `{{ .Values.` + service.Name + `.ingress.class }}` return newStandardIngress(service, mapping, serviceName, appName)
}
// NewIngressRouteFromService creates a Traefik IngressRoute from the same service config.
// This is called separately to generate the IngressRoute file in addition to Ingress.
func NewIngressRouteFromService(service types.ServiceConfig, Chart *HelmChart) Yaml {
appName := Chart.Name
if service.Labels == nil {
return nil
}
var label string
var ok bool
if label, ok = service.Labels[labels.LabelIngress]; !ok {
return nil
}
mapping, err := labelstructs.IngressFrom(label)
if err != nil {
return nil
}
serviceName := strings.ReplaceAll(service.Name, "_", "-")
return NewIngressRoute(service, Chart, mapping, serviceName, appName)
}
// newStandardIngress creates a standard Kubernetes Ingress
func newStandardIngress(service types.ServiceConfig, mapping *labelstructs.Ingress, serviceName, appName string) *Ingress {
pathType := networkv1.PathTypeImplementationSpecific
fullName := `{{ $fullname }}-` + serviceName
ingressClassName := utils.TplValue(service.Name, "ingress.class") ingressClassName := utils.TplValue(service.Name, "ingress.class")
servicePortName := utils.GetServiceNameByPort(int(*mapping.Port)) servicePortName := utils.GetServiceNameByPort(int(*mapping.Port))
@@ -174,16 +201,13 @@ func (ingress *Ingress) Yaml() ([]byte, error) {
`{{- $tlsname := printf "%s-%s-tls" $fullname "` + ingress.service.Name + `" -}}`, `{{- $tlsname := printf "%s-%s-tls" $fullname "` + ingress.service.Name + `" -}}`,
} }
for _, line := range lines { for _, line := range lines {
if strings.Contains(line, "loadBalancer: ") {
continue
}
if strings.Contains(line, "labels:") { if strings.Contains(line, "labels:") {
// add annotations above labels from values.yaml // add annotations above labels from values.yaml
indent := strings.Repeat(" ", utils.CountStartingSpaces(line))
content := `` + content := `` +
` {{- if .Values.` + serviceName + `.ingress.annotations -}}` + "\n" + indent + `{{- if .Values.` + serviceName + `.ingress.annotations -}}` + "\n" +
` {{- toYaml .Values.` + serviceName + `.ingress.annotations | nindent 4 }}` + "\n" + indent + ` {{- toYaml .Values.` + serviceName + `.ingress.annotations | nindent __indent__ }}` + "\n" +
` {{- end }}` + "\n" + indent + ` {{- end }}` + "\n" +
line line
out = append(out, content) out = append(out, content)

View File

@@ -0,0 +1,186 @@
package generator
import (
"strings"
"sigs.k8s.io/yaml"
"katenary.io/internal/generator/labels/labelstructs"
"katenary.io/internal/utils"
"github.com/compose-spec/compose-go/v2/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var _ Yaml = (*IngressRoute)(nil)
// IngressRoute represents a Traefik IngressRoute CRD
type IngressRoute struct {
metav1.TypeMeta `yaml:",inline"`
metav1.ObjectMeta `yaml:"metadata"`
Spec IngressRouteSpec `yaml:"spec"`
service *types.ServiceConfig `yaml:"-"`
appName string `yaml:"-"`
serviceName string `yaml:"-"`
}
// IngressRouteSpec defines the spec for Traefik IngressRoute
type IngressRouteSpec struct {
EntryPoints []string `json:"entryPoints,omitempty" yaml:"entryPoints,omitempty"`
Routes []IngressRouteRoute `json:"routes" yaml:"routes"`
TLS *IngressRouteTLS `json:"tls,omitempty" yaml:"tls,omitempty"`
}
// IngressRouteRoute defines a route in the IngressRoute
type IngressRouteRoute struct {
Match string `json:"match" yaml:"match"`
Kind string `json:"kind" yaml:"kind"`
Services []IngressRouteService `json:"services" yaml:"services"`
}
// IngressRouteService defines a service backend in IngressRoute
type IngressRouteService struct {
Name string `json:"name" yaml:"name"`
Port int `json:"port" yaml:"port"`
}
// IngressRouteTLS defines TLS configuration for IngressRoute
type IngressRouteTLS struct {
SecretName string `json:"secretName,omitempty" yaml:"secretName,omitempty"`
Domains []IngressRouteTLSDomain `json:"domains,omitempty" yaml:"domains,omitempty"`
}
// IngressRouteTLSDomain defines a domain for TLS
type IngressRouteTLSDomain struct {
Main string `json:"main" yaml:"main"`
}
// NewIngressRoute creates a new Traefik IngressRoute from a compose service.
func NewIngressRoute(service types.ServiceConfig, Chart *HelmChart, mapping *labelstructs.Ingress, serviceName, appName string) *IngressRoute {
fullName := `{{ $fullname }}-` + serviceName
// Build the route match rule
match := `Host("{{ tpl .Values.` + serviceName + `.ingress.host . }}")`
path := utils.TplValue(serviceName, "ingress.path")
if path != "/" && path != "" {
match += ` && PathPrefix("` + path + `")`
}
ir := &IngressRoute{
TypeMeta: metav1.TypeMeta{
Kind: "IngressRoute",
APIVersion: "traefik.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: fullName,
Labels: GetLabels(serviceName, appName),
Annotations: Annotations,
},
Spec: IngressRouteSpec{
EntryPoints: []string{"web", "websecure"},
Routes: []IngressRouteRoute{
{
Match: match,
Kind: "Rule",
Services: []IngressRouteService{
{
Name: fullName,
Port: int(*mapping.Port),
},
},
},
},
},
service: &service,
appName: appName,
serviceName: serviceName,
}
// Add TLS configuration if enabled
if mapping.TLS != nil && mapping.TLS.Enabled {
tlsSecretName := `{{ .Values.` + serviceName + `.ingress.tls.secretName | default $tlsname }}`
ir.Spec.TLS = &IngressRouteTLS{
SecretName: tlsSecretName,
Domains: []IngressRouteTLSDomain{
{
Main: `{{ tpl .Values.` + serviceName + `.ingress.host . }}`,
},
},
}
}
return ir
}
func (ir *IngressRoute) Filename() string {
return ir.serviceName + ".ingressroute.yaml"
}
func (ir *IngressRoute) Yaml() ([]byte, error) {
var ret []byte
var err error
// Manually construct YAML - sigs.k8s.io/yaml doesn't handle metav1.ObjectMeta
// with yaml:"metadata" correctly unless embedded in a standard K8s type with JSON tags.
// We build the YAML as a string to ensure proper nesting.
// Build metadata block
metadata, err := yaml.Marshal(map[string]interface{}{
"name": ir.Name,
"labels": ir.Labels,
"annotations": ir.Annotations,
})
if err != nil {
return nil, err
}
// Build spec block
spec, err := yaml.Marshal(ir.Spec)
if err != nil {
return nil, err
}
// Build final YAML with proper structure
ret = []byte("apiVersion: " + ir.APIVersion + "\n")
ret = append(ret, "kind: "+ir.Kind+"\n"...)
ret = append(ret, "metadata:\n"...)
// Indent metadata content by 2 spaces
for _, line := range strings.Split(strings.TrimRight(string(metadata), "\n"), "\n") {
ret = append(ret, " "+line+"\n"...)
}
ret = append(ret, "spec:\n"...)
// Indent spec content by 2 spaces
for _, line := range strings.Split(strings.TrimRight(string(spec), "\n"), "\n") {
ret = append(ret, " "+line+"\n"...)
}
ret = UnWrapTPL(ret)
lines := strings.Split(string(ret), "\n")
out := []string{
`{{- if .Values.` + ir.serviceName + `.ingress.ingressRouteEnabled -}}`,
`{{- $fullname := include "` + ir.appName + `.fullname" . -}}`,
`{{- $tlsname := printf "%s-%s-tls" $fullname "` + ir.serviceName + `" -}}`,
}
for _, line := range lines {
if strings.Contains(line, "labels:") {
// add annotations above labels from values.yaml (inside metadata block)
indent := strings.Repeat(" ", utils.CountStartingSpaces(line))
content := `` +
indent + `{{- if .Values.` + ir.serviceName + `.ingress.annotations -}}` + "\n" +
indent + ` {{- toYaml .Values.` + ir.serviceName + `.ingress.annotations | nindent __indent__ }}` + "\n" +
indent + ` {{- end }}` + "\n" +
line
out = append(out, content)
} else {
out = append(out, line)
}
}
out = append(out, `{{- end -}}`)
ret = []byte(strings.Join(out, "\n"))
return ret, nil
}

View File

@@ -0,0 +1,240 @@
package generator
import (
"fmt"
"os"
"strings"
"testing"
"katenary.io/internal/generator/labels"
)
func TestIngressRoute(t *testing.T) {
composeFile := `
services:
web:
image: nginx:1.29
ports:
- 80:80
labels:
%s/ingress: |-
hostname: my.test.tld
port: 80
type: ingressroute
enabled: true
`
composeFile = fmt.Sprintf(composeFile, labels.KatenaryLabelPrefix)
tmpDir := setup(composeFile)
defer teardown(tmpDir)
currentDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(currentDir)
// Test that the ingressroute file is generated
output := internalCompileTest(
t,
"-s", "templates/web/ingressroute.yaml",
)
// Check that it's a Traefik IngressRoute
if !strings.Contains(output, "kind: IngressRoute") {
t.Errorf("Expected IngressRoute kind in output")
}
if !strings.Contains(output, "apiVersion: traefik.io/v1alpha1") {
t.Errorf("Expected traefik.io/v1alpha1 apiVersion in output")
}
if !strings.Contains(output, "my.test.tld") {
t.Errorf("Expected host my.test.tld in output")
}
}
func TestIngressRouteWithTLS(t *testing.T) {
composeFile := `
services:
web:
image: nginx:1.29
ports:
- 443:443
labels:
%s/ingress: |-
hostname: secure.example.com
port: 443
type: ingressroute
enabled: true
tls:
enabled: true
`
composeFile = fmt.Sprintf(composeFile, labels.KatenaryLabelPrefix)
tmpDir := setup(composeFile)
defer teardown(tmpDir)
currentDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(currentDir)
output := internalCompileTest(
t,
"-s", "templates/web/ingressroute.yaml",
)
// Check TLS configuration
if !strings.Contains(output, "tls:") {
t.Errorf("Expected TLS configuration in IngressRoute, got: %s", output)
}
if !strings.Contains(output, "secretName:") {
t.Errorf("Expected SecretName in TLS configuration, got: %s", output)
}
}
func TestIngressRouteWithPath(t *testing.T) {
composeFile := `
services:
web:
image: nginx:1.29
ports:
- 80:80
labels:
%s/ingress: |-
hostname: app.example.com
port: 80
path: /api
type: ingressroute
enabled: true
`
composeFile = fmt.Sprintf(composeFile, labels.KatenaryLabelPrefix)
tmpDir := setup(composeFile)
defer teardown(tmpDir)
currentDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(currentDir)
output := internalCompileTest(
t,
"-s", "templates/web/ingressroute.yaml",
)
// Check path prefix in match rule
if !strings.Contains(output, "PathPrefix") {
t.Errorf("Expected PathPrefix in match rule")
}
if !strings.Contains(output, "/api") {
t.Errorf("Expected path /api in match rule")
}
}
func TestIngressRouteEntryPoints(t *testing.T) {
composeFile := `
services:
web:
image: nginx:1.29
ports:
- 80:80
labels:
%s/ingress: |-
hostname: app.example.com
port: 80
type: ingressroute
enabled: true
`
composeFile = fmt.Sprintf(composeFile, labels.KatenaryLabelPrefix)
tmpDir := setup(composeFile)
defer teardown(tmpDir)
currentDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(currentDir)
output := internalCompileTest(
t,
"-s", "templates/web/ingressroute.yaml",
)
// Check default entryPoints
if !strings.Contains(output, "web") {
t.Errorf("Expected 'web' entryPoint in IngressRoute")
}
if !strings.Contains(output, "websecure") {
t.Errorf("Expected 'websecure' entryPoint in IngressRoute")
}
}
func TestIngressRouteDisabled(t *testing.T) {
composeFile := `
services:
web:
image: nginx:1.29
ports:
- 80:80
labels:
%s/ingress: |-
hostname: app.example.com
port: 80
type: ingressroute
enabled: false
`
composeFile = fmt.Sprintf(composeFile, labels.KatenaryLabelPrefix)
tmpDir := setup(composeFile)
defer teardown(tmpDir)
currentDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(currentDir)
// When ingress is disabled, the file is generated but the helm template
// with -s flag will fail because output is empty due to conditional
// Instead, just verify the chart is valid by running helm template without -s
// The chart should lint successfully
output := internalCompileTest(
t,
)
// The output should not contain IngressRoute kind since it's disabled
if strings.Contains(output, "kind: IngressRoute") {
t.Errorf("IngressRoute should not be in output when disabled")
}
}
func TestIngressRouteMetadata(t *testing.T) {
composeFile := `
services:
web:
image: nginx:1.29
ports:
- 80:80
labels:
%s/ingress: |-
hostname: meta.example.com
port: 80
type: ingressroute
enabled: true
`
composeFile = fmt.Sprintf(composeFile, labels.KatenaryLabelPrefix)
tmpDir := setup(composeFile)
defer teardown(tmpDir)
currentDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(currentDir)
output := internalCompileTest(
t,
"-s", "templates/web/ingressroute.yaml",
)
// Check that labels are present (like other objects - no metadata: block)
if !strings.Contains(output, "labels:") {
t.Errorf("Expected labels: in IngressRoute output")
}
// Check that katenary labels are present (set by GetLabels)
if !strings.Contains(output, "katenary.v3/component") {
t.Errorf("Expected katenary.v3/component label in IngressRoute")
}
// Check that the standard labels are present
if !strings.Contains(output, "app.kubernetes.io/name") {
t.Errorf("Expected app.kubernetes.io/name label in IngressRoute")
}
}

View File

@@ -3,7 +3,6 @@ package katenaryfile
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"log"
"os" "os"
"reflect" "reflect"
"strings" "strings"
@@ -67,7 +66,7 @@ func OverrideWithConfig(project *types.Project) {
return return
} }
if err := yaml.NewDecoder(fp).Decode(&services); err != nil { if err := yaml.NewDecoder(fp).Decode(&services); err != nil {
log.Fatal(err) logger.Fatal(err)
return return
} }
for _, p := range project.Services { for _, p := range project.Services {
@@ -79,7 +78,7 @@ func OverrideWithConfig(project *types.Project) {
} }
err := getLabelContent(o, &s, labelName) err := getLabelContent(o, &s, labelName)
if err != nil { if err != nil {
log.Fatal(err) logger.Fatal(err)
} }
project.Services[name] = s project.Services[name] = s
} }
@@ -113,7 +112,7 @@ func getLabelContent(o any, service *types.ServiceConfig, labelName string) erro
c, err := yaml.Marshal(o) c, err := yaml.Marshal(o)
if err != nil { if err != nil {
log.Println(err) logger.Failure(err.Error())
return err return err
} }
val := strings.TrimSpace(string(c)) val := strings.TrimSpace(string(c))
@@ -121,7 +120,7 @@ func getLabelContent(o any, service *types.ServiceConfig, labelName string) erro
// special case, values must be set from some defaults // special case, values must be set from some defaults
ing, err := labelstructs.IngressFrom(val) ing, err := labelstructs.IngressFrom(val)
if err != nil { if err != nil {
log.Fatal(err) logger.Fatal(err)
return err return err
} }
c, err := yaml.Marshal(ing) c, err := yaml.Marshal(ing)

View File

@@ -4,13 +4,13 @@ import (
"bytes" "bytes"
_ "embed" _ "embed"
"fmt" "fmt"
"log"
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"text/template" "text/template"
"katenary.io/internal/logger"
"katenary.io/internal/utils" "katenary.io/internal/utils"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
@@ -36,6 +36,7 @@ const (
LabelEnvFrom Label = KatenaryLabelPrefix + "/env-from" LabelEnvFrom Label = KatenaryLabelPrefix + "/env-from"
LabelExchangeVolume Label = KatenaryLabelPrefix + "/exchange-volumes" LabelExchangeVolume Label = KatenaryLabelPrefix + "/exchange-volumes"
LabelValuesFrom Label = KatenaryLabelPrefix + "/values-from" LabelValuesFrom Label = KatenaryLabelPrefix + "/values-from"
LabelDependsOn Label = KatenaryLabelPrefix + "/depends-on"
) )
var ( var (
@@ -134,7 +135,7 @@ func GetLabelHelpFor(labelname string, asMarkdown bool) string {
KatenaryPrefix: KatenaryLabelPrefix, KatenaryPrefix: KatenaryLabelPrefix,
}) })
if err != nil { if err != nil {
log.Fatalf("Error executing template: %v", err) logger.Fatalf("Error executing template: %v", err)
} }
help.Long = buf.String() help.Long = buf.String()
buf.Reset() buf.Reset()
@@ -145,7 +146,7 @@ func GetLabelHelpFor(labelname string, asMarkdown bool) string {
KatenaryPrefix: KatenaryLabelPrefix, KatenaryPrefix: KatenaryLabelPrefix,
}) })
if err != nil { if err != nil {
log.Fatalf("Error executing template: %v", err) logger.Fatalf("Error executing template: %v", err)
} }
help.Example = buf.String() help.Example = buf.String()
buf.Reset() buf.Reset()
@@ -160,7 +161,7 @@ func GetLabelHelpFor(labelname string, asMarkdown bool) string {
KatenaryPrefix: KatenaryLabelPrefix, KatenaryPrefix: KatenaryLabelPrefix,
}) })
if err != nil { if err != nil {
log.Fatalf("Error executing template: %v", err) logger.Fatalf("Error executing template: %v", err)
} }
return buf.String() return buf.String()

View File

@@ -92,7 +92,12 @@
short: "Ports to be added to the service." short: "Ports to be added to the service."
long: |- long: |-
Only useful for services without exposed port. It is mandatory if the Only useful for services without exposed port. It is mandatory if the
service is a dependency of another service. service is a dependency of another service AND you want to create a
Kubernetes Service for external access.
If you only need to check if the service is ready (using depends_on),
you don't need to declare ports. The service will not be created automatically
unless you add this label.
example: |- example: |-
labels: labels:
{{ .KatenaryPrefix }}/ports: |- {{ .KatenaryPrefix }}/ports: |-
@@ -105,11 +110,25 @@
long: |- long: |-
Declare an ingress rule for the service. The port should be exposed or Declare an ingress rule for the service. The port should be exposed or
declared with {{ printf "%s/%s" .KatenaryPrefix "ports" }}. declared with {{ printf "%s/%s" .KatenaryPrefix "ports" }}.
The default ingress class is "traefik".
**Files generated:** Both `ingress.yaml` (standard Kubernetes Ingress) and
`ingressroute.yaml` (Traefik IngressRoute CRD) are generated. You can
control which one is installed via values.yaml:
- `ingress.enabled` - controls standard Ingress installation
- `ingress.ingressRouteEnabled` - controls IngressRoute installation
Setting `type: ingressroute` automatically sets `ingressRouteEnabled: true`.
example: |- example: |-
labels: labels:
{{ .KatenaryPrefix }}/ingress: |- {{ .KatenaryPrefix }}/ingress: |-
port: 80 port: 80
hostname: mywebsite.com (optional) hostname: mywebsite.com (optional)
# Use Traefik IngressRoute instead of standard Ingress
type: ingressroute
enabled: true
type: "object" type: "object"
"map-env": "map-env":
@@ -355,4 +374,36 @@
DB_USER: database.MARIADB_USER DB_USER: database.MARIADB_USER
DB_PASSWORD: database.MARIADB_PASSWORD DB_PASSWORD: database.MARIADB_PASSWORD
"depends-on":
short: "Method to check if a service is ready (for depends_on)."
long: |-
When a service uses `depends_on`, Katenary creates an initContainer to wait
for the dependent service to be ready.
By default, Katenary uses the Kubernetes API to check if the deployment's
`readyReplicas` status is greater than 0. This method does not require the
service to expose a port and does not create a Kubernetes Service automatically.
If you need to create a Kubernetes Service for external access, use the
`katenary.v3/ports` label instead.
Set this label to `legacy` to use the old netcat method that requires a port
to be defined for the dependent service.
example: |-
web:
image: nginx
depends_on:
- database
labels:
# Use legacy netcat method (requires port)
{{ .KatenaryPrefix }}/depends-on: legacy
database:
image: mysql
labels:
# Create a Kubernetes Service for external access
{{ .KatenaryPrefix }}/ports:
- 3306
type: "string"
# vim: ft=gotmpl.yaml # vim: ft=gotmpl.yaml

View File

@@ -17,7 +17,8 @@ type Ingress struct {
Annotations map[string]string `yaml:"annotations,omitempty" jsonschema:"nullable" json:"annotations,omitempty"` Annotations map[string]string `yaml:"annotations,omitempty" jsonschema:"nullable" json:"annotations,omitempty"`
Hostname string `yaml:"hostname,omitempty" json:"hostname,omitempty"` Hostname string `yaml:"hostname,omitempty" json:"hostname,omitempty"`
Path *string `yaml:"path,omitempty" json:"path,omitempty"` Path *string `yaml:"path,omitempty" json:"path,omitempty"`
Class *string `yaml:"class,omitempty" json:"class,omitempty" jsonschema:"default:-"` Class *string `yaml:"class,omitempty" json:"class,omitempty" jsonschema:"default:traefik"`
Type string `yaml:"type,omitempty" json:"type,omitempty"`
Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"` Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
TLS *TLS `yaml:"tls,omitempty" json:"tls,omitempty"` TLS *TLS `yaml:"tls,omitempty" json:"tls,omitempty"`
} }
@@ -28,7 +29,8 @@ func IngressFrom(data string) (*Ingress, error) {
Hostname: "", Hostname: "",
Path: utils.StrPtr("/"), Path: utils.StrPtr("/"),
Enabled: false, Enabled: false,
Class: utils.StrPtr("-"), Class: utils.StrPtr("traefik"),
Type: "ingress",
Port: nil, Port: nil,
TLS: &TLS{Enabled: true}, TLS: &TLS{Enabled: true},
} }

View File

@@ -2,10 +2,10 @@ package labelstructs
import ( import (
"encoding/json" "encoding/json"
"log"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"katenary.io/internal/logger"
) )
type HealthCheck struct { type HealthCheck struct {
@@ -24,13 +24,13 @@ func ProbeFrom(data string) (*HealthCheck, error) {
if livenessProbe, ok := tmp["livenessProbe"]; ok { if livenessProbe, ok := tmp["livenessProbe"]; ok {
livenessProbeBytes, err := json.Marshal(livenessProbe) livenessProbeBytes, err := json.Marshal(livenessProbe)
if err != nil { if err != nil {
log.Printf("Error marshalling livenessProbe: %v", err) logger.Warnf("Error marshalling livenessProbe: %v", err)
return nil, err return nil, err
} }
livenessProbe := &corev1.Probe{} livenessProbe := &corev1.Probe{}
err = json.Unmarshal(livenessProbeBytes, livenessProbe) err = json.Unmarshal(livenessProbeBytes, livenessProbe)
if err != nil { if err != nil {
log.Printf("Error unmarshalling livenessProbe: %v", err) logger.Warnf("Error unmarshalling livenessProbe: %v", err)
return nil, err return nil, err
} }
mapping.LivenessProbe = livenessProbe mapping.LivenessProbe = livenessProbe
@@ -39,13 +39,13 @@ func ProbeFrom(data string) (*HealthCheck, error) {
if readinessProbe, ok := tmp["readinessProbe"]; ok { if readinessProbe, ok := tmp["readinessProbe"]; ok {
readinessProbeBytes, err := json.Marshal(readinessProbe) readinessProbeBytes, err := json.Marshal(readinessProbe)
if err != nil { if err != nil {
log.Printf("Error marshalling readinessProbe: %v", err) logger.Warnf("Error marshalling readinessProbe: %v", err)
return nil, err return nil, err
} }
readinessProbe := &corev1.Probe{} readinessProbe := &corev1.Probe{}
err = json.Unmarshal(readinessProbeBytes, readinessProbe) err = json.Unmarshal(readinessProbeBytes, readinessProbe)
if err != nil { if err != nil {
log.Printf("Error unmarshalling readinessProbe: %v", err) logger.Warnf("Error unmarshalling readinessProbe: %v", err)
return nil, err return nil, err
} }
mapping.ReadinessProbe = readinessProbe mapping.ReadinessProbe = readinessProbe

View File

@@ -32,7 +32,7 @@ func NewRBAC(service types.ServiceConfig, appName string) *RBAC {
APIVersion: "rbac.authorization.k8s.io/v1", APIVersion: "rbac.authorization.k8s.io/v1",
}, },
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: utils.TplName(service.Name, appName), Name: utils.TplName(service.Name, appName, "dependency"),
Labels: GetLabels(service.Name, appName), Labels: GetLabels(service.Name, appName),
Annotations: Annotations, Annotations: Annotations,
}, },
@@ -128,6 +128,79 @@ func (r *Role) Yaml() ([]byte, error) {
} }
} }
// NewServiceAccount creates a new ServiceAccount from a compose service.
func NewServiceAccount(service types.ServiceConfig, appName string) *ServiceAccount {
return &ServiceAccount{
ServiceAccount: &corev1.ServiceAccount{
TypeMeta: metav1.TypeMeta{
Kind: "ServiceAccount",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: utils.TplName(service.Name, appName, "dependency"),
Labels: GetLabels(service.Name, appName),
Annotations: Annotations,
},
},
service: &service,
}
}
// NewRestrictedRole creates a Role with minimal permissions for init containers.
func NewRestrictedRole(service types.ServiceConfig, appName string) *Role {
return &Role{
Role: &rbacv1.Role{
TypeMeta: metav1.TypeMeta{
Kind: "Role",
APIVersion: "rbac.authorization.k8s.io/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: utils.TplName(service.Name, appName, "dependency"),
Labels: GetLabels(service.Name, appName),
Annotations: Annotations,
},
Rules: []rbacv1.PolicyRule{
{
APIGroups: []string{"apps"},
Resources: []string{"deployments"},
Verbs: []string{"get"},
},
},
},
service: &service,
}
}
// NewRestrictedRoleBinding creates a RoleBinding that binds the restricted role to the ServiceAccount.
func NewRestrictedRoleBinding(service types.ServiceConfig, appName string) *RoleBinding {
return &RoleBinding{
RoleBinding: &rbacv1.RoleBinding{
TypeMeta: metav1.TypeMeta{
Kind: "RoleBinding",
APIVersion: "rbac.authorization.k8s.io/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: utils.TplName(service.Name, appName, "dependency"),
Labels: GetLabels(service.Name, appName),
Annotations: Annotations,
},
Subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Name: utils.TplName(service.Name, appName, "dependency"),
Namespace: "{{ .Release.Namespace }}",
},
},
RoleRef: rbacv1.RoleRef{
Kind: "Role",
Name: utils.TplName(service.Name, appName, "dependency"),
APIGroup: "rbac.authorization.k8s.io",
},
},
service: &service,
}
}
// ServiceAccount is a kubernetes ServiceAccount. // ServiceAccount is a kubernetes ServiceAccount.
type ServiceAccount struct { type ServiceAccount struct {
*corev1.ServiceAccount *corev1.ServiceAccount

View File

@@ -1,11 +1,11 @@
package generator package generator
import ( import (
"log"
"os" "os"
"os/exec" "os/exec"
"testing" "testing"
"katenary.io/internal/logger"
"katenary.io/internal/parser" "katenary.io/internal/parser"
) )
@@ -23,7 +23,7 @@ func setup(content string) string {
func teardown(tmpDir string) { func teardown(tmpDir string) {
// remove the temporary directory // remove the temporary directory
log.Println("Removing temporary directory: ", tmpDir) logger.Info("Removing temporary directory: ", tmpDir)
if err := os.RemoveAll(tmpDir); err != nil { if err := os.RemoveAll(tmpDir); err != nil {
panic(err) panic(err)
} }
@@ -59,7 +59,7 @@ func compileTest(t *testing.T, force bool, options ...string) string {
ChartVersion: chartVersion, ChartVersion: chartVersion,
} }
if err := Convert(convertOptions, "compose.yml"); err != nil { if err := Convert(convertOptions, "compose.yml"); err != nil {
log.Printf("Failed to convert: %s", err) logger.Warnf("Failed to convert: %s", err)
return err.Error() return err.Error()
} }

View File

@@ -31,7 +31,9 @@ type IngressValue struct {
Host string `yaml:"host"` Host string `yaml:"host"`
Path string `yaml:"path"` Path string `yaml:"path"`
Class string `yaml:"class"` Class string `yaml:"class"`
Type string `yaml:"type"`
Enabled bool `yaml:"enabled"` Enabled bool `yaml:"enabled"`
IngressRouteEnabled bool `yaml:"ingressRouteEnabled"`
TLS TLS `yaml:"tls"` TLS TLS `yaml:"tls"`
} }
@@ -92,7 +94,8 @@ func (v *Value) AddIngress(host, path string) {
Enabled: true, Enabled: true,
Host: host, Host: host,
Path: path, Path: path,
Class: "-", Class: "traefik",
Type: "ingress",
TLS: TLS{ TLS: TLS{
Enabled: true, Enabled: true,
SecretName: "", SecretName: "",

View File

@@ -1,6 +1,12 @@
// Package logger provides simple logging functions with icons and colors. // Package logger provides simple logging functions with icons and colors.
package logger package logger
import (
"fmt"
"os"
"sync"
)
// Icon is a unicode icon // Icon is a unicode icon
type Icon string type Icon string
@@ -22,30 +28,117 @@ const (
const reset = "\033[0m" const reset = "\033[0m"
const (
ColorGreen = "\033[38;5;34m"
ColorRed = "\033[38;5;196m"
ColorOrange = "\033[38;5;214m"
ColorWarning = "\033[38;5;214m"
)
var (
warnings []string
mu sync.Mutex
)
// Print prints a message without icon.
func Print(msg ...any) {
fmt.Print(msg...)
}
// Printf prints a formatted message without icon.
func Printf(format string, msg ...any) {
fmt.Printf(format, msg...)
}
// Info prints an informational message. // Info prints an informational message.
func Info(msg ...any) { func Info(msg ...any) {
message("", IconInfo, msg...) message("", IconInfo, msg...)
} }
// Infof prints a formatted informational message.
func Infof(format string, msg ...any) {
message("", IconInfo, fmt.Sprintf(format, msg...))
}
// Warn prints a warning message. // Warn prints a warning message.
func Warn(msg ...any) { func Warn(msg ...any) {
orange := "\033[38;5;214m" mu.Lock()
message(orange, IconWarning, msg...) defer mu.Unlock()
warning := fmt.Sprint(msg...)
warnings = append(warnings, warning)
}
// Warnf prints a formatted warning message.
func Warnf(format string, msg ...any) {
mu.Lock()
defer mu.Unlock()
warning := fmt.Sprintf(format, msg...)
warnings = append(warnings, warning)
} }
// Success prints a success message. // Success prints a success message.
func Success(msg ...any) { func Success(msg ...any) {
green := "\033[38;5;34m" message(ColorGreen, IconSuccess, msg...)
message(green, IconSuccess, msg...) }
// Successf prints a formatted success message.
func Successf(format string, msg ...any) {
message(ColorGreen, IconSuccess, fmt.Sprintf(format, msg...))
} }
// Failure prints a failure message. // Failure prints a failure message.
func Failure(msg ...any) { func Failure(msg ...any) {
red := "\033[38;5;196m" message(ColorRed, IconFailure, msg...)
message(red, IconFailure, msg...) }
// Failuref prints a formatted failure message.
func Failuref(format string, msg ...any) {
message(ColorRed, IconFailure, fmt.Sprintf(format, msg...))
} }
// Log prints a message with a custom icon. // Log prints a message with a custom icon.
func Log(icon Icon, msg ...any) { func Log(icon Icon, msg ...any) {
message("", icon, msg...) message("", icon, msg...)
} }
// Logf prints a formatted message with a custom icon.
func Logf(icon Icon, format string, msg ...any) {
message("", icon, fmt.Sprintf(format, msg...))
}
func fatal(red string, icon Icon, msg ...any) {
fmt.Print(icon, " ", red)
fmt.Print(msg...)
fmt.Println(reset)
os.Exit(1)
}
func fatalf(red string, icon Icon, format string, msg ...any) {
fatal(red, icon, fmt.Sprintf(format, msg...))
}
// Fatal prints a fatal error message and exits with code 1.
func Fatal(msg ...any) {
fatal(ColorRed, IconFailure, msg...)
}
// Fatalf prints a fatal error message with formatting and exits with code 1.
func Fatalf(format string, msg ...any) {
fatalf(ColorRed, IconFailure, format, msg...)
}
// FlushWarnings prints all collected warnings at the end of the conversion.
func FlushWarnings() {
mu.Lock()
defer mu.Unlock()
if len(warnings) > 0 {
fmt.Println()
fmt.Print(ColorWarning, IconWarning, " The following issues may need attention:", reset)
fmt.Println()
for _, warning := range warnings {
fmt.Println(" •", warning)
}
fmt.Println()
warnings = nil
}
}

View File

@@ -0,0 +1,101 @@
package logger
import (
"testing"
)
func TestIcons(t *testing.T) {
tests := []struct {
name string
got Icon
expected Icon
}{
{"IconSuccess", IconSuccess, "✅"},
{"IconFailure", IconFailure, "❌"},
{"IconWarning", IconWarning, "❕"},
{"IconNote", IconNote, "📝"},
{"IconWorld", IconWorld, "🌐"},
{"IconPlug", IconPlug, "🔌"},
{"IconPackage", IconPackage, "📦"},
{"IconCabinet", IconCabinet, "🗄️"},
{"IconInfo", IconInfo, "🔵"},
{"IconSecret", IconSecret, "🔒"},
{"IconConfig", IconConfig, "🔧"},
{"IconDependency", IconDependency, "🔗"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.got != tt.expected {
t.Errorf("got %q, want %q", tt.got, tt.expected)
}
})
}
}
func TestInfo(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("Info panicked: %v", r)
}
}()
Info("test message")
}
func TestWarn(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("Warn panicked: %v", r)
}
}()
Warn("test warning")
}
func TestSuccess(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("Success panicked: %v", r)
}
}()
Success("test success")
}
func TestFailure(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("Failure panicked: %v", r)
}
}()
Failure("test failure")
}
func TestLog(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("Log panicked: %v", r)
}
}()
Log(IconInfo, "test log")
}
func TestWarningsCollection(t *testing.T) {
// Clear any existing warnings
warnings = nil
// Add some warnings
Warn("test warning 1")
Warnf("test warning 2: %s", "value")
// Check that warnings were collected
if len(warnings) != 2 {
t.Errorf("expected 2 warnings, got %d", len(warnings))
}
// Check the content of warnings
if warnings[0] != "test warning 1" {
t.Errorf("expected 'test warning 1', got '%s'", warnings[0])
}
if warnings[1] != "test warning 2: value" {
t.Errorf("expected 'test warning 2: value', got '%s'", warnings[1])
}
}

View File

@@ -3,11 +3,11 @@ package parser
import ( import (
"context" "context"
"log"
"path/filepath" "path/filepath"
"github.com/compose-spec/compose-go/v2/cli" "github.com/compose-spec/compose-go/v2/cli"
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
"katenary.io/internal/logger"
) )
func init() { func init() {
@@ -37,20 +37,25 @@ func Parse(profiles []string, envFiles []string, dockerComposeFile ...string) (*
var err error var err error
envFiles[i], err = filepath.Abs(envFiles[i]) envFiles[i], err = filepath.Abs(envFiles[i])
if err != nil { if err != nil {
log.Fatal(err) logger.Fatal(err)
} }
} }
options, err := cli.NewProjectOptions(nil, opts := []cli.ProjectOptionsFn{
cli.WithProfiles(profiles), cli.WithProfiles(profiles),
cli.WithInterpolation(true), cli.WithInterpolation(true),
cli.WithDefaultConfigPath,
cli.WithEnvFiles(envFiles...), cli.WithEnvFiles(envFiles...),
cli.WithOsEnv, cli.WithOsEnv,
cli.WithDotEnv, cli.WithDotEnv,
cli.WithNormalization(true), cli.WithNormalization(true),
cli.WithResolvedPaths(false), cli.WithResolvedPaths(false),
) }
if len(dockerComposeFile) == 0 {
opts = append(opts, cli.WithDefaultConfigPath)
}
options, err := cli.NewProjectOptions(dockerComposeFile, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -1,10 +1,11 @@
package parser package parser
import ( import (
"log"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"katenary.io/internal/logger"
) )
const composeFile = ` const composeFile = `
@@ -27,7 +28,7 @@ func setupTest() (string, error) {
func tearDownTest(tmpDir string) { func tearDownTest(tmpDir string) {
if tmpDir != "" { if tmpDir != "" {
if err := os.RemoveAll(tmpDir); err != nil { if err := os.RemoveAll(tmpDir); err != nil {
log.Fatalf("Failed to remove temporary directory %s: %s", tmpDir, err.Error()) logger.Fatalf("Failed to remove temporary directory %s: %s", tmpDir, err.Error())
} }
} }
} }

View File

@@ -3,7 +3,6 @@ package utils
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"log"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -133,8 +132,8 @@ func GetValuesFromLabel(service types.ServiceConfig, LabelValues string) map[str
labelContent := []any{} labelContent := []any{}
err := yaml.Unmarshal([]byte(v), &labelContent) err := yaml.Unmarshal([]byte(v), &labelContent)
if err != nil { if err != nil {
log.Printf("Error parsing label %s: %s", v, err) logger.Warnf("Error parsing label %s: %s", v, err)
log.Fatal(err) logger.Fatal(err)
} }
for _, value := range labelContent { for _, value := range labelContent {
@@ -150,7 +149,7 @@ func GetValuesFromLabel(service types.ServiceConfig, LabelValues string) map[str
descriptions[k.(string)] = &EnvConfig{Service: service, Description: v.(string)} descriptions[k.(string)] = &EnvConfig{Service: service, Description: v.(string)}
} }
default: default:
log.Fatalf("Unknown type in label: %s %T", LabelValues, value) logger.Fatalf("Unknown type in label: %s %T", LabelValues, value)
} }
} }
} }
@@ -171,7 +170,7 @@ func Confirm(question string, icon ...logger.Icon) bool {
} }
var response string var response string
if _, err := fmt.Scanln(&response); err != nil { if _, err := fmt.Scanln(&response); err != nil {
log.Fatalf("Error parsing response: %s", err.Error()) logger.Fatalf("Error parsing response: %s", err.Error())
} }
return strings.ToLower(response) == "y" return strings.ToLower(response) == "y"
} }