DockerCLI/cli/compose/loader/full-struct_test.go

1596 lines
34 KiB
Go
Raw Normal View History

add //go:build directives to prevent downgrading to go1.16 language This is a follow-up to 0e73168b7e6d1d029d76d05b843b1aaec46739a8 This repository is not yet a module (i.e., does not have a `go.mod`). This is not problematic when building the code in GOPATH or "vendor" mode, but when using the code as a module-dependency (in module-mode), different semantics are applied since Go1.21, which switches Go _language versions_ on a per-module, per-package, or even per-file base. A condensed summary of that logic [is as follows][1]: - For modules that have a go.mod containing a go version directive; that version is considered a minimum _required_ version (starting with the go1.19.13 and go1.20.8 patch releases: before those, it was only a recommendation). - For dependencies that don't have a go.mod (not a module), go language version go1.16 is assumed. - Likewise, for modules that have a go.mod, but the file does not have a go version directive, go language version go1.16 is assumed. - If a go.work file is present, but does not have a go version directive, language version go1.17 is assumed. When switching language versions, Go _downgrades_ the language version, which means that language features (such as generics, and `any`) are not available, and compilation fails. For example: # github.com/docker/cli/cli/context/store /go/pkg/mod/github.com/docker/cli@v25.0.0-beta.2+incompatible/cli/context/store/storeconfig.go:6:24: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod) /go/pkg/mod/github.com/docker/cli@v25.0.0-beta.2+incompatible/cli/context/store/store.go:74:12: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod) Note that these fallbacks are per-module, per-package, and can even be per-file, so _(indirect) dependencies_ can still use modern language features, as long as their respective go.mod has a version specified. Unfortunately, these failures do not occur when building locally (using vendor / GOPATH mode), but will affect consumers of the module. Obviously, this situation is not ideal, and the ultimate solution is to move to go modules (add a go.mod), but this comes with a non-insignificant risk in other areas (due to our complex dependency tree). We can revert to using go1.16 language features only, but this may be limiting, and may still be problematic when (e.g.) matching signatures of dependencies. There is an escape hatch: adding a `//go:build` directive to files that make use of go language features. From the [go toolchain docs][2]: > The go line for each module sets the language version the compiler enforces > when compiling packages in that module. The language version can be changed > on a per-file basis by using a build constraint. > > For example, a module containing code that uses the Go 1.21 language version > should have a `go.mod` file with a go line such as `go 1.21` or `go 1.21.3`. > If a specific source file should be compiled only when using a newer Go > toolchain, adding `//go:build go1.22` to that source file both ensures that > only Go 1.22 and newer toolchains will compile the file and also changes > the language version in that file to Go 1.22. This patch adds `//go:build` directives to those files using recent additions to the language. It's currently using go1.19 as version to match the version in our "vendor.mod", but we can consider being more permissive ("any" requires go1.18 or up), or more "optimistic" (force go1.21, which is the version we currently use to build). For completeness sake, note that any file _without_ a `//go:build` directive will continue to use go1.16 language version when used as a module. [1]: https://github.com/golang/go/blob/58c28ba286dd0e98fe4cca80f5d64bbcb824a685/src/cmd/go/internal/gover/version.go#L9-L56 [2]; https://go.dev/doc/toolchain#:~:text=The%20go%20line%20for,file%20to%20Go%201.22 Signed-off-by: Sebastiaan van Stijn <github@gone.nl> (cherry picked from commit 70216b662dc440faaebab531deb35f0f0c633d17) Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-12-14 07:51:57 -05:00
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.19
package loader
import (
"fmt"
"path/filepath"
"time"
"github.com/docker/cli/cli/compose/types"
)
func fullExampleConfig(workingDir, homeDir string) *types.Config {
return &types.Config{
Version: "3.10",
Services: services(workingDir, homeDir),
Networks: networks(),
Volumes: volumes(),
Configs: configs(workingDir),
Secrets: secrets(workingDir),
Extras: map[string]interface{}{
"x-foo": "bar",
"x-bar": "baz",
"x-nested": map[string]interface{}{
"foo": "bar",
"bar": "baz",
},
},
}
}
func services(workingDir, homeDir string) []types.ServiceConfig {
return []types.ServiceConfig{
{
Name: "foo",
Build: types.BuildConfig{
Context: "./dir",
Dockerfile: "Dockerfile",
Args: map[string]*string{"foo": strPtr("bar")},
Target: "foo",
Network: "foo",
CacheFrom: []string{"foo", "bar"},
ExtraHosts: types.HostsList{
"ipv4.example.com:127.0.0.1",
"ipv6.example.com:::1",
},
Labels: map[string]string{"FOO": "BAR"},
},
CapAdd: []string{"ALL"},
CapDrop: []string{"NET_ADMIN", "SYS_ADMIN"},
CgroupParent: "m-executor-abcd",
Command: []string{"bundle", "exec", "thin", "-p", "3000"},
Configs: []types.ServiceConfigObjConfig{
{
Source: "config1",
},
{
Source: "config2",
Target: "/my_config",
UID: "103",
GID: "103",
Mode: uint32Ptr(0o440),
},
},
ContainerName: "my-web-container",
DependsOn: []string{"db", "redis"},
Deploy: types.DeployConfig{
Mode: "replicated",
Replicas: uint64Ptr(6),
Labels: map[string]string{"FOO": "BAR"},
RollbackConfig: &types.UpdateConfig{
Parallelism: uint64Ptr(3),
Delay: types.Duration(10 * time.Second),
FailureAction: "continue",
Monitor: types.Duration(60 * time.Second),
MaxFailureRatio: 0.3,
Order: "start-first",
},
UpdateConfig: &types.UpdateConfig{
Parallelism: uint64Ptr(3),
Delay: types.Duration(10 * time.Second),
FailureAction: "continue",
Monitor: types.Duration(60 * time.Second),
MaxFailureRatio: 0.3,
Order: "start-first",
},
Resources: types.Resources{
Limits: &types.ResourceLimit{
NanoCPUs: "0.001",
MemoryBytes: 50 * 1024 * 1024,
Pids: 100,
},
Reservations: &types.Resource{
NanoCPUs: "0.0001",
MemoryBytes: 20 * 1024 * 1024,
GenericResources: []types.GenericResource{
{
DiscreteResourceSpec: &types.DiscreteGenericResource{
Kind: "gpu",
Value: 2,
},
},
{
DiscreteResourceSpec: &types.DiscreteGenericResource{
Kind: "ssd",
Value: 1,
},
},
},
},
},
RestartPolicy: &types.RestartPolicy{
Condition: "on-failure",
Delay: durationPtr(5 * time.Second),
MaxAttempts: uint64Ptr(3),
Window: durationPtr(2 * time.Minute),
},
Placement: types.Placement{
Constraints: []string{"node=foo"},
MaxReplicas: uint64(5),
Preferences: []types.PlacementPreferences{
{
Spread: "node.labels.az",
},
},
},
EndpointMode: "dnsrr",
},
Devices: []string{"/dev/ttyUSB0:/dev/ttyUSB0"},
DNS: []string{"8.8.8.8", "9.9.9.9"},
DNSSearch: []string{"dc1.example.com", "dc2.example.com"},
DomainName: "foo.com",
Entrypoint: []string{"/code/entrypoint.sh", "-p", "3000"},
Environment: map[string]*string{
"FOO": strPtr("foo_from_env_file"),
"BAR": strPtr("bar_from_env_file_2"),
"BAZ": strPtr("baz_from_service_def"),
"QUX": strPtr("qux_from_environment"),
},
EnvFile: []string{
"./example1.env",
"./example2.env",
},
Expose: []string{"3000", "8000"},
ExternalLinks: []string{
"redis_1",
"project_db_1:mysql",
"project_db_1:postgresql",
},
ExtraHosts: []string{
"somehost:162.242.195.82",
"otherhost:50.31.209.229",
"host.docker.internal:host-gateway",
},
Extras: map[string]interface{}{
"x-bar": "baz",
"x-foo": "bar",
},
HealthCheck: &types.HealthCheckConfig{
Test: types.HealthCheckTest([]string{"CMD-SHELL", "echo \"hello world\""}),
Interval: durationPtr(10 * time.Second),
Timeout: durationPtr(1 * time.Second),
Retries: uint64Ptr(5),
StartPeriod: durationPtr(15 * time.Second),
},
Hostname: "foo",
Image: "redis",
Ipc: "host",
Labels: map[string]string{
"com.example.description": "Accounting webapp",
"com.example.number": "42",
"com.example.empty-label": "",
},
Links: []string{
"db",
"db:database",
"redis",
},
Logging: &types.LoggingConfig{
Driver: "syslog",
Options: map[string]string{
"syslog-address": "tcp://192.168.0.42:123",
},
},
MacAddress: "02:42:ac:11:65:43",
NetworkMode: "container:0cfeab0f748b9a743dc3da582046357c6ef497631c1a016d28d2bf9b4f899f7b",
Networks: map[string]*types.ServiceNetworkConfig{
"some-network": {
Aliases: []string{"alias1", "alias3"},
Ipv4Address: "",
Ipv6Address: "",
},
"other-network": {
Ipv4Address: "172.16.238.10",
Ipv6Address: "2001:3984:3989::10",
},
"other-other-network": nil,
},
Pid: "host",
Ports: []types.ServicePortConfig{
// "3000",
{
Mode: "ingress",
Target: 3000,
Protocol: "tcp",
},
{
Mode: "ingress",
Target: 3001,
Protocol: "tcp",
},
{
Mode: "ingress",
Target: 3002,
Protocol: "tcp",
},
{
Mode: "ingress",
Target: 3003,
Protocol: "tcp",
},
{
Mode: "ingress",
Target: 3004,
Protocol: "tcp",
},
{
Mode: "ingress",
Target: 3005,
Protocol: "tcp",
},
// "8000:8000",
{
Mode: "ingress",
Target: 8000,
Published: 8000,
Protocol: "tcp",
},
// "9090-9091:8080-8081",
{
Mode: "ingress",
Target: 8080,
Published: 9090,
Protocol: "tcp",
},
{
Mode: "ingress",
Target: 8081,
Published: 9091,
Protocol: "tcp",
},
// "49100:22",
{
Mode: "ingress",
Target: 22,
Published: 49100,
Protocol: "tcp",
},
// "127.0.0.1:8001:8001",
{
Mode: "ingress",
Target: 8001,
Published: 8001,
Protocol: "tcp",
},
// "127.0.0.1:5000-5010:5000-5010",
{
Mode: "ingress",
Target: 5000,
Published: 5000,
Protocol: "tcp",
},
{
Mode: "ingress",
Target: 5001,
Published: 5001,
Protocol: "tcp",
},
{
Mode: "ingress",
Target: 5002,
Published: 5002,
Protocol: "tcp",
},
{
Mode: "ingress",
Target: 5003,
Published: 5003,
Protocol: "tcp",
},
{
Mode: "ingress",
Target: 5004,
Published: 5004,
Protocol: "tcp",
},
{
Mode: "ingress",
Target: 5005,
Published: 5005,
Protocol: "tcp",
},
{
Mode: "ingress",
Target: 5006,
Published: 5006,
Protocol: "tcp",
},
{
Mode: "ingress",
Target: 5007,
Published: 5007,
Protocol: "tcp",
},
{
Mode: "ingress",
Target: 5008,
Published: 5008,
Protocol: "tcp",
},
{
Mode: "ingress",
Target: 5009,
Published: 5009,
Protocol: "tcp",
},
{
Mode: "ingress",
Target: 5010,
Published: 5010,
Protocol: "tcp",
},
},
Privileged: true,
ReadOnly: true,
Restart: "always",
Secrets: []types.ServiceSecretConfig{
{
Source: "secret1",
},
{
Source: "secret2",
Target: "my_secret",
UID: "103",
GID: "103",
Mode: uint32Ptr(0o440),
},
},
SecurityOpt: []string{
"label=level:s0:c100,c200",
"label=type:svirt_apache_t",
},
StdinOpen: true,
StopSignal: "SIGUSR1",
StopGracePeriod: durationPtr(20 * time.Second),
Sysctls: map[string]string{
"net.core.somaxconn": "1024",
"net.ipv4.tcp_syncookies": "0",
},
Tmpfs: []string{"/run", "/tmp"},
Tty: true,
Ulimits: map[string]*types.UlimitsConfig{
"nproc": {
Single: 65535,
},
"nofile": {
Soft: 20000,
Hard: 40000,
},
},
User: "someone",
Volumes: []types.ServiceVolumeConfig{
{Target: "/var/lib/mysql", Type: "volume"},
{Source: "/opt/data", Target: "/var/lib/mysql", Type: "bind"},
{Source: workingDir, Target: "/code", Type: "bind"},
{Source: filepath.Join(workingDir, "static"), Target: "/var/www/html", Type: "bind"},
{Source: homeDir + "/configs", Target: "/etc/configs/", Type: "bind", ReadOnly: true},
{Source: "datavolume", Target: "/var/lib/mysql", Type: "volume"},
{Source: filepath.Join(workingDir, "opt"), Target: "/opt", Consistency: "cached", Type: "bind"},
{Target: "/opt", Type: "tmpfs", Tmpfs: &types.ServiceVolumeTmpfs{
Size: int64(10000),
}},
{Source: "group:mygroup", Target: "/srv", Type: "cluster"},
},
WorkingDir: "/code",
},
}
}
func networks() map[string]types.NetworkConfig {
return map[string]types.NetworkConfig{
"some-network": {},
"other-network": {
Driver: "overlay",
DriverOpts: map[string]string{
"foo": "bar",
"baz": "1",
},
Ipam: types.IPAMConfig{
Driver: "overlay",
Config: []*types.IPAMPool{
{Subnet: "172.16.238.0/24"},
{Subnet: "2001:3984:3989::/64"},
},
},
Labels: map[string]string{
"foo": "bar",
},
},
"external-network": {
Name: "external-network",
External: types.External{External: true},
},
"other-external-network": {
Name: "my-cool-network",
External: types.External{External: true},
Extras: map[string]interface{}{
"x-bar": "baz",
"x-foo": "bar",
},
},
}
}
func volumes() map[string]types.VolumeConfig {
return map[string]types.VolumeConfig{
"some-volume": {},
"other-volume": {
Driver: "flocker",
DriverOpts: map[string]string{
"foo": "bar",
"baz": "1",
},
Labels: map[string]string{
"foo": "bar",
},
},
"another-volume": {
Name: "user_specified_name",
Driver: "vsphere",
DriverOpts: map[string]string{
"foo": "bar",
"baz": "1",
},
},
"external-volume": {
Name: "external-volume",
External: types.External{External: true},
},
"other-external-volume": {
Name: "my-cool-volume",
External: types.External{External: true},
},
"external-volume3": {
Name: "this-is-volume3",
External: types.External{External: true},
Extras: map[string]interface{}{
"x-bar": "baz",
"x-foo": "bar",
},
},
"cluster-volume": {
Driver: "my-csi-driver",
Spec: &types.ClusterVolumeSpec{
Group: "mygroup",
AccessMode: &types.AccessMode{
Scope: "single",
Sharing: "none",
BlockVolume: &types.BlockVolume{},
},
AccessibilityRequirements: &types.TopologyRequirement{
Requisite: []types.Topology{
{
Segments: types.Mapping{"region": "R1", "zone": "Z1"},
},
{
Segments: types.Mapping{"region": "R1", "zone": "Z2"},
},
},
Preferred: []types.Topology{
{
Segments: types.Mapping{"region": "R1", "zone": "Z1"},
},
},
},
CapacityRange: &types.CapacityRange{
RequiredBytes: types.UnitBytes(1 * 1024 * 1024 * 1024),
LimitBytes: types.UnitBytes(8 * 1024 * 1024 * 1024),
},
Secrets: []types.VolumeSecret{
{Key: "mycsisecret", Secret: "secret1"},
{Key: "mycsisecret2", Secret: "secret4"},
},
Availability: "active",
},
},
}
}
func configs(workingDir string) map[string]types.ConfigObjConfig {
return map[string]types.ConfigObjConfig{
"config1": {
File: filepath.Join(workingDir, "config_data"),
Labels: map[string]string{
"foo": "bar",
},
},
"config2": {
Name: "my_config",
External: types.External{External: true},
},
"config3": {
Name: "config3",
External: types.External{External: true},
},
"config4": {
Name: "foo",
File: workingDir,
Extras: map[string]interface{}{
"x-bar": "baz",
"x-foo": "bar",
},
},
}
}
func secrets(workingDir string) map[string]types.SecretConfig {
return map[string]types.SecretConfig{
"secret1": {
File: filepath.Join(workingDir, "secret_data"),
Labels: map[string]string{
"foo": "bar",
},
},
"secret2": {
Name: "my_secret",
External: types.External{External: true},
},
"secret3": {
Name: "secret3",
External: types.External{External: true},
},
"secret4": {
Name: "bar",
File: workingDir,
Extras: map[string]interface{}{
"x-bar": "baz",
"x-foo": "bar",
},
},
}
}
func fullExampleYAML(workingDir string) string {
return fmt.Sprintf(`version: "3.10"
services:
foo:
build:
context: ./dir
dockerfile: Dockerfile
args:
foo: bar
labels:
FOO: BAR
cache_from:
- foo
- bar
extra_hosts:
- ipv4.example.com:127.0.0.1
- ipv6.example.com:::1
network: foo
target: foo
cap_add:
- ALL
cap_drop:
- NET_ADMIN
- SYS_ADMIN
cgroup_parent: m-executor-abcd
command:
- bundle
- exec
- thin
- -p
- "3000"
configs:
- source: config1
- source: config2
target: /my_config
uid: "103"
gid: "103"
mode: 288
container_name: my-web-container
depends_on:
- db
- redis
deploy:
mode: replicated
replicas: 6
labels:
FOO: BAR
update_config:
parallelism: 3
delay: 10s
failure_action: continue
monitor: 1m0s
max_failure_ratio: 0.3
order: start-first
rollback_config:
parallelism: 3
delay: 10s
failure_action: continue
monitor: 1m0s
max_failure_ratio: 0.3
order: start-first
resources:
limits:
cpus: "0.001"
memory: "52428800"
pids: 100
reservations:
cpus: "0.0001"
memory: "20971520"
generic_resources:
- discrete_resource_spec:
kind: gpu
value: 2
- discrete_resource_spec:
kind: ssd
value: 1
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
window: 2m0s
placement:
constraints:
- node=foo
preferences:
- spread: node.labels.az
max_replicas_per_node: 5
endpoint_mode: dnsrr
devices:
- /dev/ttyUSB0:/dev/ttyUSB0
dns:
- 8.8.8.8
- 9.9.9.9
dns_search:
- dc1.example.com
- dc2.example.com
domainname: foo.com
entrypoint:
- /code/entrypoint.sh
- -p
- "3000"
environment:
BAR: bar_from_env_file_2
BAZ: baz_from_service_def
FOO: foo_from_env_file
QUX: qux_from_environment
env_file:
- ./example1.env
- ./example2.env
expose:
- "3000"
- "8000"
external_links:
- redis_1
- project_db_1:mysql
- project_db_1:postgresql
extra_hosts:
- somehost:162.242.195.82
- otherhost:50.31.209.229
- host.docker.internal:host-gateway
hostname: foo
healthcheck:
test:
- CMD-SHELL
- echo "hello world"
timeout: 1s
interval: 10s
retries: 5
start_period: 15s
image: redis
ipc: host
labels:
com.example.description: Accounting webapp
com.example.empty-label: ""
com.example.number: "42"
links:
- db
- db:database
- redis
logging:
driver: syslog
options:
syslog-address: tcp://192.168.0.42:123
mac_address: 02:42:ac:11:65:43
network_mode: container:0cfeab0f748b9a743dc3da582046357c6ef497631c1a016d28d2bf9b4f899f7b
networks:
other-network:
ipv4_address: 172.16.238.10
ipv6_address: 2001:3984:3989::10
other-other-network: null
some-network:
aliases:
- alias1
- alias3
pid: host
ports:
- mode: ingress
target: 3000
protocol: tcp
- mode: ingress
target: 3001
protocol: tcp
- mode: ingress
target: 3002
protocol: tcp
- mode: ingress
target: 3003
protocol: tcp
- mode: ingress
target: 3004
protocol: tcp
- mode: ingress
target: 3005
protocol: tcp
- mode: ingress
target: 8000
published: 8000
protocol: tcp
- mode: ingress
target: 8080
published: 9090
protocol: tcp
- mode: ingress
target: 8081
published: 9091
protocol: tcp
- mode: ingress
target: 22
published: 49100
protocol: tcp
- mode: ingress
target: 8001
published: 8001
protocol: tcp
- mode: ingress
target: 5000
published: 5000
protocol: tcp
- mode: ingress
target: 5001
published: 5001
protocol: tcp
- mode: ingress
target: 5002
published: 5002
protocol: tcp
- mode: ingress
target: 5003
published: 5003
protocol: tcp
- mode: ingress
target: 5004
published: 5004
protocol: tcp
- mode: ingress
target: 5005
published: 5005
protocol: tcp
- mode: ingress
target: 5006
published: 5006
protocol: tcp
- mode: ingress
target: 5007
published: 5007
protocol: tcp
- mode: ingress
target: 5008
published: 5008
protocol: tcp
- mode: ingress
target: 5009
published: 5009
protocol: tcp
- mode: ingress
target: 5010
published: 5010
protocol: tcp
privileged: true
read_only: true
restart: always
secrets:
- source: secret1
- source: secret2
target: my_secret
uid: "103"
gid: "103"
mode: 288
security_opt:
- label=level:s0:c100,c200
- label=type:svirt_apache_t
stdin_open: true
stop_grace_period: 20s
stop_signal: SIGUSR1
sysctls:
net.core.somaxconn: "1024"
net.ipv4.tcp_syncookies: "0"
tmpfs:
- /run
- /tmp
tty: true
ulimits:
nofile:
soft: 20000
hard: 40000
nproc: 65535
user: someone
volumes:
- type: volume
target: /var/lib/mysql
- type: bind
source: /opt/data
target: /var/lib/mysql
- type: bind
source: /foo
target: /code
- type: bind
source: %s
target: /var/www/html
- type: bind
source: /bar/configs
target: /etc/configs/
read_only: true
- type: volume
source: datavolume
target: /var/lib/mysql
- type: bind
source: %s
target: /opt
consistency: cached
- type: tmpfs
target: /opt
tmpfs:
size: 10000
- type: cluster
source: group:mygroup
target: /srv
working_dir: /code
x-bar: baz
x-foo: bar
networks:
external-network:
name: external-network
external: true
other-external-network:
name: my-cool-network
external: true
x-bar: baz
x-foo: bar
other-network:
driver: overlay
driver_opts:
baz: "1"
foo: bar
ipam:
driver: overlay
config:
- subnet: 172.16.238.0/24
- subnet: 2001:3984:3989::/64
labels:
foo: bar
some-network: {}
volumes:
another-volume:
name: user_specified_name
driver: vsphere
driver_opts:
baz: "1"
foo: bar
cluster-volume:
driver: my-csi-driver
x-cluster-spec:
group: mygroup
access_mode:
scope: single
sharing: none
block_volume: {}
accessibility_requirements:
requisite:
- segments:
region: R1
zone: Z1
- segments:
region: R1
zone: Z2
preferred:
- segments:
region: R1
zone: Z1
capacity_range:
required_bytes: "1073741824"
limit_bytes: "8589934592"
secrets:
- key: mycsisecret
secret: secret1
- key: mycsisecret2
secret: secret4
availability: active
external-volume:
name: external-volume
external: true
external-volume3:
name: this-is-volume3
external: true
x-bar: baz
x-foo: bar
other-external-volume:
name: my-cool-volume
external: true
other-volume:
driver: flocker
driver_opts:
baz: "1"
foo: bar
labels:
foo: bar
some-volume: {}
secrets:
secret1:
file: %s/secret_data
labels:
foo: bar
secret2:
name: my_secret
external: true
secret3:
name: secret3
external: true
secret4:
name: bar
file: %s
x-bar: baz
x-foo: bar
configs:
config1:
file: %s/config_data
labels:
foo: bar
config2:
name: my_config
external: true
config3:
name: config3
external: true
config4:
name: foo
file: %s
x-bar: baz
x-foo: bar
x-bar: baz
x-foo: bar
x-nested:
bar: baz
foo: bar
`,
filepath.Join(workingDir, "static"),
filepath.Join(workingDir, "opt"),
workingDir,
workingDir,
workingDir,
workingDir)
}
func fullExampleJSON(workingDir string) string {
return fmt.Sprintf(`{
"configs": {
"config1": {
"file": "%s/config_data",
"external": false,
"labels": {
"foo": "bar"
}
},
"config2": {
"name": "my_config",
"external": true
},
"config3": {
"name": "config3",
"external": true
},
"config4": {
"name": "foo",
"file": "%s",
"external": false
}
},
"networks": {
"external-network": {
"name": "external-network",
"ipam": {},
"external": true
},
"other-external-network": {
"name": "my-cool-network",
"ipam": {},
"external": true
},
"other-network": {
"driver": "overlay",
"driver_opts": {
"baz": "1",
"foo": "bar"
},
"ipam": {
"driver": "overlay",
"config": [
{
"subnet": "172.16.238.0/24"
},
{
"subnet": "2001:3984:3989::/64"
}
]
},
"external": false,
"labels": {
"foo": "bar"
}
},
"some-network": {
"ipam": {},
"external": false
}
},
"secrets": {
"secret1": {
"file": "%s/secret_data",
"external": false,
"labels": {
"foo": "bar"
}
},
"secret2": {
"name": "my_secret",
"external": true
},
"secret3": {
"name": "secret3",
"external": true
},
"secret4": {
"name": "bar",
"file": "%s",
"external": false
}
},
"services": {
"foo": {
"build": {
"context": "./dir",
"dockerfile": "Dockerfile",
"args": {
"foo": "bar"
},
"labels": {
"FOO": "BAR"
},
"cache_from": [
"foo",
"bar"
],
"extra_hosts": [
"ipv4.example.com:127.0.0.1",
"ipv6.example.com:::1"
],
"network": "foo",
"target": "foo"
},
"cap_add": [
"ALL"
],
"cap_drop": [
"NET_ADMIN",
"SYS_ADMIN"
],
"cgroup_parent": "m-executor-abcd",
"command": [
"bundle",
"exec",
"thin",
"-p",
"3000"
],
"configs": [
{
"source": "config1"
},
{
"source": "config2",
"target": "/my_config",
"uid": "103",
"gid": "103",
"mode": 288
}
],
"container_name": "my-web-container",
"credential_spec": {},
"depends_on": [
"db",
"redis"
],
"deploy": {
"mode": "replicated",
"replicas": 6,
"labels": {
"FOO": "BAR"
},
"update_config": {
"parallelism": 3,
"delay": "10s",
"failure_action": "continue",
"monitor": "1m0s",
"max_failure_ratio": 0.3,
"order": "start-first"
},
"rollback_config": {
"parallelism": 3,
"delay": "10s",
"failure_action": "continue",
"monitor": "1m0s",
"max_failure_ratio": 0.3,
"order": "start-first"
},
"resources": {
"limits": {
"cpus": "0.001",
"memory": "52428800",
"pids": 100
},
"reservations": {
"cpus": "0.0001",
"memory": "20971520",
"generic_resources": [
{
"discrete_resource_spec": {
"kind": "gpu",
"value": 2
}
},
{
"discrete_resource_spec": {
"kind": "ssd",
"value": 1
}
}
]
}
},
"restart_policy": {
"condition": "on-failure",
"delay": "5s",
"max_attempts": 3,
"window": "2m0s"
},
"placement": {
"constraints": [
"node=foo"
],
"preferences": [
{
"spread": "node.labels.az"
}
],
"max_replicas_per_node": 5
},
"endpoint_mode": "dnsrr"
},
"devices": [
"/dev/ttyUSB0:/dev/ttyUSB0"
],
"dns": [
"8.8.8.8",
"9.9.9.9"
],
"dns_search": [
"dc1.example.com",
"dc2.example.com"
],
"domainname": "foo.com",
"entrypoint": [
"/code/entrypoint.sh",
"-p",
"3000"
],
"environment": {
"BAR": "bar_from_env_file_2",
"BAZ": "baz_from_service_def",
"FOO": "foo_from_env_file",
"QUX": "qux_from_environment"
},
"env_file": [
"./example1.env",
"./example2.env"
],
"expose": [
"3000",
"8000"
],
"external_links": [
"redis_1",
"project_db_1:mysql",
"project_db_1:postgresql"
],
"extra_hosts": [
"somehost:162.242.195.82",
"otherhost:50.31.209.229",
"host.docker.internal:host-gateway"
],
"hostname": "foo",
"healthcheck": {
"test": [
"CMD-SHELL",
"echo \"hello world\""
],
"timeout": "1s",
"interval": "10s",
"retries": 5,
"start_period": "15s"
},
"image": "redis",
"ipc": "host",
"labels": {
"com.example.description": "Accounting webapp",
"com.example.empty-label": "",
"com.example.number": "42"
},
"links": [
"db",
"db:database",
"redis"
],
"logging": {
"driver": "syslog",
"options": {
"syslog-address": "tcp://192.168.0.42:123"
}
},
"mac_address": "02:42:ac:11:65:43",
"network_mode": "container:0cfeab0f748b9a743dc3da582046357c6ef497631c1a016d28d2bf9b4f899f7b",
"networks": {
"other-network": {
"ipv4_address": "172.16.238.10",
"ipv6_address": "2001:3984:3989::10"
},
"other-other-network": null,
"some-network": {
"aliases": [
"alias1",
"alias3"
]
}
},
"pid": "host",
"ports": [
{
"mode": "ingress",
"target": 3000,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 3001,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 3002,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 3003,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 3004,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 3005,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 8000,
"published": 8000,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 8080,
"published": 9090,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 8081,
"published": 9091,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 22,
"published": 49100,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 8001,
"published": 8001,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 5000,
"published": 5000,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 5001,
"published": 5001,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 5002,
"published": 5002,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 5003,
"published": 5003,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 5004,
"published": 5004,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 5005,
"published": 5005,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 5006,
"published": 5006,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 5007,
"published": 5007,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 5008,
"published": 5008,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 5009,
"published": 5009,
"protocol": "tcp"
},
{
"mode": "ingress",
"target": 5010,
"published": 5010,
"protocol": "tcp"
}
],
"privileged": true,
"read_only": true,
"restart": "always",
"secrets": [
{
"source": "secret1"
},
{
"source": "secret2",
"target": "my_secret",
"uid": "103",
"gid": "103",
"mode": 288
}
],
"security_opt": [
"label=level:s0:c100,c200",
"label=type:svirt_apache_t"
],
"stdin_open": true,
"stop_grace_period": "20s",
"stop_signal": "SIGUSR1",
"sysctls": {
"net.core.somaxconn": "1024",
"net.ipv4.tcp_syncookies": "0"
},
"tmpfs": [
"/run",
"/tmp"
],
"tty": true,
"ulimits": {
"nofile": {
"soft": 20000,
"hard": 40000
},
"nproc": 65535
},
"user": "someone",
"volumes": [
{
"type": "volume",
"target": "/var/lib/mysql"
},
{
"type": "bind",
"source": "/opt/data",
"target": "/var/lib/mysql"
},
{
"type": "bind",
"source": "/foo",
"target": "/code"
},
{
"type": "bind",
"source": "%s",
"target": "/var/www/html"
},
{
"type": "bind",
"source": "/bar/configs",
"target": "/etc/configs/",
"read_only": true
},
{
"type": "volume",
"source": "datavolume",
"target": "/var/lib/mysql"
},
{
"type": "bind",
"source": "%s",
"target": "/opt",
"consistency": "cached"
},
{
"type": "tmpfs",
"target": "/opt",
"tmpfs": {
"size": 10000
}
},
{
"type": "cluster",
"source": "group:mygroup",
"target": "/srv"
}
],
"working_dir": "/code"
}
},
"version": "3.10",
"volumes": {
"another-volume": {
"name": "user_specified_name",
"driver": "vsphere",
"driver_opts": {
"baz": "1",
"foo": "bar"
},
"external": false
},
"cluster-volume": {
"driver": "my-csi-driver",
"external": false,
"x-cluster-spec": {
"group": "mygroup",
"access_mode": {
"scope": "single",
"sharing": "none",
"block_volume": {}
},
"accessibility_requirements": {
"requisite": [
{
"segments": {
"region": "R1",
"zone": "Z1"
}
},
{
"segments": {
"region": "R1",
"zone": "Z2"
}
}
],
"preferred": [
{
"segments": {
"region": "R1",
"zone": "Z1"
}
}
]
},
"capacity_range": {
"required_bytes": "1073741824",
"limit_bytes": "8589934592"
},
"secrets": [
{
"key": "mycsisecret",
"secret": "secret1"
},
{
"key": "mycsisecret2",
"secret": "secret4"
}
],
"availability": "active"
}
},
"external-volume": {
"name": "external-volume",
"external": true
},
"external-volume3": {
"name": "this-is-volume3",
"external": true
},
"other-external-volume": {
"name": "my-cool-volume",
"external": true
},
"other-volume": {
"driver": "flocker",
"driver_opts": {
"baz": "1",
"foo": "bar"
},
"external": false,
"labels": {
"foo": "bar"
}
},
"some-volume": {
"external": false
}
},
"x-bar": "baz",
"x-foo": "bar",
"x-nested": {
"bar": "baz",
"foo": "bar"
}
}`,
workingDir,
workingDir,
workingDir,
workingDir,
filepath.Join(workingDir, "static"),
filepath.Join(workingDir, "opt"))
}