Skip to content

Commit 6c1ee10

Browse files
committed
support refresh pull policy
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
1 parent e38b729 commit 6c1ee10

File tree

6 files changed

+68
-41
lines changed

6 files changed

+68
-41
lines changed

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ require (
77
github.com/Microsoft/go-winio v0.6.2
88
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
99
github.com/buger/goterm v1.0.4
10-
github.com/compose-spec/compose-go/v2 v2.4.8
10+
github.com/compose-spec/compose-go/v2 v2.4.9-0.20250225151507-331db8fefcb7
1111
github.com/containerd/containerd/v2 v2.0.2
1212
github.com/containerd/platforms v1.0.0-rc.1
1313
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
@@ -168,6 +168,7 @@ require (
168168
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
169169
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
170170
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
171+
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
171172
github.com/zclconf/go-cty v1.16.0 // indirect
172173
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 // indirect
173174
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 // indirect

go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e
8181
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
8282
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
8383
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
84-
github.com/compose-spec/compose-go/v2 v2.4.8 h1:7Myl8wDRl/4mRz77S+eyDJymGGEHu0diQdGSSeyq90A=
85-
github.com/compose-spec/compose-go/v2 v2.4.8/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc=
84+
github.com/compose-spec/compose-go/v2 v2.4.9-0.20250225151507-331db8fefcb7 h1:7NlxAsQcWvLpFlEHsBo80sJ1UMMs84kkf0yXGs6de2k=
85+
github.com/compose-spec/compose-go/v2 v2.4.9-0.20250225151507-331db8fefcb7/go.mod h1:6k5l/0TxCg0/2uLEhRVEsoBWBprS2uvZi32J7xub3lo=
8686
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
8787
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
8888
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
@@ -494,6 +494,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo
494494
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
495495
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
496496
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
497+
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
498+
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
497499
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
498500
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
499501
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=

pkg/api/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,7 @@ type ImageSummary struct {
532532
Repository string
533533
Tag string
534534
Size int64
535+
LastTagTime time.Time
535536
}
536537

537538
// ServiceStatus hold status about a service

pkg/compose/build.go

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"os"
2424
"strings"
2525
"sync"
26+
"time"
2627

2728
"github.com/compose-spec/compose-go/v2/types"
2829
"github.com/containerd/platforms"
@@ -70,7 +71,7 @@ const bakeSuggest = "Compose now can delegate build to bake for better performan
7071
var suggest sync.Once
7172

7273
//nolint:gocyclo
73-
func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions, localImages map[string]string) (map[string]string, error) {
74+
func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions, localImages map[string]api.ImageSummary) (map[string]string, error) {
7475
imageIDs := map[string]string{}
7576
serviceToBuild := types.Services{}
7677

@@ -282,7 +283,11 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
282283
}
283284

284285
for name, digest := range builtImages {
285-
images[name] = digest
286+
images[name] = api.ImageSummary{
287+
Repository: name,
288+
ID: digest,
289+
LastTagTime: time.Now(),
290+
}
286291
}
287292
return nil
288293
},
@@ -295,19 +300,16 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
295300
// set digest as com.docker.compose.image label so we can detect outdated containers
296301
for name, service := range project.Services {
297302
image := api.GetImageNameOrDefault(service, project.Name)
298-
digest, ok := images[image]
303+
img, ok := images[image]
299304
if ok {
300-
if service.Labels == nil {
301-
service.Labels = types.Labels{}
302-
}
303-
service.CustomLabels.Add(api.ImageDigestLabel, digest)
305+
service.CustomLabels.Add(api.ImageDigestLabel, img.ID)
304306
}
305307
project.Services[name] = service
306308
}
307309
return nil
308310
}
309311

310-
func (s *composeService) getLocalImagesDigests(ctx context.Context, project *types.Project) (map[string]string, error) {
312+
func (s *composeService) getLocalImagesDigests(ctx context.Context, project *types.Project) (map[string]api.ImageSummary, error) {
311313
var imageNames []string
312314
for _, s := range project.Services {
313315
imgName := api.GetImageNameOrDefault(s, project.Name)
@@ -319,14 +321,10 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
319321
if err != nil {
320322
return nil, err
321323
}
322-
images := map[string]string{}
323-
for name, info := range imgs {
324-
images[name] = info.ID
325-
}
326324

327325
for i, service := range project.Services {
328326
imgName := api.GetImageNameOrDefault(service, project.Name)
329-
digest, ok := images[imgName]
327+
img, ok := imgs[imgName]
330328
if !ok {
331329
continue
332330
}
@@ -335,7 +333,7 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
335333
if err != nil {
336334
return nil, err
337335
}
338-
inspect, err := s.apiClient().ImageInspect(ctx, digest)
336+
inspect, err := s.apiClient().ImageInspect(ctx, img.ID)
339337
if err != nil {
340338
return nil, err
341339
}
@@ -348,15 +346,15 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
348346
// there is a local image, but it's for the wrong platform, so
349347
// pretend it doesn't exist so that we can pull/build an image
350348
// for the correct platform instead
351-
delete(images, imgName)
349+
delete(imgs, imgName)
352350
}
353351
}
354352

355-
project.Services[i].CustomLabels.Add(api.ImageDigestLabel, digest)
353+
project.Services[i].CustomLabels.Add(api.ImageDigestLabel, img.ID)
356354

357355
}
358356

359-
return images, nil
357+
return imgs, nil
360358
}
361359

