introduce ImageDigestLabel to track image built for service

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2021-08-24 10:50:44 +02:00
parent 9c615dc22e
commit 58bfbbb288
No known key found for this signature in database
GPG Key ID: 9858809D6F8F6E7E
5 changed files with 28 additions and 38 deletions

View File

@ -47,6 +47,8 @@ const (
OneoffLabel = "com.docker.compose.oneoff" OneoffLabel = "com.docker.compose.oneoff"
// SlugLabel stores unique slug used for one-off container identity // SlugLabel stores unique slug used for one-off container identity
SlugLabel = "com.docker.compose.slug" SlugLabel = "com.docker.compose.slug"
// ImageDigestLabel stores digest of the container image used to run service
ImageDigestLabel = "com.docker.compose.image"
// VersionLabel stores the compose tool version used to run application // VersionLabel stores the compose tool version used to run application
VersionLabel = "com.docker.compose.version" VersionLabel = "com.docker.compose.version"
) )

View File

@ -28,7 +28,6 @@ import (
_ "github.com/docker/buildx/driver/docker" // required to get default driver registered _ "github.com/docker/buildx/driver/docker" // required to get default driver registered
"github.com/docker/buildx/util/buildflags" "github.com/docker/buildx/util/buildflags"
xprogress "github.com/docker/buildx/util/progress" xprogress "github.com/docker/buildx/util/progress"
moby "github.com/docker/docker/api/types"
bclient "github.com/moby/buildkit/client" bclient "github.com/moby/buildkit/client"
"github.com/moby/buildkit/session" "github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/auth/authprovider" "github.com/moby/buildkit/session/auth/authprovider"
@ -80,7 +79,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
} }
} }
_, err := s.doBuild(ctx, project, opts, Containers{}, options.Progress) _, err := s.doBuild(ctx, project, opts, options.Progress)
if err == nil { if err == nil {
if len(imagesToBuild) > 0 && !options.Quiet { if len(imagesToBuild) > 0 && !options.Quiet {
utils.DisplayScanSuggestMsg() utils.DisplayScanSuggestMsg()
@ -90,7 +89,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
return err return err
} }
func (s *composeService) ensureImagesExists(ctx context.Context, project *types.Project, observedState Containers, quietPull bool) error { func (s *composeService) ensureImagesExists(ctx context.Context, project *types.Project, quietPull bool) error {
for _, service := range project.Services { for _, service := range project.Services {
if service.Image == "" && service.Build == nil { if service.Image == "" && service.Build == nil {
return fmt.Errorf("invalid service %q. Must specify either image or build", service.Name) return fmt.Errorf("invalid service %q. Must specify either image or build", service.Name)
@ -111,37 +110,41 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
if quietPull { if quietPull {
mode = xprogress.PrinterModeQuiet mode = xprogress.PrinterModeQuiet
} }
opts, imagesToBuild, err := s.getBuildOptions(project, images) opts, err := s.getBuildOptions(project, images)
if err != nil { if err != nil {
return err return err
} }
builtImages, err := s.doBuild(ctx, project, opts, observedState, mode) builtImages, err := s.doBuild(ctx, project, opts, mode)
if err != nil { if err != nil {
return err return err
} }
if len(imagesToBuild) > 0 { if len(builtImages) > 0 {
utils.DisplayScanSuggestMsg() utils.DisplayScanSuggestMsg()
} }
for name, digest := range builtImages { for name, digest := range builtImages {
images[name] = digest images[name] = digest
} }
// set digest as service.Image // set digest as com.docker.compose.image label so we can detect outdated containers
for i, service := range project.Services { for i, service := range project.Services {
digest, ok := images[getImageName(service, project.Name)] image := getImageName(service, project.Name)
digest, ok := images[image]
if ok { if ok {
project.Services[i].Image = digest if project.Services[i].Labels == nil {
project.Services[i].Labels = types.Labels{}
}
project.Services[i].Labels[api.ImageDigestLabel] = digest
project.Services[i].Image = image
} }
} }
return nil return nil
} }
func (s *composeService) getBuildOptions(project *types.Project, images map[string]string) (map[string]build.Options, []string, error) { func (s *composeService) getBuildOptions(project *types.Project, images map[string]string) (map[string]build.Options, error) {
opts := map[string]build.Options{} opts := map[string]build.Options{}
imagesToBuild := []string{}
for _, service := range project.Services { for _, service := range project.Services {
if service.Image == "" && service.Build == nil { if service.Image == "" && service.Build == nil {
return nil, nil, fmt.Errorf("invalid service %q. Must specify either image or build", service.Name) return nil, fmt.Errorf("invalid service %q. Must specify either image or build", service.Name)
} }
imageName := getImageName(service, project.Name) imageName := getImageName(service, project.Name)
_, localImagePresent := images[imageName] _, localImagePresent := images[imageName]
@ -150,16 +153,15 @@ func (s *composeService) getBuildOptions(project *types.Project, images map[stri
if localImagePresent && service.PullPolicy != types.PullPolicyBuild { if localImagePresent && service.PullPolicy != types.PullPolicyBuild {
continue continue
} }
imagesToBuild = append(imagesToBuild, imageName)
opt, err := s.toBuildOptions(project, service, imageName) opt, err := s.toBuildOptions(project, service, imageName)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
opts[imageName] = opt opts[imageName] = opt
continue continue
} }
} }
return opts, imagesToBuild, nil return opts, nil
} }
@ -182,7 +184,7 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
return images, nil return images, nil
} }
func (s *composeService) doBuild(ctx context.Context, project *types.Project, opts map[string]build.Options, observedState Containers, mode string) (map[string]string, error) { func (s *composeService) doBuild(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
info, err := s.apiClient.Info(ctx) info, err := s.apiClient.Info(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
@ -227,18 +229,6 @@ func (s *composeService) doBuild(ctx context.Context, project *types.Project, op
return nil, WrapCategorisedComposeError(err, BuildFailure) return nil, WrapCategorisedComposeError(err, BuildFailure)
} }
cw := progress.ContextWriter(ctx)
for _, c := range observedState {
for imageName := range opts {
if c.Image == imageName {
err = s.removeContainers(ctx, cw, []moby.Container{c}, nil, false)
if err != nil {
return nil, err
}
}
}
}
imagesBuilt := map[string]string{} imagesBuilt := map[string]string{}
for name, img := range response { for name, img := range response {
if img == nil || len(img.ExporterResponse) == 0 { if img == nil || len(img.ExporterResponse) == 0 {

View File

@ -60,7 +60,7 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
return err return err
} }
err = s.ensureImagesExists(ctx, project, observedState, options.QuietPull) err = s.ensureImagesExists(ctx, project, options.QuietPull)
if err != nil { if err != nil {
return err return err
} }

View File

@ -33,12 +33,7 @@ import (
) )
func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) { func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) {
observedState, err := s.getContainers(ctx, project.Name, oneOffInclude, true) containerID, err := s.prepareRun(ctx, project, opts)
if err != nil {
return 0, err
}
containerID, err := s.prepareRun(ctx, project, observedState, opts)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -131,7 +126,7 @@ func (s *composeService) runInteractive(ctx context.Context, containerID string,
} }
} }
func (s *composeService) prepareRun(ctx context.Context, project *types.Project, observedState Containers, opts api.RunOptions) (string, error) { func (s *composeService) prepareRun(ctx context.Context, project *types.Project, opts api.RunOptions) (string, error) {
service, err := project.GetService(opts.Service) service, err := project.GetService(opts.Service)
if err != nil { if err != nil {
return "", err return "", err
@ -152,7 +147,7 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
service.Labels = service.Labels.Add(api.SlugLabel, slug) service.Labels = service.Labels.Add(api.SlugLabel, slug)
service.Labels = service.Labels.Add(api.OneoffLabel, "True") service.Labels = service.Labels.Add(api.OneoffLabel, "True")
if err := s.ensureImagesExists(ctx, project, observedState, false); err != nil { // all dependencies already checked, but might miss service img if err := s.ensureImagesExists(ctx, project, false); err != nil { // all dependencies already checked, but might miss service img
return "", err return "", err
} }
if err := s.waitDependencies(ctx, project, service); err != nil { if err := s.waitDependencies(ctx, project, service); err != nil {

View File

@ -37,7 +37,10 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
if err != nil { if err != nil {
return err return err
} }
if options.Start.Attach == nil {
return s.start(ctx, project, options.Start, nil) return s.start(ctx, project, options.Start, nil)
}
return nil
}) })
if err != nil { if err != nil {
return err return err