forked from Katenary/katenary
Compare commits
13 Commits
feat-fix-i
...
feature/be
| Author | SHA1 | Date | |
|---|---|---|---|
| 8fc9cb31c4 | |||
| 78b5af747e | |||
| 269717eb1c | |||
| 61896baad8 | |||
| feff997aba | |||
| 89e331069e | |||
| 88ce6d4579 | |||
| 3e80221641 | |||
|
|
990eda74eb | ||
| 7230081401 | |||
| f0fc694d50 | |||
| d92cc8a01c | |||
| 3abfaf591c |
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,8 @@ 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.
|
||||||
|
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 +148,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 +166,7 @@ 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
|
```yaml
|
||||||
version: "3"
|
version: "3"
|
||||||
@@ -179,14 +176,15 @@ services:
|
|||||||
image: php:8-apache
|
image: php:8-apache
|
||||||
depends_on:
|
depends_on:
|
||||||
- database
|
- database
|
||||||
|
labels:
|
||||||
|
katenary.v3/depends-on: legacy
|
||||||
|
|
||||||
database:
|
database:
|
||||||
image: mariadb
|
image: mariadb
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: foobar
|
MYSQL_ROOT_PASSWORD: foobar
|
||||||
labels:
|
ports:
|
||||||
katenary.v3/ports: |-
|
- 3306:3306
|
||||||
- 3306
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Declare ingresses
|
### Declare ingresses
|
||||||
|
|||||||
23
install.sh
23
install.sh
@@ -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"
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package generator
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"maps"
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -331,12 +330,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 +355,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 +374,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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -596,7 +595,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 +634,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)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -33,7 +33,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package generator
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -166,7 +165,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
|
||||||
@@ -201,7 +200,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,19 +262,30 @@ 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -293,6 +303,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}
|
||||||
|
SERVICE=%s
|
||||||
|
KUBERNETES_SERVICE_HOST=${KUBERNETES_SERVICE_HOST:-kubernetes.default.svc}
|
||||||
|
KUBERNETES_SERVICE_PORT=${KUBERNETES_SERVICE_PORT:-443}
|
||||||
|
|
||||||
|
until wget -q -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}/api/v1/namespaces/${NAMESPACE}/endpoints/${SERVICE}" \
|
||||||
|
| grep -q '"ready":.*true'; do
|
||||||
|
sleep 2
|
||||||
|
done`
|
||||||
|
|
||||||
|
command := []string{"/bin/sh", "-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: "busybox: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 +354,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 +363,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 +378,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 +395,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 +427,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{
|
||||||
@@ -615,7 +658,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 +703,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 +764,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() {
|
||||||
|
|||||||
@@ -142,6 +142,86 @@ 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, "busybox") {
|
||||||
|
t.Errorf("Expected busybox image, got %s", initContainer.Image)
|
||||||
|
}
|
||||||
|
|
||||||
|
fullCommand := strings.Join(initContainer.Command, " ")
|
||||||
|
if !strings.Contains(fullCommand, "wget") {
|
||||||
|
t.Errorf("Expected wget command (K8s API method), got %s", fullCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(fullCommand, "/api/v1/namespaces/") {
|
||||||
|
t.Errorf("Expected Kubernetes API call to /api/v1/namespaces/, got %s", fullCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(fullCommand, "/endpoints/") {
|
||||||
|
t.Errorf("Expected Kubernetes API call to /endpoints/, 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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package generator
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -145,7 +144,7 @@ func Generate(project *types.Project) (*HelmChart, error) {
|
|||||||
|
|
||||||
// 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
|
||||||
@@ -280,7 +279,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 +291,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,7 +433,7 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -36,7 +36,7 @@ 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"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -355,4 +355,25 @@
|
|||||||
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 service endpoint
|
||||||
|
has ready addresses. This method does not require the service to expose a port.
|
||||||
|
|
||||||
|
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
|
||||||
|
type: "string"
|
||||||
|
|
||||||
# vim: ft=gotmpl.yaml
|
# vim: ft=gotmpl.yaml
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
// Icon is a unicode icon
|
// Icon is a unicode icon
|
||||||
type Icon string
|
type Icon string
|
||||||
|
|
||||||
@@ -22,30 +27,91 @@ const (
|
|||||||
|
|
||||||
const reset = "\033[0m"
|
const reset = "\033[0m"
|
||||||
|
|
||||||
|
// 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"
|
orange := "\033[38;5;214m"
|
||||||
message(orange, IconWarning, msg...)
|
message(orange, IconWarning, msg...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Warnf prints a formatted warning message.
|
||||||
|
func Warnf(format string, msg ...any) {
|
||||||
|
orange := "\033[38;5;214m"
|
||||||
|
message(orange, IconWarning, fmt.Sprintf(format, msg...))
|
||||||
|
}
|
||||||
|
|
||||||
// Success prints a success message.
|
// Success prints a success message.
|
||||||
func Success(msg ...any) {
|
func Success(msg ...any) {
|
||||||
green := "\033[38;5;34m"
|
green := "\033[38;5;34m"
|
||||||
message(green, IconSuccess, msg...)
|
message(green, IconSuccess, msg...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Successf prints a formatted success message.
|
||||||
|
func Successf(format string, msg ...any) {
|
||||||
|
green := "\033[38;5;34m"
|
||||||
|
message(green, 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"
|
red := "\033[38;5;196m"
|
||||||
message(red, IconFailure, msg...)
|
message(red, IconFailure, msg...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Failuref prints a formatted failure message.
|
||||||
|
func Failuref(format string, msg ...any) {
|
||||||
|
red := "\033[38;5;196m"
|
||||||
|
message(red, 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) {
|
||||||
|
red := "\033[38;5;196m"
|
||||||
|
fatal(red, IconFailure, msg...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf prints a fatal error message with formatting and exits with code 1.
|
||||||
|
func Fatalf(format string, msg ...any) {
|
||||||
|
red := "\033[38;5;196m"
|
||||||
|
fatalf(red, IconFailure, format, msg...)
|
||||||
|
}
|
||||||
|
|||||||
79
internal/logger/logger_test.go
Normal file
79
internal/logger/logger_test.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
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")
|
||||||
|
}
|
||||||
@@ -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,11 +37,11 @@ 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,
|
options, err := cli.NewProjectOptions(dockerComposeFile,
|
||||||
cli.WithProfiles(profiles),
|
cli.WithProfiles(profiles),
|
||||||
cli.WithInterpolation(true),
|
cli.WithInterpolation(true),
|
||||||
cli.WithDefaultConfigPath,
|
cli.WithDefaultConfigPath,
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user