Merge pull request #1326 from shin-/compose-json-annotations

Allow marshalling of Compose config to JSON
This commit is contained in:
Sebastiaan van Stijn 2018-09-11 14:40:21 +02:00 committed by GitHub
commit 11ef349c58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 852 additions and 187 deletions

View File

@ -222,7 +222,7 @@ func fromComposeServiceConfig(s composeTypes.ServiceConfig) v1beta2.ServiceConfi
ReadOnly: s.ReadOnly, ReadOnly: s.ReadOnly,
Secrets: fromComposeServiceSecrets(s.Secrets), Secrets: fromComposeServiceSecrets(s.Secrets),
StdinOpen: s.StdinOpen, StdinOpen: s.StdinOpen,
StopGracePeriod: s.StopGracePeriod, StopGracePeriod: composetypes.ConvertDurationPtr(s.StopGracePeriod),
Tmpfs: s.Tmpfs, Tmpfs: s.Tmpfs,
Tty: s.Tty, Tty: s.Tty,
User: userID, User: userID,
@ -285,8 +285,8 @@ func fromComposeHealthcheck(h *composeTypes.HealthCheckConfig) *v1beta2.HealthCh
} }
return &v1beta2.HealthCheckConfig{ return &v1beta2.HealthCheckConfig{
Test: h.Test, Test: h.Test,
Timeout: h.Timeout, Timeout: composetypes.ConvertDurationPtr(h.Timeout),
Interval: h.Interval, Interval: composetypes.ConvertDurationPtr(h.Interval),
Retries: h.Retries, Retries: h.Retries,
} }
} }

View File

@ -10,7 +10,7 @@ import (
) )
func TestWarnings(t *testing.T) { func TestWarnings(t *testing.T) {
duration := 5 * time.Second duration := composetypes.Duration(5 * time.Second)
attempts := uint64(3) attempts := uint64(3)
config := &composetypes.Config{ config := &composetypes.Config{
Version: "3.4", Version: "3.4",
@ -26,9 +26,9 @@ func TestWarnings(t *testing.T) {
DependsOn: []string{"ignored"}, DependsOn: []string{"ignored"},
Deploy: composetypes.DeployConfig{ Deploy: composetypes.DeployConfig{
UpdateConfig: &composetypes.UpdateConfig{ UpdateConfig: &composetypes.UpdateConfig{
Delay: 5 * time.Second, Delay: composetypes.Duration(5 * time.Second),
FailureAction: "rollback", FailureAction: "rollback",
Monitor: 10 * time.Second, Monitor: composetypes.Duration(10 * time.Second),
MaxFailureRatio: 0.5, MaxFailureRatio: 0.5,
}, },
RestartPolicy: &composetypes.RestartPolicy{ RestartPolicy: &composetypes.RestartPolicy{

View File

@ -141,7 +141,7 @@ func Service(
Dir: service.WorkingDir, Dir: service.WorkingDir,
User: service.User, User: service.User,
Mounts: mounts, Mounts: mounts,
StopGracePeriod: service.StopGracePeriod, StopGracePeriod: composetypes.ConvertDurationPtr(service.StopGracePeriod),
StopSignal: service.StopSignal, StopSignal: service.StopSignal,
TTY: service.Tty, TTY: service.Tty,
OpenStdin: service.StdinOpen, OpenStdin: service.StdinOpen,
@ -414,13 +414,13 @@ func convertHealthcheck(healthcheck *composetypes.HealthCheckConfig) (*container
} }
if healthcheck.Timeout != nil { if healthcheck.Timeout != nil {
timeout = *healthcheck.Timeout timeout = time.Duration(*healthcheck.Timeout)
} }
if healthcheck.Interval != nil { if healthcheck.Interval != nil {
interval = *healthcheck.Interval interval = time.Duration(*healthcheck.Interval)
} }
if healthcheck.StartPeriod != nil { if healthcheck.StartPeriod != nil {
startPeriod = *healthcheck.StartPeriod startPeriod = time.Duration(*healthcheck.StartPeriod)
} }
if healthcheck.Retries != nil { if healthcheck.Retries != nil {
retries = int(*healthcheck.Retries) retries = int(*healthcheck.Retries)
@ -458,11 +458,12 @@ func convertRestartPolicy(restart string, source *composetypes.RestartPolicy) (*
return nil, errors.Errorf("unknown restart policy: %s", restart) return nil, errors.Errorf("unknown restart policy: %s", restart)
} }
} }
return &swarm.RestartPolicy{ return &swarm.RestartPolicy{
Condition: swarm.RestartPolicyCondition(source.Condition), Condition: swarm.RestartPolicyCondition(source.Condition),
Delay: source.Delay, Delay: composetypes.ConvertDurationPtr(source.Delay),
MaxAttempts: source.MaxAttempts, MaxAttempts: source.MaxAttempts,
Window: source.Window, Window: composetypes.ConvertDurationPtr(source.Window),
}, nil }, nil
} }
@ -476,9 +477,9 @@ func convertUpdateConfig(source *composetypes.UpdateConfig) *swarm.UpdateConfig
} }
return &swarm.UpdateConfig{ return &swarm.UpdateConfig{
Parallelism: parallel, Parallelism: parallel,
Delay: source.Delay, Delay: time.Duration(source.Delay),
FailureAction: source.FailureAction, FailureAction: source.FailureAction,
Monitor: source.Monitor, Monitor: time.Duration(source.Monitor),
MaxFailureRatio: source.MaxFailureRatio, MaxFailureRatio: source.MaxFailureRatio,
Order: source.Order, Order: source.Order,
} }

View File

@ -124,8 +124,8 @@ func TestConvertResourcesOnlyMemory(t *testing.T) {
func TestConvertHealthcheck(t *testing.T) { func TestConvertHealthcheck(t *testing.T) {
retries := uint64(10) retries := uint64(10)
timeout := 30 * time.Second timeout := composetypes.Duration(30 * time.Second)
interval := 2 * time.Millisecond interval := composetypes.Duration(2 * time.Millisecond)
source := &composetypes.HealthCheckConfig{ source := &composetypes.HealthCheckConfig{
Test: []string{"EXEC", "touch", "/foo"}, Test: []string{"EXEC", "touch", "/foo"},
Timeout: &timeout, Timeout: &timeout,
@ -134,8 +134,8 @@ func TestConvertHealthcheck(t *testing.T) {
} }
expected := &container.HealthConfig{ expected := &container.HealthConfig{
Test: source.Test, Test: source.Test,
Timeout: timeout, Timeout: time.Duration(timeout),
Interval: interval, Interval: time.Duration(interval),
Retries: 10, Retries: 10,
} }

View File

@ -65,17 +65,17 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
Labels: map[string]string{"FOO": "BAR"}, Labels: map[string]string{"FOO": "BAR"},
RollbackConfig: &types.UpdateConfig{ RollbackConfig: &types.UpdateConfig{
Parallelism: uint64Ptr(3), Parallelism: uint64Ptr(3),
Delay: time.Duration(10 * time.Second), Delay: types.Duration(10 * time.Second),
FailureAction: "continue", FailureAction: "continue",
Monitor: time.Duration(60 * time.Second), Monitor: types.Duration(60 * time.Second),
MaxFailureRatio: 0.3, MaxFailureRatio: 0.3,
Order: "start-first", Order: "start-first",
}, },
UpdateConfig: &types.UpdateConfig{ UpdateConfig: &types.UpdateConfig{
Parallelism: uint64Ptr(3), Parallelism: uint64Ptr(3),
Delay: time.Duration(10 * time.Second), Delay: types.Duration(10 * time.Second),
FailureAction: "continue", FailureAction: "continue",
Monitor: time.Duration(60 * time.Second), Monitor: types.Duration(60 * time.Second),
MaxFailureRatio: 0.3, MaxFailureRatio: 0.3,
Order: "start-first", Order: "start-first",
}, },
@ -344,7 +344,7 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
}, },
StdinOpen: true, StdinOpen: true,
StopSignal: "SIGUSR1", StopSignal: "SIGUSR1",
StopGracePeriod: durationPtr(time.Duration(20 * time.Second)), StopGracePeriod: durationPtr(20 * time.Second),
Tmpfs: []string{"/run", "/tmp"}, Tmpfs: []string{"/run", "/tmp"},
Tty: true, Tty: true,
Ulimits: map[string]*types.UlimitsConfig{ Ulimits: map[string]*types.UlimitsConfig{
@ -887,3 +887,551 @@ x-nested:
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"
],
"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"
},
"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"
}
]
},
"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"
],
"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",
"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
}
}
],
"working_dir": "/code"
}
},
"version": "3.7",
"volumes": {
"another-volume": {
"name": "user_specified_name",
"driver": "vsphere",
"driver_opts": {
"baz": "1",
"foo": "bar"
},
"external": false
},
"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"))
}

