Skip to content
This repository was archived by the owner on Jun 26, 2023. It is now read-only.

Commit ef24824

Browse files
authored
feat(compose): support docker cli plugin (#12)
* feat(compose): support docker cli plugin feat: initial compose lib usage feat(compose): test feat(compose): remove output of up and down refactor(compose): expose a deployer refactor(composebinary): move to part of libstac refactor: move libstack to root feat(compose): provide config path fixed compose tests, made yaml more readable refactor(libstack): supply ctx to methods feat(composebinary): add a fallback builder refactor(compose): move binary to internal package feat(compose): support cli plugin * fix(compose): supply url to docker plugin
1 parent d01bc85 commit ef24824

File tree

14 files changed

+587
-44
lines changed

14 files changed

+587
-44
lines changed

compose/compose.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package compose
2+
3+
import (
4+
"log"
5+
6+
libstack "github.com/portainer/docker-compose-wrapper"
7+
"github.com/portainer/docker-compose-wrapper/compose/errors"
8+
"github.com/portainer/docker-compose-wrapper/compose/internal/composebinary"
9+
"github.com/portainer/docker-compose-wrapper/compose/internal/composeplugin"
10+
)
11+
12+
// NewComposeDeployer will try to create a wrapper for docker-compose binary
13+
// if it's not availbale it will try to create a wrapper for docker-compose plugin
14+
func NewComposeDeployer(binaryPath, configPath string) (libstack.Deployer, error) {
15+
deployer, err := composebinary.NewComposeWrapper(binaryPath, configPath)
16+
if err == nil {
17+
return deployer, nil
18+
}
19+
20+
if err == errors.ErrBinaryNotFound {
21+
log.Printf("[INFO] [main,compose] [message: binary is missing, falling-back to compose plugin] [error: %s]", err)
22+
return composeplugin.NewPluginWrapper(binaryPath, configPath)
23+
}
24+
25+
return nil, err
26+
}

compose/compose_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package compose_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"os"
8+
"os/exec"
9+
"path/filepath"
10+
"strings"
11+
"testing"
12+
13+
"github.com/portainer/docker-compose-wrapper/compose"
14+
)
15+
16+
func Test_UpAndDown(t *testing.T) {
17+
deployer, _ := compose.NewComposeDeployer("", "")
18+
19+
const composeFileContent = `
20+
version: "3.9"
21+
services:
22+
busybox:
23+
image: "alpine:3.7"
24+
container_name: "test_container_one"
25+
`
26+
27+
const overrideComposeFileContent = `
28+
version: "3.9"
29+
services:
30+
busybox:
31+
image: "alpine:latest"
32+
container_name: "test_container_two"
33+
`
34+
35+
const composeContainerName = "test_container_two"
36+
37+
dir := os.TempDir()
38+
defer os.RemoveAll(dir)
39+
40+
filePathOriginal, err := createFile(dir, "docker-compose.yml", composeFileContent)
41+
if err != nil {
42+
t.Fatal(err)
43+
}
44+
45+
filePathOverride, err := createFile(dir, "docker-compose-override.yml", overrideComposeFileContent)
46+
if err != nil {
47+
t.Fatal(err)
48+
}
49+
50+
ctx := context.Background()
51+
52+
err = deployer.Deploy(ctx, "", "", "test1", []string{filePathOriginal, filePathOverride}, "")
53+
if err != nil {
54+
t.Fatal(err)
55+
}
56+
57+
if !containerExists(composeContainerName) {
58+
t.Fatal("container should exist")
59+
}
60+
61+
err = deployer.Remove(ctx, "", "", "test1", []string{filePathOriginal, filePathOverride})
62+
if err != nil {
63+
t.Fatal(err)
64+
}
65+
66+
if containerExists(composeContainerName) {
67+
t.Fatal("container should be removed")
68+
}
69+
}
70+
71+
func createFile(dir, fileName, content string) (string, error) {
72+
filePath := filepath.Join(dir, fileName)
73+
f, err := os.Create(filePath)
74+
if err != nil {
75+
return "", err
76+
}
77+
78+
f.WriteString(content)
79+
f.Close()
80+
81+
return filePath, nil
82+
}
83+
84+
func containerExists(containerName string) bool {
85+
cmd := exec.Command("docker", "ps", "-a", "-f", fmt.Sprintf("name=%s", containerName))
86+
87+
out, err := cmd.Output()
88+
if err != nil {
89+
log.Fatalf("failed to list containers: %s", err)
90+
}
91+
92+
return strings.Contains(string(out), containerName)
93+
}

