WIP - there still are problem with several things
- ports label are not well managed - no heathcheck, have to be rewritten - many problem with generated compose file from memory
This commit is contained in:
@@ -97,7 +97,7 @@ func Convert(composeFile, appVersion, appName, chartDir string, force bool) {
|
||||
fmt.Println("No compose file given")
|
||||
return
|
||||
}
|
||||
_, err := os.Stat(ComposeFile)
|
||||
_, err := os.Stat(composeFile)
|
||||
if err != nil {
|
||||
fmt.Println("No compose file found")
|
||||
os.Exit(1)
|
||||
|
@@ -3,11 +3,10 @@ package compose
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -16,7 +15,8 @@ const (
|
||||
|
||||
// Parser is a docker-compose parser.
|
||||
type Parser struct {
|
||||
Data *types.Project
|
||||
Data *types.Project
|
||||
temporary *string
|
||||
}
|
||||
|
||||
var Appname = ""
|
||||
@@ -24,30 +24,44 @@ var Appname = ""
|
||||
// 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 {
|
||||
|
||||
c := NewCompose()
|
||||
if filename != "" {
|
||||
f, err := os.Open(filename)
|
||||
p := &Parser{}
|
||||
|
||||
if len(content) > 0 {
|
||||
//write it in a temporary file
|
||||
tmp, err := os.MkdirTemp(os.TempDir(), "katenary-")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
dec := yaml.NewDecoder(f)
|
||||
err = dec.Decode(c)
|
||||
tmpfile, err := os.Create(filepath.Join(tmp, "tmp.yml"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
dec := yaml.NewDecoder(strings.NewReader(content[0]))
|
||||
err := dec.Decode(c)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
tmpfile.WriteString(content[0])
|
||||
tmpfile.Close()
|
||||
filename = tmpfile.Name()
|
||||
p.temporary = &tmp
|
||||
cli.DefaultFileNames = append([]string{filename}, cli.DefaultFileNames...)
|
||||
}
|
||||
// if filename is not in cli Default files, add it
|
||||
if len(filename) > 0 {
|
||||
found := false
|
||||
for _, f := range cli.DefaultFileNames {
|
||||
if f == filename {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// add the file at first position
|
||||
if !found {
|
||||
cli.DefaultFileNames = append([]string{filename}, cli.DefaultFileNames...)
|
||||
}
|
||||
}
|
||||
|
||||
p := &Parser{}
|
||||
log.Println(cli.DefaultFileNames)
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// Parse using compose-go parser, adapt a bit the Project and set Appname.
|
||||
func (p *Parser) Parse(appname string) {
|
||||
|
||||
// Reminder:
|
||||
@@ -66,11 +80,12 @@ func (p *Parser) Parse(appname string) {
|
||||
|
||||
proj, err := cli.ProjectFromOptions(options)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Fatal("Failed to create project", err)
|
||||
}
|
||||
|
||||
Appname = proj.Name
|
||||
|
||||
p.Data = proj
|
||||
|
||||
if p.temporary != nil {
|
||||
defer os.RemoveAll(*p.temporary)
|
||||
}
|
||||
}
|
||||
|
@@ -1,44 +0,0 @@
|
||||
package compose
|
||||
|
||||
// Compose is a complete docker-compse representation.
|
||||
type Compose struct {
|
||||
Version string `yaml:"version"`
|
||||
Services map[string]*Service `yaml:"services"`
|
||||
Volumes map[string]interface{} `yaml:"volumes"`
|
||||
}
|
||||
|
||||
// NewCompose resturs a Compose object.
|
||||
func NewCompose() *Compose {
|
||||
c := &Compose{}
|
||||
c.Services = make(map[string]*Service)
|
||||
c.Volumes = make(map[string]interface{})
|
||||
return c
|
||||
}
|
||||
|
||||
// HealthCheck manage generic type to handle TCP, HTTP and TCP health check.
|
||||
type HealthCheck struct {
|
||||
Test []string `yaml:"-"`
|
||||
RawTest interface{} `yaml:"test"`
|
||||
Interval string `yaml:"interval"`
|
||||
Timeout string `yaml:"timeout"`
|
||||
Retries int `yaml:"retries"`
|
||||
StartPeriod string `yaml:"start_period"`
|
||||
}
|
||||
|
||||
// Service represent a "service" in a docker-compose file.
|
||||
type Service struct {
|
||||
Image string `yaml:"image"`
|
||||
Ports []string `yaml:"ports"`
|
||||
Environment map[string]string `yaml:"-"`
|
||||
RawEnvironment interface{} `yaml:"environment"`
|
||||
Labels map[string]string `yaml:"labels"`
|
||||
DependsOn []string `yaml:"depends_on"`
|
||||
Volumes []string `yaml:"volumes"`
|
||||
Expose []int `yaml:"expose"`
|
||||
EnvFiles []string `yaml:"-"`
|
||||
RawEnvFiles interface{} `yaml:"env_file"`
|
||||
RawBuild interface{} `yaml:"build"`
|
||||
HealthCheck *HealthCheck `yaml:"healthcheck"`
|
||||
Command []string `yaml:"-"`
|
||||
RawCommand interface{} `yaml:"command"`
|
||||
}
|
@@ -11,9 +11,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
)
|
||||
@@ -123,33 +120,6 @@ func parseService(name string, s types.ServiceConfig, linked map[string]types.Se
|
||||
}
|
||||
}
|
||||
|
||||
// Special case, it there is no "ports", so there is no associated services...
|
||||
// But... some other deployment can wait for it, so we alert that this deployment hasn't got any
|
||||
// associated service.
|
||||
if len(s.Ports) == 0 {
|
||||
// alert any current or **future** waiters that this service is not exposed
|
||||
go func() {
|
||||
defer func() {
|
||||
// recover from panic
|
||||
if r := recover(); r != nil {
|
||||
// log the stack trace
|
||||
fmt.Println(r)
|
||||
}
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-time.Tick(1 * time.Millisecond):
|
||||
locker.Lock()
|
||||
for _, c := range serviceWaiters[name] {
|
||||
c <- -1
|
||||
close(c)
|
||||
}
|
||||
locker.Unlock()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// add the volumes in Values
|
||||
if len(VolumeValues[name]) > 0 {
|
||||
locker.Lock()
|
||||
@@ -166,6 +136,8 @@ func parseService(name string, s types.ServiceConfig, linked map[string]types.Se
|
||||
|
||||
// prepareContainer assigns image, command, env, and labels to a container.
|
||||
func prepareContainer(container *helm.Container, service types.ServiceConfig, servicename string) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
// if there is no image name, this should fail!
|
||||
if service.Image == "" {
|
||||
log.Fatal(ICON_PACKAGE+" No image name for service ", servicename)
|
||||
@@ -186,11 +158,8 @@ func generateServicesAndIngresses(name string, s types.ServiceConfig) []interfac
|
||||
logger.Magenta(ICON_SERVICE+" Generating service for ", name)
|
||||
ks := helm.NewService(name)
|
||||
|
||||
for i, p := range s.Ports {
|
||||
for _, p := range s.Ports {
|
||||
ks.Spec.Ports = append(ks.Spec.Ports, helm.NewServicePort(int(p.Target), int(p.Target)))
|
||||
if i == 0 {
|
||||
detected(name, int(p.Target))
|
||||
}
|
||||
}
|
||||
ks.Spec.Selector = buildSelector(name, s)
|
||||
|
||||
@@ -253,49 +222,6 @@ func createIngress(name string, port int, s types.ServiceConfig) *helm.Ingress {
|
||||
return ingress
|
||||
}
|
||||
|
||||
// This function is called when a possible service is detected, it append the port in a map to make others
|
||||
// to be able to get the service name. It also try to send the data to any "waiter" for this service.
|
||||
func detected(name string, port int) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
if _, ok := servicesMap[name]; ok {
|
||||
return
|
||||
}
|
||||
servicesMap[name] = port
|
||||
go func() {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
if cx, ok := serviceWaiters[name]; ok {
|
||||
for _, c := range cx {
|
||||
c <- port
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func getPort(name string) (int, error) {
|
||||
if v, ok := servicesMap[name]; ok {
|
||||
return v, nil
|
||||
}
|
||||
return -1, errors.New("Not found")
|
||||
}
|
||||
|
||||
// Waits for a service to be discovered. Sometimes, a deployment depends on another one. See the detected() function.
|
||||
func waitPort(name string) chan int {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
c := make(chan int, 0)
|
||||
serviceWaiters[name] = append(serviceWaiters[name], c)
|
||||
go func() {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
if v, ok := servicesMap[name]; ok {
|
||||
c <- v
|
||||
}
|
||||
}()
|
||||
return c
|
||||
}
|
||||
|
||||
// Build the selector for the service.
|
||||
func buildSelector(name string, s types.ServiceConfig) map[string]string {
|
||||
return map[string]string{
|
||||
@@ -355,6 +281,20 @@ func generateContainerPorts(s types.ServiceConfig, name string, container *helm.
|
||||
exists[int(port.Target)] = name
|
||||
}
|
||||
|
||||
// Get ports from label
|
||||
if v, ok := s.Labels[helm.LABEL_PORT]; ok {
|
||||
// split port by ","
|
||||
ports := strings.Split(v, ",")
|
||||
for _, port := range ports {
|
||||
port, _ := strconv.Atoi(port)
|
||||
container.Ports = append(container.Ports, &helm.ContainerPort{
|
||||
Name: name,
|
||||
ContainerPort: port,
|
||||
})
|
||||
exists[port] = name
|
||||
}
|
||||
}
|
||||
|
||||
// manage the "expose" section to be a NodePort in Kubernetes
|
||||
for _, expose := range s.Expose {
|
||||
|
||||
@@ -506,9 +446,11 @@ func prepareInitContainers(name string, s types.ServiceConfig, container *helm.C
|
||||
command := strings.ReplaceAll(strings.TrimSpace(dependScript), "__service__", dp)
|
||||
|
||||
foundPort := -1
|
||||
if defaultPort, err := getPort(dp); err != nil {
|
||||
// BUG: Sometimes the chan remains opened
|
||||
foundPort = <-waitPort(dp)
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
if defaultPort, ok := servicesMap[dp]; !ok {
|
||||
logger.Redf("Error while getting port for service %s\n", dp)
|
||||
os.Exit(1)
|
||||
} else {
|
||||
foundPort = defaultPort
|
||||
}
|
||||
|
@@ -5,10 +5,13 @@ import (
|
||||
"katenary/compose"
|
||||
"katenary/helm"
|
||||
"katenary/logger"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
)
|
||||
|
||||
const DOCKER_COMPOSE_YML = `version: '3'
|
||||
@@ -92,17 +95,19 @@ volumes:
|
||||
driver: local
|
||||
`
|
||||
|
||||
var defaultCliFiles = cli.DefaultFileNames
|
||||
|
||||
func init() {
|
||||
logger.NOLOG = true
|
||||
}
|
||||
|
||||
func setUp(t *testing.T) (string, *compose.Parser) {
|
||||
cli.DefaultFileNames = defaultCliFiles
|
||||
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)
|
||||
}
|
||||
@@ -115,7 +120,8 @@ func setUp(t *testing.T) (string, *compose.Parser) {
|
||||
// Check if the web2 service has got a command.
|
||||
func TestCommand(t *testing.T) {
|
||||
tmp, p := setUp(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
//defer os.RemoveAll(tmp)
|
||||
log.Println(tmp)
|
||||
|
||||
for _, service := range p.Data.Services {
|
||||
name := service.Name
|
||||
@@ -138,6 +144,7 @@ func TestCommand(t *testing.T) {
|
||||
commands = append(commands, line)
|
||||
}
|
||||
}
|
||||
//log.Printf("%#v\n", commands)
|
||||
ok := 0
|
||||
for _, command := range commands {
|
||||
if strings.Contains(command, "- /bin/sh") {
|
||||
@@ -242,7 +249,7 @@ func TestPorts(t *testing.T) {
|
||||
t.Log("Checking ", name, " service file")
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import (
|
||||
"katenary/compose"
|
||||
"katenary/generator/writers"
|
||||
"katenary/helm"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -26,33 +27,38 @@ func Generate(p *compose.Parser, katernayVersion, appName, appVersion, composeFi
|
||||
// try to create the directory
|
||||
err := os.MkdirAll(templatesDir, 0755)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
files := make(map[string]chan interface{})
|
||||
|
||||
// list avoided services
|
||||
// Manage services, avoid linked pods and store all services port in servicesMap
|
||||
avoids := make(map[string]bool)
|
||||
linked := make(map[string]types.ServiceConfig, 0)
|
||||
for _, service := range p.Data.Services {
|
||||
n := service.Name
|
||||
|
||||
// find port and store it in servicesMap
|
||||
for _, port := range service.Ports {
|
||||
target := int(port.Target)
|
||||
if target != 0 {
|
||||
servicesMap[n] = target
|
||||
}
|
||||
}
|
||||
|
||||
// avoid linked pods
|
||||
if _, ok := service.Labels[helm.LABEL_SAMEPOD]; ok {
|
||||
avoids[n] = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range p.Data.Services {
|
||||
name := s.Name
|
||||
|
||||
// Manage emptyDir volumes
|
||||
if empty, ok := s.Labels[helm.LABEL_EMPTYDIRS]; ok {
|
||||
// manage emptyDir volumes
|
||||
if empty, ok := service.Labels[helm.LABEL_EMPTYDIRS]; ok {
|
||||
//split empty list by coma
|
||||
emptyDirs := strings.Split(empty, ",")
|
||||
//append them in EmptyDirs
|
||||
EmptyDirs = append(EmptyDirs, emptyDirs...)
|
||||
}
|
||||
|
||||
// fetch corresponding service in "links"
|
||||
linked := make(map[string]types.ServiceConfig, 0)
|
||||
// find service linked to this one
|
||||
for _, service := range p.Data.Services {
|
||||
n := service.Name
|
||||
@@ -62,6 +68,11 @@ func Generate(p *compose.Parser, katernayVersion, appName, appVersion, composeFi
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for all services in linked map, and not in avoids map, generate the service
|
||||
for _, s := range p.Data.Services {
|
||||
name := s.Name
|
||||
|
||||
if _, found := avoids[name]; found {
|
||||
continue
|
||||
|
Reference in New Issue
Block a user