362360
// resolveAndMergeBuildArgs returns the final set of build arguments to use for the service image build.

pkg/compose/images.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,11 @@ func (s *composeService) getImageSummaries(ctx context.Context, repoTags []strin
101101
}
102102
l.Lock()
103103
summary[repoTag] = api.ImageSummary{
104-
ID: inspect.ID,
105-
Repository: repository,
106-
Tag: tag,
107-
Size: inspect.Size,
104+
ID: inspect.ID,
105+
Repository: repository,
106+
Tag: tag,
107+
Size: inspect.Size,
108+
LastTagTime: inspect.Metadata.LastTagTime,
108109
}
109110
l.Unlock()
110111
return nil

pkg/compose/pull.go

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"fmt"
2525
"io"
2626
"strings"
27+
"time"
2728

2829
"github.com/compose-spec/compose-go/v2/types"
2930
"github.com/distribution/reference"
@@ -153,7 +154,7 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
153154
return multierror.Append(nil, pullErrors...).ErrorOrNil()
154155
}
155156

156-
func imageAlreadyPresent(serviceImage string, localImages map[string]string) bool {
157+
func imageAlreadyPresent(serviceImage string, localImages map[string]api.ImageSummary) bool {
157158
normalizedImage, err := reference.ParseDockerRef(serviceImage)
158159
if err != nil {
159160
return false
@@ -288,23 +289,16 @@ func encodedAuth(ref reference.Named, configFile driver.Auth) (string, error) {
288289
return base64.URLEncoding.EncodeToString(buf), nil
289290
}
290291

291-
func (s *composeService) pullRequiredImages(ctx context.Context, project *types.Project, images map[string]string, quietPull bool) error {
292+
func (s *composeService) pullRequiredImages(ctx context.Context, project *types.Project, images map[string]api.ImageSummary, quietPull bool) error {
292293
var needPull []types.ServiceConfig
293294
for _, service := range project.Services {
294-
if service.Image == "" {
295-
continue
295+
pull, err := mustPull(service, images)
296+
if err != nil {
297+
return err
296298
}
297-
switch service.PullPolicy {
298-
case "", types.PullPolicyMissing, types.PullPolicyIfNotPresent:
299-
if _, ok := images[service.Image]; ok {
300-
continue
301-
}
302-
case types.PullPolicyNever, types.PullPolicyBuild:
303-
continue
304-
case types.PullPolicyAlways:
305-
// force pull
299+
if pull {
300+
needPull = append(needPull, service)
306301
}
307-
needPull = append(needPull, service)
308302
}
309303
if len(needPull) == 0 {
310304
return nil
@@ -314,11 +308,15 @@ func (s *composeService) pullRequiredImages(ctx context.Context, project *types.
314308
w := progress.ContextWriter(ctx)
315309
eg, ctx := errgroup.WithContext(ctx)
316310
eg.SetLimit(s.maxConcurrency)
317-
pulledImages := make([]string, len(needPull))
311+
pulledImages := make([]api.ImageSummary, len(needPull))
318312
for i, service := range needPull {
319313
eg.Go(func() error {
320314
id, err := s.pullServiceImage(ctx, service, s.configFile(), w, quietPull, project.Environment["DOCKER_DEFAULT_PLATFORM"])
321-
pulledImages[i] = id
315+
pulledImages[i] = api.ImageSummary{
316+
ID: id,
317+
Repository: service.Image,
318+
LastTagTime: time.Now(),
319+
}
322320
if err != nil && isServiceImageToBuild(service, project.Services) {
323321
// image can be built, so we can ignore pull failure
324322
return nil
@@ -328,14 +326,40 @@ func (s *composeService) pullRequiredImages(ctx context.Context, project *types.
328326
}
329327
err := eg.Wait()
330328
for i, service := range needPull {
331-
if pulledImages[i] != "" {
329+
if pulledImages[i].ID != "" {
332330
images[service.Image] = pulledImages[i]
333331
}
334332
}
335333
return err
336334
}, s.stdinfo())
337335
}
338336

337+
func mustPull(service types.ServiceConfig, images map[string]api.ImageSummary) (bool, error) {
338+
if service.Image == "" {
339+
return false, nil
340+
}
341+
policy, duration, err := service.GetPullPolicy()
342+
if err != nil {
343+
return false, err
344+
}
345+
switch policy {
346+
case types.PullPolicyAlways:
347+
// force pull
348+
return true, nil
349+
case types.PullPolicyNever, types.PullPolicyBuild:
350+
return false, nil
351+
case types.PullPolicyRefresh:
352+
img, ok := images[service.Image]
353+
if !ok {
354+
return true, nil
355+
}
356+
return time.Now().After(img.LastTagTime.Add(duration)), nil
357+
default: // Pull if missing
358+
_, ok := images[service.Image]
359+
return !ok, nil
360+
}
361+
}
362+
339363
func isServiceImageToBuild(service types.ServiceConfig, services types.Services) bool {
340364
if service.Build != nil {
341365
return true

0 commit comments

Comments
 (0)