compose/download.sh

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
IFS=$'\n\t'
4+
5+
PLATFORM=$1
6+
ARCH=$2
7+
DOCKER_COMPOSE_VERSION=$3
8+
9+
function download_binary() {
10+
local PLATFORM=$1
11+
local ARCH=$2
12+
local BINARY_VERSION=$3
13+
14+
if [ "${PLATFORM}" == 'linux' ] && [ "${ARCH}" == 'amd64' ]; then
15+
wget -O "dist/docker-compose" "https://github.com/portainer/docker-compose-linux-amd64-static-binary/releases/download/${BINARY_VERSION}/docker-compose"
16+
chmod +x "dist/docker-compose"
17+
return
18+
fi
19+
20+
if [ "${PLATFORM}" == 'mac' ]; then
21+
wget -O "dist/docker-compose" "https://github.com/docker/compose/releases/download/${BINARY_VERSION}/docker-compose-Darwin-x86_64"
22+
chmod +x "dist/docker-compose"
23+
return
24+
fi
25+
26+
if [ "${PLATFORM}" == 'win' ]; then
27+
wget -O "dist/docker-compose.exe" "https://github.com/docker/compose/releases/download/${BINARY_VERSION}/docker-compose-Windows-x86_64.exe"
28+
chmod +x "dist/docker-compose.exe"
29+
return
30+
fi
31+
}
32+
33+
function download_plugin() {
34+
local PLATFORM=$1
35+
local ARCH=$2
36+
local PLUGIN_VERSION=$3
37+
38+
if [ "${PLATFORM}" == 'mac' ]; then
39+
PLATFORM="darwin"
40+
fi
41+
42+
FILENAME="docker-compose-${PLATFORM}-${ARCH}"
43+
TARGET_FILENAME="docker-compose.plugin"
44+
if [[ "$PLATFORM" == "windows" ]]; then
45+
FILENAME="$FILENAME.exe"
46+
TARGET_FILENAME="$TARGET_FILENAME.exe"
47+
fi
48+
49+
wget -O "dist/$TARGET_FILENAME" "https://github.com/docker/compose-cli/releases/download/v$PLUGIN_VERSION/$FILENAME"
50+
chmod +x "dist/$TARGET_FILENAME"
51+
return 0
52+
}
53+
54+
if [ "${PLATFORM}" == 'linux' ] && [ "${ARCH}" != 'amd64' ]; then
55+
download_plugin "$PLATFORM" "$ARCH" "$DOCKER_COMPOSE_VERSION"
56+
fi
57+
58+
download_binary "$PLATFORM" "$ARCH" "$DOCKER_COMPOSE_VERSION"

compose/errors/errors.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package errors
2+
3+
import "errors"
4+
5+
var (
6+
// ErrBinaryNotFound is returned when docker-compose binary is not found
7+
ErrBinaryNotFound = errors.New("docker-compose binary not found")
8+
)

wrapper.go renamed to compose/internal/composebinary/composebinary.go

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,59 @@
1-
package wrapper
1+
package composebinary
22

33
import (
44
"bytes"
5+
"context"
56
"fmt"
7+
"log"
68
"os"
79
"os/exec"
810
"strings"
911

1012
"github.com/pkg/errors"
11-
)
1213

13-
var (
14-
// ErrBinaryNotFound is returned when docker-compose binary is not found
15-
ErrBinaryNotFound = errors.New("docker-compose binary not found")
14+
libstack "github.com/portainer/docker-compose-wrapper"
15+
liberrors "github.com/portainer/docker-compose-wrapper/compose/errors"
16+
"github.com/portainer/docker-compose-wrapper/compose/internal/utils"
1617
)
1718

1819
// ComposeWrapper provide a type for managing docker compose commands
1920
type ComposeWrapper struct {
2021
binaryPath string
22+
configPath string
2123
}
2224

2325
// NewComposeWrapper initializes a new ComposeWrapper service with local docker-compose binary.
24-
func NewComposeWrapper(binaryPath string) (*ComposeWrapper, error) {
25-
if !IsBinaryPresent(programPath(binaryPath, "docker-compose")) {
26-
return nil, ErrBinaryNotFound
26+
func NewComposeWrapper(binaryPath, configPath string) (libstack.Deployer, error) {
27+
if !utils.IsBinaryPresent(utils.ProgramPath(binaryPath, "docker-compose")) {
28+
return nil, liberrors.ErrBinaryNotFound
2729
}
2830

29-
return &ComposeWrapper{binaryPath: binaryPath}, nil
31+
return &ComposeWrapper{binaryPath: binaryPath, configPath: configPath}, nil
3032
}
3133

3234
// Up create and start containers
33-
func (wrapper *ComposeWrapper) Up(filePaths []string, projectDir, host, projectName, envFilePath, configPath string) ([]byte, error) {
34-
return wrapper.Command(newUpCommand(filePaths), projectDir, host, projectName, envFilePath, configPath)
35+
func (wrapper *ComposeWrapper) Deploy(ctx context.Context, workingDir, host, projectName string, filePaths []string, envFilePath string) error {
36+
output, err := wrapper.Command(newUpCommand(filePaths), workingDir, host, projectName, envFilePath, wrapper.configPath)
37+
if len(output) != 0 {
38+
log.Printf("[libstack,composebinary] [message: finish deploying] [output: %s] [err: %s]", output, err)
39+
}
40+
41+
return err
3542
}
3643

3744
// Down stop and remove containers
38-
func (wrapper *ComposeWrapper) Down(filePaths []string, projectDir string, host, projectName string) ([]byte, error) {
39-
return wrapper.Command(newDownCommand(filePaths), projectDir, host, projectName, "", "")
45+
func (wrapper *ComposeWrapper) Remove(ctx context.Context, workingDir, host, projectName string, filePaths []string) error {
46+
output, err := wrapper.Command(newDownCommand(filePaths), workingDir, host, projectName, "", "")
47+
if len(output) != 0 {
48+
log.Printf("[libstack,composebinary] [message: finish deploying] [output: %s] [err: %s]", output, err)
49+
}
50+
51+
return err
4052
}
4153

4254
// Command exectue a docker-compose commanåd
4355
func (wrapper *ComposeWrapper) Command(command composeCommand, workingDir, host, projectName, envFilePath, configPath string) ([]byte, error) {
44-
program := programPath(wrapper.binaryPath, "docker-compose")
56+
program := utils.ProgramPath(wrapper.binaryPath, "docker-compose")
4557

4658
if projectName != "" {
4759
command.WithProjectName(projectName)

0 commit comments

Comments
 (0)