2021-11-30 12:04:28 +01:00
package compose
import (
2021-12-03 11:49:32 +01:00
"fmt"
"katenary/helm"
2021-11-30 12:04:28 +01:00
"log"
"os"
2021-12-03 11:49:32 +01:00
"strings"
2021-11-30 12:04:28 +01:00
2022-04-01 10:12:14 +02:00
"github.com/google/shlex"
2021-11-30 12:04:28 +01:00
"gopkg.in/yaml.v3"
)
2021-12-05 09:05:48 +01:00
const (
ICON_EXCLAMATION = "❕"
)
2021-11-30 15:45:36 +01:00
// Parser is a docker-compose parser.
2021-11-30 12:04:28 +01:00
type Parser struct {
Data * Compose
}
var Appname = ""
2022-03-31 14:12:20 +02:00
// 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 {
2021-11-30 12:04:28 +01:00
c := NewCompose ( )
2022-03-31 14:12:20 +02:00
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 )
}
}
2021-11-30 12:04:28 +01:00
p := & Parser { Data : c }
2022-03-31 14:12:20 +02:00
return p
}
func ( p * Parser ) Parse ( appname string ) {
Appname = appname
2021-12-03 11:49:32 +01:00
services := make ( map [ string ] [ ] string )
// get the service list, to be sure that everything is ok
2022-04-01 10:12:14 +02:00
// fix ugly types
2022-04-01 09:22:00 +02:00
for _ , s := range p . Data . Services {
parseEnv ( s )
2022-04-01 10:12:14 +02:00
parseCommand ( s )
2022-04-01 10:43:08 +02:00
parseEnvFiles ( s )
2022-04-01 09:22:00 +02:00
}
2022-03-31 14:12:20 +02:00
c := p . Data
2021-12-03 11:49:32 +01:00
for name , s := range c . Services {
if portlabel , ok := s . Labels [ helm . LABEL_PORT ] ; ok {
services := strings . Split ( portlabel , "," )
for _ , serviceport := range services {
portexists := false
for _ , found := range s . Ports {
if found == serviceport {
portexists = true
}
}
if ! portexists {
s . Ports = append ( s . Ports , serviceport )
}
}
}
if len ( s . Ports ) > 0 {
services [ name ] = s . Ports
}
}
// check if dependencies are resolved
missing := [ ] string { }
for name , s := range c . Services {
for _ , dep := range s . DependsOn {
if _ , ok := services [ dep ] ; ! ok {
missing = append ( missing , fmt . Sprintf (
"The service \"%s\" hasn't got " +
"declared port for dependency from \"%s\" - please " +
"append a %s label or a \"ports\" section in the docker-compose file" ,
dep , name , helm . LABEL_PORT ) ,
)
}
}
}
if len ( missing ) > 0 {
log . Fatal ( strings . Join ( missing , "\n" ) )
}
2022-04-01 08:15:39 +02:00
// check if all "image" properties are set
missing = [ ] string { }
for name , s := range c . Services {
if s . Image == "" {
missing = append ( missing , fmt . Sprintf (
"The service \"%s\" hasn't got " +
"an image property - please " +
"append an image property in the docker-compose file" ,
name ,
) )
}
}
if len ( missing ) > 0 {
log . Fatal ( strings . Join ( missing , "\n" ) )
}
2021-12-05 09:05:48 +01:00
// check the build element
for name , s := range c . Services {
if s . RawBuild == nil {
continue
}
fmt . Println ( ICON_EXCLAMATION +
" \x1b[33myou will need to build and push your image named \"" + s . Image + "\"" +
" for the \"" + name + "\" service \x1b[0m" )
}
2022-04-01 09:22:00 +02:00
}
// manage environment variables, if the type is map[string]string so we can use it, else we need to split "=" sign
// and apply this in env variable
func parseEnv ( s * Service ) {
env := make ( map [ string ] string )
if s . RawEnvironment == nil {
return
}
switch s . RawEnvironment . ( type ) {
case map [ string ] string :
env = s . RawEnvironment . ( map [ string ] string )
case map [ string ] interface { } :
for k , v := range s . RawEnvironment . ( map [ string ] interface { } ) {
2022-04-01 10:43:08 +02:00
// force to string
env [ k ] = fmt . Sprintf ( "%v" , v )
2022-04-01 09:22:00 +02:00
}
case [ ] interface { } :
for _ , v := range s . RawEnvironment . ( [ ] interface { } ) {
// Splot the value of the env variable with "="
parts := strings . Split ( v . ( string ) , "=" )
env [ parts [ 0 ] ] = parts [ 1 ]
}
case string :
parts := strings . Split ( s . RawEnvironment . ( string ) , "=" )
env [ parts [ 0 ] ] = parts [ 1 ]
default :
log . Printf ( "%+v, %T" , s . RawEnvironment , s . RawEnvironment )
log . Fatal ( "Environment type not supported" )
}
s . Environment = env
2021-11-30 12:04:28 +01:00
}
2022-04-01 10:12:14 +02:00
func parseCommand ( s * Service ) {
if s . RawCommand == nil {
return
}
// following the command type, it can be a "slice" or a simple sting, so we need to check it
2022-04-01 16:58:13 +02:00
switch v := s . RawCommand . ( type ) {
2022-04-01 10:12:14 +02:00
case string :
// use shlex to parse the command
2022-04-01 16:58:13 +02:00
command , err := shlex . Split ( v )
2022-04-01 10:12:14 +02:00
if err != nil {
log . Fatal ( err )
}
s . Command = command
case [ ] string :
2022-04-01 16:58:13 +02:00
s . Command = v
2022-04-01 10:12:14 +02:00
case [ ] interface { } :
2022-04-01 16:58:13 +02:00
for _ , v := range v {
2022-04-01 10:12:14 +02:00
s . Command = append ( s . Command , v . ( string ) )
}
default :
log . Printf ( "%+v %T" , s . RawCommand , s . RawCommand )
log . Fatal ( "Command type not supported" )
}
}
2022-04-01 10:43:08 +02:00
func parseEnvFiles ( s * Service ) {
// Same than parseEnv, but for env files
if s . RawEnvFiles == nil {
return
}
envfiles := make ( [ ] string , 0 )
2022-04-01 16:58:13 +02:00
switch v := s . RawEnvFiles . ( type ) {
2022-04-01 10:43:08 +02:00
case [ ] string :
2022-04-01 16:58:13 +02:00
envfiles = v
2022-04-01 10:43:08 +02:00
case [ ] interface { } :
2022-04-01 16:58:13 +02:00
for _ , v := range v {
2022-04-01 10:43:08 +02:00
envfiles = append ( envfiles , v . ( string ) )
}
default :
log . Printf ( "%+v %T" , s . RawEnvFiles , s . RawEnvFiles )
log . Fatal ( "EnvFile type not supported" )
}
s . EnvFiles = envfiles
}
2022-04-01 16:58:13 +02:00
func parseHealthCheck ( s * Service ) {
// HealthCheck command can be a string or slice of strings
if s . HealthCheck . RawTest == nil {
return
}
switch v := s . HealthCheck . RawTest . ( type ) {
case string :
var err error
s . HealthCheck . Test , err = shlex . Split ( v )
if err != nil {
log . Fatal ( err )
}
case [ ] string :
s . HealthCheck . Test = v
case [ ] interface { } :
for _ , v := range v {
s . HealthCheck . Test = append ( s . HealthCheck . Test , v . ( string ) )
}
default :
log . Printf ( "%+v %T" , s . HealthCheck . RawTest , s . HealthCheck . RawTest )
log . Fatal ( "HealthCheck type not supported" )
}
}