forked from testcontainers/testcontainers-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
compose.go
184 lines (152 loc) · 5.13 KB
/
compose.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package testcontainers
import (
"context"
"errors"
"path/filepath"
"runtime"
"strings"
"sync"
"github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/flags"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/compose"
"github.com/google/uuid"
"github.com/testcontainers/testcontainers-go/wait"
)
const (
envProjectName = "COMPOSE_PROJECT_NAME"
envComposeFile = "COMPOSE_FILE"
)
var composeLogOnce sync.Once
var ErrNoStackConfigured = errors.New("no stack files configured")
type composeStackOptions struct {
Identifier string
Paths []string
}
type ComposeStackOption interface {
applyToComposeStack(o *composeStackOptions)
}
type stackUpOptions struct {
// Services defines the services user interacts with
Services []string
// Remove legacy containers for services that are not defined in the project
RemoveOrphans bool
// Wait won't return until containers reached the running|healthy state
Wait bool
// Recreate define the strategy to apply on existing containers
Recreate string
// RecreateDependencies define the strategy to apply on dependencies services
RecreateDependencies string
// Project is the compose project used to define this app. Might be nil if user ran command just with project name
Project *types.Project
}
type StackUpOption interface {
applyToStackUp(o *stackUpOptions)
}
type stackDownOptions struct {
api.DownOptions
}
type StackDownOption interface {
applyToStackDown(do *stackDownOptions)
}
// ComposeStack defines operations that can be applied to a parsed compose stack
type ComposeStack interface {
Up(ctx context.Context, opts ...StackUpOption) error
Down(ctx context.Context, opts ...StackDownOption) error
Services() []string
WaitForService(s string, strategy wait.Strategy) ComposeStack
WithEnv(m map[string]string) ComposeStack
WithOsEnv() ComposeStack
ServiceContainer(ctx context.Context, svcName string) (*DockerContainer, error)
}
// DockerCompose defines the contract for running Docker Compose
// Deprecated: DockerCompose is the old shell escape based API
// use ComposeStack instead
type DockerCompose interface {
Down() ExecError
Invoke() ExecError
WaitForService(string, wait.Strategy) DockerCompose
WithCommand([]string) DockerCompose
WithEnv(map[string]string) DockerCompose
WithExposedService(string, int, wait.Strategy) DockerCompose
}
type waitService struct {
service string
publishedPort int
}
func WithStackFiles(filePaths ...string) ComposeStackOption {
return ComposeStackFiles(filePaths)
}
func NewDockerCompose(filePaths ...string) (*dockerCompose, error) {
return NewDockerComposeWith(WithStackFiles(filePaths...))
}
func NewDockerComposeWith(opts ...ComposeStackOption) (*dockerCompose, error) {
composeOptions := composeStackOptions{
Identifier: uuid.New().String(),
}
for i := range opts {
opts[i].applyToComposeStack(&composeOptions)
}
if len(composeOptions.Paths) < 1 {
return nil, ErrNoStackConfigured
}
dockerCli, err := command.NewDockerCli()
if err != nil {
return nil, err
}
if err = dockerCli.Initialize(&flags.ClientOptions{
Common: new(flags.CommonOptions),
}, command.WithInitializeClient(makeClient)); err != nil {
return nil, err
}
composeAPI := &dockerCompose{
name: composeOptions.Identifier,
configs: composeOptions.Paths,
composeService: compose.NewComposeService(dockerCli),
dockerClient: dockerCli.Client(),
waitStrategies: make(map[string]wait.Strategy),
containers: make(map[string]*DockerContainer),
}
// log docker server info only once
composeLogOnce.Do(func() {
logDockerServerInfo(context.Background(), dockerCli.Client(), Logger)
})
return composeAPI, nil
}
// NewLocalDockerCompose returns an instance of the local Docker Compose, using an
// array of Docker Compose file paths and an identifier for the Compose execution.
//
// It will iterate through the array adding '-f compose-file-path' flags to the local
// Docker Compose execution. The identifier represents the name of the execution,
// which will define the name of the underlying Docker network and the name of the
// running Compose services.
//
// Deprecated: NewLocalDockerCompose returns a DockerCompose compatible instance which is superseded
// by ComposeStack use NewDockerCompose instead to get a ComposeStack compatible instance
func NewLocalDockerCompose(filePaths []string, identifier string, opts ...LocalDockerComposeOption) *LocalDockerCompose {
dc := &LocalDockerCompose{
LocalDockerComposeOptions: &LocalDockerComposeOptions{
Logger: Logger,
},
}
for idx := range opts {
opts[idx].ApplyToLocalCompose(dc.LocalDockerComposeOptions)
}
dc.Executable = "docker-compose"
if runtime.GOOS == "windows" {
dc.Executable = "docker-compose.exe"
}
dc.ComposeFilePaths = filePaths
dc.absComposeFilePaths = make([]string, len(filePaths))
for i, cfp := range dc.ComposeFilePaths {
abs, _ := filepath.Abs(cfp)
dc.absComposeFilePaths[i] = abs
}
_ = dc.determineVersion()
_ = dc.validate()
dc.Identifier = strings.ToLower(identifier)
dc.waitStrategySupplied = false
dc.WaitStrategyMap = make(map[waitService]wait.Strategy)
return dc
}