Skip to content

Commit

Permalink
Add support for additional tags
Browse files Browse the repository at this point in the history
Signed-off-by: GitHub <noreply@github.com>
  • Loading branch information
sambhav authored Sep 15, 2021
1 parent 4096ed7 commit 4aa9197
Show file tree
Hide file tree
Showing 12 changed files with 101 additions and 9 deletions.
7 changes: 7 additions & 0 deletions api/openapi-spec/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -5832,6 +5832,13 @@
"source"
],
"properties": {
"additionalTags": {
"type": "array",
"items": {
"type": "string"
},
"x-kubernetes-list-type": ""
},
"build": {
"$ref": "#/definitions/kpack.core.v1alpha1.ImageBuild"
},
Expand Down
1 change: 1 addition & 0 deletions docs/image.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ kpack will monitor the inputs to the image configuration to rebuild the image wh
The following defines the relevant fields of the `image` resource spec in more detail:

- `tag`: The image tag.
- `additionalTags`: Any additional list of image tags that should be published. This list of tags is mutable.
- `builder`: Configuration of the `builder` resource the image builds will use. See more info [Builder Configuration](builders.md).
- `serviceAccount`: The Service Account name that will be used for credential lookup.
- `source`: The source code that will be monitored/built into images. See the [Source Configuration](#source-config) section below.
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/build/v1alpha1/build_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func (b *Build) Validate(ctx context.Context) *apis.FieldError {

func (bs *BuildSpec) Validate(ctx context.Context) *apis.FieldError {
return validate.ListNotEmpty(bs.Tags, "tags").
Also(validate.Tags(bs.Tags)).
Also(validate.Tags(bs.Tags, "tags")).
Also(bs.Builder.Validate(ctx).ViaField("builder")).
Also(bs.Source.Validate(ctx).ViaField("source")).
Also(bs.Bindings.Validate(ctx).ViaField("bindings")).
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/build/v1alpha2/build_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func (b *Build) Validate(ctx context.Context) *apis.FieldError {

func (bs *BuildSpec) Validate(ctx context.Context) *apis.FieldError {
return validate.ListNotEmpty(bs.Tags, "tags").
Also(validate.Tags(bs.Tags)).
Also(validate.Tags(bs.Tags, "tags")).
Also(bs.Cache.Validate(ctx).ViaField("cache")).
Also(bs.Builder.Validate(ctx).ViaField("builder")).
Also(bs.Source.Validate(ctx).ViaField("source")).
Expand Down
8 changes: 5 additions & 3 deletions pkg/apis/build/v1alpha2/image_builds.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func (im *Image) SourceResolver() *SourceResolver {

func (im *Image) generateTags(buildNumber string) []string {
if im.disableAdditionalImageNames() {
return []string{im.Spec.Tag}
return append([]string{im.Spec.Tag}, im.Spec.AdditionalTags...)
}
now := time.Now()

Expand All @@ -197,9 +197,11 @@ func (im *Image) generateTags(buildNumber string) []string {
if tagName == "latest-" {
tagName = ""
}
return []string{
return append([]string{
im.Spec.Tag,
tag.RegistryStr() + "/" + tag.RepositoryStr() + ":" + tagName + "b" + buildNumber + "." + now.Format("20060102") + "." + fmt.Sprintf("%02d%02d%02d", now.Hour(), now.Minute(), now.Second())}
tag.RegistryStr() + "/" + tag.RepositoryStr() + ":" + tagName + "b" + buildNumber + "." + now.Format("20060102") + "." + fmt.Sprintf("%02d%02d%02d", now.Hour(), now.Minute(), now.Second())},
im.Spec.AdditionalTags...,
)
}

func (im *Image) generateBuildName(buildNumber string) string {
Expand Down
18 changes: 17 additions & 1 deletion pkg/apis/build/v1alpha2/image_builds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,21 @@ func testImageBuilds(t *testing.T, when spec.G, it spec.S) {
assert.Equal(t, build.Spec.Source.Registry.Image, "some-registry.io/some-image")
})

it("with excludes additional tags names when explicitly disabled", func() {
it("with excludes additional autogenerated tags names when explicitly disabled", func() {
image.Spec.Tag = "imagename/foo:test"
image.Spec.ImageTaggingStrategy = corev1alpha1.None
build := image.Build(sourceResolver, builder, latestBuild, "", "", 1)
require.Len(t, build.Spec.Tags, 1)
})

it("with adds additional tags names", func() {
image.Spec.Tag = "imagename/foo:test"
image.Spec.AdditionalTags = []string{"imagename/foo:test2", "anotherimage/foo:test3"}
image.Spec.ImageTaggingStrategy = corev1alpha1.None
build := image.Build(sourceResolver, builder, latestBuild, "", "", 1)
require.Len(t, build.Spec.Tags, 3)
})

it("generates a build with default process when set", func() {
image.Spec.DefaultProcess = "sys-info"
image.Name = "imageName"
Expand All @@ -195,6 +203,14 @@ func testImageBuilds(t *testing.T, when spec.G, it spec.S) {
require.Regexp(t, "gcr.io/imagename/notags:b1\\.\\d{8}\\.\\d{6}", build.Spec.Tags[1])
})

it("with additional tags if provided", func() {
image.Spec.Tag = "gcr.io/imagename/tagged:latest"
image.Spec.AdditionalTags = []string{"imagename/foo:test2", "anotherimage/foo:test3"}
build := image.Build(sourceResolver, builder, latestBuild, "", "", 1)
require.Len(t, build.Spec.Tags, 4)
require.Regexp(t, "gcr.io/imagename/tagged:b1\\.\\d{8}\\.\\d{6}", build.Spec.Tags[1])
})

it("without tag prefix if image name has the tag 'latest' provided", func() {
image.Spec.Tag = "gcr.io/imagename/tagged:latest"
build := image.Build(sourceResolver, builder, latestBuild, "", "", 1)
Expand Down
2 changes: 2 additions & 0 deletions pkg/apis/build/v1alpha2/image_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ type ImageSpec struct {
Build *corev1alpha1.ImageBuild `json:"build,omitempty"`
Notary *corev1alpha1.NotaryConfig `json:"notary,omitempty"`
DefaultProcess string `json:"defaultProcess,omitempty"`
// +listType
AdditionalTags []string `json:"additionalTags,omitempty"`
}

// +k8s:openapi-gen=true
Expand Down
27 changes: 27 additions & 0 deletions pkg/apis/build/v1alpha2/image_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"k8s.io/apimachinery/pkg/util/validation"
"knative.dev/pkg/apis"

"github.com/google/go-containerregistry/pkg/name"
corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1"
"github.com/pivotal/kpack/pkg/apis/validate"
)
Expand Down Expand Up @@ -80,6 +81,7 @@ func (i *Image) validateName(imageName string) *apis.FieldError {

func (is *ImageSpec) ValidateSpec(ctx context.Context) *apis.FieldError {
return is.validateTag(ctx).
Also(is.validateAdditionalTags(ctx)).
Also(validateBuilder(is.Builder).ViaField("builder")).
Also(is.Source.Validate(ctx).ViaField("source")).
Also(is.Build.Validate(ctx).ViaField("build")).
Expand All @@ -97,6 +99,31 @@ func (is *ImageSpec) validateTag(ctx context.Context) *apis.FieldError {
return validate.Tag(is.Tag)
}

func (is *ImageSpec) validateAdditionalTags(ctx context.Context) *apis.FieldError {
return validate.Tags(is.AdditionalTags, "additionalTags").Also(is.validateSameRegistry())
}

func (is *ImageSpec) validateSameRegistry() *apis.FieldError {
tag, err := name.NewTag(is.Tag, name.WeakValidation)
// We only care about the non-nil error cases here as we validate
// the tag validity in other methods which should display appropriate errors.
if err == nil {
for _, t := range is.AdditionalTags {
addT, err := name.NewTag(t, name.WeakValidation)
if err == nil {
if addT.RegistryStr() != tag.RegistryStr() {
return &apis.FieldError{
Message: "all additionalTags must have the same registry as tag",
Paths: []string{"additionalTags"},
Details: fmt.Sprintf("expected registry: %s, got: %s", tag.RegistryStr(), addT.RegistryStr()),
}
}
}
}
}
return nil
}

func (is *ImageSpec) validateVolumeCache(ctx context.Context) *apis.FieldError {
if is.Cache.Volume != nil && ctx.Value(HasDefaultStorageClass) == nil {
return apis.ErrGeneric("spec.cache.volume.size cannot be set with no default StorageClass")
Expand Down
14 changes: 14 additions & 0 deletions pkg/apis/build/v1alpha2/image_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,20 @@ func testImageValidation(t *testing.T, when spec.G, it spec.S) {
assertValidationError(image, ctx, apis.ErrInvalidValue(image.Spec.Tag, "tag").ViaField("spec"))
})

it("invalid additional image tags", func() {
image.Spec.AdditionalTags = []string{"valid/tag", "invalid/tag@sha256:thisisatag", "also/invalid@@"}
assertValidationError(image,
ctx,
apis.ErrInvalidArrayValue(image.Spec.AdditionalTags[1], "additionalTags", 1).
Also(apis.ErrInvalidArrayValue(image.Spec.AdditionalTags[2], "additionalTags", 2)).
ViaField("spec"))
})

it("tags from multiple registries", func() {
image.Spec.AdditionalTags = []string{"valid/tag", "gcr.io/valid/tag"}
assertValidationError(image, ctx, errors.New("all additionalTags must have the same registry as tag: spec.additionalTags\nexpected registry: index.docker.io, got: gcr.io"))
})

it("tag does not contain fully qualified digest", func() {
image.Spec.Tag = "some/app@sha256:72d10a33e3233657832967acffce652b729961da5247550ea58b2c2389cddc68"

Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/build/v1alpha2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pkg/apis/validate/validation_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ func Tag(value string) *apis.FieldError {
return nil
}

func Tags(tags []string) *apis.FieldError {
func Tags(tags []string, fieldName string) *apis.FieldError {
var errors *apis.FieldError = nil
for i, tag := range tags {
_, err := name.NewTag(tag, name.WeakValidation)
if err != nil {
//noinspection GoNilness
errors = errors.Also(apis.ErrInvalidArrayValue(tag, "tags", i))
errors = errors.Also(apis.ErrInvalidArrayValue(tag, fieldName, i))
}
}
return errors
Expand Down
20 changes: 19 additions & 1 deletion pkg/openapi/openapi_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 4aa9197

Please sign in to comment.