@ -23,15 +23,18 @@ import (
"path"
"path/filepath"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/compose-spec/compose-go/consts"
"github.com/compose-spec/compose-go/dotenv"
interp "github.com/compose-spec/compose-go/interpolation"
"github.com/compose-spec/compose-go/schema"
"github.com/compose-spec/compose-go/template"
"github.com/compose-spec/compose-go/types"
"github.com/compose-spec/godotenv"
"github.com/docker/go-units"
"github.com/mattn/go-shellwords"
"github.com/mitchellh/mapstructure"
@ -58,8 +61,19 @@ type Options struct {
Interpolate * interp . Options
// Discard 'env_file' entries after resolving to 'environment' section
discardEnvFiles bool
// Set project name
Name string
// Set project projectName
projectName string
// Indicates when the projectName was imperatively set or guessed from path
projectNameImperativelySet bool
}
func ( o * Options ) SetProjectName ( name string , imperativelySet bool ) {
o . projectName = normalizeProjectName ( name )
o . projectNameImperativelySet = imperativelySet
}
func ( o Options ) GetProjectName ( ) ( string , bool ) {
return o . projectName , o . projectNameImperativelySet
}
// serviceRef identifies a reference to a service. It's used to detect cyclic
@ -192,8 +206,17 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
s . EnvFile = newEnvFiles
}
projectName , projectNameImperativelySet := opts . GetProjectName ( )
model . Name = normalizeProjectName ( model . Name )
if ! projectNameImperativelySet && model . Name != "" {
projectName = model . Name
}
if projectName != "" {
configDetails . Environment [ consts . ComposeProjectName ] = projectName
}
project := & types . Project {
Name : opts . Name ,
Name : project Name,
WorkingDir : configDetails . WorkingDir ,
Services : model . Services ,
Networks : model . Networks ,
@ -221,6 +244,13 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
return project , nil
}
func normalizeProjectName ( s string ) string {
r := regexp . MustCompile ( "[a-z0-9_-]" )
s = strings . ToLower ( s )
s = strings . Join ( r . FindAllString ( s , - 1 ) , "" )
return strings . TrimLeft ( s , "_-" )
}
func parseConfig ( b [ ] byte , opts * Options ) ( map [ string ] interface { } , error ) {
yml , err := ParseYAML ( b )
if err != nil {
@ -254,7 +284,14 @@ func loadSections(filename string, config map[string]interface{}, configDetails
cfg := types . Config {
Filename : filename ,
}
name := ""
if n , ok := config [ "name" ] ; ok {
name , ok = n . ( string )
if ! ok {
return nil , errors . New ( "project name must be a string" )
}
}
cfg . Name = name
cfg . Services , err = LoadServices ( filename , getSection ( config , "services" ) , configDetails . WorkingDir , configDetails . LookupEnv , opts )
if err != nil {
return nil , err
@ -280,10 +317,6 @@ func loadSections(filename string, config map[string]interface{}, configDetails
if len ( extensions ) > 0 {
cfg . Extensions = extensions
}
if err != nil {
return nil , err
}
return & cfg , nil
}
@ -357,6 +390,7 @@ func createTransformHook(additionalTransformers ...Transformer) mapstructure.Dec
reflect . TypeOf ( types . DependsOnConfig { } ) : transformDependsOnConfig ,
reflect . TypeOf ( types . ExtendsConfig { } ) : transformExtendsConfig ,
reflect . TypeOf ( types . DeviceRequest { } ) : transformServiceDeviceRequest ,
reflect . TypeOf ( types . SSHConfig { } ) : transformSSHConfig ,
}
for _ , transformer := range additionalTransformers {
@ -559,7 +593,7 @@ func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string, l
return err
}
defer file . Close ( )
fileVars , err := go dotenv. ParseWithLookup ( file , go dotenv. LookupFn ( lookupEnv ) )
fileVars , err := dotenv. ParseWithLookup ( file , dotenv. LookupFn ( lookupEnv ) )
if err != nil {
return err
}
@ -817,6 +851,10 @@ var transformServicePort TransformerFunc = func(data interface{}) (interface{},
ports = append ( ports , v )
}
case map [ string ] interface { } :
published := value [ "published" ]
if v , ok := published . ( int ) ; ok {
value [ "published" ] = strconv . Itoa ( v )
}
ports = append ( ports , groupXFieldsIntoExtensions ( value ) )
default :
return data , errors . Errorf ( "invalid type %T for port" , value )
@ -891,7 +929,7 @@ var transformDependsOnConfig TransformerFunc = func(data interface{}) (interface
for _ , serviceIntf := range value {
service , ok := serviceIntf . ( string )
if ! ok {
return data , errors . Errorf ( "invalid type %T for service depends_on element . Expected string. ", value )
return data , errors . Errorf ( "invalid type %T for service depends_on element n, expected string ", value )
}
transformed [ service ] = map [ string ] interface { } { "condition" : types . ServiceConditionStarted }
}
@ -941,6 +979,35 @@ var transformServiceNetworkMap TransformerFunc = func(value interface{}) (interf
return value , nil
}
var transformSSHConfig TransformerFunc = func ( data interface { } ) ( interface { } , error ) {
switch value := data . ( type ) {
case map [ string ] interface { } :
var result [ ] types . SSHKey
for key , val := range value {
if val == nil {
val = ""
}
result = append ( result , types . SSHKey { ID : key , Path : val . ( string ) } )
}
return result , nil
case [ ] interface { } :
var result [ ] types . SSHKey
for _ , v := range value {
key , val := transformValueToMapEntry ( v . ( string ) , "=" , false )
result = append ( result , types . SSHKey { ID : key , Path : val . ( string ) } )
}
return result , nil
case string :
if value == "" {
value = "default"
}
key , val := transformValueToMapEntry ( value , "=" , false )
result := [ ] types . SSHKey { { ID : key , Path : val . ( string ) } }
return result , nil
}
return nil , errors . Errorf ( "expected a sting, map or a list, got %T: %#v" , data , data )
}
var transformStringOrNumberList TransformerFunc = func ( value interface { } ) ( interface { } , error ) {
list := value . ( [ ] interface { } )
result := make ( [ ] string , len ( list ) )
@ -963,47 +1030,52 @@ var transformStringList TransformerFunc = func(data interface{}) (interface{}, e
func transformMappingOrListFunc ( sep string , allowNil bool ) TransformerFunc {
return func ( data interface { } ) ( interface { } , error ) {
return transformMappingOrList ( data , sep , allowNil ) , nil
return transformMappingOrList ( data , sep , allowNil )
}
}
func transformListOrMappingFunc ( sep string , allowNil bool ) TransformerFunc {
return func ( data interface { } ) ( interface { } , error ) {
return transformListOrMapping ( data , sep , allowNil ) , nil
return transformListOrMapping ( data , sep , allowNil )
}
}
func transformListOrMapping ( listOrMapping interface { } , sep string , allowNil bool ) interface { } {
func transformListOrMapping ( listOrMapping interface { } , sep string , allowNil bool ) ( interface { } , error ) {
switch value := listOrMapping . ( type ) {
case map [ string ] interface { } :
return toStringList ( value , sep , allowNil )
return toStringList ( value , sep , allowNil ) , nil
case [ ] interface { } :
return listOrMapping
return listOrMapping , nil
}
panic ( errors . Errorf ( "expected a map or a list, got %T: %#v" , listOrMapping , listOrMapping ) )
return nil , errors . Errorf ( "expected a map or a list, got %T: %#v" , listOrMapping , listOrMapping )
}
func transformMappingOrList ( mappingOrList interface { } , sep string , allowNil bool ) interface { } {
func transformMappingOrList ( mappingOrList interface { } , sep string , allowNil bool ) ( interface { } , error ) {
switch value := mappingOrList . ( type ) {
case map [ string ] interface { } :
return toMapStringString ( value , allowNil )
return toMapStringString ( value , allowNil ) , nil
case [ ] interface { } :
result := make ( map [ string ] interface { } )
for _ , value := range value {
parts := strings . SplitN ( value . ( string ) , sep , 2 )
key , val := transformValueToMapEntry ( value . ( string ) , sep , allowNil )
result [ key ] = val
}
return result , nil
}
return nil , errors . Errorf ( "expected a map or a list, got %T: %#v" , mappingOrList , mappingOrList )
}
func transformValueToMapEntry ( value string , separator string , allowNil bool ) ( string , interface { } ) {
parts := strings . SplitN ( value , separator , 2 )
key := parts [ 0 ]
switch {
case len ( parts ) == 1 && allowNil :
result [ key ] = nil
return key , nil
case len ( parts ) == 1 && ! allowNil :
result [ key ] = ""
return key , ""
default :
result [ key ] = parts [ 1 ]
}
return key , parts [ 1 ]
}
return result
}
panic ( errors . Errorf ( "expected a map or a list, got %T: %#v" , mappingOrList , mappingOrList ) )
}
var transformShellCommand TransformerFunc = func ( value interface { } ) ( interface { } , error ) {
@ -1045,6 +1117,8 @@ var transformStringToDuration TransformerFunc = func(value interface{}) (interfa
return value , err
}
return types . Duration ( d ) , nil
case types . Duration :
return value , nil
default :
return value , errors . Errorf ( "invalid type %T for duration" , value )
}