View File

@ -7,6 +7,7 @@ import (
"reflect" "reflect"
"sort" "sort"
"strings" "strings"
"time"
interp "github.com/docker/cli/cli/compose/interpolation" interp "github.com/docker/cli/cli/compose/interpolation"
"github.com/docker/cli/cli/compose/schema" "github.com/docker/cli/cli/compose/schema"
@ -309,6 +310,7 @@ func createTransformHook(additionalTransformers ...Transformer) mapstructure.Dec
reflect.TypeOf(types.HostsList{}): transformListOrMappingFunc(":", false), reflect.TypeOf(types.HostsList{}): transformListOrMappingFunc(":", false),
reflect.TypeOf(types.ServiceVolumeConfig{}): transformServiceVolumeConfig, reflect.TypeOf(types.ServiceVolumeConfig{}): transformServiceVolumeConfig,
reflect.TypeOf(types.BuildConfig{}): transformBuildConfig, reflect.TypeOf(types.BuildConfig{}): transformBuildConfig,
reflect.TypeOf(types.Duration(0)): transformStringToDuration,
} }
for _, transformer := range additionalTransformers { for _, transformer := range additionalTransformers {
@ -854,6 +856,19 @@ func transformSize(value interface{}) (interface{}, error) {
panic(errors.Errorf("invalid type for size %T", value)) panic(errors.Errorf("invalid type for size %T", value))
} }
func transformStringToDuration(value interface{}) (interface{}, error) {
switch value := value.(type) {
case string:
d, err := time.ParseDuration(value)
if err != nil {
return value, err
}
return types.Duration(d), nil
default:
return value, errors.Errorf("invalid type %T for duration", value)
}
}
func toServicePortConfigs(value string) ([]interface{}, error) { func toServicePortConfigs(value string) ([]interface{}, error) {
var portConfigs []interface{} var portConfigs []interface{}

View File

@ -806,15 +806,16 @@ volumes:
external_volume: external_volume:
name: user_specified_name name: user_specified_name
external: external:
name: external_name name: external_name
`) `)
assert.ErrorContains(t, err, "volume.external.name and volume.name conflict; only use volume.name") assert.ErrorContains(t, err, "volume.external.name and volume.name conflict; only use volume.name")
assert.ErrorContains(t, err, "external_volume") assert.ErrorContains(t, err, "external_volume")
} }
func durationPtr(value time.Duration) *time.Duration { func durationPtr(value time.Duration) *types.Duration {
return &value result := types.Duration(value)
return &result
} }
func uint64Ptr(value uint64) *uint64 { func uint64Ptr(value uint64) *uint64 {
@ -1281,7 +1282,7 @@ secrets:
external_secret: external_secret:
name: user_specified_name name: user_specified_name
external: external:
name: external_name name: external_name
`) `)
assert.ErrorContains(t, err, "secret.external.name and secret.name conflict; only use secret.name") assert.ErrorContains(t, err, "secret.external.name and secret.name conflict; only use secret.name")
@ -1368,7 +1369,7 @@ networks:
foo: foo:
name: user_specified_name name: user_specified_name
external: external:
name: external_name name: external_name
`) `)
assert.ErrorContains(t, err, "network.external.name and network.name conflict; only use network.name") assert.ErrorContains(t, err, "network.external.name and network.name conflict; only use network.name")

