diff --git a/pkg/libstack/compose/internal/composeplugin/composeplugin.go b/pkg/libstack/compose/internal/composeplugin/composeplugin.go index 706889173..35d02c22b 100644 --- a/pkg/libstack/compose/internal/composeplugin/composeplugin.go +++ b/pkg/libstack/compose/internal/composeplugin/composeplugin.go @@ -110,6 +110,11 @@ func (wrapper *PluginWrapper) Validate(ctx context.Context, filePaths []string, return err } +func (wrapper *PluginWrapper) Config(ctx context.Context, filePaths []string, options libstack.Options) ([]byte, error) { + configArgs := append([]string{"config"}, options.ConfigOptions...) + return wrapper.command(newCommand(configArgs, filePaths), options) +} + // Command execute a docker-compose command func (wrapper *PluginWrapper) command(command composeCommand, options libstack.Options) ([]byte, error) { program := utils.ProgramPath(wrapper.binaryPath, "docker-compose") diff --git a/pkg/libstack/compose/internal/composeplugin/composeplugin_test.go b/pkg/libstack/compose/internal/composeplugin/composeplugin_test.go index 329a1ae97..960d33aa6 100644 --- a/pkg/libstack/compose/internal/composeplugin/composeplugin_test.go +++ b/pkg/libstack/compose/internal/composeplugin/composeplugin_test.go @@ -143,3 +143,230 @@ func containerExists(containerName string) bool { return strings.Contains(string(out), containerName) } + +func Test_Config(t *testing.T) { + checkPrerequisites(t) + + ctx := context.Background() + dir := t.TempDir() + projectName := "configtest" + + defer os.RemoveAll(dir) + + testCases := []struct { + name string + composeFileContent string + expectFileContent string + envFileContent string + }{ + { + name: "compose file with relative path", + composeFileContent: `services: + app: + image: 'nginx:latest' + ports: + - '80:80' + volumes: + - ./nginx-data:/data`, + expectFileContent: `name: configtest +services: + app: + image: nginx:latest + networks: + default: null + ports: + - mode: ingress + target: 80 + published: "80" + protocol: tcp + volumes: + - type: bind + source: ./nginx-data + target: /data + bind: + create_host_path: true +networks: + default: + name: configtest_default +`, + envFileContent: "", + }, + { + name: "compose file with absolute path", + composeFileContent: `services: + app: + image: 'nginx:latest' + ports: + - '80:80' + volumes: + - /nginx-data:/data`, + expectFileContent: `name: configtest +services: + app: + image: nginx:latest + networks: + default: null + ports: + - mode: ingress + target: 80 + published: "80" + protocol: tcp + volumes: + - type: bind + source: /nginx-data + target: /data + bind: + create_host_path: true +networks: + default: + name: configtest_default +`, + envFileContent: "", + }, + { + name: "compose file with declared volume", + composeFileContent: `services: + app: + image: 'nginx:latest' + ports: + - '80:80' + volumes: + - nginx-data:/data +volumes: + nginx-data: + driver: local`, + expectFileContent: `name: configtest +services: + app: + image: nginx:latest + networks: + default: null + ports: + - mode: ingress + target: 80 + published: "80" + protocol: tcp + volumes: + - type: volume + source: nginx-data + target: /data + volume: {} +networks: + default: + name: configtest_default +volumes: + nginx-data: + name: configtest_nginx-data + driver: local +`, + envFileContent: "", + }, + { + name: "compose file with relative path environment variable placeholder", + composeFileContent: `services: + nginx: + image: nginx:latest + ports: + - 8019:80 + volumes: + - ${WEB_HOME}:/usr/share/nginx/html/ + env_file: + - stack.env +`, + expectFileContent: `name: configtest +services: + nginx: + environment: + WEB_HOME: ./html + image: nginx:latest + networks: + default: null + ports: + - mode: ingress + target: 80 + published: "8019" + protocol: tcp + volumes: + - type: bind + source: ./html + target: /usr/share/nginx/html + bind: + create_host_path: true +networks: + default: + name: configtest_default +`, + envFileContent: `WEB_HOME=./html`, + }, + { + name: "compose file with absolute path environment variable placeholder", + composeFileContent: `services: + nginx: + image: nginx:latest + ports: + - 8019:80 + volumes: + - ${WEB_HOME}:/usr/share/nginx/html/ + env_file: + - stack.env +`, + + expectFileContent: `name: configtest +services: + nginx: + environment: + WEB_HOME: /usr/share/nginx/html + image: nginx:latest + networks: + default: null + ports: + - mode: ingress + target: 80 + published: "8019" + protocol: tcp + volumes: + - type: bind + source: /usr/share/nginx/html + target: /usr/share/nginx/html + bind: + create_host_path: true +networks: + default: + name: configtest_default +`, + envFileContent: `WEB_HOME=/usr/share/nginx/html`, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + composeFilePath, err := createFile(dir, "docker-compose.yml", tc.composeFileContent) + if err != nil { + t.Fatal(err) + } + + envFilePath := "" + if tc.envFileContent != "" { + envFilePath, err = createFile(dir, "stack.env", tc.envFileContent) + if err != nil { + t.Fatal(err) + } + } + + w := setup(t) + actual, err := w.Config(ctx, []string{composeFilePath}, libstack.Options{ + WorkingDir: dir, + ProjectName: projectName, + EnvFilePath: envFilePath, + ConfigOptions: []string{"--no-path-resolution"}, + }) + if err != nil { + t.Fatalf("failed to get config: %s. Error: %s", string(actual), err) + } + + if string(actual) != tc.expectFileContent { + t.Fatalf("unexpected config output: %s(len=%d), expect: %s(len=%d)", actual, len(actual), tc.expectFileContent, len(tc.expectFileContent)) + } + }) + } +} diff --git a/pkg/libstack/libstack.go b/pkg/libstack/libstack.go index 6d323cc87..e417e26d9 100644 --- a/pkg/libstack/libstack.go +++ b/pkg/libstack/libstack.go @@ -14,6 +14,7 @@ type Deployer interface { Pull(ctx context.Context, filePaths []string, options Options) error Validate(ctx context.Context, filePaths []string, options Options) error WaitForStatus(ctx context.Context, name string, status Status) <-chan WaitResult + Config(ctx context.Context, filePaths []string, options Options) ([]byte, error) } type Status string @@ -47,6 +48,8 @@ type Options struct { // By default, it is an empty string, which means it corresponds to the path of the compose file itself. // This is particularly helpful when mounting a relative path. ProjectDir string + // ConfigOptions is a list of options to pass to the docker-compose config command + ConfigOptions []string } type DeployOptions struct {