internal/docker/docker.go (151 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.
package docker
import (
"bytes"
"encoding/json"
"fmt"
"os"
"os/exec"
"strings"
"time"
"github.com/elastic/elastic-package/internal/logger"
)
// NetworkDescription describes the Docker network and connected Docker containers.
type NetworkDescription struct {
Containers map[string]struct {
Name string
}
}
// ContainerDescription describes the Docker container.
type ContainerDescription struct {
Config struct {
Image string
Labels ConfigLabels
}
ID string
State struct {
Status string
ExitCode int
Health *struct {
Status string
Log []struct {
Start time.Time
ExitCode int
Output string
}
}
}
}
// ConfigLabels are the labels included in the config in container descriptions.
type ConfigLabels struct {
ComposeProject string `json:"com.docker.compose.project"`
ComposeService string `json:"com.docker.compose.service"`
ComposeVersion string `json:"com.docker.compose.version"`
}
// String function dumps string representation of the container description.
func (c *ContainerDescription) String() string {
b, err := json.Marshal(c)
if err != nil {
return "error: can't marshal container description"
}
return string(b)
}
// Pull downloads the latest available revision of the image.
func Pull(image string) error {
cmd := exec.Command("docker", "pull", image)
if logger.IsDebugMode() {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
logger.Debugf("run command: %s", cmd)
err := cmd.Run()
if err != nil {
return fmt.Errorf("running docker command failed: %w", err)
}
return nil
}
// ContainerID function returns the container ID for a given container name.
func ContainerID(containerName string) (string, error) {
cmd := exec.Command("docker", "ps", "--filter", "name="+containerName, "--format", "{{.ID}}")
errOutput := new(bytes.Buffer)
cmd.Stderr = errOutput
logger.Debugf("output command: %s", cmd)
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("could not find \"%s\" container (stderr=%q): %w", containerName, errOutput.String(), err)
}
containerIDs := strings.Fields(string(output))
if len(containerIDs) != 1 {
return "", fmt.Errorf("expected single %s container", containerName)
}
return containerIDs[0], nil
}
// ContainerIDsWithLabel function returns all the container IDs filtering per label.
func ContainerIDsWithLabel(key, value string) ([]string, error) {
label := fmt.Sprintf("%s=%s", key, value)
cmd := exec.Command("docker", "ps", "-a", "--filter", "label="+label, "--format", "{{.ID}}")
errOutput := new(bytes.Buffer)
cmd.Stderr = errOutput
logger.Debugf("output command: %s", cmd)
output, err := cmd.Output()
if err != nil {
return []string{}, fmt.Errorf("error getting containers with label \"%s\" (stderr=%q): %w", label, errOutput.String(), err)
}
containerIDs := strings.Fields(string(output))
return containerIDs, nil
}
// InspectNetwork function returns the network description for the selected network.
func InspectNetwork(network string) ([]NetworkDescription, error) {
cmd := exec.Command("docker", "network", "inspect", network)
errOutput := new(bytes.Buffer)
cmd.Stderr = errOutput
logger.Debugf("output command: %s", cmd)
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("could not inspect the network (stderr=%q): %w", errOutput.String(), err)
}
var networkDescriptions []NetworkDescription
err = json.Unmarshal(output, &networkDescriptions)
if err != nil {
return nil, fmt.Errorf("can't unmarshal network inspect for %s (stderr=%q): %w", network, errOutput.String(), err)
}
return networkDescriptions, nil
}
// ConnectToNetwork function connects the container to the selected Docker network.
func ConnectToNetwork(containerID, network string) error {
return ConnectToNetworkWithAlias(containerID, network, []string{})
}
// ConnectToNetworkWithAlias function connects the container to the selected Docker network.
func ConnectToNetworkWithAlias(containerID, network string, aliases []string) error {
args := []string{"network", "connect", network, containerID}
if len(aliases) > 0 {
for _, alias := range aliases {
args = append(args, "--alias", alias)
}
}
cmd := exec.Command("docker", args...)
errOutput := new(bytes.Buffer)
cmd.Stderr = errOutput
logger.Debugf("run command: %s", cmd)
if err := cmd.Run(); err != nil {
return fmt.Errorf("could not attach container to the stack network (stderr=%q): %w", errOutput.String(), err)
}
return nil
}
// InspectContainers function inspects selected Docker containers.
func InspectContainers(containerIDs ...string) ([]ContainerDescription, error) {
args := []string{"inspect"}
args = append(args, containerIDs...)
cmd := exec.Command("docker", args...)
errOutput := new(bytes.Buffer)
cmd.Stderr = errOutput
logger.Debugf("output command: %s", cmd)
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("could not inspect containers (stderr=%q): %w", errOutput.String(), err)
}
var containerDescriptions []ContainerDescription
err = json.Unmarshal(output, &containerDescriptions)
if err != nil {
return nil, fmt.Errorf("can't unmarshal container inspect for %s (stderr=%q): %w", strings.Join(containerIDs, ","), errOutput.String(), err)
}
return containerDescriptions, nil
}
// Copy function copies resources from the container to the local destination.
func Copy(containerName, containerPath, localPath string) error {
cmd := exec.Command("docker", "cp", containerName+":"+containerPath, localPath)
errOutput := new(bytes.Buffer)
cmd.Stderr = errOutput
logger.Debugf("run command: %s", cmd)
if err := cmd.Run(); err != nil {
return fmt.Errorf("could not copy files from the container (stderr=%q): %w", errOutput.String(), err)
}
return nil
}