1package daemon // import "github.com/docker/docker/integration-cli/daemon"
2
3import (
4	"fmt"
5	"strings"
6	"testing"
7	"time"
8
9	"github.com/docker/docker/testutil/daemon"
10	"github.com/pkg/errors"
11	"gotest.tools/v3/assert"
12	"gotest.tools/v3/icmd"
13)
14
15// Daemon represents a Docker daemon for the testing framework.
16type Daemon struct {
17	*daemon.Daemon
18	dockerBinary string
19}
20
21// New returns a Daemon instance to be used for testing.
22// This will create a directory such as d123456789 in the folder specified by $DOCKER_INTEGRATION_DAEMON_DEST or $DEST.
23// The daemon will not automatically start.
24func New(t testing.TB, dockerBinary string, dockerdBinary string, ops ...daemon.Option) *Daemon {
25	t.Helper()
26	ops = append(ops, daemon.WithDockerdBinary(dockerdBinary))
27	d := daemon.New(t, ops...)
28	return &Daemon{
29		Daemon:       d,
30		dockerBinary: dockerBinary,
31	}
32}
33
34// Cmd executes a docker CLI command against this daemon.
35// Example: d.Cmd("version") will run docker -H unix://path/to/unix.sock version
36func (d *Daemon) Cmd(args ...string) (string, error) {
37	result := icmd.RunCmd(d.Command(args...))
38	return result.Combined(), result.Error
39}
40
41// Command creates a docker CLI command against this daemon, to be executed later.
42// Example: d.Command("version") creates a command to run "docker -H unix://path/to/unix.sock version"
43func (d *Daemon) Command(args ...string) icmd.Cmd {
44	return icmd.Command(d.dockerBinary, d.PrependHostArg(args)...)
45}
46
47// PrependHostArg prepend the specified arguments by the daemon host flags
48func (d *Daemon) PrependHostArg(args []string) []string {
49	for _, arg := range args {
50		if arg == "--host" || arg == "-H" {
51			return args
52		}
53	}
54	return append([]string{"--host", d.Sock()}, args...)
55}
56
57// GetIDByName returns the ID of an object (container, volume, …) given its name
58func (d *Daemon) GetIDByName(name string) (string, error) {
59	return d.inspectFieldWithError(name, "Id")
60}
61
62// InspectField returns the field filter by 'filter'
63func (d *Daemon) InspectField(name, filter string) (string, error) {
64	return d.inspectFilter(name, filter)
65}
66
67func (d *Daemon) inspectFilter(name, filter string) (string, error) {
68	format := fmt.Sprintf("{{%s}}", filter)
69	out, err := d.Cmd("inspect", "-f", format, name)
70	if err != nil {
71		return "", errors.Errorf("failed to inspect %s: %s", name, out)
72	}
73	return strings.TrimSpace(out), nil
74}
75
76func (d *Daemon) inspectFieldWithError(name, field string) (string, error) {
77	return d.inspectFilter(name, fmt.Sprintf(".%s", field))
78}
79
80// CheckActiveContainerCount returns the number of active containers
81// FIXME(vdemeester) should re-use ActivateContainers in some way
82func (d *Daemon) CheckActiveContainerCount(t *testing.T) (interface{}, string) {
83	t.Helper()
84	out, err := d.Cmd("ps", "-q")
85	assert.NilError(t, err)
86	if len(strings.TrimSpace(out)) == 0 {
87		return 0, ""
88	}
89	return len(strings.Split(strings.TrimSpace(out), "\n")), fmt.Sprintf("output: %q", out)
90}
91
92// WaitRun waits for a container to be running for 10s
93func (d *Daemon) WaitRun(contID string) error {
94	args := []string{"--host", d.Sock()}
95	return WaitInspectWithArgs(d.dockerBinary, contID, "{{.State.Running}}", "true", 10*time.Second, args...)
96}
97
98// WaitInspectWithArgs waits for the specified expression to be equals to the specified expected string in the given time.
99// Deprecated: use cli.WaitCmd instead
100func WaitInspectWithArgs(dockerBinary, name, expr, expected string, timeout time.Duration, arg ...string) error {
101	after := time.After(timeout)
102
103	args := append(arg, "inspect", "-f", expr, name)
104	for {
105		result := icmd.RunCommand(dockerBinary, args...)
106		if result.Error != nil {
107			if !strings.Contains(strings.ToLower(result.Stderr()), "no such") {
108				return errors.Errorf("error executing docker inspect: %v\n%s",
109					result.Stderr(), result.Stdout())
110			}
111			select {
112			case <-after:
113				return result.Error
114			default:
115				time.Sleep(10 * time.Millisecond)
116				continue
117			}
118		}
119
120		out := strings.TrimSpace(result.Stdout())
121		if out == expected {
122			break
123		}
124
125		select {
126		case <-after:
127			return errors.Errorf("condition \"%q == %q\" not true in time (%v)", out, expected, timeout)
128		default:
129		}
130
131		time.Sleep(100 * time.Millisecond)
132	}
133	return nil
134}
135