Files
katenary/generator/main.go

336 lines
8.1 KiB
Go
Raw Normal View History

2021-11-30 12:04:28 +01:00
package generator
import (
"fmt"
2021-12-01 08:31:51 +01:00
"katenary/compose"
"katenary/helm"
"log"
2021-11-30 15:35:32 +01:00
"os"
2021-11-30 12:04:28 +01:00
"strconv"
"strings"
"sync"
"errors"
)
var servicesMap = make(map[string]int)
var serviceWaiters = make(map[string][]chan int)
var locker = &sync.Mutex{}
2021-11-30 15:45:36 +01:00
// Values is kept in memory to create a values.yaml file.
2021-11-30 12:04:28 +01:00
var Values = make(map[string]map[string]interface{})
2021-11-30 17:29:42 +01:00
var VolumeValues = make(map[string]map[string]map[string]interface{})
2021-11-30 12:04:28 +01:00
2021-11-30 15:45:36 +01:00
var dependScript = `
2021-11-30 12:04:28 +01:00
OK=0
echo "Checking __service__ port"
while [ $OK != 1 ]; do
echo -n "."
nc -z {{ .Release.Name }}-__service__ __port__ && OK=1
sleep 1
done
echo
echo "Done"
`
2021-11-30 15:45:36 +01:00
// Create a Deployment for a given compose.Service. It returns a list of objects: a Deployment and a possible Service (kubernetes represnetation as maps).
2021-11-30 12:04:28 +01:00
func CreateReplicaObject(name string, s compose.Service) (ret []interface{}) {
2021-11-30 15:35:32 +01:00
Magenta("Generating deployment for ", name)
2021-12-01 15:17:34 +01:00
o := helm.NewDeployment(name)
2021-11-30 12:04:28 +01:00
ret = append(ret, o)
container := helm.NewContainer(name, s.Image, s.Environment, s.Labels)
secretsFiles := make([]string, 0)
if v, ok := s.Labels[helm.K+"/as-secret"]; ok {
secretsFiles = strings.Split(v, ",")
}
2021-12-01 11:53:10 +01:00
for _, envfile := range s.EnvFiles {
2021-12-01 12:02:44 +01:00
f := strings.ReplaceAll(envfile, "_", "-")
f = strings.ReplaceAll(f, ".env", "")
f = strings.ReplaceAll(f, ".", "-")
2021-12-01 12:02:44 +01:00
cf := f + "-" + name
isSecret := false
for _, s := range secretsFiles {
if s == envfile {
isSecret = true
}
}
var store helm.InlineConfig
if !isSecret {
Bluef("Generating configMap %s\n", cf)
store = helm.NewConfigMap(cf)
} else {
Bluef("Generating secret %s\n", cf)
store = helm.NewSecret(cf)
}
if err := store.AddEnvFile(envfile); err != nil {
2021-12-01 11:53:10 +01:00
Red(err.Error())
os.Exit(2)
}
container.EnvFrom = append(container.EnvFrom, map[string]map[string]string{
"configMapRef": {
"name": store.Metadata().Name,
2021-12-01 11:53:10 +01:00
},
})
ret = append(ret, store)
2021-12-01 14:06:06 +01:00
if isSecret {
Greenf("Done secret %s\n", cf)
} else {
Greenf("Done configMap %s\n", cf)
}
}
2021-12-01 11:53:10 +01:00
2021-11-30 12:04:28 +01:00
container.Image = "{{ .Values." + name + ".image }}"
Values[name] = map[string]interface{}{
"image": s.Image,
}
2021-11-30 15:35:32 +01:00
exists := make(map[int]string)
2021-11-30 12:04:28 +01:00
for _, port := range s.Ports {
_p := strings.Split(port, ":")
port = _p[0]
if len(_p) > 1 {
port = _p[1]
}
2021-11-30 12:04:28 +01:00
portNumber, _ := strconv.Atoi(port)
2021-11-30 15:35:32 +01:00
portName := name
for _, n := range exists {
if name == n {
portName = fmt.Sprintf("%s-%d", name, portNumber)
}
}
2021-11-30 12:04:28 +01:00
container.Ports = append(container.Ports, &helm.ContainerPort{
2021-11-30 15:35:32 +01:00
Name: portName,
2021-11-30 12:04:28 +01:00
ContainerPort: portNumber,
})
2021-11-30 15:35:32 +01:00
exists[portNumber] = name
2021-11-30 12:04:28 +01:00
}
for _, port := range s.Expose {
2021-11-30 15:35:32 +01:00
if _, exist := exists[port]; exist {
continue
}
2021-11-30 12:04:28 +01:00
container.Ports = append(container.Ports, &helm.ContainerPort{
Name: name,
ContainerPort: port,
})
}
2021-11-30 17:29:42 +01:00
volumes := make([]map[string]interface{}, 0)
mountPoints := make([]interface{}, 0)
for _, volume := range s.Volumes {
parts := strings.Split(volume, ":")
volname := parts[0]
volepath := parts[1]
if strings.HasPrefix(volname, ".") || strings.HasPrefix(volname, "/") {
Redf("You cannot, at this time, have local volume in %s service", name)
os.Exit(1)
}
pvc := helm.NewPVC(name, volname)
ret = append(ret, pvc)
volumes = append(volumes, map[string]interface{}{
"name": volname,
"persistentVolumeClaim": map[string]string{
"claimName": "{{ .Release.Name }}-" + volname,
},
})
mountPoints = append(mountPoints, map[string]interface{}{
"name": volname,
"mountPath": volepath,
})
Yellow("Generate volume values for ", volname)
locker.Lock()
if _, ok := VolumeValues[name]; !ok {
VolumeValues[name] = make(map[string]map[string]interface{})
}
VolumeValues[name][volname] = map[string]interface{}{
"enabled": false,
"capacity": "1Gi",
}
locker.Unlock()
}
container.VolumeMounts = mountPoints
o.Spec.Template.Spec.Volumes = volumes
2021-11-30 12:04:28 +01:00
o.Spec.Template.Spec.Containers = []*helm.Container{container}
o.Spec.Selector = map[string]interface{}{
"matchLabels": buildSelector(name, s),
}
o.Spec.Template.Metadata.Labels = buildSelector(name, s)
wait := &sync.WaitGroup{}
initContainers := make([]*helm.Container, 0)
for _, dp := range s.DependsOn {
//if len(s.Ports) == 0 && len(s.Expose) == 0 {
// Redf("No port exposed for %s that is in dependency", name)
// os.Exit(1)
//}
2021-11-30 15:35:32 +01:00
c := helm.NewContainer("check-"+dp, "busybox", nil, s.Labels)
2021-11-30 15:45:36 +01:00
command := strings.ReplaceAll(strings.TrimSpace(dependScript), "__service__", dp)
2021-11-30 12:04:28 +01:00
wait.Add(1)
go func(dp string) {
defer wait.Done()
p := -1
if defaultPort, err := getPort(dp); err != nil {
p = <-waitPort(dp)
} else {
p = defaultPort
}
command = strings.ReplaceAll(command, "__port__", strconv.Itoa(p))
c.Command = []string{
"sh",
"-c",
command,
}
initContainers = append(initContainers, c)
}(dp)
}
wait.Wait()
o.Spec.Template.Spec.InitContainers = initContainers
if len(s.Ports) > 0 || len(s.Expose) > 0 {
ks := createService(name, s)
2021-12-01 08:31:51 +01:00
ret = append(ret, ks...)
2021-11-30 12:04:28 +01:00
}
2021-11-30 17:29:42 +01:00
if len(VolumeValues[name]) > 0 {
Values[name]["persistence"] = VolumeValues[name]
}
2021-11-30 15:35:32 +01:00
Green("Done deployment ", name)
2021-11-30 12:04:28 +01:00
return
}
2021-11-30 15:45:36 +01:00
// Create a service (k8s).
2021-12-01 08:31:51 +01:00
func createService(name string, s compose.Service) []interface{} {
2021-11-30 12:04:28 +01:00
ret := make([]interface{}, 0)
2021-11-30 15:35:32 +01:00
Magenta("Generating service for ", name)
2021-12-01 15:17:34 +01:00
ks := helm.NewService(name)
2021-11-30 12:04:28 +01:00
defaultPort := 0
2021-11-30 15:35:32 +01:00
2021-11-30 12:04:28 +01:00
for i, p := range s.Ports {
port := strings.Split(p, ":")
src, _ := strconv.Atoi(port[0])
target := src
if len(port) > 1 {
target, _ = strconv.Atoi(port[1])
log.Println(target)
2021-11-30 12:04:28 +01:00
}
ks.Spec.Ports = append(ks.Spec.Ports, helm.NewServicePort(target, target))
2021-11-30 12:04:28 +01:00
if i == 0 {
defaultPort = target
detected(name, target)
}
}
ks.Spec.Selector = buildSelector(name, s)
2021-12-01 08:31:51 +01:00
ret = append(ret, ks)
2021-11-30 12:04:28 +01:00
if v, ok := s.Labels[helm.K+"/expose-ingress"]; ok && v == "true" {
Cyanf("Create an ingress for %d port on %s service\n", defaultPort, name)
2021-12-01 08:31:51 +01:00
ing := createIngress(name, defaultPort, s)
ret = append(ret, ing)
Green("Done ingress ", name)
2021-11-30 12:04:28 +01:00
}
2021-11-30 15:35:32 +01:00
Green("Done service ", name)
if len(s.Expose) > 0 {
Magenta("Generating service for ", name+"-external")
ks := helm.NewService(name + "-external")
ks.Spec.Type = "NodePort"
for _, p := range s.Expose {
ks.Spec.Ports = append(ks.Spec.Ports, helm.NewServicePort(p, p))
}
ks.Spec.Selector = buildSelector(name, s)
ret = append(ret, ks)
}
2021-12-01 08:31:51 +01:00
return ret
2021-11-30 12:04:28 +01:00
}
2021-11-30 15:45:36 +01:00
// Create an ingress.
2021-12-01 08:31:51 +01:00
func createIngress(name string, port int, s compose.Service) *helm.Ingress {
2021-11-30 12:04:28 +01:00
ingress := helm.NewIngress(name)
Values[name]["ingress"] = map[string]interface{}{
2021-11-30 15:35:32 +01:00
"class": "nginx",
2021-11-30 12:04:28 +01:00
"host": "chart.example.tld",
"enabled": false,
}
ingress.Spec.Rules = []helm.IngressRule{
{
Host: fmt.Sprintf("{{ .Values.%s.ingress.host }}", name),
Http: helm.IngressHttp{
Paths: []helm.IngressPath{{
Path: "/",
PathType: "Prefix",
Backend: helm.IngressBackend{
Service: helm.IngressService{
Name: "{{ .Release.Name }}-" + name,
Port: map[string]interface{}{
"number": port,
},
},
},
}},
},
},
}
2021-11-30 15:35:32 +01:00
ingress.SetIngressClass(name)
2021-11-30 12:04:28 +01:00
2021-12-01 08:31:51 +01:00
return ingress
2021-11-30 12:04:28 +01:00
}
2021-11-30 15:45:36 +01:00
// 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.
2021-11-30 12:04:28 +01:00
func detected(name string, port int) {
locker.Lock()
servicesMap[name] = port
go func() {
cx := serviceWaiters[name]
for _, c := range cx {
if v, ok := servicesMap[name]; ok {
c <- v
}
}
}()
locker.Unlock()
}
func getPort(name string) (int, error) {
if v, ok := servicesMap[name]; ok {
return v, nil
}
return -1, errors.New("Not found")
}
2021-11-30 15:45:36 +01:00
// Waits for a service to be discovered. Sometimes, a deployment depends on another one. See the detected() function.
2021-11-30 12:04:28 +01:00
func waitPort(name string) chan int {
locker.Lock()
c := make(chan int, 0)
serviceWaiters[name] = append(serviceWaiters[name], c)
go func() {
if v, ok := servicesMap[name]; ok {
c <- v
}
}()
locker.Unlock()
return c
}
func buildSelector(name string, s compose.Service) map[string]string {
return map[string]string{
"katenary.io/component": name,
"katenary.io/release": "{{ .Release.Name }}",
}
}