diff --git a/compose/parser.go b/compose/parser.go index 4929226..f8bd0d1 100644 --- a/compose/parser.go +++ b/compose/parser.go @@ -21,22 +21,40 @@ type Parser struct { var Appname = "" -// NewParser create a Parser and parse the file given in filename. -func NewParser(filename string) *Parser { +// NewParser create a Parser and parse the file given in filename. If filename is empty, we try to parse the content[0] argument that should be a valid YAML content. +func NewParser(filename string, content ...string) *Parser { - f, err := os.Open(filename) - if err != nil { - log.Fatal(err) - } c := NewCompose() - dec := yaml.NewDecoder(f) - dec.Decode(c) + if filename != "" { + f, err := os.Open(filename) + if err != nil { + log.Fatal(err) + } + dec := yaml.NewDecoder(f) + err = dec.Decode(c) + if err != nil { + log.Fatal(err) + } + } else { + dec := yaml.NewDecoder(strings.NewReader(content[0])) + err := dec.Decode(c) + if err != nil { + log.Fatal(err) + } + } p := &Parser{Data: c} + return p +} + +func (p *Parser) Parse(appname string) { + Appname = appname + services := make(map[string][]string) // get the service list, to be sure that everything is ok + c := p.Data for name, s := range c.Services { if portlabel, ok := s.Labels[helm.LABEL_PORT]; ok { services := strings.Split(portlabel, ",") @@ -87,10 +105,4 @@ func NewParser(filename string) *Parser { " for the \"" + name + "\" service \x1b[0m") } - - return p -} - -func (p *Parser) Parse(appname string) { - Appname = appname } diff --git a/compose/parser_test.go b/compose/parser_test.go new file mode 100644 index 0000000..79876b1 --- /dev/null +++ b/compose/parser_test.go @@ -0,0 +1,89 @@ +package compose + +import "testing" + +const DOCKER_COMPOSE_YML1 = ` +version: "3" + +services: + # first service, very basic + web: + image: nginx + ports: + - "80:80" + environment: + FOO: bar + BAZ: qux + networks: + - frontend + + + database: + image: postgres + networks: + - frontend + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: mysecretpassword + POSTGRES_DB: mydb + labels: + katenary.io/ports: "5432" + +` + +func TestParser(t *testing.T) { + p := NewParser("", DOCKER_COMPOSE_YML1) + p.Parse("test") + + // check if the "web" and "database" service is parsed correctly + // by checking if the "ports" and "environment" + for name, service := range p.Data.Services { + if name == "web" { + if len(service.Ports) != 1 { + t.Errorf("Expected 1 port, got %d", len(service.Ports)) + } + if service.Ports[0] != "80:80" { + t.Errorf("Expected port 80:80, got %s", service.Ports[0]) + } + if len(service.Environment) != 2 { + t.Errorf("Expected 2 environment variables, got %d", len(service.Environment)) + } + if service.Environment["FOO"] != "bar" { + t.Errorf("Expected FOO=bar, got %s", service.Environment["FOO"]) + } + if service.Environment["BAZ"] != "qux" { + t.Errorf("Expected BAZ=qux, got %s", service.Environment["BAZ"]) + } + } + // same for the "database" service + if name == "database" { + if len(service.Ports) != 1 { + t.Errorf("Expected 1 port, got %d", len(service.Ports)) + } + if service.Ports[0] != "5432" { + t.Errorf("Expected port 5432, got %s", service.Ports[0]) + } + if len(service.Environment) != 3 { + t.Errorf("Expected 3 environment variables, got %d", len(service.Environment)) + } + if service.Environment["POSTGRES_USER"] != "postgres" { + t.Errorf("Expected POSTGRES_USER=postgres, got %s", service.Environment["POSTGRES_USER"]) + } + if service.Environment["POSTGRES_PASSWORD"] != "mysecretpassword" { + t.Errorf("Expected POSTGRES_PASSWORD=mysecretpassword, got %s", service.Environment["POSTGRES_PASSWORD"]) + } + if service.Environment["POSTGRES_DB"] != "mydb" { + t.Errorf("Expected POSTGRES_DB=mydb, got %s", service.Environment["POSTGRES_DB"]) + } + // check labels + if len(service.Labels) != 1 { + t.Errorf("Expected 1 label, got %d", len(service.Labels)) + } + // is label katenary.io/ports correct? + if service.Labels["katenary.io/ports"] != "5432" { + t.Errorf("Expected katenary.io/ports=5432, got %s", service.Labels["katenary.io/ports"]) + } + } + } + +} diff --git a/generator/main_test.go b/generator/main_test.go new file mode 100644 index 0000000..5293041 --- /dev/null +++ b/generator/main_test.go @@ -0,0 +1,159 @@ +package generator + +import ( + "io/ioutil" + "katenary/compose" + "katenary/helm" + "os" + "path/filepath" + "strings" + "testing" +) + +const DOCKER_COMPOSE_YML = `version: '3' +services: + # first service, very simple + http: + image: nginx + ports: + - "80:80" + + # second service, with environment variables + http2: + image: nginx + environment: + SOME_ENV_VAR: some_value + ANOTHER_ENV_VAR: another_value + + # third service with ingress label + web: + image: nginx + labels: + katenary.io/ingress: 80 + + # fourth service is a php service depending on database + php: + image: php:7.2-apache + depends_on: + - database + environment: + SOME_ENV_VAR: some_value + ANOTHER_ENV_VAR: another_value + DB_HOST: database + labels: + katenary.io/env-to-service: DB_HOST + + database: + image: mysql:5.7 + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: database + MYSQL_USER: user + MYSQL_PASSWORD: password + volumes: + - data:/var/lib/mysql + labels: + katenary.io/ports: 3306 + + + # try to deploy 2 services but one is in the same pod than the other + http3: + image: nginx + + http4: + image: nginx + labels: + katenary.io/same-pod: http3 + +volumes: + data: + driver: local +` + +func TestGenerate(t *testing.T) { + p := compose.NewParser("", DOCKER_COMPOSE_YML) + p.Parse("testapp") + + // create a temporary directory + tmp, err := os.MkdirTemp(os.TempDir(), "katenary-test") + t.Log("Generated ", tmp, "directory") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmp) + + Generate(p, "test-0", "testapp", "1.2.3", DOCKER_COMPOSE_YML, tmp) + + // for each service name, there should be a deploument file in temporary directory + for name, service := range p.Data.Services { + path := filepath.Join(tmp, "templates", name+".deployment.yaml") + if _, found := service.Labels[helm.LABEL_SAMEPOD]; found { + // fail if the service has a deployment + if _, err := os.Stat(path); err == nil { + t.Error("Service ", name, " should not have a deployment") + } + continue + } + + // others should have a deployment file + t.Log("Checking ", name, " deployment file") + _, err := os.Stat(path) + if err != nil { + t.Fatal(err) + } + + // if the service has a port found in helm.LABEL_PORT or ports, so the service file should exist + hasPort := false + if _, found := service.Labels[helm.LABEL_PORT]; found { + hasPort = true + } + if service.Ports != nil { + hasPort = true + } + if hasPort { + path = filepath.Join(tmp, "templates", name+".service.yaml") + t.Log("Checking ", name, " service file") + _, err := os.Stat(path) + if err != nil { + t.Fatal(err) + } + } + + // the "database" service should have a pvc file in templates (name-data.pvc.yaml) + if name == "database" { + path = filepath.Join(tmp, "templates", name+"-data.pvc.yaml") + t.Log("Checking ", name, " pvc file") + _, err := os.Stat(path) + if err != nil { + t.Fatal(err) + } + } + + if name == "php" { + // the "DB_HOST" environment variable inside the template must be set to '{{ .Release.Name }}-database' + path = filepath.Join(tmp, "templates", name+".deployment.yaml") + // read the file and find the DB_HOST variable + matched := false + fp, _ := os.Open(path) + defer fp.Close() + lines, _ := ioutil.ReadAll(fp) + next := false + for _, line := range strings.Split(string(lines), "\n") { + if strings.Contains(line, "DB_HOST") { + next = true + continue + } + if next { + matched = true + if !strings.Contains(line, helm.RELEASE_NAME+"-database") { + t.Error("DB_HOST variable should be set to " + helm.RELEASE_NAME + "-database") + } + break + } + } + if !matched { + t.Error("DB_HOST variable not found in ", path) + } + } + } +} diff --git a/generator/writer.go b/generator/writer.go index ea0e69a..01f5e14 100644 --- a/generator/writer.go +++ b/generator/writer.go @@ -21,6 +21,13 @@ func Generate(p *compose.Parser, katernayVersion, appName, appVersion, composeFi helm.Appname = appName helm.Version = katernayVersion templatesDir := filepath.Join(dirName, "templates") + + // try to create the directory + err := os.MkdirAll(templatesDir, 0755) + if err != nil { + panic(err) + } + files := make(map[string]chan interface{}) // list avoided services @@ -123,7 +130,7 @@ func Generate(p *compose.Parser, katernayVersion, appName, appVersion, composeFi fp.WriteString(`# Create on ` + time.Now().Format(time.RFC3339) + "\n") fp.WriteString(`# Katenary command line: ` + strings.Join(os.Args, " ") + "\n") enc = yaml.NewEncoder(fp) - enc.SetIndent(2) + enc.SetIndent(writers.IndentSize) enc.Encode(map[string]interface{}{ "apiVersion": "v2", "name": appName, diff --git a/update/update_test.go b/update/update_test.go index 99bd9fc..69b8180 100644 --- a/update/update_test.go +++ b/update/update_test.go @@ -14,6 +14,7 @@ func TestDownloadLatestRelease(t *testing.T) { // change "exe" to /tmp/test-katenary exe = "/tmp/test-katenary" + defer os.Remove(exe) // Now call the CheckLatestVersion function version, assets, err := CheckLatestVersion()