mirror of https://github.com/portainer/portainer
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
347 lines
7.3 KiB
347 lines
7.3 KiB
package compose
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/portainer/portainer/pkg/libstack"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func Test_UpAndDown(t *testing.T) {
|
|
const projectName = "composetest"
|
|
|
|
const composeFileContent = `version: "3.9"
|
|
services:
|
|
busybox:
|
|
image: "alpine:3.7"
|
|
container_name: "composetest_container_one"`
|
|
|
|
const overrideComposeFileContent = `version: "3.9"
|
|
services:
|
|
busybox:
|
|
image: "alpine:latest"
|
|
container_name: "composetest_container_two"`
|
|
|
|
composeContainerName := projectName + "_container_two"
|
|
|
|
w := NewComposeDeployer()
|
|
|
|
dir := t.TempDir()
|
|
|
|
filePathOriginal := createFile(t, dir, "docker-compose.yml", composeFileContent)
|
|
filePathOverride := createFile(t, dir, "docker-compose-override.yml", overrideComposeFileContent)
|
|
|
|
filePaths := []string{filePathOriginal, filePathOverride}
|
|
|
|
ctx := context.Background()
|
|
|
|
err := w.Validate(ctx, filePaths, libstack.Options{ProjectName: projectName})
|
|
require.NoError(t, err)
|
|
|
|
err = w.Pull(ctx, filePaths, libstack.Options{ProjectName: projectName})
|
|
require.NoError(t, err)
|
|
|
|
require.False(t, containerExists(composeContainerName))
|
|
|
|
err = w.Deploy(ctx, filePaths, libstack.DeployOptions{
|
|
Options: libstack.Options{
|
|
ProjectName: projectName,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, containerExists(composeContainerName))
|
|
|
|
waitResult := <-w.WaitForStatus(ctx, projectName, libstack.StatusCompleted)
|
|
|
|
require.Empty(t, waitResult.ErrorMsg)
|
|
require.Equal(t, libstack.StatusCompleted, waitResult.Status)
|
|
|
|
err = w.Remove(ctx, projectName, filePaths, libstack.RemoveOptions{})
|
|
require.NoError(t, err)
|
|
|
|
require.False(t, containerExists(composeContainerName))
|
|
}
|
|
|
|
func TestRun(t *testing.T) {
|
|
w := NewComposeDeployer()
|
|
|
|
filePath := createFile(t, t.TempDir(), "docker-compose.yml", `
|
|
services:
|
|
updater:
|
|
image: alpine
|
|
`)
|
|
|
|
filePaths := []string{filePath}
|
|
serviceName := "updater"
|
|
|
|
err := w.Run(context.Background(), filePaths, serviceName, libstack.RunOptions{
|
|
Remove: true,
|
|
Options: libstack.Options{
|
|
ProjectName: "project_name",
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func createFile(t *testing.T, dir, fileName, content string) string {
|
|
filePath := filepath.Join(dir, fileName)
|
|
|
|
err := os.WriteFile(filePath, []byte(content), 0o644)
|
|
require.NoError(t, err)
|
|
|
|
return filePath
|
|
}
|
|
|
|
func containerExists(containerName string) bool {
|
|
cmd := exec.Command("docker", "ps", "-a", "-f", "name="+containerName)
|
|
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
log.Fatalf("failed to list containers: %s", err)
|
|
}
|
|
|
|
return strings.Contains(string(out), containerName)
|
|
}
|
|
|
|
func Test_Validate(t *testing.T) {
|
|
invalidComposeFileContent := `invalid-file-content`
|
|
|
|
w := NewComposeDeployer()
|
|
|
|
dir := t.TempDir()
|
|
|
|
filePathOriginal := createFile(t, dir, "docker-compose.yml", invalidComposeFileContent)
|
|
|
|
filePaths := []string{filePathOriginal}
|
|
|
|
projectName := "plugintest"
|
|
|
|
ctx := context.Background()
|
|
|
|
err := w.Validate(ctx, filePaths, libstack.Options{ProjectName: projectName})
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func Test_Config(t *testing.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 := createFile(t, dir, "docker-compose.yml", tc.composeFileContent)
|
|
|
|
envFilePath := ""
|
|
if tc.envFileContent != "" {
|
|
envFilePath = createFile(t, dir, "stack.env", tc.envFileContent)
|
|
}
|
|
|
|
w := NewComposeDeployer()
|
|
actual, err := w.Config(ctx, []string{composeFilePath}, libstack.Options{
|
|
WorkingDir: dir,
|
|
ProjectName: projectName,
|
|
EnvFilePath: envFilePath,
|
|
ConfigOptions: []string{"--no-path-resolution"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, tc.expectFileContent, string(actual))
|
|
})
|
|
}
|
|
}
|