diff --git a/loader/full-struct_test.go b/loader/full-struct_test.go index f7392c10..e699e71d 100644 --- a/loader/full-struct_test.go +++ b/loader/full-struct_test.go @@ -100,7 +100,7 @@ func services(workingDir, homeDir string) []types.ServiceConfig { }, Deploy: &types.DeployConfig{ Mode: "replicated", - Replicas: uint64Ptr(6), + Replicas: intPtr(6), Labels: map[string]string{"FOO": "BAR"}, RollbackConfig: &types.UpdateConfig{ Parallelism: uint64Ptr(3), @@ -387,7 +387,6 @@ func services(workingDir, homeDir string) []types.ServiceConfig { Privileged: true, ReadOnly: true, Restart: types.RestartPolicyAlways, - Scale: 1, Secrets: []types.ServiceSecretConfig{ { Source: "secret1", @@ -444,7 +443,6 @@ func services(workingDir, homeDir string) []types.ServiceConfig { DockerfileInline: "FROM alpine\nRUN echo \"hello\" > /world.txt\n", }, Environment: types.MappingWithEquals{}, - Scale: 1, }, } } diff --git a/loader/loader_test.go b/loader/loader_test.go index f0d9a967..1a382ed6 100644 --- a/loader/loader_test.go +++ b/loader/loader_test.go @@ -222,7 +222,6 @@ var sampleConfig = types.Config{ Networks: map[string]*types.ServiceNetworkConfig{ "with_me": nil, }, - Scale: 1, }, { Name: "bar", @@ -231,7 +230,6 @@ var sampleConfig = types.Config{ Networks: map[string]*types.ServiceNetworkConfig{ "with_ipam": nil, }, - Scale: 1, }, }, Networks: map[string]types.NetworkConfig{ @@ -817,7 +815,7 @@ networks: Disable: true, }, Deploy: &types.DeployConfig{ - Replicas: uint64Ptr(555), + Replicas: intPtr(555), UpdateConfig: &types.UpdateConfig{ Parallelism: uint64Ptr(555), MaxFailureRatio: 3.14, @@ -844,7 +842,6 @@ networks: }, Privileged: true, ReadOnly: true, - Scale: 1, ShmSize: types.UnitBytes(2 * 1024 * 1024 * 1024), StopGracePeriod: &typesDuration, StdinOpen: true, @@ -1054,7 +1051,7 @@ services: `, map[string]string{"FOO_SCALE": "2"}) assert.NilError(t, err) - assert.Equal(t, project.Services[0].Scale, 2) + assert.Equal(t, *project.Services[0].Scale, 2) } func durationPtr(value time.Duration) *types.Duration { @@ -1062,6 +1059,10 @@ func durationPtr(value time.Duration) *types.Duration { return &result } +func intPtr(value int) *int { + return &value +} + func uint64Ptr(value uint64) *uint64 { return &value } @@ -1583,7 +1584,6 @@ networks: { Name: "hello-world", Image: "redis:alpine", - Scale: 1, Networks: map[string]*types.ServiceNetworkConfig{ "network1": nil, "network3": nil, @@ -1634,7 +1634,6 @@ networks: { Name: "foo", Image: "alpine", - Scale: 1, Networks: map[string]*types.ServiceNetworkConfig{ "network1": { Ipv4Address: "10.1.0.100", @@ -1796,7 +1795,6 @@ secrets: Source: "config", }, }, - Scale: 1, Secrets: []types.ServiceSecretConfig{ { Source: "secret", @@ -1866,7 +1864,6 @@ secrets: Source: "config", }, }, - Scale: 1, Secrets: []types.ServiceSecretConfig{ { Source: "secret", @@ -1952,7 +1949,6 @@ func TestLoadWithExtends(t *testing.T) { Target: "/var/lib/mysql", Bind: &types.ServiceVolumeBind{CreateHostPath: true}, }}, - Scale: 1, }, } assert.Check(t, is.DeepEqual(expServices, actual.Services)) @@ -1982,7 +1978,6 @@ func TestLoadWithExtendsWithContextUrl(t *testing.T) { }, Environment: types.MappingWithEquals{}, Networks: map[string]*types.ServiceNetworkConfig{"default": nil}, - Scale: 1, }, } assert.Check(t, is.DeepEqual(expServices, actual.Services)) @@ -2348,7 +2343,6 @@ func TestDeviceWriteBps(t *testing.T) { Name: "foo", Image: "busybox", Environment: types.MappingWithEquals{}, - Scale: 1, BlkioConfig: &types.BlkioConfig{ DeviceReadBps: []types.ThrottleDevice{ { @@ -2392,7 +2386,6 @@ volumes: Name: "foo", Image: "busybox", Environment: types.MappingWithEquals{}, - Scale: 1, Volumes: []types.ServiceVolumeConfig{ { Type: types.VolumeTypeVolume, @@ -2418,7 +2411,6 @@ services: Name: "x-foo", Image: "busybox", Environment: types.MappingWithEquals{}, - Scale: 1, }, }) } @@ -2448,7 +2440,6 @@ services: Name: "foo", Image: "busybox", Environment: types.MappingWithEquals{}, - Scale: 1, DependsOn: types.DependsOnConfig{"imported": {Condition: "service_started", Required: true}}, }, { @@ -2459,7 +2450,6 @@ services: filepath.Join(workingDir, "testdata", "subdir", "extra.env"), }, Image: "nginx", - Scale: 1, Volumes: []types.ServiceVolumeConfig{ { Type: "bind", @@ -2586,7 +2576,6 @@ services: Name: "foo", Image: "nginx", Environment: types.MappingWithEquals{}, - Scale: 1, DependsOn: types.DependsOnConfig{ "bar": {Condition: types.ServiceConditionStarted, Required: true}, "baz": {Condition: types.ServiceConditionHealthy, Required: false}, @@ -2640,7 +2629,6 @@ services: EnvFile: types.StringList{ filepath.Join(config.WorkingDir, "testdata", "remote", "env"), }, - Scale: 1, Volumes: []types.ServiceVolumeConfig{ { Type: types.VolumeTypeBind, @@ -2833,8 +2821,12 @@ services: memswap_limit: 640kb `) assert.NilError(t, err) - assert.Equal(t, project.Services[0].MemSwapLimit, types.UnitBytes(-1)) - assert.Equal(t, project.Services[1].MemSwapLimit, types.UnitBytes(640*1024)) + test1, err := project.GetService("test1") + assert.NilError(t, err) + assert.Equal(t, test1.MemSwapLimit, types.UnitBytes(-1)) + test2, err := project.GetService("test2") + assert.NilError(t, err) + assert.Equal(t, test2.MemSwapLimit, types.UnitBytes(640*1024)) } func TestBuildUlimits(t *testing.T) { diff --git a/loader/loader_yaml_test.go b/loader/loader_yaml_test.go index c02129bb..75ae2e87 100644 --- a/loader/loader_yaml_test.go +++ b/loader/loader_yaml_test.go @@ -51,7 +51,6 @@ services: "image": "bar", "command": "echo world", "init": false, - "scale": 1, }, }, }) diff --git a/loader/normalize.go b/loader/normalize.go index a9ce9557..2d5adaa7 100644 --- a/loader/normalize.go +++ b/loader/normalize.go @@ -116,11 +116,6 @@ func Normalize(project *types.Project) error { return err } - err = relocateScale(&s) - if err != nil { - return err - } - inferImplicitDependencies(&s) project.Services[i] = s @@ -198,21 +193,6 @@ func setIfMissing(d types.DependsOnConfig, service string, dep types.ServiceDepe return d } -func relocateScale(s *types.ServiceConfig) error { - scale := uint64(s.Scale) - if scale > 1 { - logrus.Warn("`scale` is deprecated. Use the `deploy.replicas` element") - if s.Deploy == nil { - s.Deploy = &types.DeployConfig{} - } - if s.Deploy.Replicas != nil && *s.Deploy.Replicas != scale { - return errors.Wrap(errdefs.ErrInvalid, "can't use both 'scale' (deprecated) and 'deploy.replicas'") - } - s.Deploy.Replicas = &scale - } - return nil -} - // Resources with no explicit name are actually named by their key in map func setNameFromKey(project *types.Project) { for i, n := range project.Networks { diff --git a/loader/normalize_test.go b/loader/normalize_test.go index f7bf793d..9ea4a378 100644 --- a/loader/normalize_test.go +++ b/loader/normalize_test.go @@ -52,7 +52,6 @@ func TestNormalizeNetworkNames(t *testing.T) { "ZOT": nil, }, }, - Scale: 1, }, }, } diff --git a/loader/validate.go b/loader/validate.go index 923c7f66..582a3ead 100644 --- a/loader/validate.go +++ b/loader/validate.go @@ -106,6 +106,14 @@ func checkConsistency(project *types.Project) error { return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined secret %s", s.Name, secret.Source)) } } + + if s.Scale != nil && s.Deploy != nil { + if s.Deploy.Replicas != nil && *s.Scale != *s.Deploy.Replicas { + return errors.Wrap(errdefs.ErrInvalid, "can't set distinct values on 'scale' and 'deploy.replicas'") + } + s.Deploy.Replicas = s.Scale + } + } for name, secret := range project.Secrets { diff --git a/loader/with-version-struct_test.go b/loader/with-version-struct_test.go index cb9d0355..a713da36 100644 --- a/loader/with-version-struct_test.go +++ b/loader/with-version-struct_test.go @@ -45,7 +45,6 @@ func withVersionServices() []types.ServiceConfig { "default": nil, }, VolumesFrom: []string{"other"}, - Scale: 1, }, { Name: "other", @@ -56,7 +55,6 @@ func withVersionServices() []types.ServiceConfig { Volumes: []types.ServiceVolumeConfig{ {Target: "/data", Type: "volume", Volume: &types.ServiceVolumeVolume{}}, }, - Scale: 1, }, } } diff --git a/transform/services.go b/transform/services.go index a57fc613..5201e80e 100644 --- a/transform/services.go +++ b/transform/services.go @@ -22,9 +22,6 @@ import ( func transformService(data any, p tree.Path) (any, error) { value := data.(map[string]any) - if _, ok := value["scale"]; !ok { - value["scale"] = 1 // TODO(ndeloof) we should make scale a *int - } return transformMapping(value, p) } diff --git a/types/types.go b/types/types.go index 079db6fa..e3f09d8e 100644 --- a/types/types.go +++ b/types/types.go @@ -112,7 +112,7 @@ type ServiceConfig struct { ReadOnly bool `yaml:"read_only,omitempty" json:"read_only,omitempty"` Restart string `yaml:"restart,omitempty" json:"restart,omitempty"` Runtime string `yaml:"runtime,omitempty" json:"runtime,omitempty"` - Scale int `yaml:"scale,omitempty" json:"scale,omitempty"` + Scale *int `yaml:"scale,omitempty" json:"scale,omitempty"` Secrets []ServiceSecretConfig `yaml:"secrets,omitempty" json:"secrets,omitempty"` SecurityOpt []string `yaml:"security_opt,omitempty" json:"security_opt,omitempty"` ShmSize UnitBytes `yaml:"shm_size,omitempty" json:"shm_size,omitempty"` @@ -138,19 +138,10 @@ type ServiceConfig struct { func (s ServiceConfig) MarshalYAML() (interface{}, error) { type t ServiceConfig value := t(s) - value.Scale = 0 // deprecated, but default value "1" doesn't match omitempty value.Name = "" // set during map to slice conversion, not part of the yaml representation return value, nil } -// MarshalJSON makes ServiceConfig implement json.Marshaller -func (s ServiceConfig) MarshalJSON() ([]byte, error) { - type t ServiceConfig - value := t(s) - value.Scale = 0 // deprecated, but default value "1" doesn't match omitempty - return json.Marshal(value) -} - // NetworksByPriority return the service networks IDs sorted according to Priority func (s *ServiceConfig) NetworksByPriority() []string { type key struct { @@ -323,7 +314,7 @@ type LoggingConfig struct { // DeployConfig the deployment configuration for a service type DeployConfig struct { Mode string `yaml:"mode,omitempty" json:"mode,omitempty"` - Replicas *uint64 `yaml:"replicas,omitempty" json:"replicas,omitempty"` + Replicas *int `yaml:"replicas,omitempty" json:"replicas,omitempty"` Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"` UpdateConfig *UpdateConfig `yaml:"update_config,omitempty" json:"update_config,omitempty"` RollbackConfig *UpdateConfig `yaml:"rollback_config,omitempty" json:"rollback_config,omitempty"`