View File

@ -1,6 +1,7 @@
package loader package loader
import ( import (
"encoding/json"
"testing" "testing"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
@ -24,3 +25,19 @@ func TestMarshallConfig(t *testing.T) {
_, err = Load(buildConfigDetails(dict, map[string]string{})) _, err = Load(buildConfigDetails(dict, map[string]string{}))
assert.NilError(t, err) assert.NilError(t, err)
} }
func TestJSONMarshallConfig(t *testing.T) {
workingDir := "/foo"
homeDir := "/bar"
cfg := fullExampleConfig(workingDir, homeDir)
expected := fullExampleJSON(workingDir)
actual, err := json.MarshalIndent(cfg, "", " ")
assert.NilError(t, err)
assert.Check(t, is.Equal(expected, string(actual)))
dict, err := ParseYAML([]byte(expected))
assert.NilError(t, err)
_, err = Load(buildConfigDetails(dict, map[string]string{}))
assert.NilError(t, err)
}

View File

@ -1,6 +1,7 @@
package types package types
import ( import (
"encoding/json"
"fmt" "fmt"
"time" "time"
) )
@ -62,6 +63,32 @@ type ConfigDetails struct {
Environment map[string]string Environment map[string]string
} }
// Duration is a thin wrapper around time.Duration with improved JSON marshalling
type Duration time.Duration
func (d Duration) String() string {
return time.Duration(d).String()
}
// ConvertDurationPtr converts a typedefined Duration pointer to a time.Duration pointer with the same value.
func ConvertDurationPtr(d *Duration) *time.Duration {
if d == nil {
return nil
}
res := time.Duration(*d)
return &res
}
// MarshalJSON makes Duration implement json.Marshaler
func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}
// MarshalYAML makes Duration implement yaml.Marshaler
func (d Duration) MarshalYAML() (interface{}, error) {
return d.String(), nil
}
// LookupEnv provides a lookup function for environment variables // LookupEnv provides a lookup function for environment variables
func (cd ConfigDetails) LookupEnv(key string) (string, bool) { func (cd ConfigDetails) LookupEnv(key string) (string, bool) {
v, ok := cd.Environment[key] v, ok := cd.Environment[key]
@ -70,14 +97,39 @@ func (cd ConfigDetails) LookupEnv(key string) (string, bool) {
// Config is a full compose file configuration // Config is a full compose file configuration
type Config struct { type Config struct {
Filename string `yaml:"-"` Filename string `yaml:"-" json:"-"`
Version string Version string `json:"version"`
Services Services Services Services `json:"services"`
Networks map[string]NetworkConfig `yaml:",omitempty"` Networks map[string]NetworkConfig `yaml:",omitempty" json:"networks,omitempty"`
Volumes map[string]VolumeConfig `yaml:",omitempty"` Volumes map[string]VolumeConfig `yaml:",omitempty" json:"volumes,omitempty"`
Secrets map[string]SecretConfig `yaml:",omitempty"` Secrets map[string]SecretConfig `yaml:",omitempty" json:"secrets,omitempty"`
Configs map[string]ConfigObjConfig `yaml:",omitempty"` Configs map[string]ConfigObjConfig `yaml:",omitempty" json:"configs,omitempty"`
Extras map[string]interface{} `yaml:",inline"` Extras map[string]interface{} `yaml:",inline", json:"-"`
}
// MarshalJSON makes Config implement json.Marshaler
func (c Config) MarshalJSON() ([]byte, error) {
m := map[string]interface{}{
"version": c.Version,
"services": c.Services,
}
if len(c.Networks) > 0 {
m["networks"] = c.Networks
}
if len(c.Volumes) > 0 {
m["volumes"] = c.Volumes
}
if len(c.Secrets) > 0 {
m["secrets"] = c.Secrets
}
if len(c.Configs) > 0 {
m["configs"] = c.Configs
}
for k, v := range c.Extras {
m[k] = v
}
return json.Marshal(m)
} }
// Services is a list of ServiceConfig // Services is a list of ServiceConfig
@ -92,75 +144,84 @@ func (s Services) MarshalYAML() (interface{}, error) {
return services, nil return services, nil
} }
// MarshalJSON makes Services implement json.Marshaler
func (s Services) MarshalJSON() ([]byte, error) {
data, err := s.MarshalYAML()
if err != nil {
return nil, err
}
return json.MarshalIndent(data, "", " ")
}
// ServiceConfig is the configuration of one service // ServiceConfig is the configuration of one service
type ServiceConfig struct { type ServiceConfig struct {
Name string `yaml:"-"` Name string `yaml:"-" json:"-"`
Build BuildConfig `yaml:",omitempty"` Build BuildConfig `yaml:",omitempty" json:"build,omitempty"`
CapAdd []string `mapstructure:"cap_add" yaml:"cap_add,omitempty"` CapAdd []string `mapstructure:"cap_add" yaml:"cap_add,omitempty" json:"cap_add,omitempty"`
CapDrop []string `mapstructure:"cap_drop" yaml:"cap_drop,omitempty"` CapDrop []string `mapstructure:"cap_drop" yaml:"cap_drop,omitempty" json:"cap_drop,omitempty"`
CgroupParent string `mapstructure:"cgroup_parent" yaml:"cgroup_parent,omitempty"` CgroupParent string `mapstructure:"cgroup_parent" yaml:"cgroup_parent,omitempty" json:"cgroup_parent,omitempty"`
Command ShellCommand `yaml:",omitempty"` Command ShellCommand `yaml:",omitempty" json:"command,omitempty"`
Configs []ServiceConfigObjConfig `yaml:",omitempty"` Configs []ServiceConfigObjConfig `yaml:",omitempty" json:"configs,omitempty"`
ContainerName string `mapstructure:"container_name" yaml:"container_name,omitempty"` ContainerName string `mapstructure:"container_name" yaml:"container_name,omitempty" json:"container_name,omitempty"`
CredentialSpec CredentialSpecConfig `mapstructure:"credential_spec" yaml:"credential_spec,omitempty"` CredentialSpec CredentialSpecConfig `mapstructure:"credential_spec" yaml:"credential_spec,omitempty" json:"credential_spec,omitempty"`
DependsOn []string `mapstructure:"depends_on" yaml:"depends_on,omitempty"` DependsOn []string `mapstructure:"depends_on" yaml:"depends_on,omitempty" json:"depends_on,omitempty"`
Deploy DeployConfig `yaml:",omitempty"` Deploy DeployConfig `yaml:",omitempty" json:"deploy,omitempty"`
Devices []string `yaml:",omitempty"` Devices []string `yaml:",omitempty" json:"devices,omitempty"`
DNS StringList `yaml:",omitempty"` DNS StringList `yaml:",omitempty" json:"dns,omitempty"`
DNSSearch StringList `mapstructure:"dns_search" yaml:"dns_search,omitempty"` DNSSearch StringList `mapstructure:"dns_search" yaml:"dns_search,omitempty" json:"dns_search,omitempty"`
DomainName string `mapstructure:"domainname" yaml:"domainname,omitempty"` DomainName string `mapstructure:"domainname" yaml:"domainname,omitempty" json:"domainname,omitempty"`
Entrypoint ShellCommand `yaml:",omitempty"` Entrypoint ShellCommand `yaml:",omitempty" json:"entrypoint,omitempty"`
Environment MappingWithEquals `yaml:",omitempty"` Environment MappingWithEquals `yaml:",omitempty" json:"environment,omitempty"`
EnvFile StringList `mapstructure:"env_file" yaml:"env_file,omitempty"` EnvFile StringList `mapstructure:"env_file" yaml:"env_file,omitempty" json:"env_file,omitempty"`
Expose StringOrNumberList `yaml:",omitempty"` Expose StringOrNumberList `yaml:",omitempty" json:"expose,omitempty"`
ExternalLinks []string `mapstructure:"external_links" yaml:"external_links,omitempty"` ExternalLinks []string `mapstructure:"external_links" yaml:"external_links,omitempty" json:"external_links,omitempty"`
ExtraHosts HostsList `mapstructure:"extra_hosts" yaml:"extra_hosts,omitempty"` ExtraHosts HostsList `mapstructure:"extra_hosts" yaml:"extra_hosts,omitempty" json:"extra_hosts,omitempty"`
Hostname string `yaml:",omitempty"` Hostname string `yaml:",omitempty" json:"hostname,omitempty"`
HealthCheck *HealthCheckConfig `yaml:",omitempty"` HealthCheck *HealthCheckConfig `yaml:",omitempty" json:"healthcheck,omitempty"`
Image string `yaml:",omitempty"` Image string `yaml:",omitempty" json:"image,omitempty"`
Init *bool `yaml:",omitempty"` Init *bool `yaml:",omitempty" json:"init,omitempty"`
Ipc string `yaml:",omitempty"` Ipc string `yaml:",omitempty" json:"ipc,omitempty"`
Isolation string `mapstructure:"isolation" yaml:"isolation,omitempty"` Isolation string `mapstructure:"isolation" yaml:"isolation,omitempty" json:"isolation,omitempty"`
Labels Labels `yaml:",omitempty"` Labels Labels `yaml:",omitempty" json:"labels,omitempty"`
Links []string `yaml:",omitempty"` Links []string `yaml:",omitempty" json:"links,omitempty"`
Logging *LoggingConfig `yaml:",omitempty"` Logging *LoggingConfig `yaml:",omitempty" json:"logging,omitempty"`
MacAddress string `mapstructure:"mac_address" yaml:"mac_address,omitempty"` MacAddress string `mapstructure:"mac_address" yaml:"mac_address,omitempty" json:"mac_address,omitempty"`
NetworkMode string `mapstructure:"network_mode" yaml:"network_mode,omitempty"` NetworkMode string `mapstructure:"network_mode" yaml:"network_mode,omitempty" json:"network_mode,omitempty"`
Networks map[string]*ServiceNetworkConfig `yaml:",omitempty"` Networks map[string]*ServiceNetworkConfig `yaml:",omitempty" json:"networks,omitempty"`
Pid string `yaml:",omitempty"` Pid string `yaml:",omitempty" json:"pid,omitempty"`
Ports []ServicePortConfig `yaml:",omitempty"` Ports []ServicePortConfig `yaml:",omitempty" json:"ports,omitempty"`
Privileged bool `yaml:",omitempty"` Privileged bool `yaml:",omitempty" json:"privileged,omitempty"`
ReadOnly bool `mapstructure:"read_only" yaml:"read_only,omitempty"` ReadOnly bool `mapstructure:"read_only" yaml:"read_only,omitempty" json:"read_only,omitempty"`
Restart string `yaml:",omitempty"` Restart string `yaml:",omitempty" json:"restart,omitempty"`
Secrets []ServiceSecretConfig `yaml:",omitempty"` Secrets []ServiceSecretConfig `yaml:",omitempty" json:"secrets,omitempty"`
SecurityOpt []string `mapstructure:"security_opt" yaml:"security_opt,omitempty"` SecurityOpt []string `mapstructure:"security_opt" yaml:"security_opt,omitempty" json:"security_opt,omitempty"`
ShmSize string `mapstructure:"shm_size" yaml:"shm_size,omitempty"` ShmSize string `mapstructure:"shm_size" yaml:"shm_size,omitempty" json:"shm_size,omitempty"`
StdinOpen bool `mapstructure:"stdin_open" yaml:"stdin_open,omitempty"` StdinOpen bool `mapstructure:"stdin_open" yaml:"stdin_open,omitempty" json:"stdin_open,omitempty"`
StopGracePeriod *time.Duration `mapstructure:"stop_grace_period" yaml:"stop_grace_period,omitempty"` StopGracePeriod *Duration `mapstructure:"stop_grace_period" yaml:"stop_grace_period,omitempty" json:"stop_grace_period,omitempty"`
StopSignal string `mapstructure:"stop_signal" yaml:"stop_signal,omitempty"` StopSignal string `mapstructure:"stop_signal" yaml:"stop_signal,omitempty" json:"stop_signal,omitempty"`
Sysctls StringList `yaml:",omitempty"` Sysctls StringList `yaml:",omitempty" json:"sysctls,omitempty"`
Tmpfs StringList `yaml:",omitempty"` Tmpfs StringList `yaml:",omitempty" json:"tmpfs,omitempty"`
Tty bool `mapstructure:"tty" yaml:"tty,omitempty"` Tty bool `mapstructure:"tty" yaml:"tty,omitempty" json:"tty,omitempty"`
Ulimits map[string]*UlimitsConfig `yaml:",omitempty"` Ulimits map[string]*UlimitsConfig `yaml:",omitempty" json:"ulimits,omitempty"`
User string `yaml:",omitempty"` User string `yaml:",omitempty" json:"user,omitempty"`
UserNSMode string `mapstructure:"userns_mode" yaml:"userns_mode,omitempty"` UserNSMode string `mapstructure:"userns_mode" yaml:"userns_mode,omitempty" json:"userns_mode,omitempty"`
Volumes []ServiceVolumeConfig `yaml:",omitempty"` Volumes []ServiceVolumeConfig `yaml:",omitempty" json:"volumes,omitempty"`
WorkingDir string `mapstructure:"working_dir" yaml:"working_dir,omitempty"` WorkingDir string `mapstructure:"working_dir" yaml:"working_dir,omitempty" json:"working_dir,omitempty"`
Extras map[string]interface{} `yaml:",inline"` Extras map[string]interface{} `yaml:",inline" json:"-"`
} }
// BuildConfig is a type for build // BuildConfig is a type for build
// using the same format at libcompose: https://github.com/docker/libcompose/blob/master/yaml/build.go#L12 // using the same format at libcompose: https://github.com/docker/libcompose/blob/master/yaml/build.go#L12
type BuildConfig struct { type BuildConfig struct {
Context string `yaml:",omitempty"` Context string `yaml:",omitempty" json:"context,omitempty"`
Dockerfile string `yaml:",omitempty"` Dockerfile string `yaml:",omitempty" json:"dockerfile,omitempty"`
Args MappingWithEquals `yaml:",omitempty"` Args MappingWithEquals `yaml:",omitempty" json:"args,omitempty"`
Labels Labels `yaml:",omitempty"` Labels Labels `yaml:",omitempty" json:"labels,omitempty"`
CacheFrom StringList `mapstructure:"cache_from" yaml:"cache_from,omitempty"` CacheFrom StringList `mapstructure:"cache_from" yaml:"cache_from,omitempty" json:"cache_from,omitempty"`
Network string `yaml:",omitempty"` Network string `yaml:",omitempty" json:"network,omitempty"`
Target string `yaml:",omitempty"` Target string `yaml:",omitempty" json:"target,omitempty"`
} }
// ShellCommand is a string or list of string args // ShellCommand is a string or list of string args
@ -191,31 +252,31 @@ type HostsList []string
// LoggingConfig the logging configuration for a service // LoggingConfig the logging configuration for a service
type LoggingConfig struct { type LoggingConfig struct {
Driver string `yaml:",omitempty"` Driver string `yaml:",omitempty" json:"driver,omitempty"`
Options map[string]string `yaml:",omitempty"` Options map[string]string `yaml:",omitempty" json:"options,omitempty"`
} }
// DeployConfig the deployment configuration for a service // DeployConfig the deployment configuration for a service
type DeployConfig struct { type DeployConfig struct {
Mode string `yaml:",omitempty"` Mode string `yaml:",omitempty" json:"mode,omitempty"`
Replicas *uint64 `yaml:",omitempty"` Replicas *uint64 `yaml:",omitempty" json:"replicas,omitempty"`
Labels Labels `yaml:",omitempty"` Labels Labels `yaml:",omitempty" json:"labels,omitempty"`
UpdateConfig *UpdateConfig `mapstructure:"update_config" yaml:"update_config,omitempty"` UpdateConfig *UpdateConfig `mapstructure:"update_config" yaml:"update_config,omitempty" json:"update_config,omitempty"`
RollbackConfig *UpdateConfig `mapstructure:"rollback_config" yaml:"rollback_config,omitempty"` RollbackConfig *UpdateConfig `mapstructure:"rollback_config" yaml:"rollback_config,omitempty" json:"rollback_config,omitempty"`
Resources Resources `yaml:",omitempty"` Resources Resources `yaml:",omitempty" json:"resources,omitempty"`
RestartPolicy *RestartPolicy `mapstructure:"restart_policy" yaml:"restart_policy,omitempty"` RestartPolicy *RestartPolicy `mapstructure:"restart_policy" yaml:"restart_policy,omitempty" json:"restart_policy,omitempty"`
Placement Placement `yaml:",omitempty"` Placement Placement `yaml:",omitempty" json:"placement,omitempty"`
EndpointMode string `mapstructure:"endpoint_mode" yaml:"endpoint_mode,omitempty"` EndpointMode string `mapstructure:"endpoint_mode" yaml:"endpoint_mode,omitempty" json:"endpoint_mode,omitempty"`
} }
// HealthCheckConfig the healthcheck configuration for a service // HealthCheckConfig the healthcheck configuration for a service
type HealthCheckConfig struct { type HealthCheckConfig struct {
Test HealthCheckTest `yaml:",omitempty"` Test HealthCheckTest `yaml:",omitempty" json:"test,omitempty"`
Timeout *time.Duration `yaml:",omitempty"` Timeout *Duration `yaml:",omitempty" json:"timeout,omitempty"`
Interval *time.Duration `yaml:",omitempty"` Interval *Duration `yaml:",omitempty" json:"interval,omitempty"`
Retries *uint64 `yaml:",omitempty"` Retries *uint64 `yaml:",omitempty" json:"retries,omitempty"`
StartPeriod *time.Duration `mapstructure:"start_period" yaml:"start_period,omitempty"` StartPeriod *Duration `mapstructure:"start_period" yaml:"start_period,omitempty" json:"start_period,omitempty"`
Disable bool `yaml:",omitempty"` Disable bool `yaml:",omitempty" json:"disable,omitempty"`
} }
// HealthCheckTest is the command run to test the health of a service // HealthCheckTest is the command run to test the health of a service
@ -223,32 +284,32 @@ type HealthCheckTest []string
// UpdateConfig the service update configuration // UpdateConfig the service update configuration
type UpdateConfig struct { type UpdateConfig struct {
Parallelism *uint64 `yaml:",omitempty"` Parallelism *uint64 `yaml:",omitempty" json:"parallelism,omitempty"`
Delay time.Duration `yaml:",omitempty"` Delay Duration `yaml:",omitempty" json:"delay,omitempty"`
FailureAction string `mapstructure:"failure_action" yaml:"failure_action,omitempty"` FailureAction string `mapstructure:"failure_action" yaml:"failure_action,omitempty" json:"failure_action,omitempty"`
Monitor time.Duration `yaml:",omitempty"` Monitor Duration `yaml:",omitempty" json:"monitor,omitempty"`
MaxFailureRatio float32 `mapstructure:"max_failure_ratio" yaml:"max_failure_ratio,omitempty"` MaxFailureRatio float32 `mapstructure:"max_failure_ratio" yaml:"max_failure_ratio,omitempty" json:"max_failure_ratio,omitempty"`
Order string `yaml:",omitempty"` Order string `yaml:",omitempty" json:"order,omitempty"`
} }
// Resources the resource limits and reservations // Resources the resource limits and reservations
type Resources struct { type Resources struct {
Limits *Resource `yaml:",omitempty"` Limits *Resource `yaml:",omitempty" json:"limits,omitempty"`
Reservations *Resource `yaml:",omitempty"` Reservations *Resource `yaml:",omitempty" json:"reservations,omitempty"`
} }
// Resource is a resource to be limited or reserved // Resource is a resource to be limited or reserved
type Resource struct { type Resource struct {
// TODO: types to convert from units and ratios // TODO: types to convert from units and ratios
NanoCPUs string `mapstructure:"cpus" yaml:"cpus,omitempty"` NanoCPUs string `mapstructure:"cpus" yaml:"cpus,omitempty" json:"cpus,omitempty"`
MemoryBytes UnitBytes `mapstructure:"memory" yaml:"memory,omitempty"` MemoryBytes UnitBytes `mapstructure:"memory" yaml:"memory,omitempty" json:"memory,omitempty"`
GenericResources []GenericResource `mapstructure:"generic_resources" yaml:"generic_resources,omitempty"` GenericResources []GenericResource `mapstructure:"generic_resources" yaml:"generic_resources,omitempty" json:"generic_resources,omitempty"`
} }
// GenericResource represents a "user defined" resource which can // GenericResource represents a "user defined" resource which can
// only be an integer (e.g: SSD=3) for a service // only be an integer (e.g: SSD=3) for a service
type GenericResource struct { type GenericResource struct {
DiscreteResourceSpec *DiscreteGenericResource `mapstructure:"discrete_resource_spec" yaml:"discrete_resource_spec,omitempty"` DiscreteResourceSpec *DiscreteGenericResource `mapstructure:"discrete_resource_spec" yaml:"discrete_resource_spec,omitempty" json:"discrete_resource_spec,omitempty"`
} }
// DiscreteGenericResource represents a "user defined" resource which is defined // DiscreteGenericResource represents a "user defined" resource which is defined
@ -256,8 +317,8 @@ type GenericResource struct {
// "Kind" is used to describe the Kind of a resource (e.g: "GPU", "FPGA", "SSD", ...) // "Kind" is used to describe the Kind of a resource (e.g: "GPU", "FPGA", "SSD", ...)
// Value is used to count the resource (SSD=5, HDD=3, ...) // Value is used to count the resource (SSD=5, HDD=3, ...)
type DiscreteGenericResource struct { type DiscreteGenericResource struct {
Kind string Kind string `json:"kind"`
Value int64 Value int64 `json:"value"`
} }
// UnitBytes is the bytes type // UnitBytes is the bytes type
@ -268,74 +329,79 @@ func (u UnitBytes) MarshalYAML() (interface{}, error) {
return fmt.Sprintf("%d", u), nil return fmt.Sprintf("%d", u), nil
} }
// MarshalJSON makes UnitBytes implement json.Marshaler
func (u UnitBytes) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%d"`, u)), nil
}
// RestartPolicy the service restart policy // RestartPolicy the service restart policy
type RestartPolicy struct { type RestartPolicy struct {
Condition string `yaml:",omitempty"` Condition string `yaml:",omitempty" json:"condition,omitempty"`
Delay *time.Duration `yaml:",omitempty"` Delay *Duration `yaml:",omitempty" json:"delay,omitempty"`
MaxAttempts *uint64 `mapstructure:"max_attempts" yaml:"max_attempts,omitempty"` MaxAttempts *uint64 `mapstructure:"max_attempts" yaml:"max_attempts,omitempty" json:"max_attempts,omitempty"`
Window *time.Duration `yaml:",omitempty"` Window *Duration `yaml:",omitempty" json:"window,omitempty"`
} }
// Placement constraints for the service // Placement constraints for the service
type Placement struct { type Placement struct {
Constraints []string `yaml:",omitempty"` Constraints []string `yaml:",omitempty" json:"constraints,omitempty"`
Preferences []PlacementPreferences `yaml:",omitempty"` Preferences []PlacementPreferences `yaml:",omitempty" json:"preferences,omitempty"`
} }
// PlacementPreferences is the preferences for a service placement // PlacementPreferences is the preferences for a service placement
type PlacementPreferences struct { type PlacementPreferences struct {
Spread string `yaml:",omitempty"` Spread string `yaml:",omitempty" json:"spread,omitempty"`
} }
// ServiceNetworkConfig is the network configuration for a service // ServiceNetworkConfig is the network configuration for a service
type ServiceNetworkConfig struct { type ServiceNetworkConfig struct {
Aliases []string `yaml:",omitempty"` Aliases []string `yaml:",omitempty" json:"aliases,omitempty"`
Ipv4Address string `mapstructure:"ipv4_address" yaml:"ipv4_address,omitempty"` Ipv4Address string `mapstructure:"ipv4_address" yaml:"ipv4_address,omitempty" json:"ipv4_address,omitempty"`
Ipv6Address string `mapstructure:"ipv6_address" yaml:"ipv6_address,omitempty"` Ipv6Address string `mapstructure:"ipv6_address" yaml:"ipv6_address,omitempty" json:"ipv6_address,omitempty"`
} }
// ServicePortConfig is the port configuration for a service // ServicePortConfig is the port configuration for a service
type ServicePortConfig struct { type ServicePortConfig struct {
Mode string `yaml:",omitempty"` Mode string `yaml:",omitempty" json:"mode,omitempty"`
Target uint32 `yaml:",omitempty"` Target uint32 `yaml:",omitempty" json:"target,omitempty"`
Published uint32 `yaml:",omitempty"` Published uint32 `yaml:",omitempty" json:"published,omitempty"`
Protocol string `yaml:",omitempty"` Protocol string `yaml:",omitempty" json:"protocol,omitempty"`
} }
// ServiceVolumeConfig are references to a volume used by a service // ServiceVolumeConfig are references to a volume used by a service
type ServiceVolumeConfig struct { type ServiceVolumeConfig struct {
Type string `yaml:",omitempty"` Type string `yaml:",omitempty" json:"type,omitempty"`
Source string `yaml:",omitempty"` Source string `yaml:",omitempty" json:"source,omitempty"`
Target string `yaml:",omitempty"` Target string `yaml:",omitempty" json:"target,omitempty"`
ReadOnly bool `mapstructure:"read_only" yaml:"read_only,omitempty"` ReadOnly bool `mapstructure:"read_only" yaml:"read_only,omitempty" json:"read_only,omitempty"`
Consistency string `yaml:",omitempty"` Consistency string `yaml:",omitempty" json:"consistency,omitempty"`
Bind *ServiceVolumeBind `yaml:",omitempty"` Bind *ServiceVolumeBind `yaml:",omitempty" json:"bind,omitempty"`
Volume *ServiceVolumeVolume `yaml:",omitempty"` Volume *ServiceVolumeVolume `yaml:",omitempty" json:"volume,omitempty"`
Tmpfs *ServiceVolumeTmpfs `yaml:",omitempty"` Tmpfs *ServiceVolumeTmpfs `yaml:",omitempty" json:"tmpfs,omitempty"`
} }
// ServiceVolumeBind are options for a service volume of type bind // ServiceVolumeBind are options for a service volume of type bind
type ServiceVolumeBind struct { type ServiceVolumeBind struct {
Propagation string `yaml:",omitempty"` Propagation string `yaml:",omitempty" json:"propagation,omitempty"`
} }
// ServiceVolumeVolume are options for a service volume of type volume // ServiceVolumeVolume are options for a service volume of type volume
type ServiceVolumeVolume struct { type ServiceVolumeVolume struct {
NoCopy bool `mapstructure:"nocopy" yaml:"nocopy,omitempty"` NoCopy bool `mapstructure:"nocopy" yaml:"nocopy,omitempty" json:"nocopy,omitempty"`
} }
// ServiceVolumeTmpfs are options for a service volume of type tmpfs // ServiceVolumeTmpfs are options for a service volume of type tmpfs
type ServiceVolumeTmpfs struct { type ServiceVolumeTmpfs struct {
Size int64 `yaml:",omitempty"` Size int64 `yaml:",omitempty" json:"size,omitempty"`
} }
// FileReferenceConfig for a reference to a swarm file object // FileReferenceConfig for a reference to a swarm file object
type FileReferenceConfig struct { type FileReferenceConfig struct {
Source string `yaml:",omitempty"` Source string `yaml:",omitempty" json:"source,omitempty"`
Target string `yaml:",omitempty"` Target string `yaml:",omitempty" json:"target,omitempty"`
UID string `yaml:",omitempty"` UID string `yaml:",omitempty" json:"uid,omitempty"`
GID string `yaml:",omitempty"` GID string `yaml:",omitempty" json:"gid,omitempty"`
Mode *uint32 `yaml:",omitempty"` Mode *uint32 `yaml:",omitempty" json:"mode,omitempty"`
} }
// ServiceConfigObjConfig is the config obj configuration for a service // ServiceConfigObjConfig is the config obj configuration for a service
@ -346,9 +412,9 @@ type ServiceSecretConfig FileReferenceConfig
// UlimitsConfig the ulimit configuration // UlimitsConfig the ulimit configuration
type UlimitsConfig struct { type UlimitsConfig struct {
Single int `yaml:",omitempty"` Single int `yaml:",omitempty" json:"single,omitempty"`
Soft int `yaml:",omitempty"` Soft int `yaml:",omitempty" json:"soft,omitempty"`
Hard int `yaml:",omitempty"` Hard int `yaml:",omitempty" json:"hard,omitempty"`
} }
// MarshalYAML makes UlimitsConfig implement yaml.Marshaller // MarshalYAML makes UlimitsConfig implement yaml.Marshaller
@ -359,46 +425,55 @@ func (u *UlimitsConfig) MarshalYAML() (interface{}, error) {
return u, nil return u, nil
} }
// MarshalJSON makes UlimitsConfig implement json.Marshaller
func (u *UlimitsConfig) MarshalJSON() ([]byte, error) {
if u.Single != 0 {
return json.Marshal(u.Single)
}
// Pass as a value to avoid re-entering this method and use the default implementation
return json.Marshal(*u)
}
// NetworkConfig for a network // NetworkConfig for a network
type NetworkConfig struct { type NetworkConfig struct {
Name string `yaml:",omitempty"` Name string `yaml:",omitempty" json:"name,omitempty"`
Driver string `yaml:",omitempty"` Driver string `yaml:",omitempty" json:"driver,omitempty"`
DriverOpts map[string]string `mapstructure:"driver_opts" yaml:"driver_opts,omitempty"` DriverOpts map[string]string `mapstructure:"driver_opts" yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"`
Ipam IPAMConfig `yaml:",omitempty"` Ipam IPAMConfig `yaml:",omitempty" json:"ipam,omitempty"`
External External `yaml:",omitempty"` External External `yaml:",omitempty" json:"external,omitempty"`
Internal bool `yaml:",omitempty"` Internal bool `yaml:",omitempty" json:"internal,omitempty"`
Attachable bool `yaml:",omitempty"` Attachable bool `yaml:",omitempty" json:"attachable,omitempty"`
Labels Labels `yaml:",omitempty"` Labels Labels `yaml:",omitempty" json:"labels,omitempty"`
Extras map[string]interface{} `yaml:",inline"` Extras map[string]interface{} `yaml:",inline" json:"-"`
} }
// IPAMConfig for a network // IPAMConfig for a network
type IPAMConfig struct { type IPAMConfig struct {
Driver string `yaml:",omitempty"` Driver string `yaml:",omitempty" json:"driver,omitempty"`
Config []*IPAMPool `yaml:",omitempty"` Config []*IPAMPool `yaml:",omitempty" json:"config,omitempty"`
} }
// IPAMPool for a network // IPAMPool for a network
type IPAMPool struct { type IPAMPool struct {
Subnet string `yaml:",omitempty"` Subnet string `yaml:",omitempty" json:"subnet,omitempty"`
} }
// VolumeConfig for a volume // VolumeConfig for a volume
type VolumeConfig struct { type VolumeConfig struct {
Name string `yaml:",omitempty"` Name string `yaml:",omitempty" json:"name,omitempty"`
Driver string `yaml:",omitempty"` Driver string `yaml:",omitempty" json:"driver,omitempty"`
DriverOpts map[string]string `mapstructure:"driver_opts" yaml:"driver_opts,omitempty"` DriverOpts map[string]string `mapstructure:"driver_opts" yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"`
External External `yaml:",omitempty"` External External `yaml:",omitempty" json:"external,omitempty"`
Labels Labels `yaml:",omitempty"` Labels Labels `yaml:",omitempty" json:"labels,omitempty"`
Extras map[string]interface{} `yaml:",inline"` Extras map[string]interface{} `yaml:",inline" json:"-"`
} }
// External identifies a Volume or Network as a reference to a resource that is // External identifies a Volume or Network as a reference to a resource that is
// not managed, and should already exist. // not managed, and should already exist.
// External.name is deprecated and replaced by Volume.name // External.name is deprecated and replaced by Volume.name
type External struct { type External struct {
Name string `yaml:",omitempty"` Name string `yaml:",omitempty" json:"name,omitempty"`
External bool `yaml:",omitempty"` External bool `yaml:",omitempty" json:"external,omitempty"`
} }
// MarshalYAML makes External implement yaml.Marshaller // MarshalYAML makes External implement yaml.Marshaller
@ -409,19 +484,27 @@ func (e External) MarshalYAML() (interface{}, error) {
return External{Name: e.Name}, nil return External{Name: e.Name}, nil
} }
// MarshalJSON makes External implement json.Marshaller
func (e External) MarshalJSON() ([]byte, error) {
if e.Name == "" {
return []byte(fmt.Sprintf("%v", e.External)), nil
}
return []byte(fmt.Sprintf(`{"name": %q}`, e.Name)), nil
}
// CredentialSpecConfig for credential spec on Windows // CredentialSpecConfig for credential spec on Windows
type CredentialSpecConfig struct { type CredentialSpecConfig struct {
File string `yaml:",omitempty"` File string `yaml:",omitempty" json:"file,omitempty"`
Registry string `yaml:",omitempty"` Registry string `yaml:",omitempty" json:"registry,omitempty"`
} }
// FileObjectConfig is a config type for a file used by a service // FileObjectConfig is a config type for a file used by a service
type FileObjectConfig struct { type FileObjectConfig struct {
Name string `yaml:",omitempty"` Name string `yaml:",omitempty" json:"name,omitempty"`
File string `yaml:",omitempty"` File string `yaml:",omitempty" json:"file,omitempty"`
External External `yaml:",omitempty"` External External `yaml:",omitempty" json:"external,omitempty"`
Labels Labels `yaml:",omitempty"` Labels Labels `yaml:",omitempty" json:"labels,omitempty"`
Extras map[string]interface{} `yaml:",inline"` Extras map[string]interface{} `yaml:",inline" json:"-"`
} }
// SecretConfig for a secret // SecretConfig for a secret