diff --git a/loader/full-example.yml b/loader/full-example.yml index 1969527b..2687b05f 100644 --- a/loader/full-example.yml +++ b/loader/full-example.yml @@ -25,6 +25,7 @@ services: tags: - foo:v1.0.0 - docker.io/username/foo:my-other-tag + - ${COMPOSE_PROJECT_NAME}:1.0.0 cap_add: diff --git a/loader/full-struct_test.go b/loader/full-struct_test.go index 9bdb6370..66b33808 100644 --- a/loader/full-struct_test.go +++ b/loader/full-struct_test.go @@ -70,7 +70,7 @@ func services(workingDir, homeDir string) []types.ServiceConfig { Mode: uint32Ptr(0440), }, }, - Tags: []string{"foo:v1.0.0", "docker.io/username/foo:my-other-tag"}, + Tags: []string{"foo:v1.0.0", "docker.io/username/foo:my-other-tag", "full_example_project_name:1.0.0"}, }, CapAdd: []string{"ALL"}, CapDrop: []string{"NET_ADMIN", "SYS_ADMIN"}, @@ -602,6 +602,7 @@ services: tags: - foo:v1.0.0 - docker.io/username/foo:my-other-tag + - full_example_project_name:1.0.0 cap_add: - ALL cap_drop: @@ -1133,7 +1134,8 @@ func fullExampleJSON(workingDir, homeDir string) string { ], "tags": [ "foo:v1.0.0", - "docker.io/username/foo:my-other-tag" + "docker.io/username/foo:my-other-tag", + "full_example_project_name:1.0.0" ] }, "cap_add": [ diff --git a/loader/loader.go b/loader/loader.go index 01c0c5d5..2c115e3c 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -160,6 +160,8 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types. op(opts) } + projectName := projectName(configDetails, opts) + var configs []*types.Config for i, file := range configDetails.ConfigFiles { configDict := file.Config @@ -207,15 +209,6 @@ 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: projectName, WorkingDir: configDetails.WorkingDir, @@ -245,6 +238,30 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types. return project, nil } +func projectName(details types.ConfigDetails, opts *Options) string { + projectName, projectNameImperativelySet := opts.GetProjectName() + var pjNameFromConfigFile string + + for _, configFile := range details.ConfigFiles { + yml, err := ParseYAML(configFile.Content) + if err != nil { + return "" + } + if val, ok := yml["name"]; ok && val != "" { + pjNameFromConfigFile = yml["name"].(string) + } + } + pjNameFromConfigFile = NormalizeProjectName(pjNameFromConfigFile) + if !projectNameImperativelySet && pjNameFromConfigFile != "" { + projectName = pjNameFromConfigFile + } + + if _, ok := details.Environment[consts.ComposeProjectName]; !ok && projectName != "" { + details.Environment[consts.ComposeProjectName] = projectName + } + return projectName +} + func NormalizeProjectName(s string) string { r := regexp.MustCompile("[a-z0-9_-]") s = strings.ToLower(s) diff --git a/loader/loader_test.go b/loader/loader_test.go index 60569b2c..7e3684af 100644 --- a/loader/loader_test.go +++ b/loader/loader_test.go @@ -34,20 +34,32 @@ import ( ) func buildConfigDetails(yaml string, env map[string]string) types.ConfigDetails { + return buildConfigDetailsMultipleFiles(env, yaml) +} + +func buildConfigDetailsMultipleFiles(env map[string]string, yamls ...string) types.ConfigDetails { workingDir, err := os.Getwd() if err != nil { panic(err) } return types.ConfigDetails{ - WorkingDir: workingDir, - ConfigFiles: []types.ConfigFile{ - {Filename: "filename.yml", Content: []byte(yaml)}, - }, + WorkingDir: workingDir, + ConfigFiles: buildConfigFiles(yamls), Environment: env, } } +func buildConfigFiles(yamls []string) []types.ConfigFile { + configFiles := []types.ConfigFile{} + for i, yaml := range yamls { + configFiles = append(configFiles, types.ConfigFile{ + Filename: fmt.Sprintf("filename%d.yml", i), + Content: []byte(yaml)}) + } + return configFiles +} + func loadYAML(yaml string) (*types.Project, error) { return loadYAMLWithEnv(yaml, nil) } @@ -1951,3 +1963,76 @@ services: assert.NilError(t, err) assert.Equal(t, "value2", sshValue) } + +func TestProjectNameInterpolation(t *testing.T) { + t.Run("project name simple interpolation", func(t *testing.T) { + yaml := ` +name: interpolated +services: + web: + image: web + container_name: ${COMPOSE_PROJECT_NAME}-web +` + configDetails := buildConfigDetails(yaml, map[string]string{}) + + actual, err := Load(configDetails) + assert.NilError(t, err) + svc, err := actual.GetService("web") + assert.NilError(t, err) + assert.Equal(t, "interpolated-web", svc.ContainerName) + }) + + t.Run("project name interpolation with override", func(t *testing.T) { + yaml1 := ` +name: interpolated +services: + web: + image: web + container_name: ${COMPOSE_PROJECT_NAME}-web +` + yaml2 := ` +name: overrided +services: + db: + image: db + container_name: ${COMPOSE_PROJECT_NAME}-db +` + yaml3 := ` +services: + proxy: + image: proxy + container_name: ${COMPOSE_PROJECT_NAME}-proxy +` + configDetails := buildConfigDetailsMultipleFiles(map[string]string{}, yaml1, yaml2, yaml3) + + actual, err := Load(configDetails) + assert.NilError(t, err) + svc, err := actual.GetService("web") + assert.NilError(t, err) + assert.Equal(t, "overrided-web", svc.ContainerName) + + svc, err = actual.GetService("db") + assert.NilError(t, err) + assert.Equal(t, "overrided-db", svc.ContainerName) + + svc, err = actual.GetService("proxy") + assert.NilError(t, err) + assert.Equal(t, "overrided-proxy", svc.ContainerName) + }) + + t.Run("project name env variable interpolation", func(t *testing.T) { + yaml := ` +name: interpolated +services: + web: + image: web + container_name: ${COMPOSE_PROJECT_NAME}-web +` + configDetails := buildConfigDetails(yaml, map[string]string{"COMPOSE_PROJECT_NAME": "env-var"}) + actual, err := Load(configDetails) + assert.NilError(t, err) + svc, err := actual.GetService("web") + assert.NilError(t, err) + assert.Equal(t, "env-var-web", svc.ContainerName) + }) +}