mirror of https://github.com/portainer/portainer
133 lines
3.4 KiB
Go
133 lines
3.4 KiB
Go
package exec
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"strings"
|
|
|
|
portainer "github.com/portainer/portainer/api"
|
|
"github.com/portainer/portainer/api/http/proxy"
|
|
)
|
|
|
|
// ComposeWrapper is a wrapper for docker-compose binary
|
|
type ComposeWrapper struct {
|
|
binaryPath string
|
|
proxyManager *proxy.Manager
|
|
}
|
|
|
|
// NewComposeWrapper returns a docker-compose wrapper if corresponding binary present, otherwise nil
|
|
func NewComposeWrapper(binaryPath string, proxyManager *proxy.Manager) *ComposeWrapper {
|
|
if !IsBinaryPresent(programPath(binaryPath, "docker-compose")) {
|
|
return nil
|
|
}
|
|
|
|
return &ComposeWrapper{
|
|
binaryPath: binaryPath,
|
|
proxyManager: proxyManager,
|
|
}
|
|
}
|
|
|
|
// ComposeSyntaxMaxVersion returns the maximum supported version of the docker compose syntax
|
|
func (w *ComposeWrapper) ComposeSyntaxMaxVersion() string {
|
|
return portainer.ComposeSyntaxMaxVersion
|
|
}
|
|
|
|
// Up builds, (re)creates and starts containers in the background. Wraps `docker-compose up -d` command
|
|
func (w *ComposeWrapper) Up(stack *portainer.Stack, endpoint *portainer.Endpoint) error {
|
|
_, err := w.command([]string{"up", "-d"}, stack, endpoint)
|
|
return err
|
|
}
|
|
|
|
// Down stops and removes containers, networks, images, and volumes. Wraps `docker-compose down --remove-orphans` command
|
|
func (w *ComposeWrapper) Down(stack *portainer.Stack, endpoint *portainer.Endpoint) error {
|
|
_, err := w.command([]string{"down", "--remove-orphans"}, stack, endpoint)
|
|
return err
|
|
}
|
|
|
|
func (w *ComposeWrapper) command(command []string, stack *portainer.Stack, endpoint *portainer.Endpoint) ([]byte, error) {
|
|
if endpoint == nil {
|
|
return nil, errors.New("cannot call a compose command on an empty endpoint")
|
|
}
|
|
|
|
program := programPath(w.binaryPath, "docker-compose")
|
|
|
|
options := setComposeFile(stack)
|
|
|
|
options = addProjectNameOption(options, stack)
|
|
options, err := addEnvFileOption(options, stack)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !(endpoint.URL == "" || strings.HasPrefix(endpoint.URL, "unix://") || strings.HasPrefix(endpoint.URL, "npipe://")) {
|
|
|
|
proxy, err := w.proxyManager.CreateComposeProxyServer(endpoint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer proxy.Close()
|
|
|
|
options = append(options, "-H", fmt.Sprintf("http://127.0.0.1:%d", proxy.Port))
|
|
}
|
|
|
|
args := append(options, command...)
|
|
|
|
var stderr bytes.Buffer
|
|
cmd := exec.Command(program, args...)
|
|
cmd.Stderr = &stderr
|
|
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
return out, errors.New(stderr.String())
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func setComposeFile(stack *portainer.Stack) []string {
|
|
options := make([]string, 0)
|
|
|
|
if stack == nil || stack.EntryPoint == "" {
|
|
return options
|
|
}
|
|
|
|
composeFilePath := path.Join(stack.ProjectPath, stack.EntryPoint)
|
|
options = append(options, "-f", composeFilePath)
|
|
return options
|
|
}
|
|
|
|
func addProjectNameOption(options []string, stack *portainer.Stack) []string {
|
|
if stack == nil || stack.Name == "" {
|
|
return options
|
|
}
|
|
|
|
options = append(options, "-p", stack.Name)
|
|
return options
|
|
}
|
|
|
|
func addEnvFileOption(options []string, stack *portainer.Stack) ([]string, error) {
|
|
if stack == nil || stack.Env == nil || len(stack.Env) == 0 {
|
|
return options, nil
|
|
}
|
|
|
|
envFilePath := path.Join(stack.ProjectPath, "stack.env")
|
|
|
|
envfile, err := os.OpenFile(envFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
|
if err != nil {
|
|
return options, err
|
|
}
|
|
|
|
for _, v := range stack.Env {
|
|
envfile.WriteString(fmt.Sprintf("%s=%s\n", v.Name, v.Value))
|
|
}
|
|
envfile.Close()
|
|
|
|
options = append(options, "--env-file", envFilePath)
|
|
return options, nil
|
|
}
|