Skip to content

Commit

Permalink
Adds ability for user to define additional tags for images (loft-sh#1388
Browse files Browse the repository at this point in the history
)

* Adds ability for use to define additional tags for images

* Fixes tag validation logic

* addresses PR feedback

* golangci-lint
  • Loading branch information
fcaroline2020 authored Dec 5, 2024
1 parent d31fb83 commit 6e37df8
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 10 deletions.
8 changes: 8 additions & 0 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ func NewBuildCmd(flags *flags.GlobalFlags) *cobra.Command {
}
}

// validate tags
if len(cmd.Tag) > 0 {
if err := image.ValidateTags(cmd.Tag); err != nil {
return fmt.Errorf("cannot build image, %w", err)
}
}

// create a temporary workspace
exists := workspace2.Exists(ctx, devPodConfig, args, "", log.Default)
sshConfigFile, err := os.CreateTemp("", "devpodssh.config")
Expand Down Expand Up @@ -112,6 +119,7 @@ func NewBuildCmd(flags *flags.GlobalFlags) *cobra.Command {
buildCmd.Flags().BoolVar(&cmd.SkipDelete, "skip-delete", false, "If true will not delete the workspace after building it")
buildCmd.Flags().StringVar(&cmd.Machine, "machine", "", "The machine to use for this workspace. The machine needs to exist beforehand or the command will fail. If the workspace already exists, this option has no effect")
buildCmd.Flags().StringVar(&cmd.Repository, "repository", "", "The repository to push to")
buildCmd.Flags().StringSliceVar(&cmd.Tag, "tag", []string{}, "Image Tag(s) in the form of a comma separated list --tag latest,arm64 or multiple flags --tag latest --tag arm64")
buildCmd.Flags().StringSliceVar(&cmd.Platform, "platform", []string{}, "Set target platform for build")
buildCmd.Flags().BoolVar(&cmd.SkipPush, "skip-push", false, "If true will not push the image to the repository, useful for testing")
buildCmd.Flags().Var(&cmd.GitCloneStrategy, "git-clone-strategy", "The git clone strategy DevPod uses to checkout git based workspaces. Can be full (default), blobless, treeless or shallow")
Expand Down
4 changes: 4 additions & 0 deletions pkg/devcontainer/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func (r *runner) build(
ImageName: overrideBuildImageName,
PrebuildHash: imageTag,
RegistryCache: options.RegistryCache,
Tags: options.Tag,
}, nil
}

Expand Down Expand Up @@ -135,6 +136,7 @@ func (r *runner) extendImage(
ImageMetadata: extendedBuildInfo.MetadataConfig,
ImageName: imageBase,
RegistryCache: options.RegistryCache,
Tags: options.Tag,
}, nil
}

Expand Down Expand Up @@ -322,6 +324,7 @@ func (r *runner) buildImage(
ImageName: prebuildImage,
PrebuildHash: prebuildHash,
RegistryCache: options.RegistryCache,
Tags: options.Tag,
}, nil
} else if err != nil {
r.Log.Debugf("Error trying to find prebuild image %s: %v", prebuildImage, err)
Expand Down Expand Up @@ -385,6 +388,7 @@ func dockerlessFallback(
User: buildInfo.User,
},
RegistryCache: options.RegistryCache,
Tags: options.Tag,
}, nil
}

Expand Down
1 change: 1 addition & 0 deletions pkg/devcontainer/config/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type BuildInfo struct {
ImageName string
PrebuildHash string
RegistryCache string
Tags []string

Dockerless *BuildInfoDockerless
}
Expand Down
32 changes: 24 additions & 8 deletions pkg/devcontainer/prebuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/loft-sh/devpod/pkg/devcontainer/config"
"github.com/loft-sh/devpod/pkg/driver"
Expand Down Expand Up @@ -70,27 +71,42 @@ func (r *runner) Build(ctx context.Context, options provider.BuildOptions) (stri
}

