* Update command added
* Ensure that the upgraded version is really greater
* Update command should not update prerelease
* Minor presentation changes
* Fix command generation in containers from docker-compose file
- Refactored service creation
* Place the full command to the "cmd" package
* Update cobra to v1.4.0
* Updated build and release creation
* Created an install script
* Add more doc
* Add some tests...
* Add a test directive in Makefile
This commit is contained in:
2022-03-31 14:12:20 +02:00
committed by GitHub
parent d0576d4b81
commit 7b774e84d8
18 changed files with 1037 additions and 1035 deletions

View File

@@ -68,20 +68,7 @@ func parseService(name string, s *compose.Service, linked map[string]*compose.Se
o := helm.NewDeployment(name)
container := helm.NewContainer(name, s.Image, s.Environment, s.Labels)
// prepare cm and secrets
prepareEnvFromFiles(name, s, container, ret)
// check the image, and make it "variable" in values.yaml
container.Image = "{{ .Values." + name + ".image }}"
Values[name] = map[string]interface{}{
"image": s.Image,
}
// manage the healthcheck property, if any
prepareProbes(name, s, container)
// manage ports
generateContainerPorts(s, name, container)
prepareContainer(container, s, name)
// Set the container to the deployment
o.Spec.Template.Spec.Containers = []*helm.Container{container}
@@ -103,12 +90,7 @@ func parseService(name string, s *compose.Service, linked map[string]*compose.Se
// Now, the linked services
for lname, link := range linked {
container := helm.NewContainer(lname, link.Image, link.Environment, link.Labels)
container.Image = "{{ .Values." + lname + ".image }}"
Values[lname] = map[string]interface{}{
"image": link.Image,
}
prepareProbes(lname, link, container)
generateContainerPorts(link, lname, container)
prepareContainer(container, link, lname)
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.InitContainers = append(o.Spec.Template.Spec.InitContainers, prepareInitContainers(lname, link, container)...)
@@ -174,6 +156,17 @@ func parseService(name string, s *compose.Service, linked map[string]*compose.Se
ret <- nil
}
// prepareContainer assigns image, command, env, and labels to a container.
func prepareContainer(container *helm.Container, service *compose.Service, servicename string) {
container.Image = "{{ .Values." + servicename + ".image }}"
container.Command = service.Command
Values[servicename] = map[string]interface{}{
"image": service.Image,
}
prepareProbes(servicename, service, container)
generateContainerPorts(service, servicename, container)
}
// Create a service (k8s).
func generateServicesAndIngresses(name string, s *compose.Service) []interface{} {

265
generator/main_test.go Normal file
View File

@@ -0,0 +1,265 @@
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
ports:
- "80:80"
labels:
katenary.io/ingress: 80
web2:
image: nginx
command: ["/bin/sh", "-c", "while true; do echo hello; sleep 1; done"]
# 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 setUp(t *testing.T) (string, *compose.Parser) {
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)
}
Generate(p, "test-0", "testapp", "1.2.3", DOCKER_COMPOSE_YML, tmp)
return tmp, p
}
// Check if the web2 service has got a command.
func TestCommand(t *testing.T) {
tmp, p := setUp(t)
defer os.RemoveAll(tmp)
for name := range p.Data.Services {
if name == "web2" {
// Ensure that the command is correctly set
// The command should be a string array
path := filepath.Join(tmp, "templates", name+".deployment.yaml")
path = filepath.Join(tmp, "templates", name+".deployment.yaml")
fp, _ := os.Open(path)
defer fp.Close()
lines, _ := ioutil.ReadAll(fp)
next := false
commands := make([]string, 0)
for _, line := range strings.Split(string(lines), "\n") {
if strings.Contains(line, "command") {
next = true
continue
}
if next {
commands = append(commands, line)
}
}
ok := 0
for _, command := range commands {
if strings.Contains(command, "- /bin/sh") {
ok++
}
if strings.Contains(command, "- -c") {
ok++
}
if strings.Contains(command, "while true; do") {
ok++
}
}
if ok != 3 {
t.Error("Command is not correctly set")
}
}
}
}
// Check if environment is correctly set.
func TestEnvs(t *testing.T) {
tmp, p := setUp(t)
defer os.RemoveAll(tmp)
for name := range p.Data.Services {
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)
}
}
}
}
// Check if the same pod is not deployed twice.
func TestSamePod(t *testing.T) {
tmp, p := setUp(t)
defer os.RemoveAll(tmp)
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)
}
}
}
// Check if the ports are correctly set.
func TestPorts(t *testing.T) {
tmp, p := setUp(t)
defer os.RemoveAll(tmp)
for name, service := range p.Data.Services {
path := ""
// 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)
}
}
}
}
// Check if the volumes are correctly set.
func TestPVC(t *testing.T) {
tmp, p := setUp(t)
defer os.RemoveAll(tmp)
for name := range p.Data.Services {
path := filepath.Join(tmp, "templates", name+"-data.pvc.yaml")
// 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)
}
}
}
}
//Check if web service has got a ingress.
func TestIngress(t *testing.T) {
tmp, p := setUp(t)
defer os.RemoveAll(tmp)
for name := range p.Data.Services {
path := filepath.Join(tmp, "templates", name+".ingress.yaml")
// the "web" service should have a ingress file in templates (name.ingress.yaml)
if name == "web" {
path = filepath.Join(tmp, "templates", name+".ingress.yaml")
t.Log("Checking ", name, " ingress file")
_, err := os.Stat(path)
if err != nil {
t.Fatal(err)
}
}
}
}

View File

@@ -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,