2021-11-30 12:04:28 +01:00
package generator
import (
"fmt"
2021-12-01 08:31:51 +01:00
"katenary/compose"
"katenary/helm"
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 )
2021-12-01 13:55:22 +01:00
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" , "" )
2021-12-01 13:55:22 +01:00
f = strings . ReplaceAll ( f , "." , "-" )
2021-12-01 12:02:44 +01:00
cf := f + "-" + name
2021-12-01 13:55:22 +01:00
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" : {
2021-12-01 13:55:22 +01:00
"name" : store . Metadata ( ) . Name ,
2021-12-01 11:53:10 +01:00
} ,
} )
2021-12-01 13:55:22 +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 {
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 {
2021-11-30 15:35:32 +01:00
Redf ( "No port exposed for %s that is in dependency" , name )
os . Exit ( 1 )
2021-11-30 12:04:28 +01:00
}
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
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
names := make ( map [ int ] int )
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 ] )
}
ks . Spec . Ports = append ( ks . Spec . Ports , helm . NewServicePort ( src , target ) )
2021-11-30 15:35:32 +01:00
names [ target ] = 1
2021-11-30 12:04:28 +01:00
if i == 0 {
defaultPort = target
detected ( name , target )
}
}
for i , p := range s . Expose {
2021-11-30 15:35:32 +01:00
if _ , ok := names [ p ] ; ok {
continue
}
2021-11-30 12:04:28 +01:00
ks . Spec . Ports = append ( ks . Spec . Ports , helm . NewServicePort ( p , p ) )
if i == 0 {
defaultPort = p
detected ( name , p )
}
}
ks . Spec . Selector = buildSelector ( name , s )
2021-12-01 08:31:51 +01:00
ret := make ( [ ] interface { } , 0 )
ret = append ( ret , ks )
2021-11-30 12:04:28 +01:00
if v , ok := s . Labels [ helm . K + "/expose-ingress" ] ; ok && v == "true" {
2021-12-01 08:31:51 +01:00
ing := createIngress ( name , defaultPort , s )
ret = append ( ret , ing )
2021-11-30 12:04:28 +01:00
}
2021-11-30 15:35:32 +01:00
Green ( "Done service " , name )
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 }}" ,
}
}