diff --git a/cmd/compose/config.go b/cmd/compose/config.go index 8325946cdbc..b6b1f352d81 100644 --- a/cmd/compose/config.go +++ b/cmd/compose/config.go @@ -330,7 +330,7 @@ func runHash(ctx context.Context, dockerCli command.Cli, opts configOptions) err return err } - hash, err := compose.ServiceHash(s) + hash, err := compose.ServiceHash(project, s) if err != nil { return err diff --git a/pkg/compose/convergence.go b/pkg/compose/convergence.go index 3b680fe77c3..82b58e379f8 100644 --- a/pkg/compose/convergence.go +++ b/pkg/compose/convergence.go @@ -128,11 +128,11 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project, sort.Slice(containers, func(i, j int) bool { // select obsolete containers first, so they get removed as we scale down - if obsolete, _ := mustRecreate(service, containers[i], recreate); obsolete { + if obsolete, _ := mustRecreate(project, service, containers[i], recreate); obsolete { // i is obsolete, so must be first in the list return true } - if obsolete, _ := mustRecreate(service, containers[j], recreate); obsolete { + if obsolete, _ := mustRecreate(project, service, containers[j], recreate); obsolete { // j is obsolete, so must be first in the list return false } @@ -158,7 +158,7 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project, continue } - mustRecreate, err := mustRecreate(service, container, recreate) + mustRecreate, err := mustRecreate(project, service, container, recreate) if err != nil { return err } @@ -292,14 +292,14 @@ func (c *convergence) resolveSharedNamespaces(service *types.ServiceConfig) erro return nil } -func mustRecreate(expected types.ServiceConfig, actual moby.Container, policy string) (bool, error) { +func mustRecreate(project *types.Project, expected types.ServiceConfig, actual moby.Container, policy string) (bool, error) { if policy == api.RecreateNever { return false, nil } if policy == api.RecreateForce || expected.Extensions[extLifecycle] == forceRecreate { return true, nil } - configHash, err := ServiceHash(expected) + configHash, err := ServiceHash(project, expected) if err != nil { return false, err } diff --git a/pkg/compose/create.go b/pkg/compose/create.go index ccf92058057..92a2b0d3a6b 100644 --- a/pkg/compose/create.go +++ b/pkg/compose/create.go @@ -213,7 +213,7 @@ func (s *composeService) getCreateConfigs(ctx context.Context, inherit *moby.Container, opts createOptions, ) (createConfigs, error) { - labels, err := s.prepareLabels(opts.Labels, service, number) + labels, err := s.prepareLabels(opts.Labels, p, service, number) if err != nil { return createConfigs{}, err } @@ -499,8 +499,8 @@ func parseSecurityOpts(p *types.Project, securityOpts []string) ([]string, bool, return parsed, unconfined, nil } -func (s *composeService) prepareLabels(labels types.Labels, service types.ServiceConfig, number int) (map[string]string, error) { - hash, err := ServiceHash(service) +func (s *composeService) prepareLabels(labels types.Labels, project *types.Project, service types.ServiceConfig, number int) (map[string]string, error) { + hash, err := ServiceHash(project, service) if err != nil { return nil, err } diff --git a/pkg/compose/hash.go b/pkg/compose/hash.go index 284ccaa76fc..dba127f1a15 100644 --- a/pkg/compose/hash.go +++ b/pkg/compose/hash.go @@ -24,7 +24,7 @@ import ( ) // ServiceHash computes the configuration hash for a service. -func ServiceHash(o types.ServiceConfig) (string, error) { +func ServiceHash(project *types.Project, o types.ServiceConfig) (string, error) { // remove the Build config when generating the service hash o.Build = nil o.PullPolicy = "" @@ -37,5 +37,12 @@ func ServiceHash(o types.ServiceConfig) (string, error) { if err != nil { return "", err } + + for _, serviceConfig := range o.Configs { + if projectConfig, ok := project.Configs[serviceConfig.Source]; ok { + bytes = append(bytes, []byte(projectConfig.Content)...) + } + } + return digest.SHA256.FromBytes(bytes).Encoded(), nil } diff --git a/pkg/compose/hash_test.go b/pkg/compose/hash_test.go index 73b7f387735..04b448ce161 100644 --- a/pkg/compose/hash_test.go +++ b/pkg/compose/hash_test.go @@ -23,21 +23,56 @@ import ( "gotest.tools/v3/assert" ) -func TestServiceHash(t *testing.T) { - hash1, err := ServiceHash(serviceConfig(1)) +func TestServiceHashWithAllValuesTheSame(t *testing.T) { + hash1, err := ServiceHash(projectConfig("a", "b"), serviceConfig("myContext1", "always", 1)) assert.NilError(t, err) - hash2, err := ServiceHash(serviceConfig(2)) + hash2, err := ServiceHash(projectConfig("a", "b"), serviceConfig("myContext1", "always", 1)) assert.NilError(t, err) assert.Equal(t, hash1, hash2) } -func serviceConfig(replicas int) types.ServiceConfig { +func TestServiceHashWithIgnorableValues(t *testing.T) { + hash1, err := ServiceHash(&types.Project{}, serviceConfig("myContext1", "always", 1)) + assert.NilError(t, err) + hash2, err := ServiceHash(&types.Project{}, serviceConfig("myContext2", "never", 2)) + assert.NilError(t, err) + assert.Equal(t, hash1, hash2) +} + +func TestServiceHashWithChangedConfig(t *testing.T) { + hash1, err := ServiceHash(projectConfig("myConfigSource", "a"), serviceConfig("myContext1", "always", 1)) + assert.NilError(t, err) + hash2, err := ServiceHash(projectConfig("myConfigSource", "b"), serviceConfig("myContext2", "never", 2)) + assert.NilError(t, err) + assert.Assert(t, hash1 != hash2) +} + +func projectConfig(configName, configContent string) *types.Project { + return &types.Project{ + Configs: types.Configs{ + configName: types.ConfigObjConfig{ + Content: configContent, + }, + }, + } +} + +func serviceConfig(buildContext, pullPolicy string, replicas int) types.ServiceConfig { return types.ServiceConfig{ - Scale: &replicas, + Build: &types.BuildConfig{ + Context: buildContext, + }, + PullPolicy: pullPolicy, + Scale: &replicas, Deploy: &types.DeployConfig{ Replicas: &replicas, }, Name: "foo", Image: "bar", + Configs: []types.ServiceConfigObjConfig{ + { + Source: "myConfigSource", + }, + }, } }