Skip to content

Commit

Permalink
fix(hash): recreate container on project config content change
Browse files Browse the repository at this point in the history
Signed-off-by: Suleiman Dibirov <[email protected]>
  • Loading branch information
idsulik committed Jun 23, 2024
1 parent 6a000dc commit 19daddd
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 15 deletions.
2 changes: 1 addition & 1 deletion cmd/compose/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions pkg/compose/convergence.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/compose/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
9 changes: 8 additions & 1 deletion pkg/compose/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
Expand All @@ -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
}
45 changes: 40 additions & 5 deletions pkg/compose/hash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
},
}
}

0 comments on commit 19daddd

Please sign in to comment.