if isDockerComposeConfig(substitutedConfig.Config) {
r.Log.Debug("Tagging image prebuild=%s buildInfo=%s", prebuildImage, buildInfo.ImageName)
err = dockerDriver.TagDevContainer(ctx, buildInfo.ImageName, prebuildImage)
if err != nil {
if err := dockerDriver.TagDevContainer(ctx, buildInfo.ImageName, prebuildImage); err != nil {
return "", errors.Wrap(err, "tag image")
}
}

// check if we can push image
err = image.CheckPushPermissions(prebuildImage)
if err != nil {
if err := image.CheckPushPermissions(prebuildImage); err != nil {
return "", fmt.Errorf(
"cannot push to repository %s. Please make sure you are logged into the registry and credentials are available. (Error: %w)",
prebuildImage,
err,
)
}

// Setup all image tags (prebuild and any user defined tags)
imageRefs := []string{prebuildImage}

imageRepoName := strings.Split(prebuildImage, ":")
if buildInfo.Tags != nil {
for _, tag := range buildInfo.Tags {
imageRefs = append(imageRefs, imageRepoName[0]+":"+tag)
}
}

// tag the image
for _, imageRef := range imageRefs {
if err := dockerDriver.TagDevContainer(ctx, prebuildImage, imageRef); err != nil {
return "", errors.Wrap(err, "tag image")
}
}

// push the image to the registry
err = dockerDriver.PushDevContainer(ctx, prebuildImage)
if err != nil {
return "", errors.Wrap(err, "push image")
for _, imageRef := range imageRefs {
if err := dockerDriver.PushDevContainer(ctx, imageRef); err != nil {
return "", errors.Wrap(err, "push image")
}
}

return prebuildImage, nil
Expand Down
2 changes: 2 additions & 0 deletions pkg/driver/docker/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func (d *dockerDriver) BuildDevContainer(
ImageName: imageName,
PrebuildHash: prebuildHash,
RegistryCache: options.RegistryCache,
Tags: options.Tag,
}, nil
} else if err != nil {
d.Log.Debugf("Error trying to find local image %s: %v", imageName, err)
Expand Down Expand Up @@ -114,6 +115,7 @@ func (d *dockerDriver) BuildDevContainer(
ImageName: imageName,
PrebuildHash: prebuildHash,
RegistryCache: options.RegistryCache,
Tags: options.Tag,
}, nil
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/driver/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func (d *dockerDriver) PushDevContainer(ctx context.Context, image string) error
}

func (d *dockerDriver) TagDevContainer(ctx context.Context, image, tag string) error {
// push image
// Tag image
writer := d.Log.Writer(logrus.InfoLevel, false)
defer writer.Close()

Expand All @@ -127,7 +127,7 @@ func (d *dockerDriver) TagDevContainer(ctx context.Context, image, tag string) e
d.Log.Debugf("Running docker command: %s %s", d.Docker.DockerCommand, strings.Join(args, " "))
err := d.Docker.Run(ctx, args, nil, writer, writer)
if err != nil {
return errors.Wrap(err, "push image")
return errors.Wrap(err, "tag image")
}

return nil
Expand Down
23 changes: 23 additions & 0 deletions pkg/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"regexp"

"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
Expand All @@ -12,6 +13,11 @@ import (
"github.com/pkg/errors"
)

var (
dockerTagRegexp = regexp.MustCompile(`^[\w][\w.-]*$`)
DockerTagMaxSize = 128
)

func GetImage(ctx context.Context, image string) (v1.Image, error) {
ref, err := name.ParseReference(image)
if err != nil {
Expand Down Expand Up @@ -58,3 +64,20 @@ func GetImageConfig(ctx context.Context, image string) (*v1.ConfigFile, v1.Image

return configFile, img, nil
}

func ValidateTags(tags []string) error {
for _, tag := range tags {
if !IsValidDockerTag(tag) {
return fmt.Errorf(`%q is not a valid docker tag`, tag)
}
}
return nil
}

func IsValidDockerTag(tag string) bool {
return shouldNotBeSlugged(tag, dockerTagRegexp, DockerTagMaxSize)
}

func shouldNotBeSlugged(data string, regexp *regexp.Regexp, maxSize int) bool {
return len(data) == 0 || regexp.Match([]byte(data)) && len(data) <= maxSize
}
1 change: 1 addition & 0 deletions pkg/provider/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ type CLIOptions struct {
Repository string `json:"repository,omitempty"`
SkipPush bool `json:"skipPush,omitempty"`
Platform []string `json:"platform,omitempty"`
Tag []string `json:"tag,omitempty"`

ForceBuild bool `json:"forceBuild,omitempty"`
ForceDockerless bool `json:"forceDockerless,omitempty"`
Expand Down

0 comments on commit 6e37df8

Please sign in to comment.