15 Commits

Author SHA1 Message Date
165054ca53 For single env_file, it's a string...
see #8
2022-04-01 17:49:10 +02:00
a87391e726 Fix healthcheck
see #8
2022-04-01 17:39:41 +02:00
f8dcd2026b Fix test command to make it work as string / slice
see #8
2022-04-01 16:58:13 +02:00
f99f146af2 Force build with "build-all" by empty dist/* 2022-04-01 10:47:58 +02:00
e72a8a2e9c Fix envfile detection
+ The envfiles were not added!

see #8

TODO: what are the others properties to fix this way?
2022-04-01 10:43:08 +02:00
0f73aa3125 Manage ugly format for command
Some people uses pure string as command, we need to cast to []string
slice.

see #8
2022-04-01 10:12:14 +02:00
7dc5d509f7 Fix the problem with "ugly" environment syntax
We can now manage "- A=B" format as "A: B"

Some others properties than environment may have this problem (e.g.,
command) so we will fix this later.

fix #4
2022-04-01 09:22:00 +02:00
a9b75c48c4 Add test for bad volumes 2022-04-01 08:22:06 +02:00
6ea3a923cc Avoid "not mapped" volumes
fix #5
2022-04-01 08:18:45 +02:00
6cd1af015b Manage empty image name
And avoid removing charts if there are problems before generating the
helm chart

fix #6
2022-04-01 08:15:39 +02:00
68a031d0be Remove bad log 2022-04-01 08:15:01 +02:00
7ba68c2854 If there is no image name, fail!
fix #6
2022-04-01 08:04:37 +02:00
1dd8fef4b3 Fix compose file argument
fix #7
2022-04-01 07:59:53 +02:00
6a2417c361 Create dependabot.yml 2022-03-31 14:30:42 +02:00
ad316d1f49 Removed the forcing of install type 2022-03-31 14:25:40 +02:00
11 changed files with 420 additions and 45 deletions

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/" # Location of package manifests
schedule:
interval: "daily"

View File

@@ -49,7 +49,11 @@ help:
build: pull katenary build: pull katenary
build-all: pull dist dist/katenary-linux-amd64 dist/katenary-linux-arm64 dist/katenary.exe dist/katenary-darwin-amd64 dist/katenary-freebsd-amd64 dist/katenary-freebsd-arm64 build-all:
rm -f dist/*
$(MAKE) _build-all
_build-all: pull dist dist/katenary-linux-amd64 dist/katenary-linux-arm64 dist/katenary.exe dist/katenary-darwin-amd64 dist/katenary-freebsd-amd64 dist/katenary-freebsd-arm64
pull: pull:
ifneq ($(GO),local) ifneq ($(GO),local)

View File

@@ -93,13 +93,20 @@ func detectGitVersion() (string, error) {
} }
func Convert(composeFile, appVersion, appName, chartDir string, force bool) { func Convert(composeFile, appVersion, appName, chartDir string, force bool) {
composeFound := FindComposeFile() if len(composeFile) == 0 {
fmt.Println("No compose file given")
return
}
_, err := os.Stat(ComposeFile) _, err := os.Stat(ComposeFile)
if !composeFound && err != nil { if err != nil {
fmt.Println("No compose file found") fmt.Println("No compose file found")
os.Exit(1) os.Exit(1)
} }
// Parse the compose file now
p := compose.NewParser(composeFile)
p.Parse(appName)
dirname := filepath.Join(chartDir, appName) dirname := filepath.Join(chartDir, appName)
if _, err := os.Stat(dirname); err == nil && !force { if _, err := os.Stat(dirname); err == nil && !force {
response := "" response := ""
@@ -130,10 +137,6 @@ func Convert(composeFile, appVersion, appName, chartDir string, force bool) {
os.Exit(1) os.Exit(1)
} }
// Parse the compose file now
p := compose.NewParser(ComposeFile)
p.Parse(appName)
// start generator // start generator
generator.Generate(p, Version, appName, appVersion, ComposeFile, dirname) generator.Generate(p, Version, appName, appVersion, ComposeFile, dirname)

View File

@@ -7,6 +7,7 @@ import (
"os" "os"
"strings" "strings"
"github.com/google/shlex"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -54,6 +55,14 @@ func (p *Parser) Parse(appname string) {
services := make(map[string][]string) services := make(map[string][]string)
// get the service list, to be sure that everything is ok // get the service list, to be sure that everything is ok
// fix ugly types
for _, s := range p.Data.Services {
parseEnv(s)
parseCommand(s)
parseEnvFiles(s)
parseHealthCheck(s)
}
c := p.Data c := p.Data
for name, s := range c.Services { for name, s := range c.Services {
if portlabel, ok := s.Labels[helm.LABEL_PORT]; ok { if portlabel, ok := s.Labels[helm.LABEL_PORT]; ok {
@@ -94,6 +103,22 @@ func (p *Parser) Parse(appname string) {
log.Fatal(strings.Join(missing, "\n")) log.Fatal(strings.Join(missing, "\n"))
} }
// check if all "image" properties are set
missing = []string{}
for name, s := range c.Services {
if s.Image == "" {
missing = append(missing, fmt.Sprintf(
"The service \"%s\" hasn't got "+
"an image property - please "+
"append an image property in the docker-compose file",
name,
))
}
}
if len(missing) > 0 {
log.Fatal(strings.Join(missing, "\n"))
}
// check the build element // check the build element
for name, s := range c.Services { for name, s := range c.Services {
if s.RawBuild == nil { if s.RawBuild == nil {
@@ -105,4 +130,119 @@ func (p *Parser) Parse(appname string) {
" for the \"" + name + "\" service \x1b[0m") " for the \"" + name + "\" service \x1b[0m")
} }
}
// manage environment variables, if the type is map[string]string so we can use it, else we need to split "=" sign
// and apply this in env variable
func parseEnv(s *Service) {
env := make(map[string]string)
if s.RawEnvironment == nil {
return
}
switch s.RawEnvironment.(type) {
case map[string]string:
env = s.RawEnvironment.(map[string]string)
case map[string]interface{}:
for k, v := range s.RawEnvironment.(map[string]interface{}) {
// force to string
env[k] = fmt.Sprintf("%v", v)
}
case []interface{}:
for _, v := range s.RawEnvironment.([]interface{}) {
// Splot the value of the env variable with "="
parts := strings.Split(v.(string), "=")
env[parts[0]] = parts[1]
}
case string:
parts := strings.Split(s.RawEnvironment.(string), "=")
env[parts[0]] = parts[1]
default:
log.Printf("%+v, %T", s.RawEnvironment, s.RawEnvironment)
log.Fatal("Environment type not supported")
}
s.Environment = env
}
func parseCommand(s *Service) {
if s.RawCommand == nil {
return
}
// following the command type, it can be a "slice" or a simple sting, so we need to check it
switch v := s.RawCommand.(type) {
case string:
// use shlex to parse the command
command, err := shlex.Split(v)
if err != nil {
log.Fatal(err)
}
s.Command = command
case []string:
s.Command = v
case []interface{}:
for _, v := range v {
s.Command = append(s.Command, v.(string))
}
default:
log.Printf("%+v %T", s.RawCommand, s.RawCommand)
log.Fatal("Command type not supported")
}
}
func parseEnvFiles(s *Service) {
// Same than parseEnv, but for env files
if s.RawEnvFiles == nil {
return
}
envfiles := make([]string, 0)
switch v := s.RawEnvFiles.(type) {
case []string:
envfiles = v
case []interface{}:
for _, v := range v {
envfiles = append(envfiles, v.(string))
}
case string:
envfiles = append(envfiles, v)
default:
log.Printf("%+v %T", s.RawEnvFiles, s.RawEnvFiles)
log.Fatal("EnvFile type not supported")
}
s.EnvFiles = envfiles
}
func parseHealthCheck(s *Service) {
// HealthCheck command can be a string or slice of strings
if s.HealthCheck == nil {
return
}
if s.HealthCheck.RawTest == nil {
return
}
switch v := s.HealthCheck.RawTest.(type) {
case string:
c, err := shlex.Split(v)
if err != nil {
log.Fatal(err)
}
s.HealthCheck = &HealthCheck{
Test: c,
}
case []string:
s.HealthCheck = &HealthCheck{
Test: v,
}
case []interface{}:
for _, v := range v {
s.HealthCheck.Test = append(s.HealthCheck.Test, v.(string))
}
default:
log.Printf("%+v %T", s.HealthCheck.RawTest, s.HealthCheck.RawTest)
log.Fatal("HealthCheck type not supported")
}
} }

View File

@@ -1,6 +1,9 @@
package compose package compose
import "testing" import (
"katenary/logger"
"testing"
)
const DOCKER_COMPOSE_YML1 = ` const DOCKER_COMPOSE_YML1 = `
version: "3" version: "3"
@@ -29,8 +32,36 @@ services:
labels: labels:
katenary.io/ports: "5432" katenary.io/ports: "5432"
commander1:
image: foo
command: ["/bin/sh", "-c", "echo 'hello world'"]
commander2:
image: foo
command: echo "hello world"
hc1:
image: foo
healthcheck:
test: ["CMD-SHELL", "echo 'hello world1'"]
hc2:
image: foo
healthcheck:
test: echo "hello world2"
hc3:
image: foo
healthcheck:
test: ["CMD", "echo 'hello world3'"]
` `
func init() {
logger.NOLOG = true
}
func TestParser(t *testing.T) { func TestParser(t *testing.T) {
p := NewParser("", DOCKER_COMPOSE_YML1) p := NewParser("", DOCKER_COMPOSE_YML1)
p.Parse("test") p.Parse("test")
@@ -85,5 +116,84 @@ func TestParser(t *testing.T) {
} }
} }
} }
}
func TestParseCommand(t *testing.T) {
p := NewParser("", DOCKER_COMPOSE_YML1)
p.Parse("test")
for name, s := range p.Data.Services {
if name == "commander1" {
t.Log(s.Command)
if len(s.Command) != 3 {
t.Errorf("Expected 3 command, got %d", len(s.Command))
}
if s.Command[0] != "/bin/sh" {
t.Errorf("Expected /bin/sh, got %s", s.Command[0])
}
if s.Command[1] != "-c" {
t.Errorf("Expected -c, got %s", s.Command[1])
}
if s.Command[2] != "echo 'hello world'" {
t.Errorf("Expected echo 'hello world', got %s", s.Command[2])
}
}
if name == "commander2" {
t.Log(s.Command)
if len(s.Command) != 2 {
t.Errorf("Expected 1 command, got %d", len(s.Command))
}
if s.Command[0] != "echo" {
t.Errorf("Expected echo, got %s", s.Command[0])
}
if s.Command[1] != "hello world" {
t.Errorf("Expected hello world, got %s", s.Command[1])
}
}
}
}
func TestHealthChecks(t *testing.T) {
p := NewParser("", DOCKER_COMPOSE_YML1)
p.Parse("test")
for name, s := range p.Data.Services {
if name != "hc1" && name != "hc2" && name != "hc3" {
continue
}
if name == "hc1" {
if len(s.HealthCheck.Test) != 2 {
t.Errorf("Expected 2 healthcheck tests, got %d", len(s.HealthCheck.Test))
}
if s.HealthCheck.Test[0] != "CMD-SHELL" {
t.Errorf("Expected CMD-SHELL, got %s", s.HealthCheck.Test[0])
}
if s.HealthCheck.Test[1] != "echo 'hello world1'" {
t.Errorf("Expected echo 'hello world1', got %s", s.HealthCheck.Test[1])
}
}
if name == "hc2" {
if len(s.HealthCheck.Test) != 2 {
t.Errorf("Expected 2 healthcheck tests, got %d", len(s.HealthCheck.Test))
}
if s.HealthCheck.Test[0] != "echo" {
t.Errorf("Expected echo, got %s", s.HealthCheck.Test[1])
}
if s.HealthCheck.Test[1] != "hello world2" {
t.Errorf("Expected echo 'hello world2', got %s", s.HealthCheck.Test[1])
}
}
if name == "hc3" {
if len(s.HealthCheck.Test) != 2 {
t.Errorf("Expected 2 healthcheck tests, got %d", len(s.HealthCheck.Test))
}
if s.HealthCheck.Test[0] != "CMD" {
t.Errorf("Expected CMD, got %s", s.HealthCheck.Test[0])
}
if s.HealthCheck.Test[1] != "echo 'hello world3'" {
t.Errorf("Expected echo 'hello world3', got %s", s.HealthCheck.Test[1])
}
}
}
} }

View File

@@ -17,7 +17,8 @@ func NewCompose() *Compose {
// HealthCheck manage generic type to handle TCP, HTTP and TCP health check. // HealthCheck manage generic type to handle TCP, HTTP and TCP health check.
type HealthCheck struct { type HealthCheck struct {
Test []string `yaml:"test"` Test []string `yaml:"-"`
RawTest interface{} `yaml:"test"`
Interval string `yaml:"interval"` Interval string `yaml:"interval"`
Timeout string `yaml:"timeout"` Timeout string `yaml:"timeout"`
Retries int `yaml:"retries"` Retries int `yaml:"retries"`
@@ -28,13 +29,16 @@ type HealthCheck struct {
type Service struct { type Service struct {
Image string `yaml:"image"` Image string `yaml:"image"`
Ports []string `yaml:"ports"` Ports []string `yaml:"ports"`
Environment map[string]string `yaml:"environment"` Environment map[string]string `yaml:"-"`
RawEnvironment interface{} `yaml:"environment"`
Labels map[string]string `yaml:"labels"` Labels map[string]string `yaml:"labels"`
DependsOn []string `yaml:"depends_on"` DependsOn []string `yaml:"depends_on"`
Volumes []string `yaml:"volumes"` Volumes []string `yaml:"volumes"`
Expose []int `yaml:"expose"` Expose []int `yaml:"expose"`
EnvFiles []string `yaml:"env_file"` EnvFiles []string `yaml:"-"`
RawEnvFiles interface{} `yaml:"env_file"`
RawBuild interface{} `yaml:"build"` RawBuild interface{} `yaml:"build"`
HealthCheck *HealthCheck `yaml:"healthcheck"` HealthCheck *HealthCheck `yaml:"healthcheck"`
Command []string `yaml:"command"` Command []string `yaml:"-"`
RawCommand interface{} `yaml:"command"`
} }

View File

@@ -5,6 +5,7 @@ import (
"io/ioutil" "io/ioutil"
"katenary/compose" "katenary/compose"
"katenary/helm" "katenary/helm"
"katenary/logger"
"log" "log"
"net/url" "net/url"
"os" "os"
@@ -63,12 +64,13 @@ func CreateReplicaObject(name string, s *compose.Service, linked map[string]*com
// This function will try to yied deployment and services based on a service from the compose file structure. // This function will try to yied deployment and services based on a service from the compose file structure.
func parseService(name string, s *compose.Service, linked map[string]*compose.Service, ret chan interface{}) { func parseService(name string, s *compose.Service, linked map[string]*compose.Service, ret chan interface{}) {
Magenta(ICON_PACKAGE+" Generating deployment for ", name) logger.Magenta(ICON_PACKAGE+" Generating deployment for ", name)
o := helm.NewDeployment(name) o := helm.NewDeployment(name)
container := helm.NewContainer(name, s.Image, s.Environment, s.Labels) container := helm.NewContainer(name, s.Image, s.Environment, s.Labels)
prepareContainer(container, s, name) prepareContainer(container, s, name)
prepareEnvFromFiles(name, s, container, ret)
// Set the container to the deployment // Set the container to the deployment
o.Spec.Template.Spec.Containers = []*helm.Container{container} o.Spec.Template.Spec.Containers = []*helm.Container{container}
@@ -91,6 +93,7 @@ func parseService(name string, s *compose.Service, linked map[string]*compose.Se
for lname, link := range linked { for lname, link := range linked {
container := helm.NewContainer(lname, link.Image, link.Environment, link.Labels) container := helm.NewContainer(lname, link.Image, link.Environment, link.Labels)
prepareContainer(container, link, lname) prepareContainer(container, link, lname)
prepareEnvFromFiles(lname, link, container, ret)
o.Spec.Template.Spec.Containers = append(o.Spec.Template.Spec.Containers, container) o.Spec.Template.Spec.Containers = append(o.Spec.Template.Spec.Containers, container)
o.Spec.Template.Spec.Volumes = append(o.Spec.Template.Spec.Volumes, prepareVolumes(name, lname, link, container, madePVC, ret)...) o.Spec.Template.Spec.Volumes = append(o.Spec.Template.Spec.Volumes, prepareVolumes(name, lname, link, container, madePVC, ret)...)
o.Spec.Template.Spec.InitContainers = append(o.Spec.Template.Spec.InitContainers, prepareInitContainers(lname, link, container)...) o.Spec.Template.Spec.InitContainers = append(o.Spec.Template.Spec.InitContainers, prepareInitContainers(lname, link, container)...)
@@ -128,6 +131,13 @@ func parseService(name string, s *compose.Service, linked map[string]*compose.Se
if len(s.Ports) == 0 { if len(s.Ports) == 0 {
// alert any current or **future** waiters that this service is not exposed // alert any current or **future** waiters that this service is not exposed
go func() { go func() {
defer func() {
// recover from panic
if r := recover(); r != nil {
// log the stack trace
fmt.Println(r)
}
}()
for { for {
select { select {
case <-time.Tick(1 * time.Millisecond): case <-time.Tick(1 * time.Millisecond):
@@ -158,6 +168,10 @@ func parseService(name string, s *compose.Service, linked map[string]*compose.Se
// prepareContainer assigns image, command, env, and labels to a container. // prepareContainer assigns image, command, env, and labels to a container.
func prepareContainer(container *helm.Container, service *compose.Service, servicename string) { func prepareContainer(container *helm.Container, service *compose.Service, servicename string) {
// if there is no image name, this should fail!
if service.Image == "" {
log.Fatal(ICON_PACKAGE+" No image name for service ", servicename)
}
container.Image = "{{ .Values." + servicename + ".image }}" container.Image = "{{ .Values." + servicename + ".image }}"
container.Command = service.Command container.Command = service.Command
Values[servicename] = map[string]interface{}{ Values[servicename] = map[string]interface{}{
@@ -171,7 +185,7 @@ func prepareContainer(container *helm.Container, service *compose.Service, servi
func generateServicesAndIngresses(name string, s *compose.Service) []interface{} { func generateServicesAndIngresses(name string, s *compose.Service) []interface{} {
ret := make([]interface{}, 0) // can handle helm.Service or helm.Ingress ret := make([]interface{}, 0) // can handle helm.Service or helm.Ingress
Magenta(ICON_SERVICE+" Generating service for ", name) logger.Magenta(ICON_SERVICE+" Generating service for ", name)
ks := helm.NewService(name) ks := helm.NewService(name)
for i, p := range s.Ports { for i, p := range s.Ports {
@@ -194,13 +208,13 @@ func generateServicesAndIngresses(name string, s *compose.Service) []interface{}
if err != nil { if err != nil {
log.Fatalf("The given port \"%v\" as ingress port in \"%s\" service is not an integer\n", v, name) log.Fatalf("The given port \"%v\" as ingress port in \"%s\" service is not an integer\n", v, name)
} }
Cyanf(ICON_INGRESS+" Create an ingress for port %d on %s service\n", port, name) logger.Cyanf(ICON_INGRESS+" Create an ingress for port %d on %s service\n", port, name)
ing := createIngress(name, port, s) ing := createIngress(name, port, s)
ret = append(ret, ing) ret = append(ret, ing)
} }
if len(s.Expose) > 0 { if len(s.Expose) > 0 {
Magenta(ICON_SERVICE+" Generating service for ", name+"-external") logger.Magenta(ICON_SERVICE+" Generating service for ", name+"-external")
ks := helm.NewService(name + "-external") ks := helm.NewService(name + "-external")
ks.Spec.Type = "NodePort" ks.Spec.Type = "NodePort"
for _, p := range s.Expose { for _, p := range s.Expose {
@@ -311,10 +325,10 @@ func buildCMFromPath(path string) *helm.ConfigMap {
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "An error occured reading volume path %s\n", err.Error()) fmt.Fprintf(os.Stderr, "An error occured reading volume path %s\n", err.Error())
} else { } else {
ActivateColors = true logger.ActivateColors = true
Yellowf("Warning, %s is a directory, at this time we only "+ logger.Yellowf("Warning, %s is a directory, at this time we only "+
"can create configmap for first level file list\n", f) "can create configmap for first level file list\n", f)
ActivateColors = false logger.ActivateColors = false
} }
continue continue
} }
@@ -374,8 +388,15 @@ func prepareVolumes(deployment, name string, s *compose.Service, container *helm
if v, ok := s.Labels[helm.LABEL_VOL_CM]; ok { if v, ok := s.Labels[helm.LABEL_VOL_CM]; ok {
configMapsVolumes = strings.Split(v, ",") configMapsVolumes = strings.Split(v, ",")
} }
for _, volume := range s.Volumes { for _, volume := range s.Volumes {
parts := strings.Split(volume, ":") parts := strings.Split(volume, ":")
if len(parts) == 1 {
// this is a volume declaration for Docker only, avoid it
continue
}
volname := parts[0] volname := parts[0]
volepath := parts[1] volepath := parts[1]
@@ -390,9 +411,9 @@ func prepareVolumes(deployment, name string, s *compose.Service, container *helm
if !isCM && (strings.HasPrefix(volname, ".") || strings.HasPrefix(volname, "/")) { if !isCM && (strings.HasPrefix(volname, ".") || strings.HasPrefix(volname, "/")) {
// local volume cannt be mounted // local volume cannt be mounted
ActivateColors = true logger.ActivateColors = true
Redf("You cannot, at this time, have local volume in %s deployment\n", name) logger.Redf("You cannot, at this time, have local volume in %s deployment\n", name)
ActivateColors = false logger.ActivateColors = false
continue continue
} }
if isCM { if isCM {
@@ -467,7 +488,7 @@ func prepareVolumes(deployment, name string, s *compose.Service, container *helm
"mountPath": volepath, "mountPath": volepath,
}) })
Yellow(ICON_STORE+" Generate volume values", volname, "for container named", name, "in deployment", deployment) logger.Yellow(ICON_STORE+" Generate volume values", volname, "for container named", name, "in deployment", deployment)
locker.Lock() locker.Lock()
if _, ok := VolumeValues[deployment]; !ok { if _, ok := VolumeValues[deployment]; !ok {
VolumeValues[deployment] = make(map[string]map[string]interface{}) VolumeValues[deployment] = make(map[string]map[string]interface{})
@@ -586,10 +607,14 @@ func prepareProbes(name string, s *compose.Service, container *helm.Container) {
Command: c, Command: c,
} }
} }
} else if s.HealthCheck.Test[0] == "CMD" { } else if s.HealthCheck.Test[0] == "CMD" || s.HealthCheck.Test[0] == "CMD-SHELL" {
probe.Exec = &helm.Exec{ probe.Exec = &helm.Exec{
Command: s.HealthCheck.Test[1:], Command: s.HealthCheck.Test[1:],
} }
} else {
probe.Exec = &helm.Exec{
Command: s.HealthCheck.Test,
}
} }
container.LivenessProbe = probe container.LivenessProbe = probe
} }
@@ -619,16 +644,16 @@ func prepareEnvFromFiles(name string, s *compose.Service, container *helm.Contai
} }
var store helm.InlineConfig var store helm.InlineConfig
if !isSecret { if !isSecret {
Bluef(ICON_CONF+" Generating configMap %s\n", cf) logger.Bluef(ICON_CONF+" Generating configMap %s\n", cf)
store = helm.NewConfigMap(cf) store = helm.NewConfigMap(cf)
} else { } else {
Bluef(ICON_SECRET+" Generating secret %s\n", cf) logger.Bluef(ICON_SECRET+" Generating secret %s\n", cf)
store = helm.NewSecret(cf) store = helm.NewSecret(cf)
} }
if err := store.AddEnvFile(envfile); err != nil { if err := store.AddEnvFile(envfile); err != nil {
ActivateColors = true logger.ActivateColors = true
Red(err.Error()) logger.Red(err.Error())
ActivateColors = false logger.ActivateColors = false
os.Exit(2) os.Exit(2)
} }

View File

@@ -4,6 +4,7 @@ import (
"io/ioutil" "io/ioutil"
"katenary/compose" "katenary/compose"
"katenary/helm" "katenary/helm"
"katenary/logger"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -71,11 +72,30 @@ services:
labels: labels:
katenary.io/same-pod: http3 katenary.io/same-pod: http3
# unmapped volumes
novol:
image: nginx
volumes:
- /tmp/data
labels:
katenary.io/ports: 80
# use = sign for environment variables
eqenv:
image: nginx
environment:
- SOME_ENV_VAR=some_value
- ANOTHER_ENV_VAR=another_value
volumes: volumes:
data: data:
driver: local driver: local
` `
func init() {
logger.NOLOG = true
}
func setUp(t *testing.T) (string, *compose.Parser) { func setUp(t *testing.T) (string, *compose.Parser) {
p := compose.NewParser("", DOCKER_COMPOSE_YML) p := compose.NewParser("", DOCKER_COMPOSE_YML)
p.Parse("testapp") p.Parse("testapp")
@@ -263,3 +283,54 @@ func TestIngress(t *testing.T) {
} }
} }
} }
// Check unmapped volumes
func TestUnmappedVolumes(t *testing.T) {
tmp, p := setUp(t)
defer os.RemoveAll(tmp)
for name := range p.Data.Services {
if name == "novol" {
path := filepath.Join(tmp, "templates", name+".deployment.yaml")
fp, _ := os.Open(path)
defer fp.Close()
lines, _ := ioutil.ReadAll(fp)
for _, line := range strings.Split(string(lines), "\n") {
if strings.Contains(line, "novol-data") {
t.Error("novol service should not have a volume")
}
}
}
}
}
// Check if service using equal sign for environment works
func TestEqualSignOnEnv(t *testing.T) {
tmp, p := setUp(t)
defer os.RemoveAll(tmp)
// if the name is eqenv, the service should habe environment
for name, _ := range p.Data.Services {
if name == "eqenv" {
path := filepath.Join(tmp, "templates", name+".deployment.yaml")
fp, _ := os.Open(path)
defer fp.Close()
lines, _ := ioutil.ReadAll(fp)
match := 0
for _, line := range strings.Split(string(lines), "\n") {
// we must find the line with the environment variable name
if strings.Contains(line, "SOME_ENV_VAR") {
// we must find the line with the environment variable value
match++
}
if strings.Contains(line, "ANOTHER_ENV_VAR") {
// we must find the line with the environment variable value
match++
}
}
if match != 2 {
t.Error("eqenv service should have 2 environment variables")
}
}
}
}

View File

@@ -23,7 +23,6 @@ fi
BIN_URL="$BASE/katenary-$OS-$ARCH" BIN_URL="$BASE/katenary-$OS-$ARCH"
INSTALL_TYPE="global"
if [ "$INSTALL_TYPE" = "local" ]; then if [ "$INSTALL_TYPE" = "local" ]; then
echo "Installing to local directory, installing in $HOME/.local/bin" echo "Installing to local directory, installing in $HOME/.local/bin"
BIN_PATH="$HOME/.local/bin" BIN_PATH="$HOME/.local/bin"

View File

@@ -1,8 +1,9 @@
package generator package logger
import "testing" import "testing"
func TestColor(t *testing.T) { func TestColor(t *testing.T) {
NOLOG = false
Red("Red text") Red("Red text")
Grey("Grey text") Grey("Grey text")
} }

View File

@@ -1,4 +1,4 @@
package generator package logger
import ( import (
"fmt" "fmt"
@@ -9,6 +9,7 @@ import (
type Color int type Color int
var ActivateColors = false var ActivateColors = false
var NOLOG = false
const ( const (
GREY Color = 30 + iota GREY Color = 30 + iota
@@ -23,6 +24,9 @@ const (
var waiter = sync.Mutex{} var waiter = sync.Mutex{}
func color(c Color, args ...interface{}) { func color(c Color, args ...interface{}) {
if NOLOG {
return
}
if !ActivateColors { if !ActivateColors {
fmt.Println(args...) fmt.Println(args...)
return return
@@ -35,6 +39,9 @@ func color(c Color, args ...interface{}) {
} }
func colorf(c Color, format string, args ...interface{}) { func colorf(c Color, format string, args ...interface{}) {
if NOLOG {
return
}
if !ActivateColors { if !ActivateColors {
fmt.Printf(format, args...) fmt.Printf(format, args...)
return return