From 2a2ab54e04204657e644e3884e8bad1dac5efbe2 Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Tue, 26 Sep 2023 14:02:29 -0400 Subject: [PATCH 01/20] First create builder unit passes Signed-off-by: Natalie Arellano --- cmd/rebase/main.go | 2 +- pkg/apis/build/v1alpha2/build.go | 1 + pkg/apis/build/v1alpha2/build_pod.go | 1 + pkg/apis/build/v1alpha2/builder_lifecycle.go | 2 + pkg/apis/build/v1alpha2/builder_types.go | 4 +- pkg/apis/build/v1alpha2/builder_validation.go | 1 + pkg/cnb/builder_builder.go | 30 +- pkg/cnb/buildpack_metadata.go | 13 +- pkg/cnb/create_builder.go | 25 +- pkg/cnb/create_builder_test.go | 360 +++++++++++++++++- pkg/cnb/fakes_test.go | 35 +- pkg/cnb/remote_buildpack_fetcher.go | 1 + pkg/cnb/remote_store_reader.go | 1 + 13 files changed, 449 insertions(+), 27 deletions(-) diff --git a/cmd/rebase/main.go b/cmd/rebase/main.go index f372c8b84..bf46c5636 100644 --- a/cmd/rebase/main.go +++ b/cmd/rebase/main.go @@ -117,7 +117,7 @@ func rebase(tags []string, logger *log.Logger) error { rebaser := lifecycle.Rebaser{ Logger: cmd.DefaultLogger, - PlatformAPI: api.MustParse("0.9"), + PlatformAPI: api.MustParse("0.10"), } report, err := rebaser.Rebase(appImage, newBaseImage, appImage.Name(), tags[1:]) if err != nil { diff --git a/pkg/apis/build/v1alpha2/build.go b/pkg/apis/build/v1alpha2/build.go index cdd24cd0b..0714cc7e4 100644 --- a/pkg/apis/build/v1alpha2/build.go +++ b/pkg/apis/build/v1alpha2/build.go @@ -161,6 +161,7 @@ var buildSteps = map[string]struct{}{ AnalyzeContainerName: {}, DetectContainerName: {}, RestoreContainerName: {}, + ExtendContainerName: {}, BuildContainerName: {}, ExportContainerName: {}, CompletionContainerName: {}, diff --git a/pkg/apis/build/v1alpha2/build_pod.go b/pkg/apis/build/v1alpha2/build_pod.go index bf114cf19..21a89dd00 100644 --- a/pkg/apis/build/v1alpha2/build_pod.go +++ b/pkg/apis/build/v1alpha2/build_pod.go @@ -23,6 +23,7 @@ const ( AnalyzeContainerName = "analyze" DetectContainerName = "detect" RestoreContainerName = "restore" + ExtendContainerName = "extend" BuildContainerName = "build" ExportContainerName = "export" RebaseContainerName = "rebase" diff --git a/pkg/apis/build/v1alpha2/builder_lifecycle.go b/pkg/apis/build/v1alpha2/builder_lifecycle.go index df199de2e..8b1eee2ff 100644 --- a/pkg/apis/build/v1alpha2/builder_lifecycle.go +++ b/pkg/apis/build/v1alpha2/builder_lifecycle.go @@ -13,6 +13,7 @@ type BuilderRecord struct { Stack corev1alpha1.BuildStack Buildpacks corev1alpha1.BuildpackMetadataList Order []corev1alpha1.OrderEntry + OrderExtensions []corev1alpha1.OrderEntry ObservedStoreGeneration int64 ObservedStackGeneration int64 OS string @@ -30,6 +31,7 @@ func (bs *BuilderStatus) BuilderRecord(record BuilderRecord) { }, } bs.Order = record.Order + // TODO: bs.OrderExtensions = record.OrderExtensions bs.ObservedStoreGeneration = record.ObservedStoreGeneration bs.ObservedStackGeneration = record.ObservedStackGeneration bs.OS = record.OS diff --git a/pkg/apis/build/v1alpha2/builder_types.go b/pkg/apis/build/v1alpha2/builder_types.go index b81ae4023..b4449d0d4 100644 --- a/pkg/apis/build/v1alpha2/builder_types.go +++ b/pkg/apis/build/v1alpha2/builder_types.go @@ -32,7 +32,8 @@ type BuilderSpec struct { Stack corev1.ObjectReference `json:"stack,omitempty"` Store corev1.ObjectReference `json:"store,omitempty"` // +listType - Order []BuilderOrderEntry `json:"order,omitempty"` + Order []BuilderOrderEntry `json:"order,omitempty"` + OrderExtensions []BuilderOrderEntry `json:"order-extensions,omitempty"` } // +k8s:openapi-gen=true @@ -60,6 +61,7 @@ type BuilderStatus struct { corev1alpha1.Status `json:",inline"` BuilderMetadata corev1alpha1.BuildpackMetadataList `json:"builderMetadata,omitempty"` Order []corev1alpha1.OrderEntry `json:"order,omitempty"` + OrderExtensions []corev1alpha1.OrderEntry `json:"order-extensions,omitempty"` Stack corev1alpha1.BuildStack `json:"stack,omitempty"` LatestImage string `json:"latestImage,omitempty"` ObservedStackGeneration int64 `json:"observedStackGeneration,omitempty"` diff --git a/pkg/apis/build/v1alpha2/builder_validation.go b/pkg/apis/build/v1alpha2/builder_validation.go index 69d60d3c4..249c7887e 100644 --- a/pkg/apis/build/v1alpha2/builder_validation.go +++ b/pkg/apis/build/v1alpha2/builder_validation.go @@ -32,6 +32,7 @@ func (s *BuilderSpec) Validate(ctx context.Context) *apis.FieldError { Also(validateStack(s.Stack).ViaField("stack")). Also(validateStore(s.Store).ViaField("store")). Also(validateOrder(s.Order).ViaField("order")) + // TODO: validate order for extensions } func (s *NamespacedBuilderSpec) Validate(ctx context.Context) *apis.FieldError { diff --git a/pkg/cnb/builder_builder.go b/pkg/cnb/builder_builder.go index 43b5c40db..67a55cdbc 100644 --- a/pkg/cnb/builder_builder.go +++ b/pkg/cnb/builder_builder.go @@ -44,7 +44,9 @@ type builderBlder struct { LifecycleMetadata LifecycleMetadata stackId string order []corev1alpha1.OrderEntry + orderExtensions []corev1alpha1.OrderEntry buildpackLayers map[DescriptiveBuildpackInfo]buildpackLayer + extensionLayers map[DescriptiveBuildpackInfo]buildpackLayer cnbUserId int cnbGroupId int kpackVersion string @@ -56,6 +58,7 @@ type builderBlder struct { func newBuilderBldr(kpackVersion string) *builderBlder { return &builderBlder{ buildpackLayers: map[DescriptiveBuildpackInfo]buildpackLayer{}, + extensionLayers: map[DescriptiveBuildpackInfo]buildpackLayer{}, kpackVersion: kpackVersion, } } @@ -81,7 +84,7 @@ func (bb *builderBlder) AddLifecycle(lifecycleLayer v1.Layer, lifecycleMetadata bb.LifecycleMetadata = lifecycleMetadata } -func (bb *builderBlder) AddGroup(buildpacks ...RemoteBuildpackRef) { +func (bb *builderBlder) AddBuildpackGroup(buildpacks ...RemoteBuildpackRef) { group := make([]corev1alpha1.BuildpackRef, 0, len(buildpacks)) for _, b := range buildpacks { group = append(group, b.buildpackRef()) @@ -93,8 +96,21 @@ func (bb *builderBlder) AddGroup(buildpacks ...RemoteBuildpackRef) { bb.order = append(bb.order, corev1alpha1.OrderEntry{Group: group}) } +func (bb *builderBlder) AddExtensionGroup(extensions ...RemoteBuildpackRef) { + group := make([]corev1alpha1.BuildpackRef, 0, len(extensions)) + for _, b := range extensions { + group = append(group, b.buildpackRef()) + + for _, layer := range b.Layers { + bb.extensionLayers[layer.BuildpackInfo] = layer + } + } + bb.orderExtensions = append(bb.orderExtensions, corev1alpha1.OrderEntry{Group: group}) +} + func (bb *builderBlder) WriteableImage() (v1.Image, error) { buildpacks := bb.buildpacks() + extensions := bb.extensions() err := bb.validateBuilder(buildpacks) if err != nil { @@ -103,6 +119,7 @@ func (bb *builderBlder) WriteableImage() (v1.Image, error) { buildpackLayerMetadata := BuildpackLayerMetadata{} buildpackLayers := make([]v1.Layer, 0, len(bb.buildpackLayers)) + // TODO: extensionLayers := make([]v1.Layer, 0, len(bb.extensionLayers)) for _, key := range buildpacks { layer := bb.buildpackLayers[key] @@ -151,11 +168,12 @@ func (bb *builderBlder) WriteableImage() (v1.Image, error) { return nil, err } + // TODO: set extensionOrderLabel, extensionLayersLabel return imagehelpers.SetLabels(image, map[string]interface{}{ buildpackOrderLabel: bb.order, buildpackLayersLabel: buildpackLayerMetadata, lifecycleApisLabel: bb.LifecycleMetadata.APIs, - buildpackMetadataLabel: BuilderImageMetadata{ + builderMetadataLabel: BuilderImageMetadata{ Description: "Custom Builder built with kpack", Stack: StackMetadata{ RunImage: RunImageMetadata{ @@ -169,10 +187,12 @@ func (bb *builderBlder) WriteableImage() (v1.Image, error) { Version: bb.kpackVersion, }, Buildpacks: buildpacks, + Extensions: extensions, }, }) } +// TODO: add extensions func (bb *builderBlder) validateBuilder(sortedBuildpacks []DescriptiveBuildpackInfo) error { platformApis := append(bb.LifecycleMetadata.APIs.Platform.Deprecated, bb.LifecycleMetadata.APIs.Platform.Supported...) err := validatePlatformApis(platformApis) @@ -214,6 +234,10 @@ func (bb *builderBlder) buildpacks() []DescriptiveBuildpackInfo { return deterministicSortBySize(bb.buildpackLayers) } +func (bb *builderBlder) extensions() []DescriptiveBuildpackInfo { + return deterministicSortBySize(bb.extensionLayers) +} + func (bb *builderBlder) stackLayer() (v1.Layer, error) { type tomlRunImage struct { Image string `toml:"image"` @@ -255,7 +279,7 @@ func (bb *builderBlder) orderLayer() (v1.Layer, error) { orderBuf := &bytes.Buffer{} - order := make(tomlOrder, 0, len(bb.order)) + order := make(tomlOrder, 0, len(bb.order)) // TODO: update with extensions for _, o := range bb.order { bps := make([]tomlBuildpack, 0, len(o.Group)) for _, b := range o.Group { diff --git a/pkg/cnb/buildpack_metadata.go b/pkg/cnb/buildpack_metadata.go index ea2555b81..441d08a8a 100644 --- a/pkg/cnb/buildpack_metadata.go +++ b/pkg/cnb/buildpack_metadata.go @@ -5,11 +5,13 @@ import ( ) const ( - buildpackOrderLabel = "io.buildpacks.buildpack.order" - buildpackLayersLabel = "io.buildpacks.buildpack.layers" - buildpackMetadataLabel = "io.buildpacks.builder.metadata" - lifecycleVersionLabel = "io.buildpacks.lifecycle.version" - lifecycleApisLabel = "io.buildpacks.lifecycle.apis" + builderMetadataLabel = "io.buildpacks.builder.metadata" + buildpackLayersLabel = "io.buildpacks.buildpack.layers" + buildpackOrderLabel = "io.buildpacks.buildpack.order" + extensionLayersLabel = "io.buildpacks.extension.layers" + extensionOrderLabel = "io.buildpacks.buildpack.order-extensions" + lifecycleApisLabel = "io.buildpacks.lifecycle.apis" + lifecycleVersionLabel = "io.buildpacks.lifecycle.version" ) type BuildpackLayerInfo struct { @@ -36,6 +38,7 @@ type BuilderImageMetadata struct { Lifecycle LifecycleMetadata `json:"lifecycle"` CreatedBy CreatorMetadata `json:"createdBy"` Buildpacks []DescriptiveBuildpackInfo `json:"buildpacks"` + Extensions []DescriptiveBuildpackInfo `json:"extensions"` } type StackMetadata struct { diff --git a/pkg/cnb/create_builder.go b/pkg/cnb/create_builder.go index 21c05c197..3fb2f08e3 100644 --- a/pkg/cnb/create_builder.go +++ b/pkg/cnb/create_builder.go @@ -47,18 +47,34 @@ func (r *RemoteBuilderCreator) CreateBuilder(ctx context.Context, builderKeychai builderBldr.AddLifecycle(lifecycleLayer, lifecycleMetadata) + // fetch and add buildpacks for _, group := range spec.Order { buildpacks := make([]RemoteBuildpackRef, 0, len(group.Group)) - for _, buildpack := range group.Group { - remoteBuildpack, err := fetcher.ResolveAndFetch(ctx, buildpack) + for _, bp := range group.Group { + remoteBuildpack, err := fetcher.ResolveAndFetch(ctx, bp) if err != nil { return buildapi.BuilderRecord{}, err } - buildpacks = append(buildpacks, remoteBuildpack.Optional(buildpack.Optional)) + buildpacks = append(buildpacks, remoteBuildpack.Optional(bp.Optional)) } - builderBldr.AddGroup(buildpacks...) + builderBldr.AddBuildpackGroup(buildpacks...) + } + + // fetch and add extensions + for _, group := range spec.OrderExtensions { + extensions := make([]RemoteBuildpackRef, 0, len(group.Group)) + + for _, ext := range group.Group { + remoteExtension, err := fetcher.ResolveAndFetch(ctx, ext) + if err != nil { + return buildapi.BuilderRecord{}, err + } + + extensions = append(extensions, remoteExtension.Optional(true)) // extensions are always optional + } + builderBldr.AddExtensionGroup(extensions...) } writeableImage, err := builderBldr.WriteableImage() @@ -84,6 +100,7 @@ func (r *RemoteBuilderCreator) CreateBuilder(ctx context.Context, builderKeychai }, Buildpacks: buildpackMetadata(builderBldr.buildpacks()), Order: builderBldr.order, + OrderExtensions: builderBldr.orderExtensions, ObservedStackGeneration: clusterStack.Status.ObservedGeneration, ObservedStoreGeneration: fetcher.ClusterStoreObservedGeneration(), OS: config.OS, diff --git a/pkg/cnb/create_builder_test.go b/pkg/cnb/create_builder_test.go index 5fd32923d..3c9eedba6 100644 --- a/pkg/cnb/create_builder_test.go +++ b/pkg/cnb/create_builder_test.go @@ -63,7 +63,11 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { ctx = context.Background() - fetcher = &fakeFetcher{buildpacks: map[string][]buildpackLayer{}, observedGeneration: 10} + fetcher = &fakeFetcher{ + buildpacks: map[string][]buildpackLayer{}, + extensions: map[string][]buildpackLayer{}, + observedGeneration: 10, + } linuxLifecycle = &fakeLayer{ digest: "sha256:5d43d12dabe6070c4a4036e700a6f88a52278c02097b5f200e0b49b3d874c954", @@ -196,6 +200,23 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { }, }}) } + + addExtension = func(t *testing.T, id, version, homepage, api string) { + fetcher.AddExtension(t, id, version, []buildpackLayer{{ + v1Layer: buildpack1Layer, + BuildpackInfo: DescriptiveBuildpackInfo{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: id, + Version: version, + }, + Homepage: homepage, + }, + BuildpackLayerInfo: BuildpackLayerInfo{ + API: api, + LayerDiffID: buildpack1Layer.diffID, + }, + }}) + } ) it.Before(func() { @@ -515,7 +536,7 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { assert.JSONEq(t, //language=json `[{"group":[{"id":"io.buildpack.1","version":"v1"},{"id":"io.buildpack.2","version":"v2","optional":true},{"id":"io.buildpack.4","version":"v4"}]}]`, buildpackOrder) - buildpackMetadata, err := imagehelpers.GetStringLabel(savedImage, buildpackMetadataLabel) + builderMetadata, err := imagehelpers.GetStringLabel(savedImage, builderMetadataLabel) assert.NoError(t, err) assert.JSONEq(t, //language=json `{ @@ -569,7 +590,7 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { "homepage": "buildpack.1.com" } ] -}`, buildpackMetadata) +}`, builderMetadata) buildpackLayers, err := imagehelpers.GetStringLabel(savedImage, buildpackLayersLabel) assert.NoError(t, err) @@ -648,6 +669,339 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { } }) + when("provided extensions", func() { + var extensionRef = corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "some-extension-id", + Version: "v1", + }, + } + + it.Before(func() { + clusterBuilderSpec.OrderExtensions = []buildapi.BuilderOrderEntry{ + { + Group: []buildapi.BuilderBuildpackRef{{ + BuildpackRef: extensionRef, + }}, + }, + } + }) + + it.Focus("creates a custom builder with extensions", func() { // TODO: dedup assertions + addExtension(t, extensionRef.Id, extensionRef.Version, "", "0.10") + builderRecord, err := subject.CreateBuilder(ctx, builderKeychain, stackKeychain, fetcher, stack, clusterBuilderSpec) + require.NoError(t, err) + + if os == "windows" { + // TODO: expect some kind of useful error + } + + assert.Len(t, builderRecord.Buildpacks, 4) + assert.Contains(t, builderRecord.Buildpacks, corev1alpha1.BuildpackMetadata{Id: "io.buildpack.1", Version: "v1", Homepage: "buildpack.1.com"}) + assert.Contains(t, builderRecord.Buildpacks, corev1alpha1.BuildpackMetadata{Id: "io.buildpack.2", Version: "v2", Homepage: "buildpack.2.com"}) + assert.Contains(t, builderRecord.Buildpacks, corev1alpha1.BuildpackMetadata{Id: "io.buildpack.3", Version: "v3", Homepage: "buildpack.3.com"}) + assert.Contains(t, builderRecord.Buildpacks, corev1alpha1.BuildpackMetadata{Id: "io.buildpack.4", Version: "v4", Homepage: "buildpack.4.com"}) + assert.Equal(t, corev1alpha1.BuildStack{RunImage: runImage, ID: stackID}, builderRecord.Stack) + assert.Equal(t, int64(10), builderRecord.ObservedStoreGeneration) + assert.Equal(t, int64(11), builderRecord.ObservedStackGeneration) + assert.Equal(t, os, builderRecord.OS) + + assert.Equal(t, builderRecord.Order, []corev1alpha1.OrderEntry{ + { + Group: []corev1alpha1.BuildpackRef{ + { + BuildpackInfo: corev1alpha1.BuildpackInfo{Id: "io.buildpack.1", Version: "v1"}, + Optional: false, + }, + { + BuildpackInfo: corev1alpha1.BuildpackInfo{Id: "io.buildpack.2", Version: "v2"}, + Optional: true, + }, + { + BuildpackInfo: corev1alpha1.BuildpackInfo{Id: "io.buildpack.4", Version: "v4"}, + Optional: false, + }, + }, + }, + }) + + // added in this test + assert.Equal(t, builderRecord.OrderExtensions, []corev1alpha1.OrderEntry{ + { + Group: []corev1alpha1.BuildpackRef{ + { + BuildpackInfo: corev1alpha1.BuildpackInfo{Id: "some-extension-id", Version: "v1"}, + Optional: true, + }, + }, + }, + }) + + assert.Len(t, registryClient.SavedImages(), 1) + savedImage := registryClient.SavedImages()[tag] + + workingDir, err := imagehelpers.GetWorkingDir(savedImage) + require.NoError(t, err) + assert.Equal(t, "/layers", workingDir) + + hash, err := savedImage.Digest() + require.NoError(t, err) + assert.Equal(t, fmt.Sprintf("%s@%s", tag, hash), builderRecord.Image) + + layers, err := savedImage.Layers() + require.NoError(t, err) + + buildpackLayerCount := 3 + defaultDirectoryLayerCount := 1 + stackTomlLayerCount := 1 + orderTomlLayerCount := 1 + assert.Len(t, layers, + buildImageLayers+ + defaultDirectoryLayerCount+ + lifecycleImageLayers+ + stackTomlLayerCount+ + buildpackLayerCount+ + orderTomlLayerCount) + + var layerTester = layerIteratorTester(0) + + for i := 0; i < buildImageLayers; i++ { + layerTester.testNextLayer("Build Image Layer", func(index int) { + buildImgLayers, err := buildImg.Layers() + require.NoError(t, err) + + assert.Equal(t, layers[i], buildImgLayers[i]) + }) + } + + layerTester.testNextLayer("Default Directory Layer", func(index int) { + defaultDirectoryLayer := layers[index] + + assertLayerContents(t, os, defaultDirectoryLayer, map[string]content{ + "/workspace": { + typeflag: tar.TypeDir, + mode: 0755, + uid: cnbUserId, + gid: cnbGroupId, + }, + "/layers": { + typeflag: tar.TypeDir, + mode: 0755, + uid: cnbUserId, + gid: cnbGroupId, + }, + "/cnb": { + typeflag: tar.TypeDir, + mode: 0755, + }, + "/cnb/buildpacks": { + typeflag: tar.TypeDir, + mode: 0755, + }, + "/platform": { + typeflag: tar.TypeDir, + mode: 0755, + }, + "/platform/env": { + typeflag: tar.TypeDir, + mode: 0755, + }, + }) + }) + + layerTester.testNextLayer("Lifecycle Layer", func(index int) { + if os == "linux" { + assert.Equal(t, layers[index], linuxLifecycle) + } else { + assert.Equal(t, layers[index], windowsLifecycle) + } + }) + + layerTester.testNextLayer("Largest Buildpack Layer", func(index int) { + assert.Equal(t, layers[index], buildpack3Layer) + }) + + layerTester.testNextLayer("Middle Buildpack Layer", func(index int) { + assert.Equal(t, layers[index], buildpack2Layer) + }) + + layerTester.testNextLayer("Smallest Buildpack Layer", func(index int) { + assert.Equal(t, layers[index], buildpack1Layer) + }) + + layerTester.testNextLayer("stack Layer", func(index int) { + assertLayerContents(t, os, layers[index], map[string]content{ + "/cnb/stack.toml": //language=toml + { + typeflag: tar.TypeReg, + mode: 0644, + fileContent: //language=toml + `[run-image] + image = "paketo-buildpacks/run:full-cnb" +`, + }, + }) + }) + + layerTester.testNextLayer("order Layer", func(index int) { + assert.Equal(t, len(layers)-1, index) + + assertLayerContents(t, os, layers[index], map[string]content{ + "/cnb/order.toml": { + typeflag: tar.TypeReg, + mode: 0644, + fileContent: //language=toml + `[[order]] + + [[order.group]] + id = "io.buildpack.1" + version = "v1" + + [[order.group]] + id = "io.buildpack.2" + version = "v2" + optional = true + + [[order.group]] + id = "io.buildpack.4" + version = "v4" +`}}) + + }) + + buildpackOrder, err := imagehelpers.GetStringLabel(savedImage, buildpackOrderLabel) + assert.NoError(t, err) + assert.JSONEq(t, //language=json + `[{"group":[{"id":"io.buildpack.1","version":"v1"},{"id":"io.buildpack.2","version":"v2","optional":true},{"id":"io.buildpack.4","version":"v4"}]}]`, buildpackOrder) + + // changed in this test + builderMetadata, err := imagehelpers.GetStringLabel(savedImage, builderMetadataLabel) + assert.NoError(t, err) + assert.JSONEq(t, //language=json + `{ + "description": "Custom Builder built with kpack", + "stack": { + "runImage": { + "image": "paketo-buildpacks/run:full-cnb", + "mirrors": null + } + }, + "lifecycle": { + "version": "0.5.0", + "api": { + "buildpack": "0.2", + "platform": "0.1" + }, + "apis": { + "buildpack": { + "deprecated": ["0.2"], + "supported": ["0.3"] + }, + "platform": { + "deprecated": ["0.3"], + "supported": ["0.4"] + } + } + }, + "createdBy": { + "name": "kpack Builder", + "version": "v1.2.3 (git sha: abcdefg123456)" + }, + "buildpacks": [ + { + "id": "io.buildpack.4", + "version": "v4", + "homepage": "buildpack.4.com" + }, + { + "id": "io.buildpack.3", + "version": "v3", + "homepage": "buildpack.3.com" + }, + { + "id": "io.buildpack.2", + "version": "v2", + "homepage": "buildpack.2.com" + }, + { + "id": "io.buildpack.1", + "version": "v1", + "homepage": "buildpack.1.com" + } + ], + "extensions": [ + { + "id": "some-extension-id", + "version": "v1" + } + ] +}`, builderMetadata) + + buildpackLayers, err := imagehelpers.GetStringLabel(savedImage, buildpackLayersLabel) + assert.NoError(t, err) + assert.JSONEq(t, //language=json + `{ + "io.buildpack.1": { + "v1": { + "api": "0.2", + "layerDiffID": "sha256:1bf8899667b8d1e6b124f663faca32903b470831e5e4e992644ac5c839ab3462", + "stacks": [ + { + "id": "io.buildpacks.stacks.some-stack", + "mixins": ["some-mixin"] + } + ] + } + }, + "io.buildpack.2": { + "v2": { + "api": "0.3", + "layerDiffID": "sha256:2bf8899667b8d1e6b124f663faca32903b470831e5e4e992644ac5c839ab3462", + "order": [ + { + "group": [ + { + "id": "io.buildpack.3", + "version": "v2" + } + ] + } + ] + } + }, + "io.buildpack.3": { + "v3": { + "api": "0.3", + "layerDiffID": "sha256:3bf8899667b8d1e6b124f663faca32903b470831e5e4e992644ac5c839ab3462", + "stacks": [ + { + "id": "io.buildpacks.stacks.some-stack" + }, + { + "id": "io.some.other.stack" + } + ] + } + }, + "io.buildpack.4": { + "v4": { + "api": "0.3", + "layerDiffID": "sha256:3bf8899667b8d1e6b124f663faca32903b470831e5e4e992644ac5c839ab3462", + "stacks": [ + { + "id": "io.buildpacks.stacks.some-stack" + }, + { + "id": "io.some.other.stack" + } + ] + } + } +}`, buildpackLayers) + + }) + }) + when("validating buildpacks", func() { it("errors with unsupported stack", func() { addBuildpack(t, "io.buildpack.unsupported.stack", "v4", "buildpack.4.com", "0.2", diff --git a/pkg/cnb/fakes_test.go b/pkg/cnb/fakes_test.go index e9763179d..20d9c9eb4 100644 --- a/pkg/cnb/fakes_test.go +++ b/pkg/cnb/fakes_test.go @@ -8,11 +8,12 @@ import ( registryv1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/types" - buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" - corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" "github.com/pkg/errors" "github.com/stretchr/testify/assert" k8scorev1 "k8s.io/api/core/v1" + + buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" + corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" ) type fakeLayer struct { @@ -101,19 +102,28 @@ func makeObjectRef(name, kind, id, version string) buildapi.BuilderBuildpackRef type fakeFetcher struct { buildpacks map[string][]buildpackLayer + extensions map[string][]buildpackLayer observedGeneration int64 } -func (f *fakeFetcher) ResolveAndFetch(_ context.Context, buildpack buildapi.BuilderBuildpackRef) (RemoteBuildpackInfo, error) { - layers, ok := f.buildpacks[fmt.Sprintf("%s@%s", buildpack.Id, buildpack.Version)] - if !ok { - return RemoteBuildpackInfo{}, errors.New("buildpack not found") +func (f *fakeFetcher) ResolveAndFetch(_ context.Context, module buildapi.BuilderBuildpackRef) (RemoteBuildpackInfo, error) { + bpLayers, ok := f.buildpacks[fmt.Sprintf("%s@%s", module.Id, module.Version)] + if ok { + return RemoteBuildpackInfo{ + BuildpackInfo: buildpackInfoInLayers(bpLayers, module.Id, module.Version), + Layers: bpLayers, + }, nil } - return RemoteBuildpackInfo{ - BuildpackInfo: buildpackInfoInLayers(layers, buildpack.Id, buildpack.Version), - Layers: layers, - }, nil + extLayers, ok := f.extensions[fmt.Sprintf("%s@%s", module.Id, module.Version)] + if ok { + return RemoteBuildpackInfo{ + BuildpackInfo: buildpackInfoInLayers(extLayers, module.Id, module.Version), + Layers: extLayers, + }, nil + } + + return RemoteBuildpackInfo{}, errors.New("module not found") } func (f *fakeFetcher) ClusterStoreObservedGeneration() int64 { @@ -132,3 +142,8 @@ func (f *fakeFetcher) AddBuildpack(t *testing.T, id, version string, layers []bu t.Helper() f.buildpacks[fmt.Sprintf("%s@%s", id, version)] = layers } + +func (f *fakeFetcher) AddExtension(t *testing.T, id, version string, layers []buildpackLayer) { + t.Helper() + f.extensions[fmt.Sprintf("%s@%s", id, version)] = layers +} diff --git a/pkg/cnb/remote_buildpack_fetcher.go b/pkg/cnb/remote_buildpack_fetcher.go index 78d4eb28b..ef6e222a2 100644 --- a/pkg/cnb/remote_buildpack_fetcher.go +++ b/pkg/cnb/remote_buildpack_fetcher.go @@ -43,6 +43,7 @@ func (s *remoteBuildpackFetcher) ResolveAndFetch(ctx context.Context, ref builda return s.fetch(ctx, remote) } +// TODO: see if this needs to be updated for extensions func (s *remoteBuildpackFetcher) fetch(ctx context.Context, remoteBuildpack K8sRemoteBuildpack) (RemoteBuildpackInfo, error) { buildpack := remoteBuildpack.Buildpack keychain, err := s.keychainFactory.KeychainForSecretRef(ctx, remoteBuildpack.SecretRef) diff --git a/pkg/cnb/remote_store_reader.go b/pkg/cnb/remote_store_reader.go index 899d87bc7..b1369e54b 100644 --- a/pkg/cnb/remote_store_reader.go +++ b/pkg/cnb/remote_store_reader.go @@ -38,6 +38,7 @@ func (r *RemoteBuildpackReader) Read(keychain authn.Keychain, storeImages []core } } + // TODO: read extensionLayersLabel layerMetadata := BuildpackLayerMetadata{} err = imagehelpers.GetLabel(image, buildpackLayersLabel, &layerMetadata) if err != nil { From 5faeae09d047d7c8119df08b8ee79ed43e251803 Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Tue, 26 Sep 2023 16:03:49 -0400 Subject: [PATCH 02/20] More assertions Signed-off-by: Natalie Arellano --- pkg/cnb/builder_builder.go | 76 +++-- pkg/cnb/buildpack_metadata.go | 2 +- pkg/cnb/buildpack_validation.go | 10 +- pkg/cnb/create_builder_test.go | 438 +++++++++------------------ pkg/cnb/fakes_test.go | 1 + pkg/cnb/remote_buildpack_metadata.go | 3 +- 6 files changed, 212 insertions(+), 318 deletions(-) diff --git a/pkg/cnb/builder_builder.go b/pkg/cnb/builder_builder.go index 67a55cdbc..d5f8d29a0 100644 --- a/pkg/cnb/builder_builder.go +++ b/pkg/cnb/builder_builder.go @@ -98,10 +98,10 @@ func (bb *builderBlder) AddBuildpackGroup(buildpacks ...RemoteBuildpackRef) { func (bb *builderBlder) AddExtensionGroup(extensions ...RemoteBuildpackRef) { group := make([]corev1alpha1.BuildpackRef, 0, len(extensions)) - for _, b := range extensions { - group = append(group, b.buildpackRef()) + for _, ext := range extensions { + group = append(group, ext.buildpackRef()) - for _, layer := range b.Layers { + for _, layer := range ext.Layers { bb.extensionLayers[layer.BuildpackInfo] = layer } } @@ -112,19 +112,27 @@ func (bb *builderBlder) WriteableImage() (v1.Image, error) { buildpacks := bb.buildpacks() extensions := bb.extensions() - err := bb.validateBuilder(buildpacks) + err := bb.validateBuilder(buildpacks, extensions) if err != nil { return nil, err } + // buildpack layers buildpackLayerMetadata := BuildpackLayerMetadata{} buildpackLayers := make([]v1.Layer, 0, len(bb.buildpackLayers)) - // TODO: extensionLayers := make([]v1.Layer, 0, len(bb.extensionLayers)) - for _, key := range buildpacks { - layer := bb.buildpackLayers[key] - buildpackLayerMetadata.add(layer) - buildpackLayers = append(buildpackLayers, layer.v1Layer) + bpLayer := bb.buildpackLayers[key] + buildpackLayerMetadata.add(bpLayer) + buildpackLayers = append(buildpackLayers, bpLayer.v1Layer) + } + + // extension layers + extensionLayerMetadata := BuildpackLayerMetadata{} + extensionLayers := make([]v1.Layer, 0, len(bb.extensionLayers)) + for _, key := range extensions { + extLayer := bb.extensionLayers[key] + extensionLayerMetadata.add(extLayer) + extensionLayers = append(extensionLayers, extLayer.v1Layer) } defaultLayer, err := bb.defaultDirsLayer() @@ -149,6 +157,7 @@ func (bb *builderBlder) WriteableImage() (v1.Image, error) { bb.lifecycleLayer, }, buildpackLayers, + extensionLayers, []v1.Layer{ stackLayer, orderLayer, @@ -168,8 +177,7 @@ func (bb *builderBlder) WriteableImage() (v1.Image, error) { return nil, err } - // TODO: set extensionOrderLabel, extensionLayersLabel - return imagehelpers.SetLabels(image, map[string]interface{}{ + labels := map[string]interface{}{ buildpackOrderLabel: bb.order, buildpackLayersLabel: buildpackLayerMetadata, lifecycleApisLabel: bb.LifecycleMetadata.APIs, @@ -189,25 +197,37 @@ func (bb *builderBlder) WriteableImage() (v1.Image, error) { Buildpacks: buildpacks, Extensions: extensions, }, - }) + } + if len(extensionLayers) > 0 { + labels[extensionLayersLabel] = extensionLayerMetadata + } + if len(bb.orderExtensions) > 0 { + labels[extensionOrderLabel] = bb.orderExtensions + } + return imagehelpers.SetLabels(image, labels) } -// TODO: add extensions -func (bb *builderBlder) validateBuilder(sortedBuildpacks []DescriptiveBuildpackInfo) error { +func (bb *builderBlder) validateBuilder(sortedBuildpacks, sortedExtensions []DescriptiveBuildpackInfo) error { platformApis := append(bb.LifecycleMetadata.APIs.Platform.Deprecated, bb.LifecycleMetadata.APIs.Platform.Supported...) err := validatePlatformApis(platformApis) if err != nil { return err } buildpackApis := append(bb.LifecycleMetadata.APIs.Buildpack.Deprecated, bb.LifecycleMetadata.APIs.Buildpack.Supported...) + // buildpacks for _, bpInfo := range sortedBuildpacks { - bpLayerInfo := bb.buildpackLayers[bpInfo].BuildpackLayerInfo - err := bpLayerInfo.supports(buildpackApis, bb.stackId, bb.mixins, relaxedMixinContract(platformApis)) - if err != nil { + if err := bpLayerInfo.buildpackSupports(buildpackApis, bb.stackId, bb.mixins, relaxedMixinContract(platformApis)); err != nil { return errors.Wrapf(err, "validating buildpack %s", bpInfo) } } + // extensions + for _, extInfo := range sortedExtensions { + extLayerInfo := bb.extensionLayers[extInfo].BuildpackLayerInfo + if err := extLayerInfo.extensionSupports(buildpackApis); err != nil { + return errors.Wrapf(err, "validating extension %s", extInfo) + } + } return nil } @@ -274,12 +294,14 @@ func (bb *builderBlder) orderLayer() (v1.Layer, error) { type tomlOrder []tomlOrderEntry type tomlOrderFile struct { - Order tomlOrder `toml:"order"` + Order tomlOrder `toml:"order"` + OrderExtensions tomlOrder `toml:"order-extensions,omitempty"` } orderBuf := &bytes.Buffer{} - order := make(tomlOrder, 0, len(bb.order)) // TODO: update with extensions + // buildpacks + order := make(tomlOrder, 0, len(bb.order)) for _, o := range bb.order { bps := make([]tomlBuildpack, 0, len(o.Group)) for _, b := range o.Group { @@ -292,7 +314,21 @@ func (bb *builderBlder) orderLayer() (v1.Layer, error) { order = append(order, tomlOrderEntry{Group: bps}) } - err := toml.NewEncoder(orderBuf).Encode(tomlOrderFile{order}) + // extensions + orderExt := make(tomlOrder, 0, len(bb.orderExtensions)) + for _, g := range bb.orderExtensions { + exts := make([]tomlBuildpack, 0, len(g.Group)) + for _, e := range g.Group { + exts = append(exts, tomlBuildpack{ + ID: e.Id, + Version: e.Version, + Optional: e.Optional, // TODO: check + }) + } + orderExt = append(orderExt, tomlOrderEntry{Group: exts}) + } + + err := toml.NewEncoder(orderBuf).Encode(tomlOrderFile{order, orderExt}) if err != nil { return nil, err } diff --git a/pkg/cnb/buildpack_metadata.go b/pkg/cnb/buildpack_metadata.go index 441d08a8a..5b6d3acee 100644 --- a/pkg/cnb/buildpack_metadata.go +++ b/pkg/cnb/buildpack_metadata.go @@ -38,7 +38,7 @@ type BuilderImageMetadata struct { Lifecycle LifecycleMetadata `json:"lifecycle"` CreatedBy CreatorMetadata `json:"createdBy"` Buildpacks []DescriptiveBuildpackInfo `json:"buildpacks"` - Extensions []DescriptiveBuildpackInfo `json:"extensions"` + Extensions []DescriptiveBuildpackInfo `json:"extensions,omitempty"` } type StackMetadata struct { diff --git a/pkg/cnb/buildpack_validation.go b/pkg/cnb/buildpack_validation.go index 6ec318b31..000230599 100644 --- a/pkg/cnb/buildpack_validation.go +++ b/pkg/cnb/buildpack_validation.go @@ -9,7 +9,7 @@ import ( var anyStackMinimumVersion = semver.MustParse("0.5") -func (bl BuildpackLayerInfo) supports(buildpackApis []string, id string, mixins []string, relaxedMixinContract bool) error { +func (bl BuildpackLayerInfo) buildpackSupports(buildpackApis []string, id string, mixins []string, relaxedMixinContract bool) error { if len(bl.Order) != 0 { return nil //ignore meta-buildpacks } @@ -31,6 +31,13 @@ func (bl BuildpackLayerInfo) supports(buildpackApis []string, id string, mixins return errors.Errorf("stack %s is not supported", id) } +func (bl BuildpackLayerInfo) extensionSupports(buildpackApis []string) error { + if !present(buildpackApis, bl.API) { + return errors.Errorf("unsupported buildpack api: %s, expecting: %s", bl.API, strings.Join(buildpackApis, ", ")) + } + return nil +} + func validateRequiredMixins(providedMixins, requiredMixins []string, relaxedMixinContract bool) error { var missing []string for _, rm := range requiredMixins { @@ -79,6 +86,7 @@ func mixinPresent(mixins []string, mixin string, relaxedMixinContract bool) bool func stageRemoved(needle string) string { return strings.SplitN(needle, ":", 2)[1] } + func isAnystack(stackId string, buildpackVersion *semver.Version) bool { return stackId == "*" && buildpackVersion.Compare(anyStackMinimumVersion) >= 0 } diff --git a/pkg/cnb/create_builder_test.go b/pkg/cnb/create_builder_test.go index 3c9eedba6..3861c0266 100644 --- a/pkg/cnb/create_builder_test.go +++ b/pkg/cnb/create_builder_test.go @@ -200,23 +200,6 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { }, }}) } - - addExtension = func(t *testing.T, id, version, homepage, api string) { - fetcher.AddExtension(t, id, version, []buildpackLayer{{ - v1Layer: buildpack1Layer, - BuildpackInfo: DescriptiveBuildpackInfo{ - BuildpackInfo: corev1alpha1.BuildpackInfo{ - Id: id, - Version: version, - }, - Homepage: homepage, - }, - BuildpackLayerInfo: BuildpackLayerInfo{ - API: api, - LayerDiffID: buildpack1Layer.diffID, - }, - }}) - } ) it.Before(func() { @@ -366,20 +349,22 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { } }) - it("creates a custom builder", func() { - builderRecord, err := subject.CreateBuilder(ctx, builderKeychain, stackKeychain, fetcher, stack, clusterBuilderSpec) + assertBuilderRecord := func(t *testing.T, builderRecord buildapi.BuilderRecord, registryClient *registryfakes.FakeClient) v1.Image { + // image + assert.Len(t, registryClient.SavedImages(), 1) + savedImage := registryClient.SavedImages()[tag] + hash, err := savedImage.Digest() require.NoError(t, err) - + assert.Equal(t, fmt.Sprintf("%s@%s", tag, hash), builderRecord.Image) + // stack + assert.Equal(t, corev1alpha1.BuildStack{RunImage: runImage, ID: stackID}, builderRecord.Stack) + // buildpacks assert.Len(t, builderRecord.Buildpacks, 4) assert.Contains(t, builderRecord.Buildpacks, corev1alpha1.BuildpackMetadata{Id: "io.buildpack.1", Version: "v1", Homepage: "buildpack.1.com"}) assert.Contains(t, builderRecord.Buildpacks, corev1alpha1.BuildpackMetadata{Id: "io.buildpack.2", Version: "v2", Homepage: "buildpack.2.com"}) assert.Contains(t, builderRecord.Buildpacks, corev1alpha1.BuildpackMetadata{Id: "io.buildpack.3", Version: "v3", Homepage: "buildpack.3.com"}) assert.Contains(t, builderRecord.Buildpacks, corev1alpha1.BuildpackMetadata{Id: "io.buildpack.4", Version: "v4", Homepage: "buildpack.4.com"}) - assert.Equal(t, corev1alpha1.BuildStack{RunImage: runImage, ID: stackID}, builderRecord.Stack) - assert.Equal(t, int64(10), builderRecord.ObservedStoreGeneration) - assert.Equal(t, int64(11), builderRecord.ObservedStackGeneration) - assert.Equal(t, os, builderRecord.OS) - + // order assert.Equal(t, builderRecord.Order, []corev1alpha1.OrderEntry{ { Group: []corev1alpha1.BuildpackRef{ @@ -398,22 +383,32 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { }, }, }) + // store generation + assert.Equal(t, int64(10), builderRecord.ObservedStoreGeneration) + // stack generation + assert.Equal(t, int64(11), builderRecord.ObservedStackGeneration) + // os + assert.Equal(t, os, builderRecord.OS) - assert.Len(t, registryClient.SavedImages(), 1) - savedImage := registryClient.SavedImages()[tag] + return savedImage + } + assertLayers := func(t *testing.T, savedImage v1.Image, extension1Layer *fakeLayer) { + var layerTester = layerIteratorTester(0) + + // working directory workingDir, err := imagehelpers.GetWorkingDir(savedImage) require.NoError(t, err) assert.Equal(t, "/layers", workingDir) - hash, err := savedImage.Digest() - require.NoError(t, err) - assert.Equal(t, fmt.Sprintf("%s@%s", tag, hash), builderRecord.Image) - + // get layers layers, err := savedImage.Layers() require.NoError(t, err) - buildpackLayerCount := 3 + extensionLayerCount := 0 + if extension1Layer != nil { + extensionLayerCount = 1 + } defaultDirectoryLayerCount := 1 stackTomlLayerCount := 1 orderTomlLayerCount := 1 @@ -423,10 +418,9 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { lifecycleImageLayers+ stackTomlLayerCount+ buildpackLayerCount+ + extensionLayerCount+ orderTomlLayerCount) - var layerTester = layerIteratorTester(0) - for i := 0; i < buildImageLayers; i++ { layerTester.testNextLayer("Build Image Layer", func(index int) { buildImgLayers, err := buildImg.Layers() @@ -470,7 +464,6 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { }, }) }) - layerTester.testNextLayer("Lifecycle Layer", func(index int) { if os == "linux" { assert.Equal(t, layers[index], linuxLifecycle) @@ -478,19 +471,20 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { assert.Equal(t, layers[index], windowsLifecycle) } }) - layerTester.testNextLayer("Largest Buildpack Layer", func(index int) { assert.Equal(t, layers[index], buildpack3Layer) }) - layerTester.testNextLayer("Middle Buildpack Layer", func(index int) { assert.Equal(t, layers[index], buildpack2Layer) }) - layerTester.testNextLayer("Smallest Buildpack Layer", func(index int) { assert.Equal(t, layers[index], buildpack1Layer) }) - + if extension1Layer != nil { + layerTester.testNextLayer("Extension Layer", func(index int) { + assert.Equal(t, layers[index], extension1Layer) + }) + } layerTester.testNextLayer("stack Layer", func(index int) { assertLayerContents(t, os, layers[index], map[string]content{ "/cnb/stack.toml": //language=toml @@ -504,16 +498,10 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { }, }) }) - layerTester.testNextLayer("order Layer", func(index int) { assert.Equal(t, len(layers)-1, index) - assertLayerContents(t, os, layers[index], map[string]content{ - "/cnb/order.toml": { - typeflag: tar.TypeReg, - mode: 0644, - fileContent: //language=toml - `[[order]] + expectedOrderContent := `[[order]] [[order.group]] id = "io.buildpack.1" @@ -527,19 +515,28 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { [[order.group]] id = "io.buildpack.4" version = "v4" -`}}) +` + if extension1Layer != nil { + expectedOrderContent += ` +[[order-extensions]] - }) + [[order-extensions.group]] + id = "some-extension-id" + version = "v1" + optional = true +` + } - buildpackOrder, err := imagehelpers.GetStringLabel(savedImage, buildpackOrderLabel) - assert.NoError(t, err) - assert.JSONEq(t, //language=json - `[{"group":[{"id":"io.buildpack.1","version":"v1"},{"id":"io.buildpack.2","version":"v2","optional":true},{"id":"io.buildpack.4","version":"v4"}]}]`, buildpackOrder) + assertLayerContents(t, os, layers[index], map[string]content{ + "/cnb/order.toml": { + typeflag: tar.TypeReg, + mode: 0644, + fileContent://language=toml + expectedOrderContent}}) + }) + } - builderMetadata, err := imagehelpers.GetStringLabel(savedImage, builderMetadataLabel) - assert.NoError(t, err) - assert.JSONEq(t, //language=json - `{ + expectedBuilderMetadataLabel := `{ "description": "Custom Builder built with kpack", "stack": { "runImage": { @@ -590,7 +587,18 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { "homepage": "buildpack.1.com" } ] -}`, builderMetadata) +}` + + assertLabels := func(t *testing.T, savedImage v1.Image) { + buildpackOrder, err := imagehelpers.GetStringLabel(savedImage, buildpackOrderLabel) + assert.NoError(t, err) + assert.JSONEq(t, //language=json + `[{"group":[{"id":"io.buildpack.1","version":"v1"},{"id":"io.buildpack.2","version":"v2","optional":true},{"id":"io.buildpack.4","version":"v4"}]}]`, buildpackOrder) + + builderMetadata, err := imagehelpers.GetStringLabel(savedImage, builderMetadataLabel) + assert.NoError(t, err) + assert.JSONEq(t, //language=json + expectedBuilderMetadataLabel, builderMetadata) buildpackLayers, err := imagehelpers.GetStringLabel(savedImage, buildpackLayersLabel) assert.NoError(t, err) @@ -653,7 +661,15 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { } } }`, buildpackLayers) + } + it("creates a custom builder", func() { + builderRecord, err := subject.CreateBuilder(ctx, builderKeychain, stackKeychain, fetcher, stack, clusterBuilderSpec) + require.NoError(t, err) + + savedImage := assertBuilderRecord(t, builderRecord, registryClient) + assertLayers(t, savedImage, nil) + assertLabels(t, savedImage) }) it("creates images deterministically ", func() { @@ -670,14 +686,37 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { }) when("provided extensions", func() { - var extensionRef = corev1alpha1.BuildpackRef{ - BuildpackInfo: corev1alpha1.BuildpackInfo{ - Id: "some-extension-id", - Version: "v1", - }, - } + var ( + extension1Layer = &fakeLayer{ + digest: "sha256:98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4", + diffID: "sha256:98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4", + size: 1, + } + addExtension = func(t *testing.T, id, version, homepage, api string) { + fetcher.AddExtension(t, id, version, []buildpackLayer{{ + v1Layer: extension1Layer, + BuildpackInfo: DescriptiveBuildpackInfo{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: id, + Version: version, + }, + Homepage: homepage, + }, + BuildpackLayerInfo: BuildpackLayerInfo{ + API: api, + LayerDiffID: extension1Layer.diffID, + }, + }}) + } + ) - it.Before(func() { + it("creates a custom builder with extensions", func() { + extensionRef := corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "some-extension-id", + Version: "v1", + }, + } clusterBuilderSpec.OrderExtensions = []buildapi.BuilderOrderEntry{ { Group: []buildapi.BuilderBuildpackRef{{ @@ -685,10 +724,8 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { }}, }, } - }) + addExtension(t, extensionRef.Id, extensionRef.Version, "", "0.3") - it.Focus("creates a custom builder with extensions", func() { // TODO: dedup assertions - addExtension(t, extensionRef.Id, extensionRef.Version, "", "0.10") builderRecord, err := subject.CreateBuilder(ctx, builderKeychain, stackKeychain, fetcher, stack, clusterBuilderSpec) require.NoError(t, err) @@ -696,36 +733,8 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { // TODO: expect some kind of useful error } - assert.Len(t, builderRecord.Buildpacks, 4) - assert.Contains(t, builderRecord.Buildpacks, corev1alpha1.BuildpackMetadata{Id: "io.buildpack.1", Version: "v1", Homepage: "buildpack.1.com"}) - assert.Contains(t, builderRecord.Buildpacks, corev1alpha1.BuildpackMetadata{Id: "io.buildpack.2", Version: "v2", Homepage: "buildpack.2.com"}) - assert.Contains(t, builderRecord.Buildpacks, corev1alpha1.BuildpackMetadata{Id: "io.buildpack.3", Version: "v3", Homepage: "buildpack.3.com"}) - assert.Contains(t, builderRecord.Buildpacks, corev1alpha1.BuildpackMetadata{Id: "io.buildpack.4", Version: "v4", Homepage: "buildpack.4.com"}) - assert.Equal(t, corev1alpha1.BuildStack{RunImage: runImage, ID: stackID}, builderRecord.Stack) - assert.Equal(t, int64(10), builderRecord.ObservedStoreGeneration) - assert.Equal(t, int64(11), builderRecord.ObservedStackGeneration) - assert.Equal(t, os, builderRecord.OS) - - assert.Equal(t, builderRecord.Order, []corev1alpha1.OrderEntry{ - { - Group: []corev1alpha1.BuildpackRef{ - { - BuildpackInfo: corev1alpha1.BuildpackInfo{Id: "io.buildpack.1", Version: "v1"}, - Optional: false, - }, - { - BuildpackInfo: corev1alpha1.BuildpackInfo{Id: "io.buildpack.2", Version: "v2"}, - Optional: true, - }, - { - BuildpackInfo: corev1alpha1.BuildpackInfo{Id: "io.buildpack.4", Version: "v4"}, - Optional: false, - }, - }, - }, - }) - - // added in this test + // builder record + savedImage := assertBuilderRecord(t, builderRecord, registryClient) assert.Equal(t, builderRecord.OrderExtensions, []corev1alpha1.OrderEntry{ { Group: []corev1alpha1.BuildpackRef{ @@ -737,148 +746,11 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { }, }) - assert.Len(t, registryClient.SavedImages(), 1) - savedImage := registryClient.SavedImages()[tag] - - workingDir, err := imagehelpers.GetWorkingDir(savedImage) - require.NoError(t, err) - assert.Equal(t, "/layers", workingDir) - - hash, err := savedImage.Digest() - require.NoError(t, err) - assert.Equal(t, fmt.Sprintf("%s@%s", tag, hash), builderRecord.Image) + // layers + assertLayers(t, savedImage, extension1Layer) - layers, err := savedImage.Layers() - require.NoError(t, err) - - buildpackLayerCount := 3 - defaultDirectoryLayerCount := 1 - stackTomlLayerCount := 1 - orderTomlLayerCount := 1 - assert.Len(t, layers, - buildImageLayers+ - defaultDirectoryLayerCount+ - lifecycleImageLayers+ - stackTomlLayerCount+ - buildpackLayerCount+ - orderTomlLayerCount) - - var layerTester = layerIteratorTester(0) - - for i := 0; i < buildImageLayers; i++ { - layerTester.testNextLayer("Build Image Layer", func(index int) { - buildImgLayers, err := buildImg.Layers() - require.NoError(t, err) - - assert.Equal(t, layers[i], buildImgLayers[i]) - }) - } - - layerTester.testNextLayer("Default Directory Layer", func(index int) { - defaultDirectoryLayer := layers[index] - - assertLayerContents(t, os, defaultDirectoryLayer, map[string]content{ - "/workspace": { - typeflag: tar.TypeDir, - mode: 0755, - uid: cnbUserId, - gid: cnbGroupId, - }, - "/layers": { - typeflag: tar.TypeDir, - mode: 0755, - uid: cnbUserId, - gid: cnbGroupId, - }, - "/cnb": { - typeflag: tar.TypeDir, - mode: 0755, - }, - "/cnb/buildpacks": { - typeflag: tar.TypeDir, - mode: 0755, - }, - "/platform": { - typeflag: tar.TypeDir, - mode: 0755, - }, - "/platform/env": { - typeflag: tar.TypeDir, - mode: 0755, - }, - }) - }) - - layerTester.testNextLayer("Lifecycle Layer", func(index int) { - if os == "linux" { - assert.Equal(t, layers[index], linuxLifecycle) - } else { - assert.Equal(t, layers[index], windowsLifecycle) - } - }) - - layerTester.testNextLayer("Largest Buildpack Layer", func(index int) { - assert.Equal(t, layers[index], buildpack3Layer) - }) - - layerTester.testNextLayer("Middle Buildpack Layer", func(index int) { - assert.Equal(t, layers[index], buildpack2Layer) - }) - - layerTester.testNextLayer("Smallest Buildpack Layer", func(index int) { - assert.Equal(t, layers[index], buildpack1Layer) - }) - - layerTester.testNextLayer("stack Layer", func(index int) { - assertLayerContents(t, os, layers[index], map[string]content{ - "/cnb/stack.toml": //language=toml - { - typeflag: tar.TypeReg, - mode: 0644, - fileContent: //language=toml - `[run-image] - image = "paketo-buildpacks/run:full-cnb" -`, - }, - }) - }) - - layerTester.testNextLayer("order Layer", func(index int) { - assert.Equal(t, len(layers)-1, index) - - assertLayerContents(t, os, layers[index], map[string]content{ - "/cnb/order.toml": { - typeflag: tar.TypeReg, - mode: 0644, - fileContent: //language=toml - `[[order]] - - [[order.group]] - id = "io.buildpack.1" - version = "v1" - - [[order.group]] - id = "io.buildpack.2" - version = "v2" - optional = true - - [[order.group]] - id = "io.buildpack.4" - version = "v4" -`}}) - - }) - - buildpackOrder, err := imagehelpers.GetStringLabel(savedImage, buildpackOrderLabel) - assert.NoError(t, err) - assert.JSONEq(t, //language=json - `[{"group":[{"id":"io.buildpack.1","version":"v1"},{"id":"io.buildpack.2","version":"v2","optional":true},{"id":"io.buildpack.4","version":"v4"}]}]`, buildpackOrder) - - // changed in this test - builderMetadata, err := imagehelpers.GetStringLabel(savedImage, builderMetadataLabel) - assert.NoError(t, err) - assert.JSONEq(t, //language=json - `{ + // labels + expectedBuilderMetadataLabel = `{ "description": "Custom Builder built with kpack", "stack": { "runImage": { @@ -935,70 +807,46 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { "version": "v1" } ] -}`, builderMetadata) - - buildpackLayers, err := imagehelpers.GetStringLabel(savedImage, buildpackLayersLabel) +}` + assertLabels(t, savedImage) + extensionLayers, err := imagehelpers.GetStringLabel(savedImage, extensionLayersLabel) assert.NoError(t, err) assert.JSONEq(t, //language=json `{ - "io.buildpack.1": { + "some-extension-id": { "v1": { - "api": "0.2", - "layerDiffID": "sha256:1bf8899667b8d1e6b124f663faca32903b470831e5e4e992644ac5c839ab3462", - "stacks": [ - { - "id": "io.buildpacks.stacks.some-stack", - "mixins": ["some-mixin"] - } - ] - } - }, - "io.buildpack.2": { - "v2": { "api": "0.3", - "layerDiffID": "sha256:2bf8899667b8d1e6b124f663faca32903b470831e5e4e992644ac5c839ab3462", - "order": [ - { - "group": [ - { - "id": "io.buildpack.3", - "version": "v2" - } - ] - } - ] - } - }, - "io.buildpack.3": { - "v3": { - "api": "0.3", - "layerDiffID": "sha256:3bf8899667b8d1e6b124f663faca32903b470831e5e4e992644ac5c839ab3462", - "stacks": [ - { - "id": "io.buildpacks.stacks.some-stack" - }, - { - "id": "io.some.other.stack" - } - ] - } - }, - "io.buildpack.4": { - "v4": { - "api": "0.3", - "layerDiffID": "sha256:3bf8899667b8d1e6b124f663faca32903b470831e5e4e992644ac5c839ab3462", - "stacks": [ - { - "id": "io.buildpacks.stacks.some-stack" - }, - { - "id": "io.some.other.stack" - } - ] + "layerDiffID": "sha256:98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4" } } -}`, buildpackLayers) +}`, extensionLayers) + extensionOrder, err := imagehelpers.GetStringLabel(savedImage, extensionOrderLabel) + assert.NoError(t, err) + assert.JSONEq(t, //language=json + `[{"group":[{"id":"some-extension-id","optional":true,"version":"v1"}]}]`, extensionOrder) + + }) + when("validating extensions", func() { + it("errors with unsupported Buildpack API version", func() { + extensionRef := corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "some-unsupported-extension-id", + Version: "v1", + }, + } + clusterBuilderSpec.OrderExtensions = []buildapi.BuilderOrderEntry{ + { + Group: []buildapi.BuilderBuildpackRef{{ + BuildpackRef: extensionRef, + }}, + }, + } + addExtension(t, extensionRef.Id, extensionRef.Version, "", "0.1") + + _, err := subject.CreateBuilder(ctx, builderKeychain, stackKeychain, fetcher, stack, clusterBuilderSpec) + require.EqualError(t, err, "validating extension some-unsupported-extension-id@v1: unsupported buildpack api: 0.1, expecting: 0.2, 0.3") + }) }) }) @@ -1122,7 +970,7 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { require.Error(t, err, "validating buildpack io.buildpack.relaxed.old.mixin@v4: stack missing mixin(s): build:common-mixin, run:common-mixin, another-common-mixin") }) - it("errors with unsupported buildpack version", func() { + it("errors with unsupported Buildpack API version", func() { addBuildpack(t, "io.buildpack.unsupported.buildpack.api", "v4", "buildpack.4.com", "0.1", []corev1alpha1.BuildpackStack{ { diff --git a/pkg/cnb/fakes_test.go b/pkg/cnb/fakes_test.go index 20d9c9eb4..046c6f573 100644 --- a/pkg/cnb/fakes_test.go +++ b/pkg/cnb/fakes_test.go @@ -115,6 +115,7 @@ func (f *fakeFetcher) ResolveAndFetch(_ context.Context, module buildapi.Builder }, nil } + // TODO: buildpacks and extensions can have the same ID extLayers, ok := f.extensions[fmt.Sprintf("%s@%s", module.Id, module.Version)] if ok { return RemoteBuildpackInfo{ diff --git a/pkg/cnb/remote_buildpack_metadata.go b/pkg/cnb/remote_buildpack_metadata.go index e35f0a0ff..b357cb5a0 100644 --- a/pkg/cnb/remote_buildpack_metadata.go +++ b/pkg/cnb/remote_buildpack_metadata.go @@ -2,9 +2,10 @@ package cnb import ( ggcrv1 "github.com/google/go-containerregistry/pkg/v1" + k8sv1 "k8s.io/api/core/v1" + corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" "github.com/pivotal/kpack/pkg/registry" - k8sv1 "k8s.io/api/core/v1" ) type RemoteBuildpackInfo struct { From 44b354ff982adb02f81dcd66da7b5cbf2b4d21d7 Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Wed, 27 Sep 2023 13:20:36 -0400 Subject: [PATCH 03/20] Add directives for Extensions and ClusterExtensions Signed-off-by: Natalie Arellano --- cmd/webhook/main.go | 2 + .../build/v1alpha2/buildpack_validation.go | 4 +- .../build/v1alpha2/cluster_extension_types.go | 57 ++++++++++++++++++ pkg/apis/build/v1alpha2/extension_types.go | 60 +++++++++++++++++++ pkg/apis/build/v1alpha2/register.go | 4 ++ pkg/cnb/buildpack_resolver.go | 27 ++++++--- pkg/cnb/buildpack_resolver_test.go | 15 ++--- pkg/cnb/remote_buildpack_fetcher.go | 7 ++- pkg/reconciler/buildpack/buildpack.go | 1 + .../clusterbuildpack/clusterbuildpack.go | 1 + 10 files changed, 160 insertions(+), 18 deletions(-) create mode 100644 pkg/apis/build/v1alpha2/cluster_extension_types.go create mode 100644 pkg/apis/build/v1alpha2/extension_types.go diff --git a/cmd/webhook/main.go b/cmd/webhook/main.go index e4c3a9a99..c87311acd 100644 --- a/cmd/webhook/main.go +++ b/cmd/webhook/main.go @@ -31,8 +31,10 @@ var types = map[schema.GroupVersionKind]resourcesemantics.GenericCRD{ v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.BuildKind): &v1alpha2.Build{}, v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.BuilderKind): &v1alpha2.Builder{}, v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.BuildpackKind): &v1alpha2.Buildpack{}, + v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ExtensionKind): &v1alpha2.Extension{}, v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterBuilderKind): &v1alpha2.ClusterBuilder{}, v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterBuildpackKind): &v1alpha2.ClusterBuildpack{}, + v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterExtensionKind): &v1alpha2.ClusterExtension{}, v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterStoreKind): &v1alpha2.ClusterStore{}, v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterStackKind): &v1alpha2.ClusterStack{}, } diff --git a/pkg/apis/build/v1alpha2/buildpack_validation.go b/pkg/apis/build/v1alpha2/buildpack_validation.go index b6c68d8ae..e583ac847 100644 --- a/pkg/apis/build/v1alpha2/buildpack_validation.go +++ b/pkg/apis/build/v1alpha2/buildpack_validation.go @@ -3,10 +3,12 @@ package v1alpha2 import ( "context" - "github.com/pivotal/kpack/pkg/apis/validate" "knative.dev/pkg/apis" + + "github.com/pivotal/kpack/pkg/apis/validate" ) +// TODO: add for extensions func (cb *Buildpack) SetDefaults(context.Context) { if cb.Spec.ServiceAccountName == "" { cb.Spec.ServiceAccountName = "default" diff --git a/pkg/apis/build/v1alpha2/cluster_extension_types.go b/pkg/apis/build/v1alpha2/cluster_extension_types.go new file mode 100644 index 000000000..513ea45a9 --- /dev/null +++ b/pkg/apis/build/v1alpha2/cluster_extension_types.go @@ -0,0 +1,57 @@ +package v1alpha2 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + + corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" +) + +const ( + ClusterBuildpackKind = "ClusterBuildpack" + ClusterBuildpackCRName = "clusterbuildpacks.kpack.io" +) + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object,k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMetaAccessor + +// +k8s:openapi-gen=true +type ClusterExtension struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ClusterExtensionSpec `json:"spec"` + Status ClusterExtensionStatus `json:"status"` +} + +// +k8s:openapi-gen=true +type ClusterExtensionSpec struct { + // +listType + corev1alpha1.ImageSource `json:",inline"` + ServiceAccountRef *corev1.ObjectReference `json:"serviceAccountRef,omitempty"` +} + +// +k8s:openapi-gen=true +type ClusterExtensionStatus struct { + corev1alpha1.Status `json:",inline"` + + // +listType + Extensions []corev1alpha1.BuildpackStatus `json:"extensions,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// +k8s:openapi-gen=true +type ClusterExtensionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + // +k8s:listType=atomic + Items []ClusterExtension `json:"items"` +} + +func (*ClusterExtension) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind(ClusterExtensionKind) +} diff --git a/pkg/apis/build/v1alpha2/extension_types.go b/pkg/apis/build/v1alpha2/extension_types.go new file mode 100644 index 000000000..dafac9a7b --- /dev/null +++ b/pkg/apis/build/v1alpha2/extension_types.go @@ -0,0 +1,60 @@ +package v1alpha2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + + corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" +) + +const ( + ExtensionKind = "Extension" + ExtensionCRName = "extensions.kpack.io" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object,k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMetaAccessor + +// +k8s:openapi-gen=true +type Extension struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ExtensionSpec `json:"spec"` + Status ExtensionStatus `json:"status"` +} + +// +k8s:openapi-gen=true +type ExtensionSpec struct { + // +listType + corev1alpha1.ImageSource `json:",inline"` + ServiceAccountName string `json:"serviceAccountName,omitempty"` +} + +// +k8s:openapi-gen=true +type ExtensionStatus struct { + corev1alpha1.Status `json:",inline"` + + // +listType + Extensions []corev1alpha1.BuildpackStatus `json:"extensions,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// +k8s:openapi-gen=true +type ExtensionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + // +k8s:listType=atomic + Items []Extension `json:"items"` +} + +func (*Extension) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind(ExtensionKind) +} + +func (c *Extension) NamespacedName() types.NamespacedName { + return types.NamespacedName{Namespace: c.Namespace, Name: c.Name} +} diff --git a/pkg/apis/build/v1alpha2/register.go b/pkg/apis/build/v1alpha2/register.go index 8c42a86e8..88caf4a53 100644 --- a/pkg/apis/build/v1alpha2/register.go +++ b/pkg/apis/build/v1alpha2/register.go @@ -50,7 +50,9 @@ func addKnownTypes(scheme *runtime.Scheme) error { &Build{}, &BuildList{}, &Buildpack{}, + &Extension{}, &BuildpackList{}, + &ExtensionList{}, &Builder{}, &BuilderList{}, &Image{}, @@ -62,7 +64,9 @@ func addKnownTypes(scheme *runtime.Scheme) error { &ClusterStore{}, &ClusterStoreList{}, &ClusterBuildpack{}, + &ClusterExtension{}, &ClusterBuildpackList{}, + &ClusterExtensionList{}, &ClusterBuilder{}, &ClusterBuilderList{}, ) diff --git a/pkg/cnb/buildpack_resolver.go b/pkg/cnb/buildpack_resolver.go index 591e3e46c..49fc9540e 100644 --- a/pkg/cnb/buildpack_resolver.go +++ b/pkg/cnb/buildpack_resolver.go @@ -5,11 +5,12 @@ import ( "sort" "github.com/Masterminds/semver/v3" + "github.com/pkg/errors" + v1 "k8s.io/api/core/v1" + "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" "github.com/pivotal/kpack/pkg/registry" - "github.com/pkg/errors" - v1 "k8s.io/api/core/v1" ) // BuildpackResolver will attempt to resolve a Buildpack reference to a @@ -20,22 +21,32 @@ type BuildpackResolver interface { } type buildpackResolver struct { - clusterstore *v1alpha2.ClusterStore + clusterStore *v1alpha2.ClusterStore buildpacks []*v1alpha2.Buildpack clusterBuildpacks []*v1alpha2.ClusterBuildpack + extensions []*v1alpha2.Buildpack + clusterExtensions []*v1alpha2.ClusterBuildpack } -func NewBuildpackResolver(clusterStore *v1alpha2.ClusterStore, buildpacks []*v1alpha2.Buildpack, clusterBuildpacks []*v1alpha2.ClusterBuildpack) BuildpackResolver { +func NewBuildpackResolver( + clusterStore *v1alpha2.ClusterStore, + buildpacks []*v1alpha2.Buildpack, + clusterBuildpacks []*v1alpha2.ClusterBuildpack, + extensions []*v1alpha2.Buildpack, + clusterExtensions []*v1alpha2.ClusterBuildpack, +) BuildpackResolver { return &buildpackResolver{ - clusterstore: clusterStore, + clusterStore: clusterStore, buildpacks: buildpacks, clusterBuildpacks: clusterBuildpacks, + extensions: extensions, + clusterExtensions: clusterExtensions, } } func (r *buildpackResolver) ClusterStoreObservedGeneration() int64 { - if r.clusterstore != nil { - return r.clusterstore.Status.ObservedGeneration + if r.clusterStore != nil { + return r.clusterStore.Status.ObservedGeneration } return 0 } @@ -83,7 +94,7 @@ func (r *buildpackResolver) resolve(ref v1alpha2.BuilderBuildpackRef) (K8sRemote } matchingBuildpacks = append(matchingBuildpacks, cbp...) - cs, err := r.resolveFromClusterStore(ref.Id, r.clusterstore) + cs, err := r.resolveFromClusterStore(ref.Id, r.clusterStore) if err != nil { return K8sRemoteBuildpack{}, err } diff --git a/pkg/cnb/buildpack_resolver_test.go b/pkg/cnb/buildpack_resolver_test.go index e55267b13..1089fe2fb 100644 --- a/pkg/cnb/buildpack_resolver_test.go +++ b/pkg/cnb/buildpack_resolver_test.go @@ -3,11 +3,12 @@ package cnb import ( "testing" - buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" - corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" "github.com/sclevine/spec" "github.com/stretchr/testify/assert" + buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" + corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -160,7 +161,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ) when("Resolve", func() { - when("using the clusterstore", func() { + when("using the clusterStore", func() { var ( resolver BuildpackResolver store = &buildapi.ClusterStore{ @@ -182,7 +183,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ) it.Before(func() { - resolver = NewBuildpackResolver(store, nil, nil) + resolver = NewBuildpackResolver(store, nil, nil, nil, nil) }) it("finds it using id", func() { @@ -275,7 +276,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ) it.Before(func() { - resolver = NewBuildpackResolver(nil, buildpacks, nil) + resolver = NewBuildpackResolver(nil, buildpacks, nil, nil, nil) }) when("using id", func() { @@ -439,7 +440,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ) it.Before(func() { - resolver = NewBuildpackResolver(nil, nil, clusterBuildpacks) + resolver = NewBuildpackResolver(nil, nil, clusterBuildpacks, nil, nil) }) when("using id", func() { @@ -611,7 +612,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ) it.Before(func() { - resolver = NewBuildpackResolver(store, buildpacks, clusterBuildpacks) + resolver = NewBuildpackResolver(store, buildpacks, clusterBuildpacks, nil, nil) }) it("records which objects were used", func() { diff --git a/pkg/cnb/remote_buildpack_fetcher.go b/pkg/cnb/remote_buildpack_fetcher.go index ef6e222a2..3f6ddac11 100644 --- a/pkg/cnb/remote_buildpack_fetcher.go +++ b/pkg/cnb/remote_buildpack_fetcher.go @@ -26,10 +26,13 @@ type remoteBuildpackFetcher struct { func NewRemoteBuildpackFetcher( factory registry.KeychainFactory, clusterStore *buildapi.ClusterStore, - buildpacks []*buildapi.Buildpack, clusterBuildpacks []*buildapi.ClusterBuildpack, + buildpacks []*buildapi.Buildpack, + clusterBuildpacks []*buildapi.ClusterBuildpack, + extensions []*buildapi.Buildpack, + clusterExtensions []*buildapi.ClusterBuildpack, ) RemoteBuildpackFetcher { return &remoteBuildpackFetcher{ - BuildpackResolver: NewBuildpackResolver(clusterStore, buildpacks, clusterBuildpacks), + BuildpackResolver: NewBuildpackResolver(clusterStore, buildpacks, clusterBuildpacks, extensions, clusterExtensions), keychainFactory: dockercreds.NewCachedKeychainFactory(factory), } } diff --git a/pkg/reconciler/buildpack/buildpack.go b/pkg/reconciler/buildpack/buildpack.go index 2f4a83eb7..68b30b803 100644 --- a/pkg/reconciler/buildpack/buildpack.go +++ b/pkg/reconciler/buildpack/buildpack.go @@ -21,6 +21,7 @@ import ( "github.com/pivotal/kpack/pkg/registry" ) +// TODO: add for extensions const ( ReconcilerName = "Buildpacks" Kind = "Buildpack" diff --git a/pkg/reconciler/clusterbuildpack/clusterbuildpack.go b/pkg/reconciler/clusterbuildpack/clusterbuildpack.go index e3f1bd044..4f933c838 100644 --- a/pkg/reconciler/clusterbuildpack/clusterbuildpack.go +++ b/pkg/reconciler/clusterbuildpack/clusterbuildpack.go @@ -21,6 +21,7 @@ import ( "github.com/pivotal/kpack/pkg/registry" ) +// TODO: add for extensions const ( ReconcilerName = "ClusterBuildpacks" Kind = "ClusterBuildpack" From b95b212759c3c70e3f48ab31284bcf319fcb9afc Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Wed, 27 Sep 2023 13:25:35 -0400 Subject: [PATCH 04/20] Generate some code Signed-off-by: Natalie Arellano --- api/openapi-spec/swagger.json | 228 +++++++++- cmd/webhook/main.go | 16 +- .../build/v1alpha2/cluster_extension_types.go | 4 +- .../build/v1alpha2/zz_generated.deepcopy.go | 246 ++++++++++ pkg/client/clientset/versioned/clientset.go | 3 +- .../typed/build/v1alpha2/build_client.go | 10 + .../typed/build/v1alpha2/clusterextension.go | 184 ++++++++ .../typed/build/v1alpha2/extension.go | 195 ++++++++ .../build/v1alpha2/fake/fake_build_client.go | 8 + .../v1alpha2/fake/fake_clusterextension.go | 133 ++++++ .../build/v1alpha2/fake/fake_extension.go | 142 ++++++ .../build/v1alpha2/generated_expansion.go | 4 + .../build/v1alpha2/clusterextension.go | 89 ++++ .../build/v1alpha2/extension.go | 90 ++++ .../build/v1alpha2/interface.go | 14 + .../informers/externalversions/factory.go | 79 +++- .../informers/externalversions/generic.go | 4 + .../build/v1alpha2/clusterextension.go | 68 +++ .../build/v1alpha2/expansion_generated.go | 12 + .../listers/build/v1alpha2/extension.go | 99 ++++ pkg/openapi/openapi_generated.go | 422 +++++++++++++++++- .../clusterbuildpack/clusterbuildpack.go | 1 - 22 files changed, 2024 insertions(+), 27 deletions(-) create mode 100644 pkg/client/clientset/versioned/typed/build/v1alpha2/clusterextension.go create mode 100644 pkg/client/clientset/versioned/typed/build/v1alpha2/extension.go create mode 100644 pkg/client/clientset/versioned/typed/build/v1alpha2/fake/fake_clusterextension.go create mode 100644 pkg/client/clientset/versioned/typed/build/v1alpha2/fake/fake_extension.go create mode 100644 pkg/client/informers/externalversions/build/v1alpha2/clusterextension.go create mode 100644 pkg/client/informers/externalversions/build/v1alpha2/extension.go create mode 100644 pkg/client/listers/build/v1alpha2/clusterextension.go create mode 100644 pkg/client/listers/build/v1alpha2/extension.go diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index 3c9f697be..7a797215a 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -5887,6 +5887,13 @@ }, "x-kubernetes-list-type": "" }, + "order-extensions": { + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/kpack.build.v1alpha2.BuilderOrderEntry" + } + }, "stack": { "default": {}, "$ref": "#/definitions/io.k8s.api.core.v1.ObjectReference" @@ -5943,6 +5950,13 @@ "$ref": "#/definitions/kpack.core.v1alpha1.OrderEntry" } }, + "order-extensions": { + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/kpack.core.v1alpha1.OrderEntry" + } + }, "os": { "type": "string" }, @@ -6116,6 +6130,13 @@ }, "x-kubernetes-list-type": "" }, + "order-extensions": { + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/kpack.build.v1alpha2.BuilderOrderEntry" + } + }, "serviceAccountRef": { "default": {}, "$ref": "#/definitions/io.k8s.api.core.v1.ObjectReference" @@ -6193,13 +6214,11 @@ "kpack.build.v1alpha2.ClusterBuildpackSpec": { "type": "object", "properties": { + "image": { + "type": "string" + }, "serviceAccountRef": { "$ref": "#/definitions/io.k8s.api.core.v1.ObjectReference" - }, - "source": { - "default": {}, - "x-kubernetes-list-type": "", - "$ref": "#/definitions/kpack.core.v1alpha1.ImageSource" } } }, @@ -6231,6 +6250,102 @@ } } }, + "kpack.build.v1alpha2.ClusterExtension": { + "type": "object", + "required": [ + "spec", + "status" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {}, + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + }, + "spec": { + "default": {}, + "$ref": "#/definitions/kpack.build.v1alpha2.ClusterExtensionSpec" + }, + "status": { + "default": {}, + "$ref": "#/definitions/kpack.build.v1alpha2.ClusterExtensionStatus" + } + } + }, + "kpack.build.v1alpha2.ClusterExtensionList": { + "type": "object", + "required": [ + "metadata", + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/kpack.build.v1alpha2.ClusterExtension" + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {}, + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + } + }, + "kpack.build.v1alpha2.ClusterExtensionSpec": { + "type": "object", + "properties": { + "image": { + "type": "string" + }, + "serviceAccountRef": { + "$ref": "#/definitions/io.k8s.api.core.v1.ObjectReference" + } + } + }, + "kpack.build.v1alpha2.ClusterExtensionStatus": { + "type": "object", + "properties": { + "conditions": { + "description": "Conditions the latest available observations of a resource's current state.", + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/kpack.core.v1alpha1.Condition" + }, + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + }, + "extensions": { + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/kpack.core.v1alpha1.BuildpackStatus" + }, + "x-kubernetes-list-type": "" + }, + "observedGeneration": { + "description": "ObservedGeneration is the 'Generation' of the Service that was last processed by the controller.", + "type": "integer", + "format": "int64" + } + } + }, "kpack.build.v1alpha2.ClusterStack": { "type": "object", "required": [ @@ -6498,6 +6613,102 @@ } } }, + "kpack.build.v1alpha2.Extension": { + "type": "object", + "required": [ + "spec", + "status" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {}, + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + }, + "spec": { + "default": {}, + "$ref": "#/definitions/kpack.build.v1alpha2.ExtensionSpec" + }, + "status": { + "default": {}, + "$ref": "#/definitions/kpack.build.v1alpha2.ExtensionStatus" + } + } + }, + "kpack.build.v1alpha2.ExtensionList": { + "type": "object", + "required": [ + "metadata", + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/kpack.build.v1alpha2.Extension" + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {}, + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + } + }, + "kpack.build.v1alpha2.ExtensionSpec": { + "type": "object", + "properties": { + "image": { + "type": "string" + }, + "serviceAccountName": { + "type": "string" + } + } + }, + "kpack.build.v1alpha2.ExtensionStatus": { + "type": "object", + "properties": { + "conditions": { + "description": "Conditions the latest available observations of a resource's current state.", + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/kpack.core.v1alpha1.Condition" + }, + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + }, + "extensions": { + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/kpack.core.v1alpha1.BuildpackStatus" + }, + "x-kubernetes-list-type": "" + }, + "observedGeneration": { + "description": "ObservedGeneration is the 'Generation' of the Service that was last processed by the controller.", + "type": "integer", + "format": "int64" + } + } + }, "kpack.build.v1alpha2.Image": { "type": "object", "required": [ @@ -6790,6 +7001,13 @@ }, "x-kubernetes-list-type": "" }, + "order-extensions": { + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/kpack.build.v1alpha2.BuilderOrderEntry" + } + }, "serviceAccount": { "type": "string" }, diff --git a/cmd/webhook/main.go b/cmd/webhook/main.go index c87311acd..28734b077 100644 --- a/cmd/webhook/main.go +++ b/cmd/webhook/main.go @@ -27,16 +27,16 @@ import ( ) var types = map[schema.GroupVersionKind]resourcesemantics.GenericCRD{ - v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ImageKind): &v1alpha2.Image{}, - v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.BuildKind): &v1alpha2.Build{}, - v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.BuilderKind): &v1alpha2.Builder{}, - v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.BuildpackKind): &v1alpha2.Buildpack{}, - v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ExtensionKind): &v1alpha2.Extension{}, + v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ImageKind): &v1alpha2.Image{}, + v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.BuildKind): &v1alpha2.Build{}, + v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.BuilderKind): &v1alpha2.Builder{}, + v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.BuildpackKind): &v1alpha2.Buildpack{}, + // TODO: v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ExtensionKind): &v1alpha2.Extension{}, v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterBuilderKind): &v1alpha2.ClusterBuilder{}, v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterBuildpackKind): &v1alpha2.ClusterBuildpack{}, - v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterExtensionKind): &v1alpha2.ClusterExtension{}, - v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterStoreKind): &v1alpha2.ClusterStore{}, - v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterStackKind): &v1alpha2.ClusterStack{}, + // TODO: v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterExtensionKind): &v1alpha2.ClusterExtension{}, + v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterStoreKind): &v1alpha2.ClusterStore{}, + v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterStackKind): &v1alpha2.ClusterStack{}, } func init() { diff --git a/pkg/apis/build/v1alpha2/cluster_extension_types.go b/pkg/apis/build/v1alpha2/cluster_extension_types.go index 513ea45a9..798017f9f 100644 --- a/pkg/apis/build/v1alpha2/cluster_extension_types.go +++ b/pkg/apis/build/v1alpha2/cluster_extension_types.go @@ -9,8 +9,8 @@ import ( ) const ( - ClusterBuildpackKind = "ClusterBuildpack" - ClusterBuildpackCRName = "clusterbuildpacks.kpack.io" + ClusterExtensionKind = "ClusterExtension" + ClusterExtensionCRName = "clusterextensions.kpack.io" ) // +genclient diff --git a/pkg/apis/build/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/build/v1alpha2/zz_generated.deepcopy.go index d619d7ded..e22d3f26c 100644 --- a/pkg/apis/build/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/build/v1alpha2/zz_generated.deepcopy.go @@ -468,6 +468,13 @@ func (in *BuilderRecord) DeepCopyInto(out *BuilderRecord) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.OrderExtensions != nil { + in, out := &in.OrderExtensions, &out.OrderExtensions + *out = make([]v1alpha1.OrderEntry, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -493,6 +500,13 @@ func (in *BuilderSpec) DeepCopyInto(out *BuilderSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.OrderExtensions != nil { + in, out := &in.OrderExtensions, &out.OrderExtensions + *out = make([]BuilderOrderEntry, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -522,6 +536,13 @@ func (in *BuilderStatus) DeepCopyInto(out *BuilderStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.OrderExtensions != nil { + in, out := &in.OrderExtensions, &out.OrderExtensions + *out = make([]v1alpha1.OrderEntry, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } out.Stack = in.Stack return } @@ -848,6 +869,121 @@ func (in *ClusterBuildpackStatus) DeepCopy() *ClusterBuildpackStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterExtension) DeepCopyInto(out *ClusterExtension) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtension. +func (in *ClusterExtension) DeepCopy() *ClusterExtension { + if in == nil { + return nil + } + out := new(ClusterExtension) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObjectMetaAccessor is an autogenerated deepcopy function, copying the receiver, creating a new metav1.ObjectMetaAccessor. +func (in *ClusterExtension) DeepCopyObjectMetaAccessor() metav1.ObjectMetaAccessor { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterExtension) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterExtensionList) DeepCopyInto(out *ClusterExtensionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ClusterExtension, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionList. +func (in *ClusterExtensionList) DeepCopy() *ClusterExtensionList { + if in == nil { + return nil + } + out := new(ClusterExtensionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterExtensionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterExtensionSpec) DeepCopyInto(out *ClusterExtensionSpec) { + *out = *in + out.ImageSource = in.ImageSource + if in.ServiceAccountRef != nil { + in, out := &in.ServiceAccountRef, &out.ServiceAccountRef + *out = new(v1.ObjectReference) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionSpec. +func (in *ClusterExtensionSpec) DeepCopy() *ClusterExtensionSpec { + if in == nil { + return nil + } + out := new(ClusterExtensionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterExtensionStatus) DeepCopyInto(out *ClusterExtensionStatus) { + *out = *in + in.Status.DeepCopyInto(&out.Status) + if in.Extensions != nil { + in, out := &in.Extensions, &out.Extensions + *out = make([]v1alpha1.BuildpackStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterExtensionStatus. +func (in *ClusterExtensionStatus) DeepCopy() *ClusterExtensionStatus { + if in == nil { + return nil + } + out := new(ClusterExtensionStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterStack) DeepCopyInto(out *ClusterStack) { *out = *in @@ -1146,6 +1282,116 @@ func (in *CosignConfig) DeepCopy() *CosignConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Extension) DeepCopyInto(out *Extension) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Extension. +func (in *Extension) DeepCopy() *Extension { + if in == nil { + return nil + } + out := new(Extension) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObjectMetaAccessor is an autogenerated deepcopy function, copying the receiver, creating a new metav1.ObjectMetaAccessor. +func (in *Extension) DeepCopyObjectMetaAccessor() metav1.ObjectMetaAccessor { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Extension) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtensionList) DeepCopyInto(out *ExtensionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Extension, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionList. +func (in *ExtensionList) DeepCopy() *ExtensionList { + if in == nil { + return nil + } + out := new(ExtensionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExtensionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtensionSpec) DeepCopyInto(out *ExtensionSpec) { + *out = *in + out.ImageSource = in.ImageSource + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionSpec. +func (in *ExtensionSpec) DeepCopy() *ExtensionSpec { + if in == nil { + return nil + } + out := new(ExtensionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtensionStatus) DeepCopyInto(out *ExtensionStatus) { + *out = *in + in.Status.DeepCopyInto(&out.Status) + if in.Extensions != nil { + in, out := &in.Extensions, &out.Extensions + *out = make([]v1alpha1.BuildpackStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionStatus. +func (in *ExtensionStatus) DeepCopy() *ExtensionStatus { + if in == nil { + return nil + } + out := new(ExtensionStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Image) DeepCopyInto(out *Image) { *out = *in diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go index 1f9f821ba..5a3c862f1 100644 --- a/pkg/client/clientset/versioned/clientset.go +++ b/pkg/client/clientset/versioned/clientset.go @@ -35,8 +35,7 @@ type Interface interface { KpackV1alpha2() kpackv1alpha2.KpackV1alpha2Interface } -// Clientset contains the clients for groups. Each group has exactly one -// version included in a Clientset. +// Clientset contains the clients for groups. type Clientset struct { *discovery.DiscoveryClient kpackV1alpha1 *kpackv1alpha1.KpackV1alpha1Client diff --git a/pkg/client/clientset/versioned/typed/build/v1alpha2/build_client.go b/pkg/client/clientset/versioned/typed/build/v1alpha2/build_client.go index 4cc70b2c6..a97c3e66c 100644 --- a/pkg/client/clientset/versioned/typed/build/v1alpha2/build_client.go +++ b/pkg/client/clientset/versioned/typed/build/v1alpha2/build_client.go @@ -33,8 +33,10 @@ type KpackV1alpha2Interface interface { BuildpacksGetter ClusterBuildersGetter ClusterBuildpacksGetter + ClusterExtensionsGetter ClusterStacksGetter ClusterStoresGetter + ExtensionsGetter ImagesGetter SourceResolversGetter } @@ -64,6 +66,10 @@ func (c *KpackV1alpha2Client) ClusterBuildpacks() ClusterBuildpackInterface { return newClusterBuildpacks(c) } +func (c *KpackV1alpha2Client) ClusterExtensions() ClusterExtensionInterface { + return newClusterExtensions(c) +} + func (c *KpackV1alpha2Client) ClusterStacks() ClusterStackInterface { return newClusterStacks(c) } @@ -72,6 +78,10 @@ func (c *KpackV1alpha2Client) ClusterStores() ClusterStoreInterface { return newClusterStores(c) } +func (c *KpackV1alpha2Client) Extensions(namespace string) ExtensionInterface { + return newExtensions(c, namespace) +} + func (c *KpackV1alpha2Client) Images(namespace string) ImageInterface { return newImages(c, namespace) } diff --git a/pkg/client/clientset/versioned/typed/build/v1alpha2/clusterextension.go b/pkg/client/clientset/versioned/typed/build/v1alpha2/clusterextension.go new file mode 100644 index 000000000..1e2007b8b --- /dev/null +++ b/pkg/client/clientset/versioned/typed/build/v1alpha2/clusterextension.go @@ -0,0 +1,184 @@ +/* + * Copyright 2019 The original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + "context" + "time" + + v1alpha2 "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" + scheme "github.com/pivotal/kpack/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// ClusterExtensionsGetter has a method to return a ClusterExtensionInterface. +// A group's client should implement this interface. +type ClusterExtensionsGetter interface { + ClusterExtensions() ClusterExtensionInterface +} + +// ClusterExtensionInterface has methods to work with ClusterExtension resources. +type ClusterExtensionInterface interface { + Create(ctx context.Context, clusterExtension *v1alpha2.ClusterExtension, opts v1.CreateOptions) (*v1alpha2.ClusterExtension, error) + Update(ctx context.Context, clusterExtension *v1alpha2.ClusterExtension, opts v1.UpdateOptions) (*v1alpha2.ClusterExtension, error) + UpdateStatus(ctx context.Context, clusterExtension *v1alpha2.ClusterExtension, opts v1.UpdateOptions) (*v1alpha2.ClusterExtension, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha2.ClusterExtension, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha2.ClusterExtensionList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.ClusterExtension, err error) + ClusterExtensionExpansion +} + +// clusterExtensions implements ClusterExtensionInterface +type clusterExtensions struct { + client rest.Interface +} + +// newClusterExtensions returns a ClusterExtensions +func newClusterExtensions(c *KpackV1alpha2Client) *clusterExtensions { + return &clusterExtensions{ + client: c.RESTClient(), + } +} + +// Get takes name of the clusterExtension, and returns the corresponding clusterExtension object, and an error if there is any. +func (c *clusterExtensions) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.ClusterExtension, err error) { + result = &v1alpha2.ClusterExtension{} + err = c.client.Get(). + Resource("clusterextensions"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ClusterExtensions that match those selectors. +func (c *clusterExtensions) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.ClusterExtensionList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha2.ClusterExtensionList{} + err = c.client.Get(). + Resource("clusterextensions"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested clusterExtensions. +func (c *clusterExtensions) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Resource("clusterextensions"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a clusterExtension and creates it. Returns the server's representation of the clusterExtension, and an error, if there is any. +func (c *clusterExtensions) Create(ctx context.Context, clusterExtension *v1alpha2.ClusterExtension, opts v1.CreateOptions) (result *v1alpha2.ClusterExtension, err error) { + result = &v1alpha2.ClusterExtension{} + err = c.client.Post(). + Resource("clusterextensions"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(clusterExtension). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a clusterExtension and updates it. Returns the server's representation of the clusterExtension, and an error, if there is any. +func (c *clusterExtensions) Update(ctx context.Context, clusterExtension *v1alpha2.ClusterExtension, opts v1.UpdateOptions) (result *v1alpha2.ClusterExtension, err error) { + result = &v1alpha2.ClusterExtension{} + err = c.client.Put(). + Resource("clusterextensions"). + Name(clusterExtension.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(clusterExtension). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *clusterExtensions) UpdateStatus(ctx context.Context, clusterExtension *v1alpha2.ClusterExtension, opts v1.UpdateOptions) (result *v1alpha2.ClusterExtension, err error) { + result = &v1alpha2.ClusterExtension{} + err = c.client.Put(). + Resource("clusterextensions"). + Name(clusterExtension.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(clusterExtension). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the clusterExtension and deletes it. Returns an error if one occurs. +func (c *clusterExtensions) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Resource("clusterextensions"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *clusterExtensions) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Resource("clusterextensions"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched clusterExtension. +func (c *clusterExtensions) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.ClusterExtension, err error) { + result = &v1alpha2.ClusterExtension{} + err = c.client.Patch(pt). + Resource("clusterextensions"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/build/v1alpha2/extension.go b/pkg/client/clientset/versioned/typed/build/v1alpha2/extension.go new file mode 100644 index 000000000..d87c3a871 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/build/v1alpha2/extension.go @@ -0,0 +1,195 @@ +/* + * Copyright 2019 The original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + "context" + "time" + + v1alpha2 "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" + scheme "github.com/pivotal/kpack/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// ExtensionsGetter has a method to return a ExtensionInterface. +// A group's client should implement this interface. +type ExtensionsGetter interface { + Extensions(namespace string) ExtensionInterface +} + +// ExtensionInterface has methods to work with Extension resources. +type ExtensionInterface interface { + Create(ctx context.Context, extension *v1alpha2.Extension, opts v1.CreateOptions) (*v1alpha2.Extension, error) + Update(ctx context.Context, extension *v1alpha2.Extension, opts v1.UpdateOptions) (*v1alpha2.Extension, error) + UpdateStatus(ctx context.Context, extension *v1alpha2.Extension, opts v1.UpdateOptions) (*v1alpha2.Extension, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha2.Extension, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha2.ExtensionList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.Extension, err error) + ExtensionExpansion +} + +// extensions implements ExtensionInterface +type extensions struct { + client rest.Interface + ns string +} + +// newExtensions returns a Extensions +func newExtensions(c *KpackV1alpha2Client, namespace string) *extensions { + return &extensions{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the extension, and returns the corresponding extension object, and an error if there is any. +func (c *extensions) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.Extension, err error) { + result = &v1alpha2.Extension{} + err = c.client.Get(). + Namespace(c.ns). + Resource("extensions"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Extensions that match those selectors. +func (c *extensions) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.ExtensionList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha2.ExtensionList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("extensions"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested extensions. +func (c *extensions) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("extensions"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a extension and creates it. Returns the server's representation of the extension, and an error, if there is any. +func (c *extensions) Create(ctx context.Context, extension *v1alpha2.Extension, opts v1.CreateOptions) (result *v1alpha2.Extension, err error) { + result = &v1alpha2.Extension{} + err = c.client.Post(). + Namespace(c.ns). + Resource("extensions"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(extension). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a extension and updates it. Returns the server's representation of the extension, and an error, if there is any. +func (c *extensions) Update(ctx context.Context, extension *v1alpha2.Extension, opts v1.UpdateOptions) (result *v1alpha2.Extension, err error) { + result = &v1alpha2.Extension{} + err = c.client.Put(). + Namespace(c.ns). + Resource("extensions"). + Name(extension.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(extension). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *extensions) UpdateStatus(ctx context.Context, extension *v1alpha2.Extension, opts v1.UpdateOptions) (result *v1alpha2.Extension, err error) { + result = &v1alpha2.Extension{} + err = c.client.Put(). + Namespace(c.ns). + Resource("extensions"). + Name(extension.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(extension). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the extension and deletes it. Returns an error if one occurs. +func (c *extensions) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("extensions"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *extensions) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("extensions"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched extension. +func (c *extensions) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.Extension, err error) { + result = &v1alpha2.Extension{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("extensions"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/build/v1alpha2/fake/fake_build_client.go b/pkg/client/clientset/versioned/typed/build/v1alpha2/fake/fake_build_client.go index 6e91f1358..c186747b1 100644 --- a/pkg/client/clientset/versioned/typed/build/v1alpha2/fake/fake_build_client.go +++ b/pkg/client/clientset/versioned/typed/build/v1alpha2/fake/fake_build_client.go @@ -48,6 +48,10 @@ func (c *FakeKpackV1alpha2) ClusterBuildpacks() v1alpha2.ClusterBuildpackInterfa return &FakeClusterBuildpacks{c} } +func (c *FakeKpackV1alpha2) ClusterExtensions() v1alpha2.ClusterExtensionInterface { + return &FakeClusterExtensions{c} +} + func (c *FakeKpackV1alpha2) ClusterStacks() v1alpha2.ClusterStackInterface { return &FakeClusterStacks{c} } @@ -56,6 +60,10 @@ func (c *FakeKpackV1alpha2) ClusterStores() v1alpha2.ClusterStoreInterface { return &FakeClusterStores{c} } +func (c *FakeKpackV1alpha2) Extensions(namespace string) v1alpha2.ExtensionInterface { + return &FakeExtensions{c, namespace} +} + func (c *FakeKpackV1alpha2) Images(namespace string) v1alpha2.ImageInterface { return &FakeImages{c, namespace} } diff --git a/pkg/client/clientset/versioned/typed/build/v1alpha2/fake/fake_clusterextension.go b/pkg/client/clientset/versioned/typed/build/v1alpha2/fake/fake_clusterextension.go new file mode 100644 index 000000000..ef495b218 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/build/v1alpha2/fake/fake_clusterextension.go @@ -0,0 +1,133 @@ +/* + * Copyright 2019 The original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha2 "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeClusterExtensions implements ClusterExtensionInterface +type FakeClusterExtensions struct { + Fake *FakeKpackV1alpha2 +} + +var clusterextensionsResource = schema.GroupVersionResource{Group: "kpack.io", Version: "v1alpha2", Resource: "clusterextensions"} + +var clusterextensionsKind = schema.GroupVersionKind{Group: "kpack.io", Version: "v1alpha2", Kind: "ClusterExtension"} + +// Get takes name of the clusterExtension, and returns the corresponding clusterExtension object, and an error if there is any. +func (c *FakeClusterExtensions) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.ClusterExtension, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootGetAction(clusterextensionsResource, name), &v1alpha2.ClusterExtension{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.ClusterExtension), err +} + +// List takes label and field selectors, and returns the list of ClusterExtensions that match those selectors. +func (c *FakeClusterExtensions) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.ClusterExtensionList, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootListAction(clusterextensionsResource, clusterextensionsKind, opts), &v1alpha2.ClusterExtensionList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha2.ClusterExtensionList{ListMeta: obj.(*v1alpha2.ClusterExtensionList).ListMeta} + for _, item := range obj.(*v1alpha2.ClusterExtensionList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested clusterExtensions. +func (c *FakeClusterExtensions) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewRootWatchAction(clusterextensionsResource, opts)) +} + +// Create takes the representation of a clusterExtension and creates it. Returns the server's representation of the clusterExtension, and an error, if there is any. +func (c *FakeClusterExtensions) Create(ctx context.Context, clusterExtension *v1alpha2.ClusterExtension, opts v1.CreateOptions) (result *v1alpha2.ClusterExtension, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootCreateAction(clusterextensionsResource, clusterExtension), &v1alpha2.ClusterExtension{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.ClusterExtension), err +} + +// Update takes the representation of a clusterExtension and updates it. Returns the server's representation of the clusterExtension, and an error, if there is any. +func (c *FakeClusterExtensions) Update(ctx context.Context, clusterExtension *v1alpha2.ClusterExtension, opts v1.UpdateOptions) (result *v1alpha2.ClusterExtension, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateAction(clusterextensionsResource, clusterExtension), &v1alpha2.ClusterExtension{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.ClusterExtension), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeClusterExtensions) UpdateStatus(ctx context.Context, clusterExtension *v1alpha2.ClusterExtension, opts v1.UpdateOptions) (*v1alpha2.ClusterExtension, error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(clusterextensionsResource, "status", clusterExtension), &v1alpha2.ClusterExtension{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.ClusterExtension), err +} + +// Delete takes name of the clusterExtension and deletes it. Returns an error if one occurs. +func (c *FakeClusterExtensions) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewRootDeleteActionWithOptions(clusterextensionsResource, name, opts), &v1alpha2.ClusterExtension{}) + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeClusterExtensions) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewRootDeleteCollectionAction(clusterextensionsResource, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha2.ClusterExtensionList{}) + return err +} + +// Patch applies the patch and returns the patched clusterExtension. +func (c *FakeClusterExtensions) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.ClusterExtension, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceAction(clusterextensionsResource, name, pt, data, subresources...), &v1alpha2.ClusterExtension{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.ClusterExtension), err +} diff --git a/pkg/client/clientset/versioned/typed/build/v1alpha2/fake/fake_extension.go b/pkg/client/clientset/versioned/typed/build/v1alpha2/fake/fake_extension.go new file mode 100644 index 000000000..8b2cefc1f --- /dev/null +++ b/pkg/client/clientset/versioned/typed/build/v1alpha2/fake/fake_extension.go @@ -0,0 +1,142 @@ +/* + * Copyright 2019 The original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha2 "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeExtensions implements ExtensionInterface +type FakeExtensions struct { + Fake *FakeKpackV1alpha2 + ns string +} + +var extensionsResource = schema.GroupVersionResource{Group: "kpack.io", Version: "v1alpha2", Resource: "extensions"} + +var extensionsKind = schema.GroupVersionKind{Group: "kpack.io", Version: "v1alpha2", Kind: "Extension"} + +// Get takes name of the extension, and returns the corresponding extension object, and an error if there is any. +func (c *FakeExtensions) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.Extension, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(extensionsResource, c.ns, name), &v1alpha2.Extension{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.Extension), err +} + +// List takes label and field selectors, and returns the list of Extensions that match those selectors. +func (c *FakeExtensions) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.ExtensionList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(extensionsResource, extensionsKind, c.ns, opts), &v1alpha2.ExtensionList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha2.ExtensionList{ListMeta: obj.(*v1alpha2.ExtensionList).ListMeta} + for _, item := range obj.(*v1alpha2.ExtensionList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested extensions. +func (c *FakeExtensions) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(extensionsResource, c.ns, opts)) + +} + +// Create takes the representation of a extension and creates it. Returns the server's representation of the extension, and an error, if there is any. +func (c *FakeExtensions) Create(ctx context.Context, extension *v1alpha2.Extension, opts v1.CreateOptions) (result *v1alpha2.Extension, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(extensionsResource, c.ns, extension), &v1alpha2.Extension{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.Extension), err +} + +// Update takes the representation of a extension and updates it. Returns the server's representation of the extension, and an error, if there is any. +func (c *FakeExtensions) Update(ctx context.Context, extension *v1alpha2.Extension, opts v1.UpdateOptions) (result *v1alpha2.Extension, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(extensionsResource, c.ns, extension), &v1alpha2.Extension{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.Extension), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeExtensions) UpdateStatus(ctx context.Context, extension *v1alpha2.Extension, opts v1.UpdateOptions) (*v1alpha2.Extension, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(extensionsResource, "status", c.ns, extension), &v1alpha2.Extension{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.Extension), err +} + +// Delete takes name of the extension and deletes it. Returns an error if one occurs. +func (c *FakeExtensions) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(extensionsResource, c.ns, name, opts), &v1alpha2.Extension{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeExtensions) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(extensionsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha2.ExtensionList{}) + return err +} + +// Patch applies the patch and returns the patched extension. +func (c *FakeExtensions) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.Extension, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(extensionsResource, c.ns, name, pt, data, subresources...), &v1alpha2.Extension{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.Extension), err +} diff --git a/pkg/client/clientset/versioned/typed/build/v1alpha2/generated_expansion.go b/pkg/client/clientset/versioned/typed/build/v1alpha2/generated_expansion.go index 198507aa7..1880e4657 100644 --- a/pkg/client/clientset/versioned/typed/build/v1alpha2/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/build/v1alpha2/generated_expansion.go @@ -28,10 +28,14 @@ type ClusterBuilderExpansion interface{} type ClusterBuildpackExpansion interface{} +type ClusterExtensionExpansion interface{} + type ClusterStackExpansion interface{} type ClusterStoreExpansion interface{} +type ExtensionExpansion interface{} + type ImageExpansion interface{} type SourceResolverExpansion interface{} diff --git a/pkg/client/informers/externalversions/build/v1alpha2/clusterextension.go b/pkg/client/informers/externalversions/build/v1alpha2/clusterextension.go new file mode 100644 index 000000000..515fddc9e --- /dev/null +++ b/pkg/client/informers/externalversions/build/v1alpha2/clusterextension.go @@ -0,0 +1,89 @@ +/* + * Copyright 2019 The original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + "context" + time "time" + + buildv1alpha2 "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" + versioned "github.com/pivotal/kpack/pkg/client/clientset/versioned" + internalinterfaces "github.com/pivotal/kpack/pkg/client/informers/externalversions/internalinterfaces" + v1alpha2 "github.com/pivotal/kpack/pkg/client/listers/build/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// ClusterExtensionInformer provides access to a shared informer and lister for +// ClusterExtensions. +type ClusterExtensionInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha2.ClusterExtensionLister +} + +type clusterExtensionInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewClusterExtensionInformer constructs a new informer for ClusterExtension type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewClusterExtensionInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredClusterExtensionInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredClusterExtensionInformer constructs a new informer for ClusterExtension type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredClusterExtensionInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.KpackV1alpha2().ClusterExtensions().List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.KpackV1alpha2().ClusterExtensions().Watch(context.TODO(), options) + }, + }, + &buildv1alpha2.ClusterExtension{}, + resyncPeriod, + indexers, + ) +} + +func (f *clusterExtensionInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredClusterExtensionInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *clusterExtensionInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&buildv1alpha2.ClusterExtension{}, f.defaultInformer) +} + +func (f *clusterExtensionInformer) Lister() v1alpha2.ClusterExtensionLister { + return v1alpha2.NewClusterExtensionLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/build/v1alpha2/extension.go b/pkg/client/informers/externalversions/build/v1alpha2/extension.go new file mode 100644 index 000000000..b9007c767 --- /dev/null +++ b/pkg/client/informers/externalversions/build/v1alpha2/extension.go @@ -0,0 +1,90 @@ +/* + * Copyright 2019 The original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + "context" + time "time" + + buildv1alpha2 "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" + versioned "github.com/pivotal/kpack/pkg/client/clientset/versioned" + internalinterfaces "github.com/pivotal/kpack/pkg/client/informers/externalversions/internalinterfaces" + v1alpha2 "github.com/pivotal/kpack/pkg/client/listers/build/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// ExtensionInformer provides access to a shared informer and lister for +// Extensions. +type ExtensionInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha2.ExtensionLister +} + +type extensionInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewExtensionInformer constructs a new informer for Extension type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewExtensionInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredExtensionInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredExtensionInformer constructs a new informer for Extension type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredExtensionInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.KpackV1alpha2().Extensions(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.KpackV1alpha2().Extensions(namespace).Watch(context.TODO(), options) + }, + }, + &buildv1alpha2.Extension{}, + resyncPeriod, + indexers, + ) +} + +func (f *extensionInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredExtensionInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *extensionInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&buildv1alpha2.Extension{}, f.defaultInformer) +} + +func (f *extensionInformer) Lister() v1alpha2.ExtensionLister { + return v1alpha2.NewExtensionLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/build/v1alpha2/interface.go b/pkg/client/informers/externalversions/build/v1alpha2/interface.go index 97f4916c5..9dbef4c9a 100644 --- a/pkg/client/informers/externalversions/build/v1alpha2/interface.go +++ b/pkg/client/informers/externalversions/build/v1alpha2/interface.go @@ -34,10 +34,14 @@ type Interface interface { ClusterBuilders() ClusterBuilderInformer // ClusterBuildpacks returns a ClusterBuildpackInformer. ClusterBuildpacks() ClusterBuildpackInformer + // ClusterExtensions returns a ClusterExtensionInformer. + ClusterExtensions() ClusterExtensionInformer // ClusterStacks returns a ClusterStackInformer. ClusterStacks() ClusterStackInformer // ClusterStores returns a ClusterStoreInformer. ClusterStores() ClusterStoreInformer + // Extensions returns a ExtensionInformer. + Extensions() ExtensionInformer // Images returns a ImageInformer. Images() ImageInformer // SourceResolvers returns a SourceResolverInformer. @@ -80,6 +84,11 @@ func (v *version) ClusterBuildpacks() ClusterBuildpackInformer { return &clusterBuildpackInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// ClusterExtensions returns a ClusterExtensionInformer. +func (v *version) ClusterExtensions() ClusterExtensionInformer { + return &clusterExtensionInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} + // ClusterStacks returns a ClusterStackInformer. func (v *version) ClusterStacks() ClusterStackInformer { return &clusterStackInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} @@ -90,6 +99,11 @@ func (v *version) ClusterStores() ClusterStoreInformer { return &clusterStoreInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// Extensions returns a ExtensionInformer. +func (v *version) Extensions() ExtensionInformer { + return &extensionInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Images returns a ImageInformer. func (v *version) Images() ImageInformer { return &imageInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/client/informers/externalversions/factory.go b/pkg/client/informers/externalversions/factory.go index 3c52fd62f..efe0c7c1a 100644 --- a/pkg/client/informers/externalversions/factory.go +++ b/pkg/client/informers/externalversions/factory.go @@ -47,6 +47,11 @@ type sharedInformerFactory struct { // startedInformers is used for tracking which informers have been started. // This allows Start() to be called multiple times safely. startedInformers map[reflect.Type]bool + // wg tracks how many goroutines were started. + wg sync.WaitGroup + // shuttingDown is true when Shutdown has been called. It may still be running + // because it needs to wait for goroutines. + shuttingDown bool } // WithCustomResyncConfig sets a custom resync period for the specified informer types. @@ -107,20 +112,39 @@ func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResy return factory } -// Start initializes all requested informers. func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { f.lock.Lock() defer f.lock.Unlock() + if f.shuttingDown { + return + } + for informerType, informer := range f.informers { if !f.startedInformers[informerType] { - go informer.Run(stopCh) + f.wg.Add(1) + // We need a new variable in each loop iteration, + // otherwise the goroutine would use the loop variable + // and that keeps changing. + informer := informer + go func() { + defer f.wg.Done() + informer.Run(stopCh) + }() f.startedInformers[informerType] = true } } } -// WaitForCacheSync waits for all started informers' cache were synced. +func (f *sharedInformerFactory) Shutdown() { + f.lock.Lock() + f.shuttingDown = true + f.lock.Unlock() + + // Will return immediately if there is nothing to wait for. + f.wg.Wait() +} + func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { informers := func() map[reflect.Type]cache.SharedIndexInformer { f.lock.Lock() @@ -167,11 +191,58 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal // SharedInformerFactory provides shared informers for resources in all known // API group versions. +// +// It is typically used like this: +// +// ctx, cancel := context.Background() +// defer cancel() +// factory := NewSharedInformerFactory(client, resyncPeriod) +// defer factory.WaitForStop() // Returns immediately if nothing was started. +// genericInformer := factory.ForResource(resource) +// typedInformer := factory.SomeAPIGroup().V1().SomeType() +// factory.Start(ctx.Done()) // Start processing these informers. +// synced := factory.WaitForCacheSync(ctx.Done()) +// for v, ok := range synced { +// if !ok { +// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v) +// return +// } +// } +// +// // Creating informers can also be created after Start, but then +// // Start must be called again: +// anotherGenericInformer := factory.ForResource(resource) +// factory.Start(ctx.Done()) type SharedInformerFactory interface { internalinterfaces.SharedInformerFactory - ForResource(resource schema.GroupVersionResource) (GenericInformer, error) + + // Start initializes all requested informers. They are handled in goroutines + // which run until the stop channel gets closed. + Start(stopCh <-chan struct{}) + + // Shutdown marks a factory as shutting down. At that point no new + // informers can be started anymore and Start will return without + // doing anything. + // + // In addition, Shutdown blocks until all goroutines have terminated. For that + // to happen, the close channel(s) that they were started with must be closed, + // either before Shutdown gets called or while it is waiting. + // + // Shutdown may be called multiple times, even concurrently. All such calls will + // block until all goroutines have terminated. + Shutdown() + + // WaitForCacheSync blocks until all started informers' caches were synced + // or the stop channel gets closed. WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + // ForResource gives generic access to a shared informer of the matching type. + ForResource(resource schema.GroupVersionResource) (GenericInformer, error) + + // InternalInformerFor returns the SharedIndexInformer for obj using an internal + // client. + InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer + Kpack() build.Interface } diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 00a5f8a24..3926ceb78 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -80,10 +80,14 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Kpack().V1alpha2().ClusterBuilders().Informer()}, nil case v1alpha2.SchemeGroupVersion.WithResource("clusterbuildpacks"): return &genericInformer{resource: resource.GroupResource(), informer: f.Kpack().V1alpha2().ClusterBuildpacks().Informer()}, nil + case v1alpha2.SchemeGroupVersion.WithResource("clusterextensions"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Kpack().V1alpha2().ClusterExtensions().Informer()}, nil case v1alpha2.SchemeGroupVersion.WithResource("clusterstacks"): return &genericInformer{resource: resource.GroupResource(), informer: f.Kpack().V1alpha2().ClusterStacks().Informer()}, nil case v1alpha2.SchemeGroupVersion.WithResource("clusterstores"): return &genericInformer{resource: resource.GroupResource(), informer: f.Kpack().V1alpha2().ClusterStores().Informer()}, nil + case v1alpha2.SchemeGroupVersion.WithResource("extensions"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Kpack().V1alpha2().Extensions().Informer()}, nil case v1alpha2.SchemeGroupVersion.WithResource("images"): return &genericInformer{resource: resource.GroupResource(), informer: f.Kpack().V1alpha2().Images().Informer()}, nil case v1alpha2.SchemeGroupVersion.WithResource("sourceresolvers"): diff --git a/pkg/client/listers/build/v1alpha2/clusterextension.go b/pkg/client/listers/build/v1alpha2/clusterextension.go new file mode 100644 index 000000000..cab266de8 --- /dev/null +++ b/pkg/client/listers/build/v1alpha2/clusterextension.go @@ -0,0 +1,68 @@ +/* + * Copyright 2019 The original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + v1alpha2 "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// ClusterExtensionLister helps list ClusterExtensions. +// All objects returned here must be treated as read-only. +type ClusterExtensionLister interface { + // List lists all ClusterExtensions in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.ClusterExtension, err error) + // Get retrieves the ClusterExtension from the index for a given name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha2.ClusterExtension, error) + ClusterExtensionListerExpansion +} + +// clusterExtensionLister implements the ClusterExtensionLister interface. +type clusterExtensionLister struct { + indexer cache.Indexer +} + +// NewClusterExtensionLister returns a new ClusterExtensionLister. +func NewClusterExtensionLister(indexer cache.Indexer) ClusterExtensionLister { + return &clusterExtensionLister{indexer: indexer} +} + +// List lists all ClusterExtensions in the indexer. +func (s *clusterExtensionLister) List(selector labels.Selector) (ret []*v1alpha2.ClusterExtension, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.ClusterExtension)) + }) + return ret, err +} + +// Get retrieves the ClusterExtension from the index for a given name. +func (s *clusterExtensionLister) Get(name string) (*v1alpha2.ClusterExtension, error) { + obj, exists, err := s.indexer.GetByKey(name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha2.Resource("clusterextension"), name) + } + return obj.(*v1alpha2.ClusterExtension), nil +} diff --git a/pkg/client/listers/build/v1alpha2/expansion_generated.go b/pkg/client/listers/build/v1alpha2/expansion_generated.go index e369f428b..ffe88d54c 100644 --- a/pkg/client/listers/build/v1alpha2/expansion_generated.go +++ b/pkg/client/listers/build/v1alpha2/expansion_generated.go @@ -50,6 +50,10 @@ type ClusterBuilderListerExpansion interface{} // ClusterBuildpackLister. type ClusterBuildpackListerExpansion interface{} +// ClusterExtensionListerExpansion allows custom methods to be added to +// ClusterExtensionLister. +type ClusterExtensionListerExpansion interface{} + // ClusterStackListerExpansion allows custom methods to be added to // ClusterStackLister. type ClusterStackListerExpansion interface{} @@ -58,6 +62,14 @@ type ClusterStackListerExpansion interface{} // ClusterStoreLister. type ClusterStoreListerExpansion interface{} +// ExtensionListerExpansion allows custom methods to be added to +// ExtensionLister. +type ExtensionListerExpansion interface{} + +// ExtensionNamespaceListerExpansion allows custom methods to be added to +// ExtensionNamespaceLister. +type ExtensionNamespaceListerExpansion interface{} + // ImageListerExpansion allows custom methods to be added to // ImageLister. type ImageListerExpansion interface{} diff --git a/pkg/client/listers/build/v1alpha2/extension.go b/pkg/client/listers/build/v1alpha2/extension.go new file mode 100644 index 000000000..7303c45bd --- /dev/null +++ b/pkg/client/listers/build/v1alpha2/extension.go @@ -0,0 +1,99 @@ +/* + * Copyright 2019 The original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + v1alpha2 "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// ExtensionLister helps list Extensions. +// All objects returned here must be treated as read-only. +type ExtensionLister interface { + // List lists all Extensions in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.Extension, err error) + // Extensions returns an object that can list and get Extensions. + Extensions(namespace string) ExtensionNamespaceLister + ExtensionListerExpansion +} + +// extensionLister implements the ExtensionLister interface. +type extensionLister struct { + indexer cache.Indexer +} + +// NewExtensionLister returns a new ExtensionLister. +func NewExtensionLister(indexer cache.Indexer) ExtensionLister { + return &extensionLister{indexer: indexer} +} + +// List lists all Extensions in the indexer. +func (s *extensionLister) List(selector labels.Selector) (ret []*v1alpha2.Extension, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.Extension)) + }) + return ret, err +} + +// Extensions returns an object that can list and get Extensions. +func (s *extensionLister) Extensions(namespace string) ExtensionNamespaceLister { + return extensionNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// ExtensionNamespaceLister helps list and get Extensions. +// All objects returned here must be treated as read-only. +type ExtensionNamespaceLister interface { + // List lists all Extensions in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.Extension, err error) + // Get retrieves the Extension from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha2.Extension, error) + ExtensionNamespaceListerExpansion +} + +// extensionNamespaceLister implements the ExtensionNamespaceLister +// interface. +type extensionNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Extensions in the indexer for a given namespace. +func (s extensionNamespaceLister) List(selector labels.Selector) (ret []*v1alpha2.Extension, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.Extension)) + }) + return ret, err +} + +// Get retrieves the Extension from the indexer for a given namespace and name. +func (s extensionNamespaceLister) Get(name string) (*v1alpha2.Extension, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha2.Resource("extension"), name) + } + return obj.(*v1alpha2.Extension), nil +} diff --git a/pkg/openapi/openapi_generated.go b/pkg/openapi/openapi_generated.go index 341644396..8838aa4a1 100644 --- a/pkg/openapi/openapi_generated.go +++ b/pkg/openapi/openapi_generated.go @@ -89,6 +89,10 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ClusterBuildpackList": schema_pkg_apis_build_v1alpha2_ClusterBuildpackList(ref), "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ClusterBuildpackSpec": schema_pkg_apis_build_v1alpha2_ClusterBuildpackSpec(ref), "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ClusterBuildpackStatus": schema_pkg_apis_build_v1alpha2_ClusterBuildpackStatus(ref), + "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ClusterExtension": schema_pkg_apis_build_v1alpha2_ClusterExtension(ref), + "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ClusterExtensionList": schema_pkg_apis_build_v1alpha2_ClusterExtensionList(ref), + "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ClusterExtensionSpec": schema_pkg_apis_build_v1alpha2_ClusterExtensionSpec(ref), + "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ClusterExtensionStatus": schema_pkg_apis_build_v1alpha2_ClusterExtensionStatus(ref), "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ClusterStack": schema_pkg_apis_build_v1alpha2_ClusterStack(ref), "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ClusterStackList": schema_pkg_apis_build_v1alpha2_ClusterStackList(ref), "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ClusterStackSpec": schema_pkg_apis_build_v1alpha2_ClusterStackSpec(ref), @@ -101,6 +105,10 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ClusterStoreStatus": schema_pkg_apis_build_v1alpha2_ClusterStoreStatus(ref), "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.CosignAnnotation": schema_pkg_apis_build_v1alpha2_CosignAnnotation(ref), "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.CosignConfig": schema_pkg_apis_build_v1alpha2_CosignConfig(ref), + "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.Extension": schema_pkg_apis_build_v1alpha2_Extension(ref), + "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ExtensionList": schema_pkg_apis_build_v1alpha2_ExtensionList(ref), + "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ExtensionSpec": schema_pkg_apis_build_v1alpha2_ExtensionSpec(ref), + "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ExtensionStatus": schema_pkg_apis_build_v1alpha2_ExtensionStatus(ref), "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.Image": schema_pkg_apis_build_v1alpha2_Image(ref), "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ImageBuild": schema_pkg_apis_build_v1alpha2_ImageBuild(ref), "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ImageBuilder": schema_pkg_apis_build_v1alpha2_ImageBuilder(ref), @@ -2702,6 +2710,19 @@ func schema_pkg_apis_build_v1alpha2_BuilderSpec(ref common.ReferenceCallback) co }, }, }, + "order-extensions": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/pivotal/kpack/pkg/apis/build/v1alpha2.BuilderOrderEntry"), + }, + }, + }, + }, + }, }, }, }, @@ -2769,6 +2790,19 @@ func schema_pkg_apis_build_v1alpha2_BuilderStatus(ref common.ReferenceCallback) }, }, }, + "order-extensions": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/pivotal/kpack/pkg/apis/core/v1alpha1.OrderEntry"), + }, + }, + }, + }, + }, "stack": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, @@ -3122,6 +3156,19 @@ func schema_pkg_apis_build_v1alpha2_ClusterBuilderSpec(ref common.ReferenceCallb }, }, }, + "order-extensions": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/pivotal/kpack/pkg/apis/build/v1alpha2.BuilderOrderEntry"), + }, + }, + }, + }, + }, "serviceAccountRef": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, @@ -3237,15 +3284,189 @@ func schema_pkg_apis_build_v1alpha2_ClusterBuildpackSpec(ref common.ReferenceCal SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ - "source": { + "image": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "serviceAccountRef": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/api/core/v1.ObjectReference"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ObjectReference"}, + } +} + +func schema_pkg_apis_build_v1alpha2_ClusterBuildpackStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "observedGeneration": { + SchemaProps: spec.SchemaProps{ + Description: "ObservedGeneration is the 'Generation' of the Service that was last processed by the controller.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Conditions the latest available observations of a resource's current state.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/pivotal/kpack/pkg/apis/core/v1alpha1.Condition"), + }, + }, + }, + }, + }, + "buildpacks": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-list-type": "", }, }, + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/pivotal/kpack/pkg/apis/core/v1alpha1.BuildpackStatus"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/pivotal/kpack/pkg/apis/core/v1alpha1.BuildpackStatus", "github.com/pivotal/kpack/pkg/apis/core/v1alpha1.Condition"}, + } +} + +func schema_pkg_apis_build_v1alpha2_ClusterExtension(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/pivotal/kpack/pkg/apis/core/v1alpha1.ImageSource"), + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ClusterExtensionSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ClusterExtensionStatus"), + }, + }, + }, + Required: []string{"spec", "status"}, + }, + }, + Dependencies: []string{ + "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ClusterExtensionSpec", "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ClusterExtensionStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_build_v1alpha2_ClusterExtensionList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ClusterExtension"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ClusterExtension", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_build_v1alpha2_ClusterExtensionSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "image": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", }, }, "serviceAccountRef": { @@ -3257,11 +3478,11 @@ func schema_pkg_apis_build_v1alpha2_ClusterBuildpackSpec(ref common.ReferenceCal }, }, Dependencies: []string{ - "github.com/pivotal/kpack/pkg/apis/core/v1alpha1.ImageSource", "k8s.io/api/core/v1.ObjectReference"}, + "k8s.io/api/core/v1.ObjectReference"}, } } -func schema_pkg_apis_build_v1alpha2_ClusterBuildpackStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_build_v1alpha2_ClusterExtensionStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -3294,7 +3515,7 @@ func schema_pkg_apis_build_v1alpha2_ClusterBuildpackStatus(ref common.ReferenceC }, }, }, - "buildpacks": { + "extensions": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-list-type": "", @@ -3831,6 +4052,184 @@ func schema_pkg_apis_build_v1alpha2_CosignConfig(ref common.ReferenceCallback) c } } +func schema_pkg_apis_build_v1alpha2_Extension(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ExtensionSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ExtensionStatus"), + }, + }, + }, + Required: []string{"spec", "status"}, + }, + }, + Dependencies: []string{ + "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ExtensionSpec", "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.ExtensionStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_build_v1alpha2_ExtensionList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/pivotal/kpack/pkg/apis/build/v1alpha2.Extension"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/pivotal/kpack/pkg/apis/build/v1alpha2.Extension", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_build_v1alpha2_ExtensionSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "image": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "serviceAccountName": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_build_v1alpha2_ExtensionStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "observedGeneration": { + SchemaProps: spec.SchemaProps{ + Description: "ObservedGeneration is the 'Generation' of the Service that was last processed by the controller.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Conditions the latest available observations of a resource's current state.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/pivotal/kpack/pkg/apis/core/v1alpha1.Condition"), + }, + }, + }, + }, + }, + "extensions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "", + }, + }, + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/pivotal/kpack/pkg/apis/core/v1alpha1.BuildpackStatus"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/pivotal/kpack/pkg/apis/core/v1alpha1.BuildpackStatus", "github.com/pivotal/kpack/pkg/apis/core/v1alpha1.Condition"}, + } +} + func schema_pkg_apis_build_v1alpha2_Image(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -4411,6 +4810,19 @@ func schema_pkg_apis_build_v1alpha2_NamespacedBuilderSpec(ref common.ReferenceCa }, }, }, + "order-extensions": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/pivotal/kpack/pkg/apis/build/v1alpha2.BuilderOrderEntry"), + }, + }, + }, + }, + }, "serviceAccountName": { SchemaProps: spec.SchemaProps{ Type: []string{"string"}, diff --git a/pkg/reconciler/clusterbuildpack/clusterbuildpack.go b/pkg/reconciler/clusterbuildpack/clusterbuildpack.go index 4f933c838..e3f1bd044 100644 --- a/pkg/reconciler/clusterbuildpack/clusterbuildpack.go +++ b/pkg/reconciler/clusterbuildpack/clusterbuildpack.go @@ -21,7 +21,6 @@ import ( "github.com/pivotal/kpack/pkg/registry" ) -// TODO: add for extensions const ( ReconcilerName = "ClusterBuildpacks" Kind = "ClusterBuildpack" From 512c1d290aac1b78a1d4d2d9c54f755d00301064 Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Wed, 27 Sep 2023 13:29:53 -0400 Subject: [PATCH 05/20] Add extension validations Signed-off-by: Natalie Arellano --- cmd/webhook/main.go | 16 +++++----- .../build/v1alpha2/buildpack_validation.go | 1 - .../v1alpha2/cluster_extension_validation.go | 29 +++++++++++++++++++ .../build/v1alpha2/extension_validation.go | 23 +++++++++++++++ .../clusterbuildpack/clusterbuildpack.go | 1 + 5 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 pkg/apis/build/v1alpha2/cluster_extension_validation.go create mode 100644 pkg/apis/build/v1alpha2/extension_validation.go diff --git a/cmd/webhook/main.go b/cmd/webhook/main.go index 28734b077..c87311acd 100644 --- a/cmd/webhook/main.go +++ b/cmd/webhook/main.go @@ -27,16 +27,16 @@ import ( ) var types = map[schema.GroupVersionKind]resourcesemantics.GenericCRD{ - v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ImageKind): &v1alpha2.Image{}, - v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.BuildKind): &v1alpha2.Build{}, - v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.BuilderKind): &v1alpha2.Builder{}, - v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.BuildpackKind): &v1alpha2.Buildpack{}, - // TODO: v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ExtensionKind): &v1alpha2.Extension{}, + v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ImageKind): &v1alpha2.Image{}, + v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.BuildKind): &v1alpha2.Build{}, + v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.BuilderKind): &v1alpha2.Builder{}, + v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.BuildpackKind): &v1alpha2.Buildpack{}, + v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ExtensionKind): &v1alpha2.Extension{}, v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterBuilderKind): &v1alpha2.ClusterBuilder{}, v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterBuildpackKind): &v1alpha2.ClusterBuildpack{}, - // TODO: v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterExtensionKind): &v1alpha2.ClusterExtension{}, - v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterStoreKind): &v1alpha2.ClusterStore{}, - v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterStackKind): &v1alpha2.ClusterStack{}, + v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterExtensionKind): &v1alpha2.ClusterExtension{}, + v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterStoreKind): &v1alpha2.ClusterStore{}, + v1alpha2.SchemeGroupVersion.WithKind(v1alpha2.ClusterStackKind): &v1alpha2.ClusterStack{}, } func init() { diff --git a/pkg/apis/build/v1alpha2/buildpack_validation.go b/pkg/apis/build/v1alpha2/buildpack_validation.go index e583ac847..04aa7c837 100644 --- a/pkg/apis/build/v1alpha2/buildpack_validation.go +++ b/pkg/apis/build/v1alpha2/buildpack_validation.go @@ -8,7 +8,6 @@ import ( "github.com/pivotal/kpack/pkg/apis/validate" ) -// TODO: add for extensions func (cb *Buildpack) SetDefaults(context.Context) { if cb.Spec.ServiceAccountName == "" { cb.Spec.ServiceAccountName = "default" diff --git a/pkg/apis/build/v1alpha2/cluster_extension_validation.go b/pkg/apis/build/v1alpha2/cluster_extension_validation.go new file mode 100644 index 000000000..c59790b1c --- /dev/null +++ b/pkg/apis/build/v1alpha2/cluster_extension_validation.go @@ -0,0 +1,29 @@ +package v1alpha2 + +import ( + "context" + + "knative.dev/pkg/apis" + + "github.com/pivotal/kpack/pkg/apis/validate" +) + +func (s *ClusterExtension) SetDefaults(context.Context) { +} + +func (s *ClusterExtension) Validate(ctx context.Context) *apis.FieldError { + return s.Spec.Validate(ctx).ViaField("spec") +} + +func (s *ClusterExtensionSpec) Validate(ctx context.Context) *apis.FieldError { + if s.ServiceAccountRef != nil { + if s.ServiceAccountRef.Name == "" { + return apis.ErrMissingField("name").ViaField("serviceAccountRef") + } + if s.ServiceAccountRef.Namespace == "" { + return apis.ErrMissingField("namespace").ViaField("serviceAccountRef") + } + } + + return validate.Image(s.Image) +} diff --git a/pkg/apis/build/v1alpha2/extension_validation.go b/pkg/apis/build/v1alpha2/extension_validation.go new file mode 100644 index 000000000..f6c71c585 --- /dev/null +++ b/pkg/apis/build/v1alpha2/extension_validation.go @@ -0,0 +1,23 @@ +package v1alpha2 + +import ( + "context" + + "knative.dev/pkg/apis" + + "github.com/pivotal/kpack/pkg/apis/validate" +) + +func (e *Extension) SetDefaults(context.Context) { + if e.Spec.ServiceAccountName == "" { + e.Spec.ServiceAccountName = "default" + } +} + +func (e *Extension) Validate(ctx context.Context) *apis.FieldError { + return e.Spec.Validate(ctx).ViaField("spec") +} + +func (s *ExtensionSpec) Validate(ctx context.Context) *apis.FieldError { + return validate.Image(s.Image) +} diff --git a/pkg/reconciler/clusterbuildpack/clusterbuildpack.go b/pkg/reconciler/clusterbuildpack/clusterbuildpack.go index e3f1bd044..4f933c838 100644 --- a/pkg/reconciler/clusterbuildpack/clusterbuildpack.go +++ b/pkg/reconciler/clusterbuildpack/clusterbuildpack.go @@ -21,6 +21,7 @@ import ( "github.com/pivotal/kpack/pkg/registry" ) +// TODO: add for extensions const ( ReconcilerName = "ClusterBuildpacks" Kind = "ClusterBuildpack" From 11552f160bc013b2e750bf0f44683ebf87b120d6 Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Wed, 27 Sep 2023 13:38:32 -0400 Subject: [PATCH 06/20] Feed extensions to remote buildpack fetcher Signed-off-by: Natalie Arellano --- cmd/controller/main.go | 4 +++- pkg/cnb/buildpack_resolver.go | 8 ++++---- pkg/cnb/remote_buildpack_fetcher.go | 4 ++-- pkg/reconciler/builder/builder.go | 18 +++++++++++++++++- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/cmd/controller/main.go b/cmd/controller/main.go index edc305ba6..14f1c095c 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -116,8 +116,10 @@ func main() { sourceResolverInformer := informerFactory.Kpack().V1alpha2().SourceResolvers() builderInformer := informerFactory.Kpack().V1alpha2().Builders() buildpackInformer := informerFactory.Kpack().V1alpha2().Buildpacks() + extensionInformer := informerFactory.Kpack().V1alpha2().Extensions() clusterBuilderInformer := informerFactory.Kpack().V1alpha2().ClusterBuilders() clusterBuildpackInformer := informerFactory.Kpack().V1alpha2().ClusterBuildpacks() + clusterExtensionInformer := informerFactory.Kpack().V1alpha2().ClusterExtensions() clusterStoreInformer := informerFactory.Kpack().V1alpha2().ClusterStores() clusterStackInformer := informerFactory.Kpack().V1alpha2().ClusterStacks() @@ -203,7 +205,7 @@ func main() { buildController := build.NewController(ctx, options, k8sClient, buildInformer, podInformer, metadataRetriever, buildpodGenerator, podProgressLogger, keychainFactory, *injectedSidecarSupport) imageController := image.NewController(ctx, options, k8sClient, imageInformer, buildInformer, duckBuilderInformer, sourceResolverInformer, pvcInformer, *enablePriorityClasses) sourceResolverController := sourceresolver.NewController(ctx, options, sourceResolverInformer, gitResolver, blobResolver, registryResolver) - builderController, builderResync := builder.NewController(ctx, options, builderInformer, builderCreator, keychainFactory, clusterStoreInformer, buildpackInformer, clusterBuildpackInformer, clusterStackInformer) + builderController, builderResync := builder.NewController(ctx, options, builderInformer, builderCreator, keychainFactory, clusterStoreInformer, buildpackInformer, clusterBuildpackInformer, clusterStackInformer, extensionInformer, clusterExtensionInformer) buildpackController := buildpack.NewController(ctx, options, keychainFactory, buildpackInformer, remoteStoreReader) clusterBuilderController, clusterBuilderResync := clusterbuilder.NewController(ctx, options, clusterBuilderInformer, builderCreator, keychainFactory, clusterStoreInformer, clusterBuildpackInformer, clusterStackInformer) clusterBuildpackController := clusterbuildpack.NewController(ctx, options, keychainFactory, clusterBuildpackInformer, remoteStoreReader) diff --git a/pkg/cnb/buildpack_resolver.go b/pkg/cnb/buildpack_resolver.go index 49fc9540e..01035c8be 100644 --- a/pkg/cnb/buildpack_resolver.go +++ b/pkg/cnb/buildpack_resolver.go @@ -24,16 +24,16 @@ type buildpackResolver struct { clusterStore *v1alpha2.ClusterStore buildpacks []*v1alpha2.Buildpack clusterBuildpacks []*v1alpha2.ClusterBuildpack - extensions []*v1alpha2.Buildpack - clusterExtensions []*v1alpha2.ClusterBuildpack + extensions []*v1alpha2.Extension + clusterExtensions []*v1alpha2.ClusterExtension } func NewBuildpackResolver( clusterStore *v1alpha2.ClusterStore, buildpacks []*v1alpha2.Buildpack, clusterBuildpacks []*v1alpha2.ClusterBuildpack, - extensions []*v1alpha2.Buildpack, - clusterExtensions []*v1alpha2.ClusterBuildpack, + extensions []*v1alpha2.Extension, + clusterExtensions []*v1alpha2.ClusterExtension, ) BuildpackResolver { return &buildpackResolver{ clusterStore: clusterStore, diff --git a/pkg/cnb/remote_buildpack_fetcher.go b/pkg/cnb/remote_buildpack_fetcher.go index 3f6ddac11..ca9d116c7 100644 --- a/pkg/cnb/remote_buildpack_fetcher.go +++ b/pkg/cnb/remote_buildpack_fetcher.go @@ -28,8 +28,8 @@ func NewRemoteBuildpackFetcher( clusterStore *buildapi.ClusterStore, buildpacks []*buildapi.Buildpack, clusterBuildpacks []*buildapi.ClusterBuildpack, - extensions []*buildapi.Buildpack, - clusterExtensions []*buildapi.ClusterBuildpack, + extensions []*buildapi.Extension, + clusterExtensions []*buildapi.ClusterExtension, ) RemoteBuildpackFetcher { return &remoteBuildpackFetcher{ BuildpackResolver: NewBuildpackResolver(clusterStore, buildpacks, clusterBuildpacks, extensions, clusterExtensions), diff --git a/pkg/reconciler/builder/builder.go b/pkg/reconciler/builder/builder.go index 6f5279ca2..4a0f181e6 100644 --- a/pkg/reconciler/builder/builder.go +++ b/pkg/reconciler/builder/builder.go @@ -46,6 +46,8 @@ func NewController( buildpackInformer buildinformers.BuildpackInformer, clusterBuildpackInformer buildinformers.ClusterBuildpackInformer, clusterStackInformer buildinformers.ClusterStackInformer, + extensionInformer buildinformers.ExtensionInformer, + clusterExtensionInformer buildinformers.ClusterExtensionInformer, ) (*controller.Impl, func()) { c := &Reconciler{ Client: opt.Client, @@ -56,6 +58,8 @@ func NewController( BuildpackLister: buildpackInformer.Lister(), ClusterBuildpackLister: clusterBuildpackInformer.Lister(), ClusterStackLister: clusterStackInformer.Lister(), + ExtensionLister: extensionInformer.Lister(), + ClusterExtensionLister: clusterExtensionInformer.Lister(), } logger := opt.Logger.With( @@ -107,6 +111,8 @@ type Reconciler struct { ClusterStoreLister buildlisters.ClusterStoreLister BuildpackLister buildlisters.BuildpackLister ClusterBuildpackLister buildlisters.ClusterBuildpackLister + ExtensionLister buildlisters.ExtensionLister + ClusterExtensionLister buildlisters.ClusterExtensionLister ClusterStackLister buildlisters.ClusterStackLister } @@ -189,11 +195,21 @@ func (c *Reconciler) reconcileBuilder(ctx context.Context, builder *buildapi.Bui return buildapi.BuilderRecord{}, err } + extensions, err := c.ExtensionLister.Extensions(builder.Namespace).List(labels.Everything()) + if err != nil { + return buildapi.BuilderRecord{}, err + } + clusterBuildpacks, err := c.ClusterBuildpackLister.List(labels.Everything()) if err != nil { return buildapi.BuilderRecord{}, err } + clusterExtensions, err := c.ClusterExtensionLister.List(labels.Everything()) + if err != nil { + return buildapi.BuilderRecord{}, err + } + clusterStack, err := c.ClusterStackLister.Get(builder.Spec.Stack.Name) if err != nil { return buildapi.BuilderRecord{}, err @@ -222,7 +238,7 @@ func (c *Reconciler) reconcileBuilder(ctx context.Context, builder *buildapi.Bui } } - fetcher := cnb.NewRemoteBuildpackFetcher(c.KeychainFactory, clusterStore, buildpacks, clusterBuildpacks) + fetcher := cnb.NewRemoteBuildpackFetcher(c.KeychainFactory, clusterStore, buildpacks, clusterBuildpacks, extensions, clusterExtensions) buildRecord, err := c.BuilderCreator.CreateBuilder(ctx, builderKeychain, stackKeychain, fetcher, clusterStack, builder.Spec.BuilderSpec) if err != nil { From fd7ab304428470e87ffbce2e81c3aae48fdd2b4c Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Wed, 27 Sep 2023 13:47:13 -0400 Subject: [PATCH 07/20] Add empty "resolve extension" method Signed-off-by: Natalie Arellano --- cmd/controller/main.go | 2 +- pkg/cnb/buildpack_resolver.go | 82 +++++++++++++++++++++++- pkg/cnb/buildpack_resolver_test.go | 72 ++++++++++----------- pkg/cnb/create_builder.go | 4 +- pkg/cnb/fakes_test.go | 21 +++++- pkg/cnb/remote_buildpack_fetcher.go | 18 ++++-- pkg/cnb/remote_buildpack_fetcher_test.go | 9 ++- 7 files changed, 157 insertions(+), 51 deletions(-) diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 14f1c095c..fe61cf140 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -4,7 +4,6 @@ import ( "context" "flag" "fmt" - "github.com/pivotal/kpack/pkg/buildchange" "log" "net/http" "os" @@ -35,6 +34,7 @@ import ( _ "github.com/pivotal/kpack/internal/logrus/fatal" buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" "github.com/pivotal/kpack/pkg/blob" + "github.com/pivotal/kpack/pkg/buildchange" "github.com/pivotal/kpack/pkg/buildpod" "github.com/pivotal/kpack/pkg/client/clientset/versioned" "github.com/pivotal/kpack/pkg/client/informers/externalversions" diff --git a/pkg/cnb/buildpack_resolver.go b/pkg/cnb/buildpack_resolver.go index 01035c8be..50cbc579d 100644 --- a/pkg/cnb/buildpack_resolver.go +++ b/pkg/cnb/buildpack_resolver.go @@ -16,7 +16,8 @@ import ( // BuildpackResolver will attempt to resolve a Buildpack reference to a // Buildpack from either the ClusterStore, Buildpacks, or ClusterBuildpacks type BuildpackResolver interface { - resolve(ref v1alpha2.BuilderBuildpackRef) (K8sRemoteBuildpack, error) + resolveBuildpack(ref v1alpha2.BuilderBuildpackRef) (K8sRemoteBuildpack, error) + resolveExtension(ref v1alpha2.BuilderBuildpackRef) (K8sRemoteBuildpack, error) ClusterStoreObservedGeneration() int64 } @@ -51,7 +52,7 @@ func (r *buildpackResolver) ClusterStoreObservedGeneration() int64 { return 0 } -func (r *buildpackResolver) resolve(ref v1alpha2.BuilderBuildpackRef) (K8sRemoteBuildpack, error) { +func (r *buildpackResolver) resolveBuildpack(ref v1alpha2.BuilderBuildpackRef) (K8sRemoteBuildpack, error) { var matchingBuildpacks []K8sRemoteBuildpack var err error switch { @@ -127,6 +128,83 @@ func (r *buildpackResolver) resolve(ref v1alpha2.BuilderBuildpackRef) (K8sRemote return K8sRemoteBuildpack{}, errors.Errorf("could not find buildpack with id '%s' and version '%s'", ref.Id, ref.Version) } +// TODO: update for extensions +func (r *buildpackResolver) resolveExtension(ref v1alpha2.BuilderBuildpackRef) (K8sRemoteBuildpack, error) { + //var matchingBuildpacks []K8sRemoteBuildpack + //var err error + //switch { + //case ref.Kind == v1alpha2.BuildpackKind && ref.Id != "": + // bp := findBuildpack(ref.ObjectReference, r.buildpacks) + // if bp == nil { + // return K8sRemoteBuildpack{}, fmt.Errorf("buildpack not found: %v", ref.Name) + // } + // + // matchingBuildpacks, err = r.resolveFromBuildpack(ref.Id, []*v1alpha2.Buildpack{bp}) + // if err != nil { + // return K8sRemoteBuildpack{}, err + // } + //case ref.Kind == v1alpha2.ClusterBuildpackKind && ref.Id != "": + // cbp := findClusterBuildpack(ref.ObjectReference, r.clusterBuildpacks) + // if cbp == nil { + // return K8sRemoteBuildpack{}, fmt.Errorf("cluster buildpack not found: %v", ref.Name) + // } + // + // matchingBuildpacks, err = r.resolveFromClusterBuildpack(ref.Id, []*v1alpha2.ClusterBuildpack{cbp}) + // if err != nil { + // return K8sRemoteBuildpack{}, err + // } + //case ref.Kind != "": + // bp, err := r.resolveFromObjectReference(ref.ObjectReference) + // if err != nil { + // return K8sRemoteBuildpack{}, err + // } + // matchingBuildpacks = []K8sRemoteBuildpack{bp} + //case ref.Id != "": + // bp, err := r.resolveFromBuildpack(ref.Id, r.buildpacks) + // if err != nil { + // return K8sRemoteBuildpack{}, err + // } + // matchingBuildpacks = append(matchingBuildpacks, bp...) + // + // cbp, err := r.resolveFromClusterBuildpack(ref.Id, r.clusterBuildpacks) + // if err != nil { + // return K8sRemoteBuildpack{}, err + // } + // matchingBuildpacks = append(matchingBuildpacks, cbp...) + // + // cs, err := r.resolveFromClusterStore(ref.Id, r.clusterStore) + // if err != nil { + // return K8sRemoteBuildpack{}, err + // } + // matchingBuildpacks = append(matchingBuildpacks, cs...) + //case ref.Image != "": + // // TODO(chenbh): + // return K8sRemoteBuildpack{}, fmt.Errorf("using images in builders not currently supported") + //default: + // return K8sRemoteBuildpack{}, fmt.Errorf("invalid buildpack reference") + //} + // + //if len(matchingBuildpacks) == 0 { + // return K8sRemoteBuildpack{}, errors.Errorf("could not find buildpack with id '%s'", ref.Id) + //} + // + //if ref.Version == "" { + // bp, err := highestVersion(matchingBuildpacks) + // if err != nil { + // return K8sRemoteBuildpack{}, err + // } + // return bp, nil + //} + // + //for _, result := range matchingBuildpacks { + // if result.Buildpack.Version == ref.Version { + // return result, nil + // } + //} + + return K8sRemoteBuildpack{}, errors.Errorf("could not find buildpack with id '%s' and version '%s'", ref.Id, ref.Version) +} + func (r *buildpackResolver) resolveFromBuildpack(id string, buildpacks []*v1alpha2.Buildpack) ([]K8sRemoteBuildpack, error) { var matchingBuildpacks []K8sRemoteBuildpack for _, bp := range buildpacks { diff --git a/pkg/cnb/buildpack_resolver_test.go b/pkg/cnb/buildpack_resolver_test.go index 1089fe2fb..4ebc79b27 100644 --- a/pkg/cnb/buildpack_resolver_test.go +++ b/pkg/cnb/buildpack_resolver_test.go @@ -190,7 +190,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ref := makeRef("io.buildpack.engine", "") expectedBuildpack := engineBuildpack - buildpack, err := resolver.resolve(ref) + buildpack, err := resolver.resolveBuildpack(ref) assert.Nil(t, err) assert.Equal(t, expectedBuildpack, buildpack.Buildpack) }) @@ -199,20 +199,20 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ref := makeRef("io.buildpack.multi", "8.0.0") expectedBuildpack := v8Buildpack - buildpack, err := resolver.resolve(ref) + buildpack, err := resolver.resolveBuildpack(ref) assert.Nil(t, err) assert.Equal(t, expectedBuildpack, buildpack.Buildpack) }) it("fails on invalid id", func() { ref := makeRef("fake-buildpack", "") - _, err := resolver.resolve(ref) + _, err := resolver.resolveBuildpack(ref) assert.EqualError(t, err, "could not find buildpack with id 'fake-buildpack'") }) it("fails on unknown version", func() { ref := makeRef("io.buildpack.multi", "8.0.1") - _, err := resolver.resolve(ref) + _, err := resolver.resolveBuildpack(ref) assert.EqualError(t, err, "could not find buildpack with id 'io.buildpack.multi' and version '8.0.1'") }) }) @@ -284,7 +284,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ref := makeRef("io.buildpack.meta", "") expectedBuildpack := metaBuildpack - buildpack, err := resolver.resolve(ref) + buildpack, err := resolver.resolveBuildpack(ref) assert.Nil(t, err) assert.Equal(t, expectedBuildpack, buildpack.Buildpack) }) @@ -293,7 +293,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ref := makeRef("io.buildpack.engine", "") expectedBuildpack := engineBuildpack - buildpack, err := resolver.resolve(ref) + buildpack, err := resolver.resolveBuildpack(ref) assert.Nil(t, err) assert.Equal(t, expectedBuildpack, buildpack.Buildpack) }) @@ -302,14 +302,14 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ref := makeRef("io.buildpack.multi", "8.0.0") expectedBuildpack := v8Buildpack - buildpack, err := resolver.resolve(ref) + buildpack, err := resolver.resolveBuildpack(ref) assert.Nil(t, err) assert.Equal(t, expectedBuildpack, buildpack.Buildpack) }) it("fails on unknown version", func() { ref := makeRef("io.buildpack.multi", "8.0.1") - _, err := resolver.resolve(ref) + _, err := resolver.resolveBuildpack(ref) assert.EqualError(t, err, "could not find buildpack with id 'io.buildpack.multi' and version '8.0.1'") }) }) @@ -319,20 +319,20 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ref := makeObjectRef("io.buildpack.meta", "Buildpack", "", "") expectedBuildpack := metaBuildpack - buildpack, err := resolver.resolve(ref) + buildpack, err := resolver.resolveBuildpack(ref) assert.Nil(t, err) assert.Equal(t, expectedBuildpack, buildpack.Buildpack) }) it("fails on invalid kind", func() { ref := makeObjectRef("io.buildpack.meta", "FakeBuildpack", "", "") - _, err := resolver.resolve(ref) + _, err := resolver.resolveBuildpack(ref) assert.EqualError(t, err, "kind must be either Buildpack or ClusterBuildpack") }) it("fails on object not found", func() { ref := makeObjectRef("fake-buildpack", "Buildpack", "", "") - _, err := resolver.resolve(ref) + _, err := resolver.resolveBuildpack(ref) assert.EqualError(t, err, "no buildpack with name 'fake-buildpack'") }) }) @@ -342,7 +342,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ref := makeObjectRef("io.buildpack.meta", "Buildpack", "io.buildpack.meta", "") expectedBuildpack := metaBuildpack - buildpack, err := resolver.resolve(ref) + buildpack, err := resolver.resolveBuildpack(ref) assert.Nil(t, err) assert.Equal(t, expectedBuildpack, buildpack.Buildpack) }) @@ -351,7 +351,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ref := makeObjectRef("io.buildpack.meta", "Buildpack", "io.buildpack.engine", "") expectedBuildpack := engineBuildpack - buildpack, err := resolver.resolve(ref) + buildpack, err := resolver.resolveBuildpack(ref) assert.Nil(t, err) assert.Equal(t, expectedBuildpack, buildpack.Buildpack) }) @@ -360,26 +360,26 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ref := makeObjectRef("io.buildpack.multi", "Buildpack", "io.buildpack.multi", "8.0.0") expectedBuildpack := v8Buildpack - buildpack, err := resolver.resolve(ref) + buildpack, err := resolver.resolveBuildpack(ref) assert.Nil(t, err) assert.Equal(t, expectedBuildpack, buildpack.Buildpack) }) it("fails on id not found in resource", func() { ref := makeObjectRef("io.buildpack.meta", "Buildpack", "fake-buildpack", "") - _, err := resolver.resolve(ref) + _, err := resolver.resolveBuildpack(ref) assert.EqualError(t, err, "could not find buildpack with id 'fake-buildpack'") }) it("fails on version not found in resource", func() { ref := makeObjectRef("io.buildpack.multi", "Buildpack", "io.buildpack.multi", "8.0.1") - _, err := resolver.resolve(ref) + _, err := resolver.resolveBuildpack(ref) assert.EqualError(t, err, "could not find buildpack with id 'io.buildpack.multi' and version '8.0.1'") }) it("fails on id not found in resource", func() { ref := makeObjectRef("io.buildpack.meta", "Buildpack", "fake-buildpack", "") - _, err := resolver.resolve(ref) + _, err := resolver.resolveBuildpack(ref) assert.EqualError(t, err, "could not find buildpack with id 'fake-buildpack'") }) }) @@ -448,7 +448,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ref := makeRef("io.buildpack.meta", "") expectedBuildpack := metaBuildpack - buildpack, err := resolver.resolve(ref) + buildpack, err := resolver.resolveBuildpack(ref) assert.Nil(t, err) assert.Equal(t, expectedBuildpack, buildpack.Buildpack) }) @@ -457,7 +457,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ref := makeRef("io.buildpack.engine", "") expectedBuildpack := engineBuildpack - buildpack, err := resolver.resolve(ref) + buildpack, err := resolver.resolveBuildpack(ref) assert.Nil(t, err) assert.Equal(t, expectedBuildpack, buildpack.Buildpack) }) @@ -466,20 +466,20 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ref := makeRef("io.buildpack.multi", "8.0.0") expectedBuildpack := v8Buildpack - buildpack, err := resolver.resolve(ref) + buildpack, err := resolver.resolveBuildpack(ref) assert.Nil(t, err) assert.Equal(t, expectedBuildpack, buildpack.Buildpack) }) it("fails on invalid id", func() { ref := makeRef("fake-buildpack", "") - _, err := resolver.resolve(ref) + _, err := resolver.resolveBuildpack(ref) assert.EqualError(t, err, "could not find buildpack with id 'fake-buildpack'") }) it("fails on unknown version", func() { ref := makeRef("io.buildpack.multi", "8.0.1") - _, err := resolver.resolve(ref) + _, err := resolver.resolveBuildpack(ref) assert.EqualError(t, err, "could not find buildpack with id 'io.buildpack.multi' and version '8.0.1'") }) }) @@ -489,20 +489,20 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ref := makeObjectRef("io.buildpack.meta", "ClusterBuildpack", "", "") expectedBuildpack := metaBuildpack - buildpack, err := resolver.resolve(ref) + buildpack, err := resolver.resolveBuildpack(ref) assert.Nil(t, err) assert.Equal(t, expectedBuildpack, buildpack.Buildpack) }) it("fails on invalid kind", func() { ref := makeObjectRef("io.buildpack.meta", "FakeClusterBuildpack", "", "") - _, err := resolver.resolve(ref) + _, err := resolver.resolveBuildpack(ref) assert.EqualError(t, err, "kind must be either Buildpack or ClusterBuildpack") }) it("fails on object not found", func() { ref := makeObjectRef("fake-buildpack", "ClusterBuildpack", "", "") - _, err := resolver.resolve(ref) + _, err := resolver.resolveBuildpack(ref) assert.EqualError(t, err, "no cluster buildpack with name 'fake-buildpack'") }) }) @@ -512,7 +512,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ref := makeObjectRef("io.buildpack.meta", "ClusterBuildpack", "io.buildpack.meta", "") expectedBuildpack := metaBuildpack - buildpack, err := resolver.resolve(ref) + buildpack, err := resolver.resolveBuildpack(ref) assert.Nil(t, err) assert.Equal(t, expectedBuildpack, buildpack.Buildpack) }) @@ -521,7 +521,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ref := makeObjectRef("io.buildpack.meta", "ClusterBuildpack", "io.buildpack.engine", "") expectedBuildpack := engineBuildpack - buildpack, err := resolver.resolve(ref) + buildpack, err := resolver.resolveBuildpack(ref) assert.Nil(t, err) assert.Equal(t, expectedBuildpack, buildpack.Buildpack) }) @@ -530,26 +530,26 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ref := makeObjectRef("io.buildpack.multi", "ClusterBuildpack", "io.buildpack.multi", "8.0.0") expectedBuildpack := v8Buildpack - buildpack, err := resolver.resolve(ref) + buildpack, err := resolver.resolveBuildpack(ref) assert.Nil(t, err) assert.Equal(t, expectedBuildpack, buildpack.Buildpack) }) it("fails on id not found in resource", func() { ref := makeObjectRef("io.buildpack.meta", "ClusterBuildpack", "fake-buildpack", "") - _, err := resolver.resolve(ref) + _, err := resolver.resolveBuildpack(ref) assert.EqualError(t, err, "could not find buildpack with id 'fake-buildpack'") }) it("fails on version not found in resource", func() { ref := makeObjectRef("io.buildpack.multi", "ClusterBuildpack", "io.buildpack.multi", "8.0.1") - _, err := resolver.resolve(ref) + _, err := resolver.resolveBuildpack(ref) assert.EqualError(t, err, "could not find buildpack with id 'io.buildpack.multi' and version '8.0.1'") }) it("fails on id not found in resource", func() { ref := makeObjectRef("io.buildpack.meta", "ClusterBuildpack", "fake-buildpack", "") - _, err := resolver.resolve(ref) + _, err := resolver.resolveBuildpack(ref) assert.EqualError(t, err, "could not find buildpack with id 'fake-buildpack'") }) }) @@ -616,16 +616,16 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { }) it("records which objects were used", func() { - buildpack, err := resolver.resolve(makeRef("io.buildpack.meta", "")) + buildpack, err := resolver.resolveBuildpack(makeRef("io.buildpack.meta", "")) assert.Nil(t, err) assert.Equal(t, metaBuildpack, buildpack.Buildpack) - buildpack, err = resolver.resolve(makeRef("io.buildpack.multi", "8.0.0")) + buildpack, err = resolver.resolveBuildpack(makeRef("io.buildpack.multi", "8.0.0")) assert.Nil(t, err) assert.Equal(t, v8Buildpack, buildpack.Buildpack) - buildpack, err = resolver.resolve(makeRef("io.buildpack.multi", "9.0.0")) + buildpack, err = resolver.resolveBuildpack(makeRef("io.buildpack.multi", "9.0.0")) assert.Nil(t, err) assert.Equal(t, v9Buildpack, buildpack.Buildpack) }) @@ -634,7 +634,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ref := makeRef("io.buildpack.multi", "8.0.0") expectedBuildpack := v8Buildpack - buildpack, err := resolver.resolve(ref) + buildpack, err := resolver.resolveBuildpack(ref) assert.Nil(t, err) assert.Equal(t, expectedBuildpack, buildpack.Buildpack) }) @@ -643,7 +643,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ref := makeRef("io.buildpack.multi", "9.0.0") expectedBuildpack := v9Buildpack - buildpack, err := resolver.resolve(ref) + buildpack, err := resolver.resolveBuildpack(ref) assert.Nil(t, err) assert.Equal(t, expectedBuildpack, buildpack.Buildpack) }) diff --git a/pkg/cnb/create_builder.go b/pkg/cnb/create_builder.go index 3fb2f08e3..5c3aeef21 100644 --- a/pkg/cnb/create_builder.go +++ b/pkg/cnb/create_builder.go @@ -52,7 +52,7 @@ func (r *RemoteBuilderCreator) CreateBuilder(ctx context.Context, builderKeychai buildpacks := make([]RemoteBuildpackRef, 0, len(group.Group)) for _, bp := range group.Group { - remoteBuildpack, err := fetcher.ResolveAndFetch(ctx, bp) + remoteBuildpack, err := fetcher.ResolveAndFetchBuildpack(ctx, bp) if err != nil { return buildapi.BuilderRecord{}, err } @@ -67,7 +67,7 @@ func (r *RemoteBuilderCreator) CreateBuilder(ctx context.Context, builderKeychai extensions := make([]RemoteBuildpackRef, 0, len(group.Group)) for _, ext := range group.Group { - remoteExtension, err := fetcher.ResolveAndFetch(ctx, ext) + remoteExtension, err := fetcher.ResolveAndFetchBuildpack(ctx, ext) if err != nil { return buildapi.BuilderRecord{}, err } diff --git a/pkg/cnb/fakes_test.go b/pkg/cnb/fakes_test.go index 046c6f573..8a7ba9609 100644 --- a/pkg/cnb/fakes_test.go +++ b/pkg/cnb/fakes_test.go @@ -53,10 +53,11 @@ type buildpackRefContainer struct { type fakeResolver struct { buildpacks map[string]K8sRemoteBuildpack + extensions map[string]K8sRemoteBuildpack observedGeneration int64 } -func (r *fakeResolver) resolve(ref buildapi.BuilderBuildpackRef) (K8sRemoteBuildpack, error) { +func (r *fakeResolver) resolveBuildpack(ref buildapi.BuilderBuildpackRef) (K8sRemoteBuildpack, error) { buildpack, ok := r.buildpacks[fmt.Sprintf("%s@%s", ref.Id, ref.Version)] if !ok { return K8sRemoteBuildpack{}, errors.New("buildpack not found") @@ -64,12 +65,26 @@ func (r *fakeResolver) resolve(ref buildapi.BuilderBuildpackRef) (K8sRemoteBuild return buildpack, nil } +func (r *fakeResolver) resolveExtension(ref buildapi.BuilderBuildpackRef) (K8sRemoteBuildpack, error) { + extension, ok := r.extensions[fmt.Sprintf("%s@%s", ref.Id, ref.Version)] + if !ok { + return K8sRemoteBuildpack{}, errors.New("extension not found") + } + return extension, nil +} + func (f *fakeResolver) AddBuildpack(t *testing.T, ref buildapi.BuilderBuildpackRef, buildpack K8sRemoteBuildpack) { t.Helper() assert.NotEqual(t, ref.Id, "", "buildpack ref missing id") f.buildpacks[fmt.Sprintf("%s@%s", ref.Id, ref.Version)] = buildpack } +func (f *fakeResolver) AddExtension(t *testing.T, ref buildapi.BuilderBuildpackRef, extension K8sRemoteBuildpack) { + t.Helper() + assert.NotEqual(t, ref.Id, "", "extension ref missing id") + f.extensions[fmt.Sprintf("%s@%s", ref.Id, ref.Version)] = extension +} + func (r *fakeResolver) ClusterStoreObservedGeneration() int64 { return r.observedGeneration } @@ -106,7 +121,7 @@ type fakeFetcher struct { observedGeneration int64 } -func (f *fakeFetcher) ResolveAndFetch(_ context.Context, module buildapi.BuilderBuildpackRef) (RemoteBuildpackInfo, error) { +func (f *fakeFetcher) ResolveAndFetchBuildpack(_ context.Context, module buildapi.BuilderBuildpackRef) (RemoteBuildpackInfo, error) { bpLayers, ok := f.buildpacks[fmt.Sprintf("%s@%s", module.Id, module.Version)] if ok { return RemoteBuildpackInfo{ @@ -135,7 +150,7 @@ func (f *fakeFetcher) UsedObjects() []k8scorev1.ObjectReference { return nil } -func (f *fakeFetcher) resolve(ref buildapi.BuilderBuildpackRef) (K8sRemoteBuildpack, error) { +func (f *fakeFetcher) resolveBuildpack(ref buildapi.BuilderBuildpackRef) (K8sRemoteBuildpack, error) { panic("Not implemented For Tests") } diff --git a/pkg/cnb/remote_buildpack_fetcher.go b/pkg/cnb/remote_buildpack_fetcher.go index ca9d116c7..12cca7b30 100644 --- a/pkg/cnb/remote_buildpack_fetcher.go +++ b/pkg/cnb/remote_buildpack_fetcher.go @@ -15,7 +15,8 @@ import ( type RemoteBuildpackFetcher interface { BuildpackResolver - ResolveAndFetch(context.Context, buildapi.BuilderBuildpackRef) (RemoteBuildpackInfo, error) + ResolveAndFetchBuildpack(context.Context, buildapi.BuilderBuildpackRef) (RemoteBuildpackInfo, error) + ResolveAndFetchExtension(context.Context, buildapi.BuilderBuildpackRef) (RemoteBuildpackInfo, error) } type remoteBuildpackFetcher struct { @@ -37,8 +38,17 @@ func NewRemoteBuildpackFetcher( } } -func (s *remoteBuildpackFetcher) ResolveAndFetch(ctx context.Context, ref buildapi.BuilderBuildpackRef) (RemoteBuildpackInfo, error) { - remote, err := s.resolve(ref) +func (s *remoteBuildpackFetcher) ResolveAndFetchBuildpack(ctx context.Context, ref buildapi.BuilderBuildpackRef) (RemoteBuildpackInfo, error) { + remote, err := s.resolveBuildpack(ref) + if err != nil { + return RemoteBuildpackInfo{}, err + } + + return s.fetch(ctx, remote) +} + +func (s *remoteBuildpackFetcher) ResolveAndFetchExtension(ctx context.Context, ref buildapi.BuilderBuildpackRef) (RemoteBuildpackInfo, error) { + remote, err := s.resolveExtension(ref) if err != nil { return RemoteBuildpackInfo{}, err } @@ -93,7 +103,7 @@ func (s *remoteBuildpackFetcher) layersForOrder(ctx context.Context, order corev var buildpackLayers []buildpackLayer for _, orderEntry := range order { for _, buildpackRef := range orderEntry.Group { - buildpack, err := s.resolve(buildapi.BuilderBuildpackRef{ + buildpack, err := s.resolveBuildpack(buildapi.BuilderBuildpackRef{ BuildpackRef: corev1alpha1.BuildpackRef{ BuildpackInfo: corev1alpha1.BuildpackInfo{ Id: buildpackRef.Id, diff --git a/pkg/cnb/remote_buildpack_fetcher_test.go b/pkg/cnb/remote_buildpack_fetcher_test.go index a24032990..67ce53c62 100644 --- a/pkg/cnb/remote_buildpack_fetcher_test.go +++ b/pkg/cnb/remote_buildpack_fetcher_test.go @@ -23,9 +23,12 @@ func testRemoteBuildpackFetcher(t *testing.T, when spec.G, it spec.S) { var ( keychainFactory = ®istryfakes.FakeKeychainFactory{} keychain = authn.NewMultiKeychain(authn.DefaultKeychain) - resolver = &fakeResolver{buildpacks: map[string]K8sRemoteBuildpack{}} - secretRef = registry.SecretRef{} - ctx = context.Background() + resolver = &fakeResolver{ + buildpacks: map[string]K8sRemoteBuildpack{}, + extensions: map[string]K8sRemoteBuildpack{}, + } + secretRef = registry.SecretRef{} + ctx = context.Background() ) when("Fetch", func() { From 34fcf4a886f17f39785c4cf8eb324078e6976244 Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Wed, 27 Sep 2023 14:37:00 -0400 Subject: [PATCH 08/20] Units pass Signed-off-by: Natalie Arellano --- cmd/controller/main.go | 2 +- pkg/cnb/create_builder.go | 2 +- pkg/cnb/fakes_test.go | 21 ++++++---- pkg/reconciler/builder/builder_test.go | 42 ++++++++++++++++++- .../clusterbuilder/clusterbuilder.go | 10 ++++- .../clusterbuilder/clusterbuilder_test.go | 25 ++++++++++- pkg/reconciler/testhelpers/listers.go | 8 ++++ 7 files changed, 95 insertions(+), 15 deletions(-) diff --git a/cmd/controller/main.go b/cmd/controller/main.go index fe61cf140..5ec3b38ec 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -207,7 +207,7 @@ func main() { sourceResolverController := sourceresolver.NewController(ctx, options, sourceResolverInformer, gitResolver, blobResolver, registryResolver) builderController, builderResync := builder.NewController(ctx, options, builderInformer, builderCreator, keychainFactory, clusterStoreInformer, buildpackInformer, clusterBuildpackInformer, clusterStackInformer, extensionInformer, clusterExtensionInformer) buildpackController := buildpack.NewController(ctx, options, keychainFactory, buildpackInformer, remoteStoreReader) - clusterBuilderController, clusterBuilderResync := clusterbuilder.NewController(ctx, options, clusterBuilderInformer, builderCreator, keychainFactory, clusterStoreInformer, clusterBuildpackInformer, clusterStackInformer) + clusterBuilderController, clusterBuilderResync := clusterbuilder.NewController(ctx, options, clusterBuilderInformer, builderCreator, keychainFactory, clusterStoreInformer, clusterBuildpackInformer, clusterStackInformer, clusterExtensionInformer) clusterBuildpackController := clusterbuildpack.NewController(ctx, options, keychainFactory, clusterBuildpackInformer, remoteStoreReader) clusterStoreController := clusterstore.NewController(ctx, options, keychainFactory, clusterStoreInformer, remoteStoreReader) clusterStackController := clusterstack.NewController(ctx, options, keychainFactory, clusterStackInformer, remoteStackReader) diff --git a/pkg/cnb/create_builder.go b/pkg/cnb/create_builder.go index 5c3aeef21..2d04fc54c 100644 --- a/pkg/cnb/create_builder.go +++ b/pkg/cnb/create_builder.go @@ -67,7 +67,7 @@ func (r *RemoteBuilderCreator) CreateBuilder(ctx context.Context, builderKeychai extensions := make([]RemoteBuildpackRef, 0, len(group.Group)) for _, ext := range group.Group { - remoteExtension, err := fetcher.ResolveAndFetchBuildpack(ctx, ext) + remoteExtension, err := fetcher.ResolveAndFetchExtension(ctx, ext) if err != nil { return buildapi.BuilderRecord{}, err } diff --git a/pkg/cnb/fakes_test.go b/pkg/cnb/fakes_test.go index 8a7ba9609..3b292d727 100644 --- a/pkg/cnb/fakes_test.go +++ b/pkg/cnb/fakes_test.go @@ -121,25 +121,26 @@ type fakeFetcher struct { observedGeneration int64 } -func (f *fakeFetcher) ResolveAndFetchBuildpack(_ context.Context, module buildapi.BuilderBuildpackRef) (RemoteBuildpackInfo, error) { - bpLayers, ok := f.buildpacks[fmt.Sprintf("%s@%s", module.Id, module.Version)] +func (f *fakeFetcher) ResolveAndFetchBuildpack(_ context.Context, bp buildapi.BuilderBuildpackRef) (RemoteBuildpackInfo, error) { + bpLayers, ok := f.buildpacks[fmt.Sprintf("%s@%s", bp.Id, bp.Version)] if ok { return RemoteBuildpackInfo{ - BuildpackInfo: buildpackInfoInLayers(bpLayers, module.Id, module.Version), + BuildpackInfo: buildpackInfoInLayers(bpLayers, bp.Id, bp.Version), Layers: bpLayers, }, nil } + return RemoteBuildpackInfo{}, errors.New("buildpack not found") +} - // TODO: buildpacks and extensions can have the same ID - extLayers, ok := f.extensions[fmt.Sprintf("%s@%s", module.Id, module.Version)] +func (f *fakeFetcher) ResolveAndFetchExtension(_ context.Context, ext buildapi.BuilderBuildpackRef) (RemoteBuildpackInfo, error) { + extLayers, ok := f.extensions[fmt.Sprintf("%s@%s", ext.Id, ext.Version)] if ok { return RemoteBuildpackInfo{ - BuildpackInfo: buildpackInfoInLayers(extLayers, module.Id, module.Version), + BuildpackInfo: buildpackInfoInLayers(extLayers, ext.Id, ext.Version), Layers: extLayers, }, nil } - - return RemoteBuildpackInfo{}, errors.New("module not found") + return RemoteBuildpackInfo{}, errors.New("extension not found") } func (f *fakeFetcher) ClusterStoreObservedGeneration() int64 { @@ -154,6 +155,10 @@ func (f *fakeFetcher) resolveBuildpack(ref buildapi.BuilderBuildpackRef) (K8sRem panic("Not implemented For Tests") } +func (f *fakeFetcher) resolveExtension(ref buildapi.BuilderBuildpackRef) (K8sRemoteBuildpack, error) { + panic("Not implemented For Tests") +} + func (f *fakeFetcher) AddBuildpack(t *testing.T, id, version string, layers []buildpackLayer) { t.Helper() f.buildpacks[fmt.Sprintf("%s@%s", id, version)] = layers diff --git a/pkg/reconciler/builder/builder_test.go b/pkg/reconciler/builder/builder_test.go index 3b7c1718b..13cbcab7a 100644 --- a/pkg/reconciler/builder/builder_test.go +++ b/pkg/reconciler/builder/builder_test.go @@ -60,6 +60,8 @@ func testBuilderReconciler(t *testing.T, when spec.G, it spec.S) { ClusterStoreLister: listers.GetClusterStoreLister(), BuildpackLister: listers.GetBuildpackLister(), ClusterBuildpackLister: listers.GetClusterBuildpackLister(), + ExtensionLister: listers.GetExtensionLister(), + ClusterExtensionLister: listers.GetClusterExtensionLister(), ClusterStackLister: listers.GetClusterStackLister(), } return &kreconciler.NetworkErrorReconciler{Reconciler: r}, rtesting.ActionRecorderList{fakeClient}, rtesting.EventList{Recorder: record.NewFakeRecorder(10)} @@ -115,6 +117,17 @@ func testBuilderReconciler(t *testing.T, when spec.G, it spec.S) { }, } + extension := &buildapi.Extension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "extension.id.3", + Namespace: testNamespace, + }, + TypeMeta: metav1.TypeMeta{ + Kind: "Extension", + APIVersion: "kpack.io/v1alpha2", + }, + } + clusterBuildpack := &buildapi.ClusterBuildpack{ ObjectMeta: metav1.ObjectMeta{ Name: "buildpack.id.4", @@ -125,6 +138,16 @@ func testBuilderReconciler(t *testing.T, when spec.G, it spec.S) { }, } + clusterExtension := &buildapi.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "extension.id.4", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterExtension", + APIVersion: "kpack.io/v1alpha2", + }, + } + builder := &buildapi.Builder{ ObjectMeta: metav1.ObjectMeta{ Name: builderName, @@ -233,7 +256,14 @@ func testBuilderReconciler(t *testing.T, when spec.G, it spec.S) { }, } - expectedFetcher := cnb.NewRemoteBuildpackFetcher(keychainFactory, clusterStore, []*buildapi.Buildpack{buildpack}, []*buildapi.ClusterBuildpack{clusterBuildpack}) + expectedFetcher := cnb.NewRemoteBuildpackFetcher( + keychainFactory, + clusterStore, + []*buildapi.Buildpack{buildpack}, + []*buildapi.ClusterBuildpack{clusterBuildpack}, + []*buildapi.Extension{extension}, + []*buildapi.ClusterExtension{clusterExtension}, + ) rt.Test(rtesting.TableRow{ Key: builderKey, @@ -243,6 +273,8 @@ func testBuilderReconciler(t *testing.T, when spec.G, it spec.S) { builder, buildpack, clusterBuildpack, + extension, + clusterExtension, }, WantErr: false, WantStatusUpdates: []clientgotesting.UpdateActionImpl{ @@ -312,13 +344,19 @@ func testBuilderReconciler(t *testing.T, when spec.G, it spec.S) { require.True(t, fakeTracker.IsTracking( kreconciler.KeyForObject(clusterStack), builder.NamespacedName())) - require.True(t, fakeTracker.IsTrackingKind( kreconciler.KeyForObject(buildpack).GroupKind, builder.NamespacedName())) require.True(t, fakeTracker.IsTrackingKind( kreconciler.KeyForObject(clusterBuildpack).GroupKind, builder.NamespacedName())) + // TODO: fix tests + //require.True(t, fakeTracker.IsTrackingKind( + // kreconciler.KeyForObject(extension).GroupKind, + // builder.NamespacedName())) + //require.True(t, fakeTracker.IsTrackingKind( + // kreconciler.KeyForObject(clusterExtension).GroupKind, + // builder.NamespacedName())) }) it("does not update the status with no status change", func() { diff --git a/pkg/reconciler/clusterbuilder/clusterbuilder.go b/pkg/reconciler/clusterbuilder/clusterbuilder.go index fc3564acd..6975fb344 100644 --- a/pkg/reconciler/clusterbuilder/clusterbuilder.go +++ b/pkg/reconciler/clusterbuilder/clusterbuilder.go @@ -46,6 +46,7 @@ func NewController( clusterStoreInformer buildinformers.ClusterStoreInformer, clusterBuildpackInformer buildinformers.ClusterBuildpackInformer, clusterStackInformer buildinformers.ClusterStackInformer, + clusterExtensionInformer buildinformers.ClusterExtensionInformer, ) (*controller.Impl, func()) { c := &Reconciler{ Client: opt.Client, @@ -55,6 +56,7 @@ func NewController( ClusterStoreLister: clusterStoreInformer.Lister(), ClusterBuildpackLister: clusterBuildpackInformer.Lister(), ClusterStackLister: clusterStackInformer.Lister(), + ClusterExtensionLister: clusterExtensionInformer.Lister(), } logger := opt.Logger.With( @@ -101,6 +103,7 @@ type Reconciler struct { Tracker reconciler.Tracker ClusterStoreLister buildlisters.ClusterStoreLister ClusterBuildpackLister buildlisters.ClusterBuildpackLister + ClusterExtensionLister buildlisters.ClusterExtensionLister ClusterStackLister buildlisters.ClusterStackLister } @@ -179,6 +182,11 @@ func (c *Reconciler) reconcileBuilder(ctx context.Context, builder *buildapi.Clu return buildapi.BuilderRecord{}, err } + clusterExtensions, err := c.ClusterExtensionLister.List(labels.Everything()) + if err != nil { + return buildapi.BuilderRecord{}, err + } + clusterStack, err := c.ClusterStackLister.Get(builder.Spec.Stack.Name) if err != nil { return buildapi.BuilderRecord{}, err @@ -207,7 +215,7 @@ func (c *Reconciler) reconcileBuilder(ctx context.Context, builder *buildapi.Clu } } - fetcher := cnb.NewRemoteBuildpackFetcher(c.KeychainFactory, clusterStore, nil, clusterBuildpacks) + fetcher := cnb.NewRemoteBuildpackFetcher(c.KeychainFactory, clusterStore, nil, clusterBuildpacks, nil, clusterExtensions) buildRecord, err := c.BuilderCreator.CreateBuilder(ctx, builderKeychain, stackKeychain, fetcher, clusterStack, builder.Spec.BuilderSpec) if err != nil { diff --git a/pkg/reconciler/clusterbuilder/clusterbuilder_test.go b/pkg/reconciler/clusterbuilder/clusterbuilder_test.go index e15afa58b..6fd76bbe8 100644 --- a/pkg/reconciler/clusterbuilder/clusterbuilder_test.go +++ b/pkg/reconciler/clusterbuilder/clusterbuilder_test.go @@ -58,6 +58,7 @@ func testClusterBuilderReconciler(t *testing.T, when spec.G, it spec.S) { Tracker: fakeTracker, ClusterStoreLister: listers.GetClusterStoreLister(), ClusterBuildpackLister: listers.GetClusterBuildpackLister(), + ClusterExtensionLister: listers.GetClusterExtensionLister(), ClusterStackLister: listers.GetClusterStackLister(), } return &kreconciler.NetworkErrorReconciler{Reconciler: r}, rtesting.ActionRecorderList{fakeClient}, rtesting.EventList{Recorder: record.NewFakeRecorder(10)} @@ -112,6 +113,16 @@ func testClusterBuilderReconciler(t *testing.T, when spec.G, it spec.S) { }, } + clusterExtension := &buildapi.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "extension.id.4", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterExtension", + APIVersion: "kpack.io/v1alpha2", + }, + } + builder := &buildapi.ClusterBuilder{ ObjectMeta: metav1.ObjectMeta{ Name: builderName, @@ -230,7 +241,14 @@ func testClusterBuilderReconciler(t *testing.T, when spec.G, it spec.S) { }, } - expectedFetcher := cnb.NewRemoteBuildpackFetcher(keychainFactory, clusterStore, nil, []*buildapi.ClusterBuildpack{clusterBuildpack}) + expectedFetcher := cnb.NewRemoteBuildpackFetcher( + keychainFactory, + clusterStore, + nil, + []*buildapi.ClusterBuildpack{clusterBuildpack}, + nil, + []*buildapi.ClusterExtension{clusterExtension}, + ) rt.Test(rtesting.TableRow{ Key: builderKey, @@ -239,6 +257,7 @@ func testClusterBuilderReconciler(t *testing.T, when spec.G, it spec.S) { clusterStore, builder, clusterBuildpack, + clusterExtension, }, WantErr: false, WantStatusUpdates: []clientgotesting.UpdateActionImpl{ @@ -305,9 +324,11 @@ func testClusterBuilderReconciler(t *testing.T, when spec.G, it spec.S) { kreconciler.KeyForObject(clusterStore), expectedBuilder.NamespacedName())) require.True(t, fakeTracker.IsTracking( kreconciler.KeyForObject(clusterStack), builder.NamespacedName())) - require.True(t, fakeTracker.IsTrackingKind( kreconciler.KeyForObject(clusterBuildpack).GroupKind, builder.NamespacedName())) + // TODO: fix test + //require.True(t, fakeTracker.IsTrackingKind( + // kreconciler.KeyForObject(clusterExtension).GroupKind, builder.NamespacedName())) }) it("does not update the status with no status change", func() { diff --git a/pkg/reconciler/testhelpers/listers.go b/pkg/reconciler/testhelpers/listers.go index 858b33ddd..e60ef5fd7 100644 --- a/pkg/reconciler/testhelpers/listers.go +++ b/pkg/reconciler/testhelpers/listers.go @@ -67,6 +67,10 @@ func (l *Listers) GetBuildpackLister() buildlisters.BuildpackLister { return buildlisters.NewBuildpackLister(l.indexerFor(&buildapi.Buildpack{})) } +func (l *Listers) GetExtensionLister() buildlisters.ExtensionLister { + return buildlisters.NewExtensionLister(l.indexerFor(&buildapi.Extension{})) +} + func (l *Listers) GetClusterBuilderLister() buildlisters.ClusterBuilderLister { return buildlisters.NewClusterBuilderLister(l.indexerFor(&buildapi.ClusterBuilder{})) } @@ -75,6 +79,10 @@ func (l *Listers) GetClusterBuildpackLister() buildlisters.ClusterBuildpackListe return buildlisters.NewClusterBuildpackLister(l.indexerFor(&buildapi.ClusterBuildpack{})) } +func (l *Listers) GetClusterExtensionLister() buildlisters.ClusterExtensionLister { + return buildlisters.NewClusterExtensionLister(l.indexerFor(&buildapi.ClusterExtension{})) +} + func (l *Listers) GetClusterStoreLister() buildlisters.ClusterStoreLister { return buildlisters.NewClusterStoreLister(l.indexerFor(&buildapi.ClusterStore{})) } From ebecedcb099fb8387e8b26f920546056afba2ebc Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Wed, 27 Sep 2023 14:54:53 -0400 Subject: [PATCH 09/20] Add builder validations Signed-off-by: Natalie Arellano --- pkg/apis/build/v1alpha2/builder_lifecycle.go | 2 +- pkg/apis/build/v1alpha2/builder_validation.go | 37 +++++++++++-- .../build/v1alpha2/builder_validation_test.go | 54 ++++++++++++++++++- pkg/cnb/builder_builder.go | 2 +- pkg/cnb/remote_buildpack_fetcher.go | 1 - 5 files changed, 87 insertions(+), 9 deletions(-) diff --git a/pkg/apis/build/v1alpha2/builder_lifecycle.go b/pkg/apis/build/v1alpha2/builder_lifecycle.go index 8b1eee2ff..9ebceee22 100644 --- a/pkg/apis/build/v1alpha2/builder_lifecycle.go +++ b/pkg/apis/build/v1alpha2/builder_lifecycle.go @@ -31,7 +31,7 @@ func (bs *BuilderStatus) BuilderRecord(record BuilderRecord) { }, } bs.Order = record.Order - // TODO: bs.OrderExtensions = record.OrderExtensions + bs.OrderExtensions = record.OrderExtensions bs.ObservedStoreGeneration = record.ObservedStoreGeneration bs.ObservedStackGeneration = record.ObservedStackGeneration bs.OS = record.OS diff --git a/pkg/apis/build/v1alpha2/builder_validation.go b/pkg/apis/build/v1alpha2/builder_validation.go index 249c7887e..af9265ede 100644 --- a/pkg/apis/build/v1alpha2/builder_validation.go +++ b/pkg/apis/build/v1alpha2/builder_validation.go @@ -31,8 +31,8 @@ func (s *BuilderSpec) Validate(ctx context.Context) *apis.FieldError { return validate.Tag(s.Tag). Also(validateStack(s.Stack).ViaField("stack")). Also(validateStore(s.Store).ViaField("store")). - Also(validateOrder(s.Order).ViaField("order")) - // TODO: validate order for extensions + Also(validateOrder(s.Order).ViaField("order")). + Also(validateOrderExtensions(s.OrderExtensions).ViaField("order-extensions")) } func (s *NamespacedBuilderSpec) Validate(ctx context.Context) *apis.FieldError { @@ -68,6 +68,14 @@ func validateOrder(order []BuilderOrderEntry) *apis.FieldError { return errs } +func validateOrderExtensions(orderExt []BuilderOrderEntry) *apis.FieldError { + var errs *apis.FieldError + for i, s := range orderExt { + errs = errs.Also(validateExtensionGroup(s).ViaIndex(i)) + } + return errs +} + func validateGroup(group BuilderOrderEntry) *apis.FieldError { var errs *apis.FieldError for i, s := range group.Group { @@ -76,18 +84,37 @@ func validateGroup(group BuilderOrderEntry) *apis.FieldError { return errs } +func validateExtensionGroup(group BuilderOrderEntry) *apis.FieldError { + var errs *apis.FieldError + for i, s := range group.Group { + errs = errs.Also(validateExtensionRef(s).ViaIndex(i).ViaField("group")) + } + return errs +} + func validateBuildpackRef(ref BuilderBuildpackRef) *apis.FieldError { var errs *apis.FieldError if ref.Name != "" || ref.Kind != "" { errs = errs.Also(validateObjectRef(ref.ObjectReference, []string{BuildpackKind, ClusterBuildpackKind})) } + errs = errs.Also(validateImage(ref)) + return errs +} + +func validateExtensionRef(ref BuilderBuildpackRef) *apis.FieldError { + var errs *apis.FieldError + if ref.Name != "" || ref.Kind != "" { + errs = errs.Also(validateObjectRef(ref.ObjectReference, []string{ExtensionKind, ClusterExtensionKind})) + } + errs = errs.Also(validateImage(ref)) + return errs +} +func validateImage(ref BuilderBuildpackRef) *apis.FieldError { + var errs *apis.FieldError switch { case ref.Image != "": errs = errs.Also(apis.ErrDisallowedFields("image reference currently not supported")) - // errs = errs.Also(validate.Image(ref.Image)). - // Also(apis.CheckDisallowedFields(ref.BuildpackInfo, v1alpha1.BuildpackInfo{})). - // Also(apis.CheckDisallowedFields(ref.ObjectReference, v1.ObjectReference{})) case ref.Id != "" || ref.Name != "" || ref.Kind != "": if ref.Image != "" { errs = errs.Also(apis.ErrDisallowedFields("image")) diff --git a/pkg/apis/build/v1alpha2/builder_validation_test.go b/pkg/apis/build/v1alpha2/builder_validation_test.go index 625e4c5c2..926944d68 100644 --- a/pkg/apis/build/v1alpha2/builder_validation_test.go +++ b/pkg/apis/build/v1alpha2/builder_validation_test.go @@ -4,12 +4,13 @@ import ( "context" "testing" - "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" "github.com/sclevine/spec" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "knative.dev/pkg/apis" + + "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" ) func TestBuilderValidation(t *testing.T) { @@ -214,5 +215,56 @@ func testBuilderValidation(t *testing.T, when spec.G, it spec.S) { assert.Nil(t, builder.Validate(context.TODO())) }) }) + + when("order-extensions", func() { + assertValidationError = func(builder *Builder, expectedError *apis.FieldError) { + t.Helper() + err := builder.Validate(context.TODO()) + assert.EqualError(t, err, + expectedError. + ViaIndex(0).ViaField("group"). + ViaIndex(0).ViaField("spec", "order-extensions").Error(), + ) + } + + it("invalid object kind", func() { + builder.Spec.OrderExtensions = []BuilderOrderEntry{{ + Group: []BuilderBuildpackRef{{ + ObjectReference: corev1.ObjectReference{ + Name: "some-extension", + Kind: "FakeExtension", + }, + }}, + }} + + assertValidationError(builder, apis.ErrInvalidValue("FakeExtension", "kind", "must be one of Extension, ClusterExtension")) + }) + + it("invalid when image is used", func() { + builder.Spec.OrderExtensions = []BuilderOrderEntry{{ + Group: []BuilderBuildpackRef{{ + Image: "some-registry.io/extension", + }}, + }} + + assertValidationError(builder, apis.ErrDisallowedFields("image reference currently not supported")) + }) + + it("valid when both id and object are defined", func() { + builder.Spec.OrderExtensions = []BuilderOrderEntry{{Group: []BuilderBuildpackRef{{ + BuildpackRef: v1alpha1.BuildpackRef{ + BuildpackInfo: v1alpha1.BuildpackInfo{ + Id: "some-extension", + Version: "v1", + }, + }, + ObjectReference: corev1.ObjectReference{ + Name: "some-extension", + Kind: "Extension", + }, + }}}} + assert.Nil(t, builder.Validate(context.TODO())) + }) + }) }) } diff --git a/pkg/cnb/builder_builder.go b/pkg/cnb/builder_builder.go index d5f8d29a0..c49e92be5 100644 --- a/pkg/cnb/builder_builder.go +++ b/pkg/cnb/builder_builder.go @@ -322,7 +322,7 @@ func (bb *builderBlder) orderLayer() (v1.Layer, error) { exts = append(exts, tomlBuildpack{ ID: e.Id, Version: e.Version, - Optional: e.Optional, // TODO: check + Optional: e.Optional, }) } orderExt = append(orderExt, tomlOrderEntry{Group: exts}) diff --git a/pkg/cnb/remote_buildpack_fetcher.go b/pkg/cnb/remote_buildpack_fetcher.go index 12cca7b30..422024d8f 100644 --- a/pkg/cnb/remote_buildpack_fetcher.go +++ b/pkg/cnb/remote_buildpack_fetcher.go @@ -56,7 +56,6 @@ func (s *remoteBuildpackFetcher) ResolveAndFetchExtension(ctx context.Context, r return s.fetch(ctx, remote) } -// TODO: see if this needs to be updated for extensions func (s *remoteBuildpackFetcher) fetch(ctx context.Context, remoteBuildpack K8sRemoteBuildpack) (RemoteBuildpackInfo, error) { buildpack := remoteBuildpack.Buildpack keychain, err := s.keychainFactory.KeychainForSecretRef(ctx, remoteBuildpack.SecretRef) From b5ae35cc51449f13bfd190344e3d8d366c7367c5 Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Wed, 27 Sep 2023 17:46:51 -0400 Subject: [PATCH 10/20] Add resolve extension Signed-off-by: Natalie Arellano --- pkg/cnb/buildpack_resolver.go | 251 +++++++++++++++------- pkg/cnb/buildpack_resolver_test.go | 322 ++++++++++++++++++++++++++++- 2 files changed, 494 insertions(+), 79 deletions(-) diff --git a/pkg/cnb/buildpack_resolver.go b/pkg/cnb/buildpack_resolver.go index 50cbc579d..6fd9b7d70 100644 --- a/pkg/cnb/buildpack_resolver.go +++ b/pkg/cnb/buildpack_resolver.go @@ -77,7 +77,7 @@ func (r *buildpackResolver) resolveBuildpack(ref v1alpha2.BuilderBuildpackRef) ( return K8sRemoteBuildpack{}, err } case ref.Kind != "": - bp, err := r.resolveFromObjectReference(ref.ObjectReference) + bp, err := r.resolveBuildpackFromObjectReference(ref.ObjectReference) if err != nil { return K8sRemoteBuildpack{}, err } @@ -128,81 +128,70 @@ func (r *buildpackResolver) resolveBuildpack(ref v1alpha2.BuilderBuildpackRef) ( return K8sRemoteBuildpack{}, errors.Errorf("could not find buildpack with id '%s' and version '%s'", ref.Id, ref.Version) } -// TODO: update for extensions func (r *buildpackResolver) resolveExtension(ref v1alpha2.BuilderBuildpackRef) (K8sRemoteBuildpack, error) { - //var matchingBuildpacks []K8sRemoteBuildpack - //var err error - //switch { - //case ref.Kind == v1alpha2.BuildpackKind && ref.Id != "": - // bp := findBuildpack(ref.ObjectReference, r.buildpacks) - // if bp == nil { - // return K8sRemoteBuildpack{}, fmt.Errorf("buildpack not found: %v", ref.Name) - // } - // - // matchingBuildpacks, err = r.resolveFromBuildpack(ref.Id, []*v1alpha2.Buildpack{bp}) - // if err != nil { - // return K8sRemoteBuildpack{}, err - // } - //case ref.Kind == v1alpha2.ClusterBuildpackKind && ref.Id != "": - // cbp := findClusterBuildpack(ref.ObjectReference, r.clusterBuildpacks) - // if cbp == nil { - // return K8sRemoteBuildpack{}, fmt.Errorf("cluster buildpack not found: %v", ref.Name) - // } - // - // matchingBuildpacks, err = r.resolveFromClusterBuildpack(ref.Id, []*v1alpha2.ClusterBuildpack{cbp}) - // if err != nil { - // return K8sRemoteBuildpack{}, err - // } - //case ref.Kind != "": - // bp, err := r.resolveFromObjectReference(ref.ObjectReference) - // if err != nil { - // return K8sRemoteBuildpack{}, err - // } - // matchingBuildpacks = []K8sRemoteBuildpack{bp} - //case ref.Id != "": - // bp, err := r.resolveFromBuildpack(ref.Id, r.buildpacks) - // if err != nil { - // return K8sRemoteBuildpack{}, err - // } - // matchingBuildpacks = append(matchingBuildpacks, bp...) - // - // cbp, err := r.resolveFromClusterBuildpack(ref.Id, r.clusterBuildpacks) - // if err != nil { - // return K8sRemoteBuildpack{}, err - // } - // matchingBuildpacks = append(matchingBuildpacks, cbp...) - // - // cs, err := r.resolveFromClusterStore(ref.Id, r.clusterStore) - // if err != nil { - // return K8sRemoteBuildpack{}, err - // } - // matchingBuildpacks = append(matchingBuildpacks, cs...) - //case ref.Image != "": - // // TODO(chenbh): - // return K8sRemoteBuildpack{}, fmt.Errorf("using images in builders not currently supported") - //default: - // return K8sRemoteBuildpack{}, fmt.Errorf("invalid buildpack reference") - //} - // - //if len(matchingBuildpacks) == 0 { - // return K8sRemoteBuildpack{}, errors.Errorf("could not find buildpack with id '%s'", ref.Id) - //} - // - //if ref.Version == "" { - // bp, err := highestVersion(matchingBuildpacks) - // if err != nil { - // return K8sRemoteBuildpack{}, err - // } - // return bp, nil - //} - // - //for _, result := range matchingBuildpacks { - // if result.Buildpack.Version == ref.Version { - // return result, nil - // } - //} + var ( + matching []K8sRemoteBuildpack + err error + ) + switch { + case ref.Kind == v1alpha2.ExtensionKind && ref.Id != "": + ext := findExtension(ref.ObjectReference, r.extensions) + if ext == nil { + return K8sRemoteBuildpack{}, fmt.Errorf("extension not found: %v", ref.Name) + } + matching, err = resolveFromExtension(ref.Id, []*v1alpha2.Extension{ext}) + if err != nil { + return K8sRemoteBuildpack{}, err + } + case ref.Kind == v1alpha2.ClusterExtensionKind && ref.Id != "": + ext := findClusterExtension(ref.ObjectReference, r.clusterExtensions) + if ext == nil { + return K8sRemoteBuildpack{}, fmt.Errorf("cluster extension not found: %v", ref.Name) + } + matching, err = resolveFromClusterExtension(ref.Id, []*v1alpha2.ClusterExtension{ext}) + if err != nil { + return K8sRemoteBuildpack{}, err + } + case ref.Kind != "": + ext, err := r.resolveExtensionFromObjectReference(ref.ObjectReference) + if err != nil { + return K8sRemoteBuildpack{}, err + } + matching = []K8sRemoteBuildpack{ext} + case ref.Id != "": + ext, err := resolveFromExtension(ref.Id, r.extensions) + if err != nil { + return K8sRemoteBuildpack{}, err + } + matching = append(matching, ext...) + clusterExt, err := resolveFromClusterExtension(ref.Id, r.clusterExtensions) + if err != nil { + return K8sRemoteBuildpack{}, err + } + matching = append(matching, clusterExt...) + case ref.Image != "": + // TODO: add test + return K8sRemoteBuildpack{}, fmt.Errorf("using images in builders not currently supported") + default: + return K8sRemoteBuildpack{}, fmt.Errorf("invalid extension reference") + } - return K8sRemoteBuildpack{}, errors.Errorf("could not find buildpack with id '%s' and version '%s'", ref.Id, ref.Version) + if len(matching) == 0 { + return K8sRemoteBuildpack{}, errors.Errorf("could not find extension with id '%s'", ref.Id) + } + if ref.Version == "" { + resolved, err := highestVersion(matching) + if err != nil { + return K8sRemoteBuildpack{}, err + } + return resolved, nil + } + for _, result := range matching { + if result.Buildpack.Version == ref.Version { + return result, nil + } + } + return K8sRemoteBuildpack{}, errors.Errorf("could not find extension with id '%s' and version '%s'", ref.Id, ref.Version) } func (r *buildpackResolver) resolveFromBuildpack(id string, buildpacks []*v1alpha2.Buildpack) ([]K8sRemoteBuildpack, error) { @@ -224,6 +213,25 @@ func (r *buildpackResolver) resolveFromBuildpack(id string, buildpacks []*v1alph return matchingBuildpacks, nil } +func resolveFromExtension(id string, extensions []*v1alpha2.Extension) ([]K8sRemoteBuildpack, error) { + var matching []K8sRemoteBuildpack + for _, ext := range extensions { + for _, status := range ext.Status.Extensions { + if status.Id == id { + matching = append(matching, K8sRemoteBuildpack{ + Buildpack: status, + SecretRef: registry.SecretRef{ + ServiceAccount: ext.Spec.ServiceAccountName, + Namespace: ext.Namespace, + }, + source: v1.ObjectReference{Name: ext.Name, Namespace: ext.Namespace, Kind: ext.Kind}, + }) + } + } + } + return matching, nil +} + func (r *buildpackResolver) resolveFromClusterBuildpack(id string, clusterBuildpacks []*v1alpha2.ClusterBuildpack) ([]K8sRemoteBuildpack, error) { var matchingBuildpacks []K8sRemoteBuildpack for _, cbp := range clusterBuildpacks { @@ -248,6 +256,29 @@ func (r *buildpackResolver) resolveFromClusterBuildpack(id string, clusterBuildp return matchingBuildpacks, nil } +func resolveFromClusterExtension(id string, extensions []*v1alpha2.ClusterExtension) ([]K8sRemoteBuildpack, error) { + var matching []K8sRemoteBuildpack + for _, ext := range extensions { + for _, status := range ext.Status.Extensions { + if status.Id == id { + secretRef := registry.SecretRef{} + if ext.Spec.ServiceAccountRef != nil { + secretRef = registry.SecretRef{ + ServiceAccount: ext.Spec.ServiceAccountRef.Name, + Namespace: ext.Spec.ServiceAccountRef.Namespace, + } + } + matching = append(matching, K8sRemoteBuildpack{ + Buildpack: status, + SecretRef: secretRef, + source: v1.ObjectReference{Name: ext.Name, Namespace: ext.Namespace, Kind: ext.Kind}, + }) + } + } + } + return matching, nil +} + func (r *buildpackResolver) resolveFromClusterStore(id string, store *v1alpha2.ClusterStore) ([]K8sRemoteBuildpack, error) { if store == nil { return nil, nil @@ -274,9 +305,9 @@ func (r *buildpackResolver) resolveFromClusterStore(id string, store *v1alpha2.C return matchingBuildpacks, nil } -// resolveFromObjectReference will get the object and figure out the root +// resolveBuildpackFromObjectReference will get the object and figure out the root // buildpack by converting it to a buildpack dependency tree -func (r *buildpackResolver) resolveFromObjectReference(ref v1.ObjectReference) (K8sRemoteBuildpack, error) { +func (r *buildpackResolver) resolveBuildpackFromObjectReference(ref v1.ObjectReference) (K8sRemoteBuildpack, error) { var ( bps []corev1alpha1.BuildpackStatus secretRef registry.SecretRef @@ -325,6 +356,56 @@ func (r *buildpackResolver) resolveFromObjectReference(ref v1.ObjectReference) ( }, nil } +// resolveExtensionFromObjectReference will get the object +func (r *buildpackResolver) resolveExtensionFromObjectReference(ref v1.ObjectReference) (K8sRemoteBuildpack, error) { + var ( + extensions []corev1alpha1.BuildpackStatus + secretRef registry.SecretRef + objRef v1.ObjectReference + ) + switch ref.Kind { + case v1alpha2.ExtensionKind: + ext := findExtension(ref, r.extensions) + if ext == nil { + return K8sRemoteBuildpack{}, fmt.Errorf("no extension with name '%v'", ref.Name) + } + + extensions = ext.Status.Extensions + objRef = v1.ObjectReference{Name: ext.Name, Namespace: ext.Namespace, Kind: ext.Kind} + secretRef = registry.SecretRef{ + ServiceAccount: ext.Spec.ServiceAccountName, + Namespace: ext.Namespace, + } + case v1alpha2.ClusterExtensionKind: + ext := findClusterExtension(ref, r.clusterExtensions) + if ext == nil { + return K8sRemoteBuildpack{}, fmt.Errorf("no cluster extension with name '%v'", ref.Name) + } + + extensions = ext.Status.Extensions + objRef = v1.ObjectReference{Name: ext.Name, Namespace: ext.Namespace, Kind: ext.Kind} + if ext.Spec.ServiceAccountRef != nil { + secretRef = registry.SecretRef{ + ServiceAccount: ext.Spec.ServiceAccountRef.Name, + Namespace: ext.Spec.ServiceAccountRef.Namespace, + } + } + default: + return K8sRemoteBuildpack{}, fmt.Errorf("kind must be either %v or %v", v1alpha2.ExtensionKind, v1alpha2.ClusterExtensionKind) + } + + trees := NewTree(extensions) + if len(trees) != 1 { + return K8sRemoteBuildpack{}, fmt.Errorf("unexpected number of root buildpacks: %v", len(trees)) + } + + return K8sRemoteBuildpack{ + Buildpack: *trees[0].Buildpack, + SecretRef: secretRef, + source: objRef, + }, nil +} + // TODO: combine findBuildpack and findClusterBuildpack into a single func // if/when golang generics has support for field values func findBuildpack(ref v1.ObjectReference, buildpacks []*v1alpha2.Buildpack) *v1alpha2.Buildpack { @@ -336,6 +417,15 @@ func findBuildpack(ref v1.ObjectReference, buildpacks []*v1alpha2.Buildpack) *v1 return nil } +func findExtension(ref v1.ObjectReference, extensions []*v1alpha2.Extension) *v1alpha2.Extension { + for _, ext := range extensions { + if ext.Name == ref.Name { + return ext + } + } + return nil +} + func findClusterBuildpack(ref v1.ObjectReference, clusterBuildpacks []*v1alpha2.ClusterBuildpack) *v1alpha2.ClusterBuildpack { for _, cbp := range clusterBuildpacks { if cbp.Name == ref.Name { @@ -345,6 +435,15 @@ func findClusterBuildpack(ref v1.ObjectReference, clusterBuildpacks []*v1alpha2. return nil } +func findClusterExtension(ref v1.ObjectReference, extensions []*v1alpha2.ClusterExtension) *v1alpha2.ClusterExtension { + for _, ext := range extensions { + if ext.Name == ref.Name { + return ext + } + } + return nil +} + // TODO: error if the highest version has multiple diff ids func highestVersion(matchingBuildpacks []K8sRemoteBuildpack) (K8sRemoteBuildpack, error) { for _, bp := range matchingBuildpacks { diff --git a/pkg/cnb/buildpack_resolver_test.go b/pkg/cnb/buildpack_resolver_test.go index 4ebc79b27..1ecc50e2e 100644 --- a/pkg/cnb/buildpack_resolver_test.go +++ b/pkg/cnb/buildpack_resolver_test.go @@ -160,7 +160,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { } ) - when("Resolve", func() { + when("resolveBuildpack", func() { when("using the clusterStore", func() { var ( resolver BuildpackResolver @@ -648,8 +648,324 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { assert.Equal(t, expectedBuildpack, buildpack.Buildpack) }) }) + }) + + when("resolveExtension", func() { + when("using the extension resources", func() { + var ( + resolver BuildpackResolver + extensions = []*buildapi.Extension{ + { + TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "Extension"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "io.buildpack.multi-8.0.0", + Namespace: testNamespace, + }, + Status: buildapi.ExtensionStatus{ + Extensions: []corev1alpha1.BuildpackStatus{ + v8Buildpack, + }, + }, + }, + { + TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "Extension"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "io.buildpack.multi-9.0.0", + Namespace: testNamespace, + }, + Status: buildapi.ExtensionStatus{ + Extensions: []corev1alpha1.BuildpackStatus{ + v9Buildpack, + }, + }, + }, + } + ) + + it.Before(func() { + resolver = NewBuildpackResolver(nil, nil, nil, extensions, nil) + }) + + when("using id", func() { + it("finds it using id", func() { + ref := makeRef("io.buildpack.multi", "") + expected := v9Buildpack + + actual, err := resolver.resolveExtension(ref) + assert.Nil(t, err) + assert.Equal(t, expected, actual.Buildpack) + }) + + it("finds it using id and version", func() { + ref := makeRef("io.buildpack.multi", "8.0.0") + expected := v8Buildpack + + actual, err := resolver.resolveExtension(ref) + assert.Nil(t, err) + assert.Equal(t, expected, actual.Buildpack) + }) + + it("fails on unknown version", func() { + ref := makeRef("io.buildpack.multi", "8.0.1") + _, err := resolver.resolveExtension(ref) + assert.EqualError(t, err, "could not find extension with id 'io.buildpack.multi' and version '8.0.1'") + }) + }) + + when("using object ref", func() { + it("finds the resource", func() { + ref := makeObjectRef("io.buildpack.multi-9.0.0", "Extension", "", "") + expected := v9Buildpack + + actual, err := resolver.resolveExtension(ref) + assert.Nil(t, err) + assert.Equal(t, expected, actual.Buildpack) + }) + + it("fails on invalid kind", func() { + ref := makeObjectRef("io.buildpack.multi", "FakeExtension", "", "") + _, err := resolver.resolveExtension(ref) + assert.EqualError(t, err, "kind must be either Extension or ClusterExtension") + }) + + it("fails on object not found", func() { + ref := makeObjectRef("fake-extension", "Extension", "", "") + _, err := resolver.resolveExtension(ref) + assert.EqualError(t, err, "no extension with name 'fake-extension'") + }) + }) + + when("using id and object ref together", func() { + it("finds id in resource", func() { + ref := makeObjectRef("io.buildpack.multi-9.0.0", "Extension", "io.buildpack.multi", "") + expected := v9Buildpack + + actual, err := resolver.resolveExtension(ref) + assert.Nil(t, err) + assert.Equal(t, expected, actual.Buildpack) + }) + + it("finds the correct version in resource", func() { + ref := makeObjectRef("io.buildpack.multi-9.0.0", "Extension", "io.buildpack.multi", "9.0.0") + expected := v9Buildpack + + actual, err := resolver.resolveExtension(ref) + assert.Nil(t, err) + assert.Equal(t, expected, actual.Buildpack) + }) + + it("fails on id not found in resource", func() { + ref := makeObjectRef("io.buildpack.multi-9.0.0", "Extension", "fake-extension", "") + _, err := resolver.resolveExtension(ref) + assert.EqualError(t, err, "could not find extension with id 'fake-extension'") + }) + + it("fails on version not found in resource", func() { + ref := makeObjectRef("io.buildpack.multi-9.0.0", "Extension", "io.buildpack.multi", "9.0.1") + _, err := resolver.resolveExtension(ref) + assert.EqualError(t, err, "could not find extension with id 'io.buildpack.multi' and version '9.0.1'") + }) + + it("fails on id not found in resource", func() { + ref := makeObjectRef("io.buildpack.multi-9.0.0", "Extension", "fake-extension", "") + _, err := resolver.resolveExtension(ref) + assert.EqualError(t, err, "could not find extension with id 'fake-extension'") + }) + }) + }) + + when("using the clusterExtension resources", func() { + var ( + resolver BuildpackResolver + clusterExtensions = []*buildapi.ClusterExtension{ + { + TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterBuildpack"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "io.buildpack.multi-8.0.0", + }, + Status: buildapi.ClusterExtensionStatus{ + Extensions: []corev1alpha1.BuildpackStatus{ + v8Buildpack, + }, + }, + }, + { + TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterBuildpack"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "io.buildpack.multi-9.0.0", + }, + Status: buildapi.ClusterExtensionStatus{ + Extensions: []corev1alpha1.BuildpackStatus{ + v9Buildpack, + }, + }, + }, + } + ) + + it.Before(func() { + resolver = NewBuildpackResolver(nil, nil, nil, nil, clusterExtensions) + }) + + when("using id", func() { + it("finds it using id", func() { + ref := makeRef("io.buildpack.multi", "") + expected := v9Buildpack + + actual, err := resolver.resolveExtension(ref) + assert.Nil(t, err) + assert.Equal(t, expected, actual.Buildpack) + }) + + it("finds it using id and version", func() { + ref := makeRef("io.buildpack.multi", "8.0.0") + expected := v8Buildpack + + actual, err := resolver.resolveExtension(ref) + assert.Nil(t, err) + assert.Equal(t, expected, actual.Buildpack) + }) + + it("fails on invalid id", func() { + ref := makeRef("fake-extension", "") + _, err := resolver.resolveExtension(ref) + assert.EqualError(t, err, "could not find extension with id 'fake-extension'") + }) + + it("fails on unknown version", func() { + ref := makeRef("io.buildpack.multi", "8.0.1") + _, err := resolver.resolveExtension(ref) + assert.EqualError(t, err, "could not find extension with id 'io.buildpack.multi' and version '8.0.1'") + }) + }) + + when("using object ref", func() { + it("finds the resource", func() { + ref := makeObjectRef("io.buildpack.multi-9.0.0", "ClusterExtension", "", "") + expected := v9Buildpack + + actual, err := resolver.resolveExtension(ref) + assert.Nil(t, err) + assert.Equal(t, expected, actual.Buildpack) + }) + + it("fails on invalid kind", func() { + ref := makeObjectRef("io.buildpack.multi", "FakeExtension", "", "") + _, err := resolver.resolveExtension(ref) + assert.EqualError(t, err, "kind must be either Extension or ClusterExtension") + }) + + it("fails on object not found", func() { + ref := makeObjectRef("fake-extension", "ClusterExtension", "", "") + _, err := resolver.resolveExtension(ref) + assert.EqualError(t, err, "no cluster extension with name 'fake-extension'") + }) + }) + + when("using id and object ref together", func() { + it("finds id in resource", func() { + ref := makeObjectRef("io.buildpack.multi-9.0.0", "ClusterExtension", "io.buildpack.multi", "") + expected := v9Buildpack + + actual, err := resolver.resolveExtension(ref) + assert.Nil(t, err) + assert.Equal(t, expected, actual.Buildpack) + }) + + it("finds the correct version in resource", func() { + ref := makeObjectRef("io.buildpack.multi-9.0.0", "ClusterExtension", "io.buildpack.multi", "9.0.0") + expected := v9Buildpack + + actual, err := resolver.resolveExtension(ref) + assert.Nil(t, err) + assert.Equal(t, expected, actual.Buildpack) + }) + + it("fails on id not found in resource", func() { + ref := makeObjectRef("io.buildpack.multi-9.0.0", "ClusterExtension", "fake-extension", "") + _, err := resolver.resolveExtension(ref) + assert.EqualError(t, err, "could not find extension with id 'fake-extension'") + }) + + it("fails on version not found in resource", func() { + ref := makeObjectRef("io.buildpack.multi-9.0.0", "ClusterExtension", "io.buildpack.multi", "9.0.1") + _, err := resolver.resolveExtension(ref) + assert.EqualError(t, err, "could not find extension with id 'io.buildpack.multi' and version '9.0.1'") + }) + + it("fails on id not found in resource", func() { + ref := makeObjectRef("io.buildpack.multi-9.0.0", "ClusterExtension", "fake-extension", "") + _, err := resolver.resolveExtension(ref) + assert.EqualError(t, err, "could not find extension with id 'fake-extension'") + }) + }) + }) + + when("using multiple resource kinds", func() { + var ( + resolver BuildpackResolver + extensions = []*buildapi.Extension{ + { + TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "Extension"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "io.buildpack.multi-8.0.0", + Namespace: testNamespace, + }, + Status: buildapi.ExtensionStatus{ + Extensions: []corev1alpha1.BuildpackStatus{ + v8Buildpack, + }, + }, + }, + } + clusterExtensions = []*buildapi.ClusterExtension{ + { + TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterBuildpack"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "io.buildpack.multi-8.0.0", + }, + Status: buildapi.ClusterExtensionStatus{ + Extensions: []corev1alpha1.BuildpackStatus{ + v8Buildpack, + }, + }, + }, + { + TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterBuildpack"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "io.buildpack.multi-9.0.0", + }, + Status: buildapi.ClusterExtensionStatus{ + Extensions: []corev1alpha1.BuildpackStatus{ + v9Buildpack, + }, + }, + }, + } + ) + + it.Before(func() { + resolver = NewBuildpackResolver(nil, nil, nil, extensions, clusterExtensions) + }) - // when("resolving via image", func() { - // }) + it("records which objects were used", func() { + actual, err := resolver.resolveExtension(makeRef("io.buildpack.multi", "8.0.0")) + assert.Nil(t, err) + assert.Equal(t, v8Buildpack, actual.Buildpack) + + actual, err = resolver.resolveExtension(makeRef("io.buildpack.multi", "9.0.0")) + assert.Nil(t, err) + assert.Equal(t, v9Buildpack, actual.Buildpack) + }) + + it("resolves extensions before anything else", func() { + ref := makeRef("io.buildpack.multi", "8.0.0") + expected := v8Buildpack + + actual, err := resolver.resolveExtension(ref) + assert.Nil(t, err) + assert.Equal(t, expected, actual.Buildpack) + }) + }) }) } From 8ed1ecc35d02931c9fa77adb9dc7c9a60e0810f8 Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Thu, 28 Sep 2023 09:20:39 -0400 Subject: [PATCH 11/20] Add test for ref image Signed-off-by: Natalie Arellano --- pkg/cnb/buildpack_resolver_test.go | 176 +++++++++++++---------------- 1 file changed, 76 insertions(+), 100 deletions(-) diff --git a/pkg/cnb/buildpack_resolver_test.go b/pkg/cnb/buildpack_resolver_test.go index 1ecc50e2e..9f307390c 100644 --- a/pkg/cnb/buildpack_resolver_test.go +++ b/pkg/cnb/buildpack_resolver_test.go @@ -18,6 +18,8 @@ func TestBuildpackResolver(t *testing.T) { func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { var ( + resolver BuildpackResolver + testNamespace = "some-namespace" engineBuildpack = corev1alpha1.BuildpackStatus{ @@ -161,10 +163,21 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { ) when("resolveBuildpack", func() { + when("provided image", func() { + it.Before(func() { + resolver = NewBuildpackResolver(nil, nil, nil, nil, nil) + }) + + it("fails", func() { + ref := buildapi.BuilderBuildpackRef{Image: "some-image"} + _, err := resolver.resolveBuildpack(ref) + assert.EqualError(t, err, "using images in builders not currently supported") + }) + }) + when("using the clusterStore", func() { var ( - resolver BuildpackResolver - store = &buildapi.ClusterStore{ + store = &buildapi.ClusterStore{ TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterStore"}, ObjectMeta: metav1.ObjectMeta{ Name: "some-store", @@ -219,7 +232,6 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { when("using the buildpack resources", func() { var ( - resolver BuildpackResolver buildpacks = []*buildapi.Buildpack{ { TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "Buildpack"}, @@ -387,7 +399,6 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { when("using the clusterbuildpack resources", func() { var ( - resolver BuildpackResolver clusterBuildpacks = []*buildapi.ClusterBuildpack{ { TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterBuildpack"}, @@ -557,8 +568,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { when("using multiple resource kinds", func() { var ( - resolver BuildpackResolver - store = &buildapi.ClusterStore{ + store = &buildapi.ClusterStore{ TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterStore"}, ObjectMeta: metav1.ObjectMeta{ Name: "some-store", @@ -651,37 +661,73 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { }) when("resolveExtension", func() { - when("using the extension resources", func() { - var ( - resolver BuildpackResolver - extensions = []*buildapi.Extension{ - { - TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "Extension"}, - ObjectMeta: metav1.ObjectMeta{ - Name: "io.buildpack.multi-8.0.0", - Namespace: testNamespace, + var ( + resolver BuildpackResolver + extensions = []*buildapi.Extension{ + { + TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "Extension"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "io.buildpack.multi-8.0.0", + Namespace: testNamespace, + }, + Status: buildapi.ExtensionStatus{ + Extensions: []corev1alpha1.BuildpackStatus{ + v8Buildpack, }, - Status: buildapi.ExtensionStatus{ - Extensions: []corev1alpha1.BuildpackStatus{ - v8Buildpack, - }, + }, + }, + { + TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "Extension"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "io.buildpack.multi-9.0.0", + Namespace: testNamespace, + }, + Status: buildapi.ExtensionStatus{ + Extensions: []corev1alpha1.BuildpackStatus{ + v9Buildpack, }, }, - { - TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "Extension"}, - ObjectMeta: metav1.ObjectMeta{ - Name: "io.buildpack.multi-9.0.0", - Namespace: testNamespace, + }, + } + clusterExtensions = []*buildapi.ClusterExtension{ + { + TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterBuildpack"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "io.buildpack.multi-8.0.0", + }, + Status: buildapi.ClusterExtensionStatus{ + Extensions: []corev1alpha1.BuildpackStatus{ + v8Buildpack, }, - Status: buildapi.ExtensionStatus{ - Extensions: []corev1alpha1.BuildpackStatus{ - v9Buildpack, - }, + }, + }, + { + TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterBuildpack"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "io.buildpack.multi-9.0.0", + }, + Status: buildapi.ClusterExtensionStatus{ + Extensions: []corev1alpha1.BuildpackStatus{ + v9Buildpack, }, }, - } - ) + }, + } + ) + when("provided image", func() { + it.Before(func() { + resolver = NewBuildpackResolver(nil, nil, nil, extensions, clusterExtensions) + }) + + it("fails", func() { + ref := buildapi.BuilderBuildpackRef{Image: "some-image"} + _, err := resolver.resolveExtension(ref) + assert.EqualError(t, err, "using images in builders not currently supported") + }) + }) + + when("using the extension resources", func() { it.Before(func() { resolver = NewBuildpackResolver(nil, nil, nil, extensions, nil) }) @@ -775,34 +821,6 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { }) when("using the clusterExtension resources", func() { - var ( - resolver BuildpackResolver - clusterExtensions = []*buildapi.ClusterExtension{ - { - TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterBuildpack"}, - ObjectMeta: metav1.ObjectMeta{ - Name: "io.buildpack.multi-8.0.0", - }, - Status: buildapi.ClusterExtensionStatus{ - Extensions: []corev1alpha1.BuildpackStatus{ - v8Buildpack, - }, - }, - }, - { - TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterBuildpack"}, - ObjectMeta: metav1.ObjectMeta{ - Name: "io.buildpack.multi-9.0.0", - }, - Status: buildapi.ClusterExtensionStatus{ - Extensions: []corev1alpha1.BuildpackStatus{ - v9Buildpack, - }, - }, - }, - } - ) - it.Before(func() { resolver = NewBuildpackResolver(nil, nil, nil, nil, clusterExtensions) }) @@ -902,48 +920,6 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { }) when("using multiple resource kinds", func() { - var ( - resolver BuildpackResolver - extensions = []*buildapi.Extension{ - { - TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "Extension"}, - ObjectMeta: metav1.ObjectMeta{ - Name: "io.buildpack.multi-8.0.0", - Namespace: testNamespace, - }, - Status: buildapi.ExtensionStatus{ - Extensions: []corev1alpha1.BuildpackStatus{ - v8Buildpack, - }, - }, - }, - } - clusterExtensions = []*buildapi.ClusterExtension{ - { - TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterBuildpack"}, - ObjectMeta: metav1.ObjectMeta{ - Name: "io.buildpack.multi-8.0.0", - }, - Status: buildapi.ClusterExtensionStatus{ - Extensions: []corev1alpha1.BuildpackStatus{ - v8Buildpack, - }, - }, - }, - { - TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterBuildpack"}, - ObjectMeta: metav1.ObjectMeta{ - Name: "io.buildpack.multi-9.0.0", - }, - Status: buildapi.ClusterExtensionStatus{ - Extensions: []corev1alpha1.BuildpackStatus{ - v9Buildpack, - }, - }, - }, - } - ) - it.Before(func() { resolver = NewBuildpackResolver(nil, nil, nil, extensions, clusterExtensions) }) From 71e25d24797df9af99fa92acedc8fbc053cccfa4 Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Thu, 28 Sep 2023 13:19:29 -0400 Subject: [PATCH 12/20] Remove duplication by adding ModuleResource interface Signed-off-by: Natalie Arellano --- pkg/apis/build/v1alpha2/buildpack_types.go | 22 +- .../build/v1alpha2/cluster_buildpack_types.go | 29 +- .../build/v1alpha2/cluster_extension_types.go | 29 +- pkg/apis/build/v1alpha2/extension_types.go | 22 +- pkg/cnb/buildpack_resolver.go | 434 +++++------------- pkg/cnb/buildpack_resolver_test.go | 58 +-- pkg/cnb/remote_buildpack_fetcher.go | 26 +- pkg/git/url_parser_test.go | 2 +- 8 files changed, 274 insertions(+), 348 deletions(-) diff --git a/pkg/apis/build/v1alpha2/buildpack_types.go b/pkg/apis/build/v1alpha2/buildpack_types.go index 50f4af8d4..8ffc906f3 100644 --- a/pkg/apis/build/v1alpha2/buildpack_types.go +++ b/pkg/apis/build/v1alpha2/buildpack_types.go @@ -51,10 +51,26 @@ type BuildpackList struct { Items []Buildpack `json:"items"` } -func (*Buildpack) GetGroupVersionKind() schema.GroupVersionKind { +func (b *Buildpack) GetGroupVersionKind() schema.GroupVersionKind { return SchemeGroupVersion.WithKind(BuildpackKind) } -func (c *Buildpack) NamespacedName() types.NamespacedName { - return types.NamespacedName{Namespace: c.Namespace, Name: c.Name} +func (b *Buildpack) NamespacedName() types.NamespacedName { + return types.NamespacedName{Namespace: b.Namespace, Name: b.Name} +} + +func (b *Buildpack) ModulesStatus() []corev1alpha1.BuildpackStatus { + return b.Status.Buildpacks +} + +func (b *Buildpack) ServiceAccountName() string { + return b.Spec.ServiceAccountName +} + +func (b *Buildpack) ServiceAccountNamespace() string { + return b.Namespace +} + +func (b *Buildpack) TypeMD() metav1.TypeMeta { + return b.TypeMeta } diff --git a/pkg/apis/build/v1alpha2/cluster_buildpack_types.go b/pkg/apis/build/v1alpha2/cluster_buildpack_types.go index e5b4b7dac..6ee106cc2 100644 --- a/pkg/apis/build/v1alpha2/cluster_buildpack_types.go +++ b/pkg/apis/build/v1alpha2/cluster_buildpack_types.go @@ -4,6 +4,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" ) @@ -52,6 +53,32 @@ type ClusterBuildpackList struct { Items []ClusterBuildpack `json:"items"` } -func (*ClusterBuildpack) GetGroupVersionKind() schema.GroupVersionKind { +func (b *ClusterBuildpack) GetGroupVersionKind() schema.GroupVersionKind { return SchemeGroupVersion.WithKind(ClusterBuildpackKind) } + +func (b *ClusterBuildpack) NamespacedName() types.NamespacedName { + return types.NamespacedName{Namespace: b.Namespace, Name: b.Name} +} + +func (b *ClusterBuildpack) ModulesStatus() []corev1alpha1.BuildpackStatus { + return b.Status.Buildpacks +} + +func (b *ClusterBuildpack) ServiceAccountName() string { + if b.Spec.ServiceAccountRef == nil { + return "" + } + return b.Spec.ServiceAccountRef.Name +} + +func (b *ClusterBuildpack) ServiceAccountNamespace() string { + if b.Spec.ServiceAccountRef == nil { + return "" + } + return b.Spec.ServiceAccountRef.Namespace +} + +func (b *ClusterBuildpack) TypeMD() metav1.TypeMeta { + return b.TypeMeta +} diff --git a/pkg/apis/build/v1alpha2/cluster_extension_types.go b/pkg/apis/build/v1alpha2/cluster_extension_types.go index 798017f9f..a46c445e0 100644 --- a/pkg/apis/build/v1alpha2/cluster_extension_types.go +++ b/pkg/apis/build/v1alpha2/cluster_extension_types.go @@ -4,6 +4,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" ) @@ -52,6 +53,32 @@ type ClusterExtensionList struct { Items []ClusterExtension `json:"items"` } -func (*ClusterExtension) GetGroupVersionKind() schema.GroupVersionKind { +func (e *ClusterExtension) GetGroupVersionKind() schema.GroupVersionKind { return SchemeGroupVersion.WithKind(ClusterExtensionKind) } + +func (e *ClusterExtension) NamespacedName() types.NamespacedName { + return types.NamespacedName{Namespace: e.Namespace, Name: e.Name} +} + +func (e *ClusterExtension) ModulesStatus() []corev1alpha1.BuildpackStatus { + return e.Status.Extensions +} + +func (e *ClusterExtension) ServiceAccountName() string { + if e.Spec.ServiceAccountRef == nil { + return "" + } + return e.Spec.ServiceAccountRef.Name +} + +func (e *ClusterExtension) ServiceAccountNamespace() string { + if e.Spec.ServiceAccountRef == nil { + return "" + } + return e.Spec.ServiceAccountRef.Namespace +} + +func (e *ClusterExtension) TypeMD() metav1.TypeMeta { + return e.TypeMeta +} diff --git a/pkg/apis/build/v1alpha2/extension_types.go b/pkg/apis/build/v1alpha2/extension_types.go index dafac9a7b..8b708f31f 100644 --- a/pkg/apis/build/v1alpha2/extension_types.go +++ b/pkg/apis/build/v1alpha2/extension_types.go @@ -51,10 +51,26 @@ type ExtensionList struct { Items []Extension `json:"items"` } -func (*Extension) GetGroupVersionKind() schema.GroupVersionKind { +func (e *Extension) GetGroupVersionKind() schema.GroupVersionKind { return SchemeGroupVersion.WithKind(ExtensionKind) } -func (c *Extension) NamespacedName() types.NamespacedName { - return types.NamespacedName{Namespace: c.Namespace, Name: c.Name} +func (e *Extension) NamespacedName() types.NamespacedName { + return types.NamespacedName{Namespace: e.Namespace, Name: e.Name} +} + +func (e *Extension) ModulesStatus() []corev1alpha1.BuildpackStatus { + return e.Status.Extensions +} + +func (e *Extension) ServiceAccountName() string { + return e.Spec.ServiceAccountName +} + +func (e *Extension) ServiceAccountNamespace() string { + return e.Namespace +} + +func (e *Extension) TypeMD() metav1.TypeMeta { + return e.TypeMeta } diff --git a/pkg/cnb/buildpack_resolver.go b/pkg/cnb/buildpack_resolver.go index 6fd9b7d70..f41bb1e39 100644 --- a/pkg/cnb/buildpack_resolver.go +++ b/pkg/cnb/buildpack_resolver.go @@ -3,10 +3,13 @@ package cnb import ( "fmt" "sort" + "strings" "github.com/Masterminds/semver/v3" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" @@ -23,18 +26,18 @@ type BuildpackResolver interface { type buildpackResolver struct { clusterStore *v1alpha2.ClusterStore - buildpacks []*v1alpha2.Buildpack - clusterBuildpacks []*v1alpha2.ClusterBuildpack - extensions []*v1alpha2.Extension - clusterExtensions []*v1alpha2.ClusterExtension + buildpacks []ModuleResource + clusterBuildpacks []ModuleResource + extensions []ModuleResource + clusterExtensions []ModuleResource } func NewBuildpackResolver( clusterStore *v1alpha2.ClusterStore, - buildpacks []*v1alpha2.Buildpack, - clusterBuildpacks []*v1alpha2.ClusterBuildpack, - extensions []*v1alpha2.Extension, - clusterExtensions []*v1alpha2.ClusterExtension, + buildpacks []ModuleResource, + clusterBuildpacks []ModuleResource, + extensions []ModuleResource, + clusterExtensions []ModuleResource, ) BuildpackResolver { return &buildpackResolver{ clusterStore: clusterStore, @@ -53,131 +56,104 @@ func (r *buildpackResolver) ClusterStoreObservedGeneration() int64 { } func (r *buildpackResolver) resolveBuildpack(ref v1alpha2.BuilderBuildpackRef) (K8sRemoteBuildpack, error) { - var matchingBuildpacks []K8sRemoteBuildpack - var err error - switch { - case ref.Kind == v1alpha2.BuildpackKind && ref.Id != "": - bp := findBuildpack(ref.ObjectReference, r.buildpacks) - if bp == nil { - return K8sRemoteBuildpack{}, fmt.Errorf("buildpack not found: %v", ref.Name) - } - - matchingBuildpacks, err = r.resolveFromBuildpack(ref.Id, []*v1alpha2.Buildpack{bp}) - if err != nil { - return K8sRemoteBuildpack{}, err - } - case ref.Kind == v1alpha2.ClusterBuildpackKind && ref.Id != "": - cbp := findClusterBuildpack(ref.ObjectReference, r.clusterBuildpacks) - if cbp == nil { - return K8sRemoteBuildpack{}, fmt.Errorf("cluster buildpack not found: %v", ref.Name) - } - - matchingBuildpacks, err = r.resolveFromClusterBuildpack(ref.Id, []*v1alpha2.ClusterBuildpack{cbp}) - if err != nil { - return K8sRemoteBuildpack{}, err - } - case ref.Kind != "": - bp, err := r.resolveBuildpackFromObjectReference(ref.ObjectReference) - if err != nil { - return K8sRemoteBuildpack{}, err - } - matchingBuildpacks = []K8sRemoteBuildpack{bp} - case ref.Id != "": - bp, err := r.resolveFromBuildpack(ref.Id, r.buildpacks) - if err != nil { - return K8sRemoteBuildpack{}, err - } - matchingBuildpacks = append(matchingBuildpacks, bp...) - - cbp, err := r.resolveFromClusterBuildpack(ref.Id, r.clusterBuildpacks) - if err != nil { - return K8sRemoteBuildpack{}, err - } - matchingBuildpacks = append(matchingBuildpacks, cbp...) - - cs, err := r.resolveFromClusterStore(ref.Id, r.clusterStore) - if err != nil { - return K8sRemoteBuildpack{}, err - } - matchingBuildpacks = append(matchingBuildpacks, cs...) - case ref.Image != "": - // TODO(chenbh): - return K8sRemoteBuildpack{}, fmt.Errorf("using images in builders not currently supported") - default: - return K8sRemoteBuildpack{}, fmt.Errorf("invalid buildpack reference") + resolveFromBuildpacks := func(id string) ([]K8sRemoteBuildpack, error) { + return resolveFromID(id, r.buildpacks) } - - if len(matchingBuildpacks) == 0 { - return K8sRemoteBuildpack{}, errors.Errorf("could not find buildpack with id '%s'", ref.Id) + resolveFromClusterBuildpacks := func(id string) ([]K8sRemoteBuildpack, error) { + return resolveFromID(id, r.clusterBuildpacks) } - - if ref.Version == "" { - bp, err := highestVersion(matchingBuildpacks) - if err != nil { - return K8sRemoteBuildpack{}, err - } - return bp, nil + resolveFromStore := func(id string) ([]K8sRemoteBuildpack, error) { + return resolveFromClusterStore(ref.Id, r.clusterStore) } + return r.resolveModule( + ref, + "buildpack", + []string{v1alpha2.BuildpackKind, v1alpha2.ClusterBuildpackKind}, + resolveFromBuildpacks, resolveFromClusterBuildpacks, resolveFromStore, + ) +} - for _, result := range matchingBuildpacks { - if result.Buildpack.Version == ref.Version { - return result, nil - } - } +type resolveByID func(string) ([]K8sRemoteBuildpack, error) - return K8sRemoteBuildpack{}, errors.Errorf("could not find buildpack with id '%s' and version '%s'", ref.Id, ref.Version) +func (r *buildpackResolver) resolveExtension(ref v1alpha2.BuilderBuildpackRef) (K8sRemoteBuildpack, error) { + resolveFromExtensions := func(id string) ([]K8sRemoteBuildpack, error) { + return resolveFromID(id, r.extensions) + } + resolveFromClusterExtensions := func(id string) ([]K8sRemoteBuildpack, error) { + return resolveFromID(id, r.clusterExtensions) + } + return r.resolveModule( + ref, + "extension", + []string{v1alpha2.ExtensionKind, v1alpha2.ClusterExtensionKind}, + resolveFromExtensions, resolveFromClusterExtensions, + ) } -func (r *buildpackResolver) resolveExtension(ref v1alpha2.BuilderBuildpackRef) (K8sRemoteBuildpack, error) { +func (r *buildpackResolver) resolveModule( + ref v1alpha2.BuilderBuildpackRef, + moduleName string, + allowedKinds []string, + resolveFuncs ...resolveByID, +) (K8sRemoteBuildpack, error) { var ( matching []K8sRemoteBuildpack err error ) - switch { - case ref.Kind == v1alpha2.ExtensionKind && ref.Id != "": - ext := findExtension(ref.ObjectReference, r.extensions) - if ext == nil { - return K8sRemoteBuildpack{}, fmt.Errorf("extension not found: %v", ref.Name) - } - matching, err = resolveFromExtension(ref.Id, []*v1alpha2.Extension{ext}) - if err != nil { - return K8sRemoteBuildpack{}, err - } - case ref.Kind == v1alpha2.ClusterExtensionKind && ref.Id != "": - ext := findClusterExtension(ref.ObjectReference, r.clusterExtensions) - if ext == nil { - return K8sRemoteBuildpack{}, fmt.Errorf("cluster extension not found: %v", ref.Name) - } - matching, err = resolveFromClusterExtension(ref.Id, []*v1alpha2.ClusterExtension{ext}) - if err != nil { - return K8sRemoteBuildpack{}, err - } - case ref.Kind != "": - ext, err := r.resolveExtensionFromObjectReference(ref.ObjectReference) - if err != nil { - return K8sRemoteBuildpack{}, err - } - matching = []K8sRemoteBuildpack{ext} - case ref.Id != "": - ext, err := resolveFromExtension(ref.Id, r.extensions) - if err != nil { - return K8sRemoteBuildpack{}, err + var searchCollection []ModuleResource + switch ref.Kind { + case v1alpha2.BuildpackKind: + searchCollection = r.buildpacks + case v1alpha2.ClusterBuildpackKind: + searchCollection = r.clusterBuildpacks + case v1alpha2.ExtensionKind: + searchCollection = r.extensions + case v1alpha2.ClusterExtensionKind: + searchCollection = r.clusterExtensions + } + var foundByKindAndID bool + for _, kind := range allowedKinds { + if ref.Kind == kind && ref.Id != "" { + foundByKindAndID = true + found := findByName(ref.ObjectReference, searchCollection) + if found == nil { + return K8sRemoteBuildpack{}, fmt.Errorf("%s not found: %v", kind, ref.Name) + } + matching, err = resolveFromID(ref.Id, []ModuleResource{found}) + if err != nil { + return K8sRemoteBuildpack{}, err + } } - matching = append(matching, ext...) - clusterExt, err := resolveFromClusterExtension(ref.Id, r.clusterExtensions) - if err != nil { - return K8sRemoteBuildpack{}, err + } + if !foundByKindAndID { + switch { + case ref.Kind != "": + if searchCollection == nil { + return K8sRemoteBuildpack{}, fmt.Errorf("kind must be one of: %s", strings.Join(allowedKinds, ", ")) + } + found, err := r.resolveFromObjectRef(ref.ObjectReference, searchCollection) + if err != nil { + return K8sRemoteBuildpack{}, err + } + matching = []K8sRemoteBuildpack{found} + case ref.Id != "": + for _, resolveFunc := range resolveFuncs { + found, err := resolveFunc(ref.Id) + if err != nil { + return K8sRemoteBuildpack{}, err + } + matching = append(matching, found...) + } + case ref.Image != "": + // TODO: add test + return K8sRemoteBuildpack{}, fmt.Errorf("using images in builders not currently supported") + default: + return K8sRemoteBuildpack{}, fmt.Errorf("invalid reference") } - matching = append(matching, clusterExt...) - case ref.Image != "": - // TODO: add test - return K8sRemoteBuildpack{}, fmt.Errorf("using images in builders not currently supported") - default: - return K8sRemoteBuildpack{}, fmt.Errorf("invalid extension reference") } if len(matching) == 0 { - return K8sRemoteBuildpack{}, errors.Errorf("could not find extension with id '%s'", ref.Id) + return K8sRemoteBuildpack{}, errors.Errorf("could not find %s with id '%s'", moduleName, ref.Id) } if ref.Version == "" { resolved, err := highestVersion(matching) @@ -191,40 +167,29 @@ func (r *buildpackResolver) resolveExtension(ref v1alpha2.BuilderBuildpackRef) ( return result, nil } } - return K8sRemoteBuildpack{}, errors.Errorf("could not find extension with id '%s' and version '%s'", ref.Id, ref.Version) + return K8sRemoteBuildpack{}, errors.Errorf("could not find %s with id '%s' and version '%s'", moduleName, ref.Id, ref.Version) } -func (r *buildpackResolver) resolveFromBuildpack(id string, buildpacks []*v1alpha2.Buildpack) ([]K8sRemoteBuildpack, error) { - var matchingBuildpacks []K8sRemoteBuildpack - for _, bp := range buildpacks { - for _, status := range bp.Status.Buildpacks { - if status.Id == id { - matchingBuildpacks = append(matchingBuildpacks, K8sRemoteBuildpack{ - Buildpack: status, - SecretRef: registry.SecretRef{ - ServiceAccount: bp.Spec.ServiceAccountName, - Namespace: bp.Namespace, - }, - source: v1.ObjectReference{Name: bp.Name, Namespace: bp.Namespace, Kind: bp.Kind}, - }) - } - } - } - return matchingBuildpacks, nil +type ModuleResource interface { + ModulesStatus() []corev1alpha1.BuildpackStatus + NamespacedName() types.NamespacedName + ServiceAccountName() string + ServiceAccountNamespace() string + TypeMD() metav1.TypeMeta } -func resolveFromExtension(id string, extensions []*v1alpha2.Extension) ([]K8sRemoteBuildpack, error) { +func resolveFromID(id string, resources []ModuleResource) ([]K8sRemoteBuildpack, error) { var matching []K8sRemoteBuildpack - for _, ext := range extensions { - for _, status := range ext.Status.Extensions { + for _, resource := range resources { + for _, status := range resource.ModulesStatus() { if status.Id == id { matching = append(matching, K8sRemoteBuildpack{ Buildpack: status, SecretRef: registry.SecretRef{ - ServiceAccount: ext.Spec.ServiceAccountName, - Namespace: ext.Namespace, + ServiceAccount: resource.ServiceAccountName(), + Namespace: resource.ServiceAccountNamespace(), }, - source: v1.ObjectReference{Name: ext.Name, Namespace: ext.Namespace, Kind: ext.Kind}, + source: v1.ObjectReference{Name: resource.NamespacedName().Name, Namespace: resource.NamespacedName().Namespace, Kind: resource.TypeMD().Kind}, }) } } @@ -232,54 +197,7 @@ func resolveFromExtension(id string, extensions []*v1alpha2.Extension) ([]K8sRem return matching, nil } -func (r *buildpackResolver) resolveFromClusterBuildpack(id string, clusterBuildpacks []*v1alpha2.ClusterBuildpack) ([]K8sRemoteBuildpack, error) { - var matchingBuildpacks []K8sRemoteBuildpack - for _, cbp := range clusterBuildpacks { - for _, status := range cbp.Status.Buildpacks { - if status.Id == id { - secretRef := registry.SecretRef{} - - if cbp.Spec.ServiceAccountRef != nil { - secretRef = registry.SecretRef{ - ServiceAccount: cbp.Spec.ServiceAccountRef.Name, - Namespace: cbp.Spec.ServiceAccountRef.Namespace, - } - } - matchingBuildpacks = append(matchingBuildpacks, K8sRemoteBuildpack{ - Buildpack: status, - SecretRef: secretRef, - source: v1.ObjectReference{Name: cbp.Name, Namespace: cbp.Namespace, Kind: cbp.Kind}, - }) - } - } - } - return matchingBuildpacks, nil -} - -func resolveFromClusterExtension(id string, extensions []*v1alpha2.ClusterExtension) ([]K8sRemoteBuildpack, error) { - var matching []K8sRemoteBuildpack - for _, ext := range extensions { - for _, status := range ext.Status.Extensions { - if status.Id == id { - secretRef := registry.SecretRef{} - if ext.Spec.ServiceAccountRef != nil { - secretRef = registry.SecretRef{ - ServiceAccount: ext.Spec.ServiceAccountRef.Name, - Namespace: ext.Spec.ServiceAccountRef.Namespace, - } - } - matching = append(matching, K8sRemoteBuildpack{ - Buildpack: status, - SecretRef: secretRef, - source: v1.ObjectReference{Name: ext.Name, Namespace: ext.Namespace, Kind: ext.Kind}, - }) - } - } - } - return matching, nil -} - -func (r *buildpackResolver) resolveFromClusterStore(id string, store *v1alpha2.ClusterStore) ([]K8sRemoteBuildpack, error) { +func resolveFromClusterStore(id string, store *v1alpha2.ClusterStore) ([]K8sRemoteBuildpack, error) { if store == nil { return nil, nil } @@ -305,100 +223,29 @@ func (r *buildpackResolver) resolveFromClusterStore(id string, store *v1alpha2.C return matchingBuildpacks, nil } -// resolveBuildpackFromObjectReference will get the object and figure out the root -// buildpack by converting it to a buildpack dependency tree -func (r *buildpackResolver) resolveBuildpackFromObjectReference(ref v1.ObjectReference) (K8sRemoteBuildpack, error) { +// resolveFromObjectRef will get the object +// and figure out the root buildpack by converting it to a buildpack dependency tree. +func (r *buildpackResolver) resolveFromObjectRef(ref v1.ObjectReference, searchCollection []ModuleResource) (K8sRemoteBuildpack, error) { var ( - bps []corev1alpha1.BuildpackStatus - secretRef registry.SecretRef + modules []corev1alpha1.BuildpackStatus objRef v1.ObjectReference + secretRef registry.SecretRef ) - switch ref.Kind { - case v1alpha2.BuildpackKind: - bp := findBuildpack(ref, r.buildpacks) - if bp == nil { - return K8sRemoteBuildpack{}, fmt.Errorf("no buildpack with name '%v'", ref.Name) - } - - bps = bp.Status.Buildpacks - objRef = v1.ObjectReference{Name: bp.Name, Namespace: bp.Namespace, Kind: bp.Kind} - secretRef = registry.SecretRef{ - ServiceAccount: bp.Spec.ServiceAccountName, - Namespace: bp.Namespace, - } - case v1alpha2.ClusterBuildpackKind: - cbp := findClusterBuildpack(ref, r.clusterBuildpacks) - if cbp == nil { - return K8sRemoteBuildpack{}, fmt.Errorf("no cluster buildpack with name '%v'", ref.Name) - } - - bps = cbp.Status.Buildpacks - objRef = v1.ObjectReference{Name: cbp.Name, Namespace: cbp.Namespace, Kind: cbp.Kind} - if cbp.Spec.ServiceAccountRef != nil { - secretRef = registry.SecretRef{ - ServiceAccount: cbp.Spec.ServiceAccountRef.Name, - Namespace: cbp.Spec.ServiceAccountRef.Namespace, - } - } - default: - return K8sRemoteBuildpack{}, fmt.Errorf("kind must be either %v or %v", v1alpha2.BuildpackKind, v1alpha2.ClusterBuildpackKind) + found := findByName(ref, searchCollection) + if found == nil { + return K8sRemoteBuildpack{}, fmt.Errorf("no %s with name '%v'", ref.Kind, ref.Name) } - - trees := NewTree(bps) - if len(trees) != 1 { - return K8sRemoteBuildpack{}, fmt.Errorf("unexpected number of root buildpacks: %v", len(trees)) - } - - return K8sRemoteBuildpack{ - Buildpack: *trees[0].Buildpack, - SecretRef: secretRef, - source: objRef, - }, nil -} - -// resolveExtensionFromObjectReference will get the object -func (r *buildpackResolver) resolveExtensionFromObjectReference(ref v1.ObjectReference) (K8sRemoteBuildpack, error) { - var ( - extensions []corev1alpha1.BuildpackStatus - secretRef registry.SecretRef - objRef v1.ObjectReference - ) - switch ref.Kind { - case v1alpha2.ExtensionKind: - ext := findExtension(ref, r.extensions) - if ext == nil { - return K8sRemoteBuildpack{}, fmt.Errorf("no extension with name '%v'", ref.Name) - } - - extensions = ext.Status.Extensions - objRef = v1.ObjectReference{Name: ext.Name, Namespace: ext.Namespace, Kind: ext.Kind} - secretRef = registry.SecretRef{ - ServiceAccount: ext.Spec.ServiceAccountName, - Namespace: ext.Namespace, - } - case v1alpha2.ClusterExtensionKind: - ext := findClusterExtension(ref, r.clusterExtensions) - if ext == nil { - return K8sRemoteBuildpack{}, fmt.Errorf("no cluster extension with name '%v'", ref.Name) - } - - extensions = ext.Status.Extensions - objRef = v1.ObjectReference{Name: ext.Name, Namespace: ext.Namespace, Kind: ext.Kind} - if ext.Spec.ServiceAccountRef != nil { - secretRef = registry.SecretRef{ - ServiceAccount: ext.Spec.ServiceAccountRef.Name, - Namespace: ext.Spec.ServiceAccountRef.Namespace, - } - } - default: - return K8sRemoteBuildpack{}, fmt.Errorf("kind must be either %v or %v", v1alpha2.ExtensionKind, v1alpha2.ClusterExtensionKind) + modules = found.ModulesStatus() + objRef = v1.ObjectReference{Name: found.NamespacedName().Name, Namespace: found.NamespacedName().Namespace, Kind: found.TypeMD().Kind} + secretRef = registry.SecretRef{ + ServiceAccount: found.ServiceAccountName(), + Namespace: found.ServiceAccountNamespace(), } - trees := NewTree(extensions) + trees := NewTree(modules) if len(trees) != 1 { - return K8sRemoteBuildpack{}, fmt.Errorf("unexpected number of root buildpacks: %v", len(trees)) + return K8sRemoteBuildpack{}, fmt.Errorf("unexpected number of root modules: %v", len(trees)) } - return K8sRemoteBuildpack{ Buildpack: *trees[0].Buildpack, SecretRef: secretRef, @@ -406,39 +253,10 @@ func (r *buildpackResolver) resolveExtensionFromObjectReference(ref v1.ObjectRef }, nil } -// TODO: combine findBuildpack and findClusterBuildpack into a single func -// if/when golang generics has support for field values -func findBuildpack(ref v1.ObjectReference, buildpacks []*v1alpha2.Buildpack) *v1alpha2.Buildpack { - for _, bp := range buildpacks { - if bp.Name == ref.Name { - return bp - } - } - return nil -} - -func findExtension(ref v1.ObjectReference, extensions []*v1alpha2.Extension) *v1alpha2.Extension { - for _, ext := range extensions { - if ext.Name == ref.Name { - return ext - } - } - return nil -} - -func findClusterBuildpack(ref v1.ObjectReference, clusterBuildpacks []*v1alpha2.ClusterBuildpack) *v1alpha2.ClusterBuildpack { - for _, cbp := range clusterBuildpacks { - if cbp.Name == ref.Name { - return cbp - } - } - return nil -} - -func findClusterExtension(ref v1.ObjectReference, extensions []*v1alpha2.ClusterExtension) *v1alpha2.ClusterExtension { - for _, ext := range extensions { - if ext.Name == ref.Name { - return ext +func findByName(ref v1.ObjectReference, resources []ModuleResource) ModuleResource { + for _, resource := range resources { + if resource.NamespacedName().Name == ref.Name { + return resource } } return nil diff --git a/pkg/cnb/buildpack_resolver_test.go b/pkg/cnb/buildpack_resolver_test.go index 9f307390c..3a8507661 100644 --- a/pkg/cnb/buildpack_resolver_test.go +++ b/pkg/cnb/buildpack_resolver_test.go @@ -232,8 +232,8 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { when("using the buildpack resources", func() { var ( - buildpacks = []*buildapi.Buildpack{ - { + buildpacks = []ModuleResource{ + &buildapi.Buildpack{ TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "Buildpack"}, ObjectMeta: metav1.ObjectMeta{ Name: "io.buildpack.meta", @@ -247,7 +247,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { }, }, }, - { + &buildapi.Buildpack{ TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "Buildpack"}, ObjectMeta: metav1.ObjectMeta{ Name: "io.buildpack.multi-8.0.0", @@ -259,7 +259,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { }, }, }, - { + &buildapi.Buildpack{ TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "Buildpack"}, ObjectMeta: metav1.ObjectMeta{ Name: "io.buildpack.multi-9.0.0", @@ -271,7 +271,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { }, }, }, - { + &buildapi.Buildpack{ TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "Buildpack"}, ObjectMeta: metav1.ObjectMeta{ Name: "io.buildpack.multi", @@ -339,13 +339,13 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { it("fails on invalid kind", func() { ref := makeObjectRef("io.buildpack.meta", "FakeBuildpack", "", "") _, err := resolver.resolveBuildpack(ref) - assert.EqualError(t, err, "kind must be either Buildpack or ClusterBuildpack") + assert.EqualError(t, err, "kind must be one of: Buildpack, ClusterBuildpack") }) it("fails on object not found", func() { ref := makeObjectRef("fake-buildpack", "Buildpack", "", "") _, err := resolver.resolveBuildpack(ref) - assert.EqualError(t, err, "no buildpack with name 'fake-buildpack'") + assert.EqualError(t, err, "no Buildpack with name 'fake-buildpack'") }) }) @@ -399,8 +399,8 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { when("using the clusterbuildpack resources", func() { var ( - clusterBuildpacks = []*buildapi.ClusterBuildpack{ - { + clusterBuildpacks = []ModuleResource{ + &buildapi.ClusterBuildpack{ TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterBuildpack"}, ObjectMeta: metav1.ObjectMeta{ Name: "io.buildpack.meta", @@ -413,7 +413,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { }, }, }, - { + &buildapi.ClusterBuildpack{ TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterBuildpack"}, ObjectMeta: metav1.ObjectMeta{ Name: "io.buildpack.multi-8.0.0", @@ -424,7 +424,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { }, }, }, - { + &buildapi.ClusterBuildpack{ TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterBuildpack"}, ObjectMeta: metav1.ObjectMeta{ Name: "io.buildpack.multi-9.0.0", @@ -435,7 +435,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { }, }, }, - { + &buildapi.ClusterBuildpack{ TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterBuildpack"}, ObjectMeta: metav1.ObjectMeta{ Name: "io.buildpack.multi", @@ -508,13 +508,13 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { it("fails on invalid kind", func() { ref := makeObjectRef("io.buildpack.meta", "FakeClusterBuildpack", "", "") _, err := resolver.resolveBuildpack(ref) - assert.EqualError(t, err, "kind must be either Buildpack or ClusterBuildpack") + assert.EqualError(t, err, "kind must be one of: Buildpack, ClusterBuildpack") }) it("fails on object not found", func() { ref := makeObjectRef("fake-buildpack", "ClusterBuildpack", "", "") _, err := resolver.resolveBuildpack(ref) - assert.EqualError(t, err, "no cluster buildpack with name 'fake-buildpack'") + assert.EqualError(t, err, "no ClusterBuildpack with name 'fake-buildpack'") }) }) @@ -581,8 +581,8 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { }, }, } - buildpacks = []*buildapi.Buildpack{ - { + buildpacks = []ModuleResource{ + &buildapi.Buildpack{ TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "Buildpack"}, ObjectMeta: metav1.ObjectMeta{ Name: "io.buildpack.multi-8.0.0", @@ -595,8 +595,8 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { }, }, } - clusterBuildpacks = []*buildapi.ClusterBuildpack{ - { + clusterBuildpacks = []ModuleResource{ + &buildapi.ClusterBuildpack{ TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterBuildpack"}, ObjectMeta: metav1.ObjectMeta{ Name: "io.buildpack.multi-8.0.0", @@ -607,7 +607,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { }, }, }, - { + &buildapi.ClusterBuildpack{ TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterBuildpack"}, ObjectMeta: metav1.ObjectMeta{ Name: "io.buildpack.multi-9.0.0", @@ -663,8 +663,8 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { when("resolveExtension", func() { var ( resolver BuildpackResolver - extensions = []*buildapi.Extension{ - { + extensions = []ModuleResource{ + &buildapi.Extension{ TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "Extension"}, ObjectMeta: metav1.ObjectMeta{ Name: "io.buildpack.multi-8.0.0", @@ -676,7 +676,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { }, }, }, - { + &buildapi.Extension{ TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "Extension"}, ObjectMeta: metav1.ObjectMeta{ Name: "io.buildpack.multi-9.0.0", @@ -689,8 +689,8 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { }, }, } - clusterExtensions = []*buildapi.ClusterExtension{ - { + clusterExtensions = []ModuleResource{ + &buildapi.ClusterExtension{ TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterBuildpack"}, ObjectMeta: metav1.ObjectMeta{ Name: "io.buildpack.multi-8.0.0", @@ -701,7 +701,7 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { }, }, }, - { + &buildapi.ClusterExtension{ TypeMeta: metav1.TypeMeta{APIVersion: "v1alpha2", Kind: "ClusterBuildpack"}, ObjectMeta: metav1.ObjectMeta{ Name: "io.buildpack.multi-9.0.0", @@ -771,13 +771,13 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { it("fails on invalid kind", func() { ref := makeObjectRef("io.buildpack.multi", "FakeExtension", "", "") _, err := resolver.resolveExtension(ref) - assert.EqualError(t, err, "kind must be either Extension or ClusterExtension") + assert.EqualError(t, err, "kind must be one of: Extension, ClusterExtension") }) it("fails on object not found", func() { ref := makeObjectRef("fake-extension", "Extension", "", "") _, err := resolver.resolveExtension(ref) - assert.EqualError(t, err, "no extension with name 'fake-extension'") + assert.EqualError(t, err, "no Extension with name 'fake-extension'") }) }) @@ -870,13 +870,13 @@ func testBuildpackResolver(t *testing.T, when spec.G, it spec.S) { it("fails on invalid kind", func() { ref := makeObjectRef("io.buildpack.multi", "FakeExtension", "", "") _, err := resolver.resolveExtension(ref) - assert.EqualError(t, err, "kind must be either Extension or ClusterExtension") + assert.EqualError(t, err, "kind must be one of: Extension, ClusterExtension") }) it("fails on object not found", func() { ref := makeObjectRef("fake-extension", "ClusterExtension", "", "") _, err := resolver.resolveExtension(ref) - assert.EqualError(t, err, "no cluster extension with name 'fake-extension'") + assert.EqualError(t, err, "no ClusterExtension with name 'fake-extension'") }) }) diff --git a/pkg/cnb/remote_buildpack_fetcher.go b/pkg/cnb/remote_buildpack_fetcher.go index 422024d8f..7de707335 100644 --- a/pkg/cnb/remote_buildpack_fetcher.go +++ b/pkg/cnb/remote_buildpack_fetcher.go @@ -32,9 +32,31 @@ func NewRemoteBuildpackFetcher( extensions []*buildapi.Extension, clusterExtensions []*buildapi.ClusterExtension, ) RemoteBuildpackFetcher { + rBuildpacks := make([]ModuleResource, len(buildpacks)) + for i := range buildpacks { + rBuildpacks[i] = ModuleResource(buildpacks[i]) + } + rClusterBuildpacks := make([]ModuleResource, len(clusterBuildpacks)) + for i := range clusterBuildpacks { + rClusterBuildpacks[i] = ModuleResource(clusterBuildpacks[i]) + } + rExtensions := make([]ModuleResource, len(extensions)) + for i := range extensions { + rExtensions[i] = ModuleResource(extensions[i]) + } + rClusterExtensions := make([]ModuleResource, len(clusterExtensions)) + for i := range clusterExtensions { + rClusterExtensions[i] = ModuleResource(clusterExtensions[i]) + } return &remoteBuildpackFetcher{ - BuildpackResolver: NewBuildpackResolver(clusterStore, buildpacks, clusterBuildpacks, extensions, clusterExtensions), - keychainFactory: dockercreds.NewCachedKeychainFactory(factory), + BuildpackResolver: NewBuildpackResolver( + clusterStore, + rBuildpacks, + rClusterBuildpacks, + rExtensions, + rClusterExtensions, + ), + keychainFactory: dockercreds.NewCachedKeychainFactory(factory), } } diff --git a/pkg/git/url_parser_test.go b/pkg/git/url_parser_test.go index 874fa1495..3f73a0b3e 100644 --- a/pkg/git/url_parser_test.go +++ b/pkg/git/url_parser_test.go @@ -8,7 +8,7 @@ import ( ) func TestParseURL(t *testing.T) { - spec.Focus(t, "Test Parse Git URL", testParseURL) + spec.Focus(t, "Test Parse Git URL", testParseURL) // TODO: should this be .Focus? } func testParseURL(t *testing.T, when spec.G, it spec.S) { From 2663049c4b9800ffa8514a22b72d5944c9d61202 Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Thu, 28 Sep 2023 13:23:49 -0400 Subject: [PATCH 13/20] Remove TODO Signed-off-by: Natalie Arellano --- pkg/cnb/buildpack_resolver.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/cnb/buildpack_resolver.go b/pkg/cnb/buildpack_resolver.go index f41bb1e39..7d2743335 100644 --- a/pkg/cnb/buildpack_resolver.go +++ b/pkg/cnb/buildpack_resolver.go @@ -145,7 +145,6 @@ func (r *buildpackResolver) resolveModule( matching = append(matching, found...) } case ref.Image != "": - // TODO: add test return K8sRemoteBuildpack{}, fmt.Errorf("using images in builders not currently supported") default: return K8sRemoteBuildpack{}, fmt.Errorf("invalid reference") From 6a60008b0f19f7141d782e1bd1833cd382138fd0 Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Thu, 28 Sep 2023 13:38:28 -0400 Subject: [PATCH 14/20] Return a useful error on Windows Signed-off-by: Natalie Arellano --- pkg/cnb/create_builder.go | 4 ++++ pkg/cnb/create_builder_test.go | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/pkg/cnb/create_builder.go b/pkg/cnb/create_builder.go index 2d04fc54c..9b6d99ea1 100644 --- a/pkg/cnb/create_builder.go +++ b/pkg/cnb/create_builder.go @@ -2,6 +2,7 @@ package cnb import ( "context" + "errors" "github.com/google/go-containerregistry/pkg/authn" ggcrv1 "github.com/google/go-containerregistry/pkg/v1" @@ -63,6 +64,9 @@ func (r *RemoteBuilderCreator) CreateBuilder(ctx context.Context, builderKeychai } // fetch and add extensions + if builderBldr.os == "windows" && len(spec.OrderExtensions) > 0 { + return buildapi.BuilderRecord{}, errors.New("image extensions are not supported for Windows builds") + } for _, group := range spec.OrderExtensions { extensions := make([]RemoteBuildpackRef, 0, len(group.Group)) diff --git a/pkg/cnb/create_builder_test.go b/pkg/cnb/create_builder_test.go index 3861c0266..64a0997a3 100644 --- a/pkg/cnb/create_builder_test.go +++ b/pkg/cnb/create_builder_test.go @@ -727,10 +727,12 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { addExtension(t, extensionRef.Id, extensionRef.Version, "", "0.3") builderRecord, err := subject.CreateBuilder(ctx, builderKeychain, stackKeychain, fetcher, stack, clusterBuilderSpec) - require.NoError(t, err) if os == "windows" { - // TODO: expect some kind of useful error + assert.Error(t, err, "image extensions are not supported for Windows builds") + return + } else { + require.NoError(t, err) } // builder record @@ -829,6 +831,9 @@ func testCreateBuilderOs(os string, t *testing.T, when spec.G, it spec.S) { when("validating extensions", func() { it("errors with unsupported Buildpack API version", func() { + if os == "windows" { + return + } extensionRef := corev1alpha1.BuildpackRef{ BuildpackInfo: corev1alpha1.BuildpackInfo{ Id: "some-unsupported-extension-id", From dd5c1151cae3ff43fe427c88f6f700f22d60cc11 Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Thu, 28 Sep 2023 13:50:37 -0400 Subject: [PATCH 15/20] Add extension reader Signed-off-by: Natalie Arellano --- pkg/cnb/remote_store_reader.go | 37 +++++++++++-------- pkg/cnb/remote_store_reader_test.go | 10 ++--- pkg/reconciler/buildpack/buildpack.go | 4 +- .../buildpackfakes/fake_store_reader.go | 2 +- .../clusterbuildpack/clusterbuildpack.go | 4 +- .../fake_store_reader.go | 2 +- pkg/reconciler/clusterstore/clusterstore.go | 4 +- .../clusterstorefakes/fake_store_reader.go | 2 +- 8 files changed, 36 insertions(+), 29 deletions(-) diff --git a/pkg/cnb/remote_store_reader.go b/pkg/cnb/remote_store_reader.go index b1369e54b..ac7afe6ff 100644 --- a/pkg/cnb/remote_store_reader.go +++ b/pkg/cnb/remote_store_reader.go @@ -16,7 +16,15 @@ type RemoteBuildpackReader struct { RegistryClient RegistryClient } -func (r *RemoteBuildpackReader) Read(keychain authn.Keychain, storeImages []corev1alpha1.ImageSource) ([]corev1alpha1.BuildpackStatus, error) { +func (r *RemoteBuildpackReader) ReadBuildpack(keychain authn.Keychain, storeImages []corev1alpha1.ImageSource) ([]corev1alpha1.BuildpackStatus, error) { + return r.readModule(keychain, storeImages, buildpackLayersLabel) +} + +func (r *RemoteBuildpackReader) ReadExtension(keychain authn.Keychain, storeImages []corev1alpha1.ImageSource) ([]corev1alpha1.BuildpackStatus, error) { + return r.readModule(keychain, storeImages, extensionLayersLabel) +} + +func (r *RemoteBuildpackReader) readModule(keychain authn.Keychain, storeImages []corev1alpha1.ImageSource, layersLabelName string) ([]corev1alpha1.BuildpackStatus, error) { var g errgroup.Group c := make(chan corev1alpha1.BuildpackStatus) @@ -28,19 +36,18 @@ func (r *RemoteBuildpackReader) Read(keychain authn.Keychain, storeImages []core return err } - bpMetadata := BuildpackageMetadata{} + packageMetadata := BuildpackageMetadata{} if ok, err := imagehelpers.HasLabel(image, buildpackageMetadataLabel); err != nil { return err } else if ok { - err := imagehelpers.GetLabel(image, buildpackageMetadataLabel, &bpMetadata) + err := imagehelpers.GetLabel(image, buildpackageMetadataLabel, &packageMetadata) if err != nil { return err } } - // TODO: read extensionLayersLabel layerMetadata := BuildpackLayerMetadata{} - err = imagehelpers.GetLabel(image, buildpackLayersLabel, &layerMetadata) + err = imagehelpers.GetLabel(image, layersLabelName, &layerMetadata) if err != nil { return err } @@ -48,9 +55,9 @@ func (r *RemoteBuildpackReader) Read(keychain authn.Keychain, storeImages []core for id := range layerMetadata { for version, metadata := range layerMetadata[id] { packageInfo := corev1alpha1.BuildpackageInfo{ - Id: bpMetadata.Id, - Version: bpMetadata.Version, - Homepage: bpMetadata.Homepage, + Id: packageMetadata.Id, + Version: packageMetadata.Version, + Homepage: packageMetadata.Homepage, } info := corev1alpha1.BuildpackInfo{ @@ -101,18 +108,18 @@ func (r *RemoteBuildpackReader) Read(keychain authn.Keychain, storeImages []core close(c) }() - var buildpacks []corev1alpha1.BuildpackStatus + var statuses []corev1alpha1.BuildpackStatus for b := range c { - buildpacks = append(buildpacks, b) + statuses = append(statuses, b) } - sort.Slice(buildpacks, func(i, j int) bool { - if buildpacks[i].String() == buildpacks[j].String() { - return buildpacks[i].StoreImage.Image < buildpacks[j].StoreImage.Image + sort.Slice(statuses, func(i, j int) bool { + if statuses[i].String() == statuses[j].String() { + return statuses[i].StoreImage.Image < statuses[j].StoreImage.Image } - return buildpacks[i].String() < buildpacks[j].String() + return statuses[i].String() < statuses[j].String() }) - return buildpacks, g.Wait() + return statuses, g.Wait() } diff --git a/pkg/cnb/remote_store_reader_test.go b/pkg/cnb/remote_store_reader_test.go index 9e9424c6c..20fde4b47 100644 --- a/pkg/cnb/remote_store_reader_test.go +++ b/pkg/cnb/remote_store_reader_test.go @@ -205,7 +205,7 @@ func testRemoteStoreReader(t *testing.T, when spec.G, it spec.S) { }) it("returns all buildpacks from multiple images", func() { - storeBuildpacks, err := remoteStoreReader.Read(expectedKeychain, []corev1alpha1.ImageSource{ + storeBuildpacks, err := remoteStoreReader.ReadBuildpack(expectedKeychain, []corev1alpha1.ImageSource{ { Image: buildpackageA, }, @@ -356,7 +356,7 @@ func testRemoteStoreReader(t *testing.T, when spec.G, it spec.S) { }) it("returns all buildpacks in a deterministic order", func() { - expectedBuildpackOrder, err := remoteStoreReader.Read(expectedKeychain, []corev1alpha1.ImageSource{ + expectedBuildpackOrder, err := remoteStoreReader.ReadBuildpack(expectedKeychain, []corev1alpha1.ImageSource{ { Image: buildpackageA, }, @@ -367,7 +367,7 @@ func testRemoteStoreReader(t *testing.T, when spec.G, it spec.S) { require.NoError(t, err) for i := 1; i <= 50; i++ { - subsequentOrder, err := remoteStoreReader.Read(expectedKeychain, []corev1alpha1.ImageSource{ + subsequentOrder, err := remoteStoreReader.ReadBuildpack(expectedKeychain, []corev1alpha1.ImageSource{ { Image: buildpackageA, }, @@ -445,11 +445,11 @@ func testRemoteStoreReader(t *testing.T, when spec.G, it spec.S) { Image: "image/with_duplicates", }, } - expectedBuildpackOrder, err := remoteStoreReader.Read(expectedKeychain, images) + expectedBuildpackOrder, err := remoteStoreReader.ReadBuildpack(expectedKeychain, images) require.NoError(t, err) for i := 1; i <= 50; i++ { - subsequentOrder, err := remoteStoreReader.Read(expectedKeychain, images) + subsequentOrder, err := remoteStoreReader.ReadBuildpack(expectedKeychain, images) require.NoError(t, err) require.Equal(t, expectedBuildpackOrder, subsequentOrder) diff --git a/pkg/reconciler/buildpack/buildpack.go b/pkg/reconciler/buildpack/buildpack.go index 68b30b803..b818d6059 100644 --- a/pkg/reconciler/buildpack/buildpack.go +++ b/pkg/reconciler/buildpack/buildpack.go @@ -29,7 +29,7 @@ const ( //go:generate counterfeiter . StoreReader type StoreReader interface { - Read(keychain authn.Keychain, storeImages []corev1alpha1.ImageSource) ([]corev1alpha1.BuildpackStatus, error) + ReadBuildpack(keychain authn.Keychain, storeImages []corev1alpha1.ImageSource) ([]corev1alpha1.BuildpackStatus, error) } func NewController( @@ -129,7 +129,7 @@ func (c *Reconciler) reconcileBuildpackStatus(ctx context.Context, buildpack *bu return buildpack, err } - buildpacks, err := c.StoreReader.Read(keychain, []corev1alpha1.ImageSource{buildpack.Spec.ImageSource}) + buildpacks, err := c.StoreReader.ReadBuildpack(keychain, []corev1alpha1.ImageSource{buildpack.Spec.ImageSource}) if err != nil { buildpack.Status = buildapi.BuildpackStatus{ Status: corev1alpha1.CreateStatusWithReadyCondition(buildpack.Generation, err), diff --git a/pkg/reconciler/buildpack/buildpackfakes/fake_store_reader.go b/pkg/reconciler/buildpack/buildpackfakes/fake_store_reader.go index 428c8df29..ef38f006d 100644 --- a/pkg/reconciler/buildpack/buildpackfakes/fake_store_reader.go +++ b/pkg/reconciler/buildpack/buildpackfakes/fake_store_reader.go @@ -28,7 +28,7 @@ type FakeStoreReader struct { invocationsMutex sync.RWMutex } -func (fake *FakeStoreReader) Read(arg1 authn.Keychain, arg2 []v1alpha1.ImageSource) ([]v1alpha1.BuildpackStatus, error) { +func (fake *FakeStoreReader) ReadBuildpack(arg1 authn.Keychain, arg2 []v1alpha1.ImageSource) ([]v1alpha1.BuildpackStatus, error) { var arg2Copy []v1alpha1.ImageSource if arg2 != nil { arg2Copy = make([]v1alpha1.ImageSource, len(arg2)) diff --git a/pkg/reconciler/clusterbuildpack/clusterbuildpack.go b/pkg/reconciler/clusterbuildpack/clusterbuildpack.go index 4f933c838..300539b35 100644 --- a/pkg/reconciler/clusterbuildpack/clusterbuildpack.go +++ b/pkg/reconciler/clusterbuildpack/clusterbuildpack.go @@ -29,7 +29,7 @@ const ( //go:generate counterfeiter . StoreReader type StoreReader interface { - Read(keychain authn.Keychain, storeImages []corev1alpha1.ImageSource) ([]corev1alpha1.BuildpackStatus, error) + ReadBuildpack(keychain authn.Keychain, storeImages []corev1alpha1.ImageSource) ([]corev1alpha1.BuildpackStatus, error) } func NewController( @@ -129,7 +129,7 @@ func (c *Reconciler) reoncileClusterBuildpackStatus(ctx context.Context, cluster return clusterBuildpack, err } - buildpacks, err := c.StoreReader.Read(keychain, []corev1alpha1.ImageSource{clusterBuildpack.Spec.ImageSource}) + buildpacks, err := c.StoreReader.ReadBuildpack(keychain, []corev1alpha1.ImageSource{clusterBuildpack.Spec.ImageSource}) if err != nil { clusterBuildpack.Status = buildapi.ClusterBuildpackStatus{ Status: corev1alpha1.CreateStatusWithReadyCondition(clusterBuildpack.Generation, err), diff --git a/pkg/reconciler/clusterbuildpack/clusterbuildpackfakes/fake_store_reader.go b/pkg/reconciler/clusterbuildpack/clusterbuildpackfakes/fake_store_reader.go index 88282f611..d7dc8905b 100644 --- a/pkg/reconciler/clusterbuildpack/clusterbuildpackfakes/fake_store_reader.go +++ b/pkg/reconciler/clusterbuildpack/clusterbuildpackfakes/fake_store_reader.go @@ -28,7 +28,7 @@ type FakeStoreReader struct { invocationsMutex sync.RWMutex } -func (fake *FakeStoreReader) Read(arg1 authn.Keychain, arg2 []v1alpha1.ImageSource) ([]v1alpha1.BuildpackStatus, error) { +func (fake *FakeStoreReader) ReadBuildpack(arg1 authn.Keychain, arg2 []v1alpha1.ImageSource) ([]v1alpha1.BuildpackStatus, error) { var arg2Copy []v1alpha1.ImageSource if arg2 != nil { arg2Copy = make([]v1alpha1.ImageSource, len(arg2)) diff --git a/pkg/reconciler/clusterstore/clusterstore.go b/pkg/reconciler/clusterstore/clusterstore.go index f99834524..9273d0233 100644 --- a/pkg/reconciler/clusterstore/clusterstore.go +++ b/pkg/reconciler/clusterstore/clusterstore.go @@ -28,7 +28,7 @@ const ( //go:generate counterfeiter . StoreReader type StoreReader interface { - Read(keychain authn.Keychain, storeImages []corev1alpha1.ImageSource) ([]corev1alpha1.BuildpackStatus, error) + ReadBuildpack(keychain authn.Keychain, storeImages []corev1alpha1.ImageSource) ([]corev1alpha1.BuildpackStatus, error) } func NewController( @@ -128,7 +128,7 @@ func (c *Reconciler) reconcileClusterStoreStatus(ctx context.Context, clusterSto return clusterStore, err } - buildpacks, err := c.StoreReader.Read(keychain, clusterStore.Spec.Sources) + buildpacks, err := c.StoreReader.ReadBuildpack(keychain, clusterStore.Spec.Sources) if err != nil { clusterStore.Status = buildapi.ClusterStoreStatus{ Status: corev1alpha1.CreateStatusWithReadyCondition(clusterStore.Generation, err), diff --git a/pkg/reconciler/clusterstore/clusterstorefakes/fake_store_reader.go b/pkg/reconciler/clusterstore/clusterstorefakes/fake_store_reader.go index cc030c62a..0631770bc 100644 --- a/pkg/reconciler/clusterstore/clusterstorefakes/fake_store_reader.go +++ b/pkg/reconciler/clusterstore/clusterstorefakes/fake_store_reader.go @@ -28,7 +28,7 @@ type FakeStoreReader struct { invocationsMutex sync.RWMutex } -func (fake *FakeStoreReader) Read(arg1 authn.Keychain, arg2 []v1alpha1.ImageSource) ([]v1alpha1.BuildpackStatus, error) { +func (fake *FakeStoreReader) ReadBuildpack(arg1 authn.Keychain, arg2 []v1alpha1.ImageSource) ([]v1alpha1.BuildpackStatus, error) { var arg2Copy []v1alpha1.ImageSource if arg2 != nil { arg2Copy = make([]v1alpha1.ImageSource, len(arg2)) From 160ce195795cb67e68651a57a49e1cc7b7cb183a Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Wed, 4 Oct 2023 11:31:06 -0400 Subject: [PATCH 16/20] Add basic e2e test for extensions Signed-off-by: Natalie Arellano --- DEVELOPMENT.md | 6 - Makefile | 2 +- cmd/controller/main.go | 6 + config/clusterextension.yaml | 31 ++ config/controllerrole.yaml | 4 + config/extension.yaml | 32 ++ go.sum | 2 +- pkg/apis/build/v1alpha2/build_pod.go | 60 +++- pkg/apis/build/v1alpha2/build_pod_test.go | 84 +++++ pkg/apis/build/v1alpha2/builder_conversion.go | 4 +- .../build/v1alpha2/builder_conversion_test.go | 6 +- pkg/apis/build/v1alpha2/builder_lifecycle.go | 4 +- pkg/apis/build/v1alpha2/builder_types.go | 19 +- .../build/v1alpha2/zz_generated.deepcopy.go | 4 +- pkg/buildpod/generator.go | 28 +- pkg/buildpod/generator_test.go | 67 +++- pkg/cnb/builder_builder.go | 4 +- pkg/cnb/create_builder.go | 1 + pkg/duckbuilder/duck_builder.go | 3 +- pkg/duckbuilder/duck_builder_test.go | 2 +- pkg/duckbuilder/informer_test.go | 4 +- pkg/reconciler/builder/builder_test.go | 6 +- pkg/reconciler/buildpack/buildpack.go | 1 - .../clusterbuilder/clusterbuilder_test.go | 6 +- .../clusterbuildpack/clusterbuildpack.go | 1 - .../clusterextension/clusterextension.go | 143 ++++++++ .../fake_store_reader.go | 125 +++++++ pkg/reconciler/extension/extension.go | 144 ++++++++ .../extensionfakes/fake_store_reader.go | 125 +++++++ pkg/reconciler/image/image_test.go | 8 +- test/execute_build_test.go | 332 +++++++++++++++++- 31 files changed, 1182 insertions(+), 82 deletions(-) create mode 100644 config/clusterextension.yaml create mode 100644 config/extension.yaml create mode 100644 pkg/reconciler/clusterextension/clusterextension.go create mode 100644 pkg/reconciler/clusterextension/clusterextensionfakes/fake_store_reader.go create mode 100644 pkg/reconciler/extension/extension.go create mode 100644 pkg/reconciler/extension/extensionfakes/fake_store_reader.go diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index c7f5d0680..8fc21be6e 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -91,12 +91,6 @@ IMAGE_REGISTRY=gcr.io/ \ make e2e ``` -* The IMAGE_REGISTRY environment variable must point at a registry with local write access - e.g. - -```bash -export IMAGE_REGISTRY="gcr.io/" -``` - * The KPACK_TEST_NAMESPACE_LABELS environment variable allows you to define additional labels for the test namespace, e.g. ```bash diff --git a/Makefile b/Makefile index 08fd0b127..75ba5909a 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,6 @@ unit-ci: $(GOCMD) test ./pkg/... -coverprofile=coverage.txt -covermode=atomic e2e: - $(GOCMD) test --timeout=30m -v ./test/... + $(GOCMD) test --timeout=30m -failfast -v ./test/... .PHONY: unit unit-ci e2e diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 5ec3b38ec..96b723a8b 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -50,8 +50,10 @@ import ( "github.com/pivotal/kpack/pkg/reconciler/buildpack" "github.com/pivotal/kpack/pkg/reconciler/clusterbuilder" "github.com/pivotal/kpack/pkg/reconciler/clusterbuildpack" + "github.com/pivotal/kpack/pkg/reconciler/clusterextension" "github.com/pivotal/kpack/pkg/reconciler/clusterstack" "github.com/pivotal/kpack/pkg/reconciler/clusterstore" + "github.com/pivotal/kpack/pkg/reconciler/extension" "github.com/pivotal/kpack/pkg/reconciler/image" "github.com/pivotal/kpack/pkg/reconciler/lifecycle" "github.com/pivotal/kpack/pkg/reconciler/sourceresolver" @@ -207,8 +209,10 @@ func main() { sourceResolverController := sourceresolver.NewController(ctx, options, sourceResolverInformer, gitResolver, blobResolver, registryResolver) builderController, builderResync := builder.NewController(ctx, options, builderInformer, builderCreator, keychainFactory, clusterStoreInformer, buildpackInformer, clusterBuildpackInformer, clusterStackInformer, extensionInformer, clusterExtensionInformer) buildpackController := buildpack.NewController(ctx, options, keychainFactory, buildpackInformer, remoteStoreReader) + extensionController := extension.NewController(ctx, options, keychainFactory, extensionInformer, remoteStoreReader) clusterBuilderController, clusterBuilderResync := clusterbuilder.NewController(ctx, options, clusterBuilderInformer, builderCreator, keychainFactory, clusterStoreInformer, clusterBuildpackInformer, clusterStackInformer, clusterExtensionInformer) clusterBuildpackController := clusterbuildpack.NewController(ctx, options, keychainFactory, clusterBuildpackInformer, remoteStoreReader) + clusterExtensionController := clusterextension.NewController(ctx, options, keychainFactory, clusterExtensionInformer, remoteStoreReader) clusterStoreController := clusterstore.NewController(ctx, options, keychainFactory, clusterStoreInformer, remoteStoreReader) clusterStackController := clusterstack.NewController(ctx, options, keychainFactory, clusterStackInformer, remoteStackReader) lifecycleController := lifecycle.NewController(ctx, options, k8sClient, config.LifecycleConfigName, lifecycleConfigmapInformer, lifecycleProvider) @@ -243,8 +247,10 @@ func main() { run(buildController, routinesPerController), run(builderController, routinesPerController), run(buildpackController, routinesPerController), + run(extensionController, routinesPerController), run(clusterBuilderController, routinesPerController), run(clusterBuildpackController, routinesPerController), + run(clusterExtensionController, routinesPerController), run(clusterStoreController, routinesPerController), run(lifecycleController, routinesPerController), run(sourceResolverController, 2*routinesPerController), diff --git a/config/clusterextension.yaml b/config/clusterextension.yaml new file mode 100644 index 000000000..8e8b01147 --- /dev/null +++ b/config/clusterextension.yaml @@ -0,0 +1,31 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: clusterextensions.kpack.io +spec: + group: kpack.io + versions: + - name: v1alpha2 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + x-kubernetes-preserve-unknown-fields: true + subresources: + status: {} + additionalPrinterColumns: + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" + names: + kind: ClusterExtension + listKind: ClusterExtensionList + singular: clusterextension + plural: clusterextensions + shortNames: + - clstext + - clstexts + categories: + - kpack + scope: Cluster diff --git a/config/controllerrole.yaml b/config/controllerrole.yaml index 61e5b2af0..972037141 100644 --- a/config/controllerrole.yaml +++ b/config/controllerrole.yaml @@ -23,10 +23,14 @@ rules: - builders/status - buildpacks - buildpacks/status + - extensions + - extensions/status - clusterbuilders - clusterbuilders/status - clusterbuildpacks - clusterbuildpacks/status + - clusterextensions + - clusterextensions/status - clusterstores - clusterstores/status - clusterstacks diff --git a/config/extension.yaml b/config/extension.yaml new file mode 100644 index 000000000..005c47028 --- /dev/null +++ b/config/extension.yaml @@ -0,0 +1,32 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: extensions.kpack.io +spec: + group: kpack.io + versions: + - name: v1alpha2 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + x-kubernetes-preserve-unknown-fields: true + subresources: + status: {} + additionalPrinterColumns: + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" + names: + kind: Extension + listKind: ExtensionList + singular: extension + plural: extensions + shortNames: + - ext + - exts + categories: + - kpack + scope: Namespaced + diff --git a/go.sum b/go.sum index c3b91b4d8..875708d30 100644 --- a/go.sum +++ b/go.sum @@ -58,7 +58,7 @@ dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= -github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230618160516-e936619f9f18 h1:rd389Q26LMy03gG4anandGFC2LW/xvjga5GezeeaxQk= +github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d h1:zjqpY4C7H15HjRPEenkS4SAn3Jy2eRRjkjZbGR30TOg= github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0 h1:8+4G8JaejP8Xa6W46PzJEwisNgBXMvFcz78N6zG/ARw= github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0/go.mod h1:GgeIE+1be8Ivm7Sh4RgwI42aTtC9qrcj+Y9Y6CjJhJs= github.com/Azure/azure-sdk-for-go v55.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= diff --git a/pkg/apis/build/v1alpha2/build_pod.go b/pkg/apis/build/v1alpha2/build_pod.go index 21a89dd00..3a0ead662 100644 --- a/pkg/apis/build/v1alpha2/build_pod.go +++ b/pkg/apis/build/v1alpha2/build_pod.go @@ -126,12 +126,13 @@ func (c BuildContext) os() string { } type BuildPodBuilderConfig struct { - StackID string - RunImage string - Uid int64 - Gid int64 - PlatformAPIs []string - OS string + StackID string + RunImage string + Uid int64 + Gid int64 + PlatformAPIs []string + OS string + HasExtensions bool } var ( @@ -629,6 +630,10 @@ func (b *Build) BuildPod(images BuildPodImages, buildContext BuildContext) (*cor }, } + if buildContext.BuildPodBuilderConfig.HasExtensions && buildContext.os() != "windows" { + b.useImageExtensions(pod) + } + if buildContext.InjectedSidecarSupport && buildContext.os() != "windows" { pod = b.useStandardContainers(images.BuildWaiterImage, pod) } @@ -712,8 +717,42 @@ func setUpBuildWaiter(container corev1.Container, waitFile string) corev1.Contai } -func (b *Build) useStandardContainers(buildWaiterImage string, pod *corev1.Pod) *corev1.Pod { +func (b *Build) useImageExtensions(pod *corev1.Pod) { + lifecycleExperimentalEnvVar := corev1.EnvVar{Name: "CNB_EXPERIMENTAL_MODE", Value: "warn"} + kanikoVolume := corev1.Volume{ + Name: "kaniko", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + } + pod.Spec.Volumes = append(pod.Spec.Volumes, kanikoVolume) + kanikoMount := corev1.VolumeMount{ + Name: "kaniko", + MountPath: "/kaniko", + } + + for idx, container := range pod.Spec.InitContainers { + container.Env = append(container.Env, lifecycleExperimentalEnvVar) + switch container.Name { + case RestoreContainerName: + container.VolumeMounts = append(container.VolumeMounts, kanikoMount) + container.Args = append(container.Args, fmt.Sprintf("-build-image=%s", b.Spec.Builder.Image)) + case BuildContainerName: + runAsNonRoot := false + rootUser := int64(0) + container.Name = ExtendContainerName + container.Command = []string{"/cnb/lifecycle/extender"} + container.VolumeMounts = append(container.VolumeMounts, kanikoMount) + container.SecurityContext.RunAsNonRoot = &runAsNonRoot + container.SecurityContext.RunAsUser = &rootUser + } + pod.Spec.InitContainers[idx] = container + } + +} + +func (b *Build) useStandardContainers(buildWaiterImage string, pod *corev1.Pod) *corev1.Pod { containers := pod.Spec.InitContainers pod.Spec.InitContainers = []corev1.Container{ { @@ -1085,7 +1124,12 @@ func (b *Build) setupCosignVolumes(secrets []corev1.Secret) ([]corev1.Volume, [] } var ( - supportedPlatformAPIVersions = []*semver.Version{semver.MustParse("0.9"), semver.MustParse("0.8"), semver.MustParse("0.7")} + supportedPlatformAPIVersions = []*semver.Version{ + semver.MustParse("0.10"), + semver.MustParse("0.9"), + semver.MustParse("0.8"), + semver.MustParse("0.7"), + } ) func (bc BuildContext) highestSupportedPlatformAPI(b *Build) (*semver.Version, error) { diff --git a/pkg/apis/build/v1alpha2/build_pod_test.go b/pkg/apis/build/v1alpha2/build_pod_test.go index 6ec78d9fb..e89ee8d10 100644 --- a/pkg/apis/build/v1alpha2/build_pod_test.go +++ b/pkg/apis/build/v1alpha2/build_pod_test.go @@ -889,6 +889,7 @@ func testBuildPod(t *testing.T, when spec.G, it spec.S) { "someimage/name:tag3", }, pod.Spec.InitContainers[5].Args) }) + it("configures export step with non-web default process", func() { build.Spec.DefaultProcess = "sys-info" pod, err := build.BuildPod(config, buildContext) @@ -2460,6 +2461,89 @@ func testBuildPod(t *testing.T, when spec.G, it spec.S) { }) }) + when("builder has extensions", func() { + it.Before(func() { + buildContext.BuildPodBuilderConfig.HasExtensions = true + }) + + it("sets CNB_EXPERIMENTAL_MODE=warn in the lifecycle env", func() { + pod, err := build.BuildPod(config, buildContext) + require.NoError(t, err) + + for _, container := range pod.Spec.InitContainers { + assert.Contains(t, container.Env, + corev1.EnvVar{ + Name: "CNB_EXPERIMENTAL_MODE", + Value: "warn", + }, + ) + } + }) + + it("provides -build-image to the restorer", func() { + pod, err := build.BuildPod(config, buildContext) + require.NoError(t, err) + + assert.Contains(t, pod.Spec.InitContainers[3].Args, "-build-image="+builderImage) + }) + + it("adds kaniko volume to pod and mounts it during restore and extend", func() { + pod, err := build.BuildPod(config, buildContext) + require.NoError(t, err) + + assert.Contains(t, pod.Spec.Volumes, corev1.Volume{ + Name: "kaniko", + VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}, + }) + + for _, container := range pod.Spec.InitContainers { + switch container.Name { + case buildapi.RestoreContainerName, buildapi.ExtendContainerName: + assert.Contains(t, container.VolumeMounts, corev1.VolumeMount{ + Name: "kaniko", + MountPath: "/kaniko", + }) + default: + assert.NotContains(t, container.VolumeMounts, corev1.VolumeMount{ + Name: "kaniko", + MountPath: "/kaniko", + }) + } + } + }) + + it("runs the extender (as root) instead of the builder", func() { + pod, err := build.BuildPod(config, buildContext) + require.NoError(t, err) + + assert.Equal(t, buildapi.ExtendContainerName, pod.Spec.InitContainers[4].Name) + assert.Equal(t, []string{"/cnb/lifecycle/extender"}, pod.Spec.InitContainers[4].Command) + + for _, container := range pod.Spec.InitContainers { + actualRunAsNonRoot := container.SecurityContext.RunAsNonRoot + actualRunAsUser := container.SecurityContext.RunAsUser + switch container.Name { + case buildapi.ExtendContainerName: + assert.Equal(t, false, *actualRunAsNonRoot) + assert.Equal(t, int64(0), *actualRunAsUser) + default: + assert.Equal(t, true, *actualRunAsNonRoot) + assert.NotEqual(t, nil, actualRunAsUser) // in real life this would be the user from the builder + } + } + }) + + it("is possible to use standard containers", func() { + buildContext.InjectedSidecarSupport = true + config.BuildWaiterImage = "some-image" + + pod, err := build.BuildPod(config, buildContext) + require.NoError(t, err) + + assert.Equal(t, buildapi.ExtendContainerName, pod.Spec.Containers[4].Name) + }) + }) + when("complying with the restricted pod security standard", func() { // enforces https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted var pod *corev1.Pod diff --git a/pkg/apis/build/v1alpha2/builder_conversion.go b/pkg/apis/build/v1alpha2/builder_conversion.go index 3c0cf6ef8..19b5e3c49 100644 --- a/pkg/apis/build/v1alpha2/builder_conversion.go +++ b/pkg/apis/build/v1alpha2/builder_conversion.go @@ -82,7 +82,7 @@ func (bs *NamespacedBuilderSpec) convertFrom(from *v1alpha1.NamespacedBuilderSpe func (bst *BuilderStatus) convertFrom(from *v1alpha1.BuilderStatus) { bst.Status = from.Status - bst.BuilderMetadata = from.BuilderMetadata + bst.BuilderMetadataBuildpacks = from.BuilderMetadata bst.Order = from.Order bst.Stack = from.Stack bst.LatestImage = from.LatestImage @@ -93,7 +93,7 @@ func (bst *BuilderStatus) convertFrom(from *v1alpha1.BuilderStatus) { func (bst *BuilderStatus) convertTo(to *v1alpha1.BuilderStatus) { to.Status = bst.Status - to.BuilderMetadata = bst.BuilderMetadata + to.BuilderMetadata = bst.BuilderMetadataBuildpacks to.Order = bst.Order to.Stack = bst.Stack to.LatestImage = bst.LatestImage diff --git a/pkg/apis/build/v1alpha2/builder_conversion_test.go b/pkg/apis/build/v1alpha2/builder_conversion_test.go index 94330e400..4de87d954 100644 --- a/pkg/apis/build/v1alpha2/builder_conversion_test.go +++ b/pkg/apis/build/v1alpha2/builder_conversion_test.go @@ -58,9 +58,9 @@ func testBuilderConversion(t *testing.T, when spec.G, it spec.S) { ServiceAccountName: "some-service-account", }, Status: BuilderStatus{ - Status: corev1alpha1.Status{Conditions: corev1alpha1.Conditions{{Type: "some-type"}}}, - BuilderMetadata: nil, - Order: nil, + Status: corev1alpha1.Status{Conditions: corev1alpha1.Conditions{{Type: "some-type"}}}, + BuilderMetadataBuildpacks: nil, + Order: nil, Stack: corev1alpha1.BuildStack{ RunImage: "", ID: "", diff --git a/pkg/apis/build/v1alpha2/builder_lifecycle.go b/pkg/apis/build/v1alpha2/builder_lifecycle.go index 9ebceee22..d351d654b 100644 --- a/pkg/apis/build/v1alpha2/builder_lifecycle.go +++ b/pkg/apis/build/v1alpha2/builder_lifecycle.go @@ -12,6 +12,7 @@ type BuilderRecord struct { Image string Stack corev1alpha1.BuildStack Buildpacks corev1alpha1.BuildpackMetadataList + Extensions corev1alpha1.BuildpackMetadataList Order []corev1alpha1.OrderEntry OrderExtensions []corev1alpha1.OrderEntry ObservedStoreGeneration int64 @@ -21,7 +22,8 @@ type BuilderRecord struct { func (bs *BuilderStatus) BuilderRecord(record BuilderRecord) { bs.Stack = record.Stack - bs.BuilderMetadata = record.Buildpacks + bs.BuilderMetadataBuildpacks = record.Buildpacks + bs.BuilderMetadataExtensions = record.Extensions bs.LatestImage = record.Image bs.Conditions = corev1alpha1.Conditions{ { diff --git a/pkg/apis/build/v1alpha2/builder_types.go b/pkg/apis/build/v1alpha2/builder_types.go index b4449d0d4..e39d2c836 100644 --- a/pkg/apis/build/v1alpha2/builder_types.go +++ b/pkg/apis/build/v1alpha2/builder_types.go @@ -58,15 +58,16 @@ type NamespacedBuilderSpec struct { // +k8s:openapi-gen=true type BuilderStatus struct { - corev1alpha1.Status `json:",inline"` - BuilderMetadata corev1alpha1.BuildpackMetadataList `json:"builderMetadata,omitempty"` - Order []corev1alpha1.OrderEntry `json:"order,omitempty"` - OrderExtensions []corev1alpha1.OrderEntry `json:"order-extensions,omitempty"` - Stack corev1alpha1.BuildStack `json:"stack,omitempty"` - LatestImage string `json:"latestImage,omitempty"` - ObservedStackGeneration int64 `json:"observedStackGeneration,omitempty"` - ObservedStoreGeneration int64 `json:"observedStoreGeneration,omitempty"` - OS string `json:"os,omitempty"` + corev1alpha1.Status `json:",inline"` + BuilderMetadataBuildpacks corev1alpha1.BuildpackMetadataList `json:"builderMetadata,omitempty"` + BuilderMetadataExtensions corev1alpha1.BuildpackMetadataList `json:"builderMetadataExtensions,omitempty"` + Order []corev1alpha1.OrderEntry `json:"order,omitempty"` + OrderExtensions []corev1alpha1.OrderEntry `json:"order-extensions,omitempty"` + Stack corev1alpha1.BuildStack `json:"stack,omitempty"` + LatestImage string `json:"latestImage,omitempty"` + ObservedStackGeneration int64 `json:"observedStackGeneration,omitempty"` + ObservedStoreGeneration int64 `json:"observedStoreGeneration,omitempty"` + OS string `json:"os,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apis/build/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/build/v1alpha2/zz_generated.deepcopy.go index e22d3f26c..52001390b 100644 --- a/pkg/apis/build/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/build/v1alpha2/zz_generated.deepcopy.go @@ -524,8 +524,8 @@ func (in *BuilderSpec) DeepCopy() *BuilderSpec { func (in *BuilderStatus) DeepCopyInto(out *BuilderStatus) { *out = *in in.Status.DeepCopyInto(&out.Status) - if in.BuilderMetadata != nil { - in, out := &in.BuilderMetadata, &out.BuilderMetadata + if in.BuilderMetadataBuildpacks != nil { + in, out := &in.BuilderMetadataBuildpacks, &out.BuilderMetadataBuildpacks *out = make(v1alpha1.BuildpackMetadataList, len(*in)) copy(*out, *in) } diff --git a/pkg/buildpod/generator.go b/pkg/buildpod/generator.go index 41a8b2964..49c997aff 100644 --- a/pkg/buildpod/generator.go +++ b/pkg/buildpod/generator.go @@ -2,10 +2,12 @@ package buildpod import ( "context" + "encoding/json" "fmt" "strconv" "github.com/Masterminds/semver/v3" + "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/platform" "github.com/google/go-containerregistry/pkg/authn" ggcrv1 "github.com/google/go-containerregistry/pkg/v1" @@ -27,6 +29,7 @@ import ( const ( builderMetadataLabel = "io.buildpacks.builder.metadata" + extensionOrderLabel = "io.buildpacks.buildpack.order-extensions" cnbUserId = "CNB_USER_ID" cnbGroupId = "CNB_GROUP_ID" ) @@ -254,15 +257,28 @@ func (g *Generator) fetchBuilderConfig(ctx context.Context, build BuildPodable) } return buildapi.BuildPodBuilderConfig{ - StackID: stackId, - RunImage: metadata.Stack.RunImage.Image, - PlatformAPIs: append(metadata.Lifecycle.APIs.Platform.Deprecated, metadata.Lifecycle.APIs.Platform.Supported...), - Uid: uid, - Gid: gid, - OS: config.OS, + StackID: stackId, + RunImage: metadata.Stack.RunImage.Image, + PlatformAPIs: append(metadata.Lifecycle.APIs.Platform.Deprecated, metadata.Lifecycle.APIs.Platform.Supported...), + Uid: uid, + Gid: gid, + OS: config.OS, + HasExtensions: hasExtensions(image), }, nil } +func hasExtensions(image ggcrv1.Image) bool { + orderExtLabel, err := imagehelpers.GetStringLabel(image, extensionOrderLabel) + if err != nil { + return false + } + var orderExt []buildpack.GroupElement + if err := json.Unmarshal([]byte(orderExtLabel), &orderExt); err != nil { + return false + } + return len(orderExt) > 0 +} + func parseCNBID(image ggcrv1.Image, env string) (int64, error) { v, err := imagehelpers.GetEnv(image, env) if err != nil { diff --git a/pkg/buildpod/generator_test.go b/pkg/buildpod/generator_test.go index 61d615c6c..aa92399a9 100644 --- a/pkg/buildpod/generator_test.go +++ b/pkg/buildpod/generator_test.go @@ -51,10 +51,11 @@ func TestGenerator(t *testing.T) { func testGenerator(t *testing.T, when spec.G, it spec.S) { when("Generate", func() { const ( - serviceAccountName = "serviceAccountName" - namespace = "some-namespace" - windowsBuilderImage = "builder/windows" - linuxBuilderImage = "builder/linux" + serviceAccountName = "serviceAccountName" + namespace = "some-namespace" + windowsBuilderImage = "builder/windows" + linuxBuilderImage = "builder/linux" + linuxBuilderImageWithExtensions = "builder/linux-with-extensions" ) var ( @@ -192,8 +193,9 @@ func testGenerator(t *testing.T, when spec.G, it spec.S) { it.Before(func() { keychainFactory.AddKeychainForSecretRef(t, secretRef, keychain) - imageFetcher.AddImage(linuxBuilderImage, createImage(t, "linux"), keychain) - imageFetcher.AddImage(windowsBuilderImage, createImage(t, "windows"), keychain) + imageFetcher.AddImage(linuxBuilderImage, createImage(t, "linux", false), keychain) + imageFetcher.AddImage(linuxBuilderImageWithExtensions, createImage(t, "linux", true), keychain) + imageFetcher.AddImage(windowsBuilderImage, createImage(t, "windows", false), keychain) }) it("invokes the BuildPod with the builder and env config", func() { @@ -238,6 +240,51 @@ func testGenerator(t *testing.T, when spec.G, it spec.S) { }}, build.buildPodCalls) }) + when("order contains extensions", func() { + it("invokes the BuildPod with the builder and env config", func() { + var build = &testBuildPodable{ + serviceAccount: serviceAccountName, + namespace: namespace, + buildBuilderSpec: corev1alpha1.BuildBuilderSpec{ + Image: linuxBuilderImageWithExtensions, + ImagePullSecrets: builderPullSecrets, + }, + } + + pod, err := generator.Generate(context.TODO(), build) + require.NoError(t, err) + assert.NotNil(t, pod) + + assert.Equal(t, []buildPodCall{{ + BuildPodImages: buildPodConfig, + BuildContext: buildapi.BuildContext{ + Secrets: []corev1.Secret{ + *gitSecret, + *dockerSecret, + }, + BuildPodBuilderConfig: buildapi.BuildPodBuilderConfig{ + StackID: "some.stack.id", + RunImage: "some-registry.io/run-image", + Uid: 1234, + Gid: 5678, + PlatformAPIs: []string{"0.4", "0.5", "0.6"}, + OS: "linux", + HasExtensions: true, + }, + Bindings: []buildapi.ServiceBinding{}, + ImagePullSecrets: []corev1.LocalObjectReference{ + { + Name: "image-pull-1", + }, + { + Name: "image-pull-2", + }, + }, + }, + }}, build.buildPodCalls) + }) + }) + it("dedups duplicate secrets on the service account", func() { var build = &testBuildPodable{ serviceAccount: serviceAccountName, @@ -602,7 +649,7 @@ func (tb *testBuildPodable) Services() buildapi.Services { return tb.services } -func createImage(t *testing.T, os string) ggcrv1.Image { +func createImage(t *testing.T, os string, withExtensions bool) ggcrv1.Image { image := randomImage(t) var err error @@ -654,6 +701,12 @@ func createImage(t *testing.T, os string) ggcrv1.Image { }`) require.NoError(t, err) + if withExtensions { + image, err = imagehelpers.SetStringLabel(image, "io.buildpacks.buildpack.order-extensions", //language=json + `[{"group":[{"id":"samples/curl","version":"0.0.1"}]}]`) + require.NoError(t, err) + } + image, err = imagehelpers.SetEnv(image, "CNB_USER_ID", "1234") require.NoError(t, err) diff --git a/pkg/cnb/builder_builder.go b/pkg/cnb/builder_builder.go index c49e92be5..0b7b6e7f9 100644 --- a/pkg/cnb/builder_builder.go +++ b/pkg/cnb/builder_builder.go @@ -35,7 +35,7 @@ const ( var ( normalizedTime = time.Date(1980, time.January, 1, 0, 0, 1, 0, time.UTC) - supportedPlatformApis = []string{"0.3", "0.4", "0.5", "0.6", "0.7", "0.8"} + supportedPlatformApis = []string{"0.3", "0.4", "0.5", "0.6", "0.7", "0.8", "0.9", "0.10"} ) type builderBlder struct { @@ -328,7 +328,7 @@ func (bb *builderBlder) orderLayer() (v1.Layer, error) { orderExt = append(orderExt, tomlOrderEntry{Group: exts}) } - err := toml.NewEncoder(orderBuf).Encode(tomlOrderFile{order, orderExt}) + err := toml.NewEncoder(orderBuf).Encode(tomlOrderFile{Order: order, OrderExtensions: orderExt}) if err != nil { return nil, err } diff --git a/pkg/cnb/create_builder.go b/pkg/cnb/create_builder.go index 9b6d99ea1..3e8f1da5d 100644 --- a/pkg/cnb/create_builder.go +++ b/pkg/cnb/create_builder.go @@ -103,6 +103,7 @@ func (r *RemoteBuilderCreator) CreateBuilder(ctx context.Context, builderKeychai ID: clusterStack.Status.Id, }, Buildpacks: buildpackMetadata(builderBldr.buildpacks()), + Extensions: buildpackMetadata(builderBldr.extensions()), Order: builderBldr.order, OrderExtensions: builderBldr.orderExtensions, ObservedStackGeneration: clusterStack.Status.ObservedGeneration, diff --git a/pkg/duckbuilder/duck_builder.go b/pkg/duckbuilder/duck_builder.go index 0b61a4960..3a7942ee4 100644 --- a/pkg/duckbuilder/duck_builder.go +++ b/pkg/duckbuilder/duck_builder.go @@ -44,8 +44,9 @@ func (b *DuckBuilder) BuildBuilderSpec() corev1alpha1.BuildBuilderSpec { } } +// TODO: add for extensions? func (b *DuckBuilder) BuildpackMetadata() corev1alpha1.BuildpackMetadataList { - return b.Status.BuilderMetadata + return b.Status.BuilderMetadataBuildpacks } func (b *DuckBuilder) RunImage() string { diff --git a/pkg/duckbuilder/duck_builder_test.go b/pkg/duckbuilder/duck_builder_test.go index 80b6b8f33..eae18bb47 100644 --- a/pkg/duckbuilder/duck_builder_test.go +++ b/pkg/duckbuilder/duck_builder_test.go @@ -38,7 +38,7 @@ func testDuckBuilder(t *testing.T, when spec.G, it spec.S) { }, }, }, - BuilderMetadata: corev1alpha1.BuildpackMetadataList{ + BuilderMetadataBuildpacks: corev1alpha1.BuildpackMetadataList{ { Id: "test.builder", Version: "test.version", diff --git a/pkg/duckbuilder/informer_test.go b/pkg/duckbuilder/informer_test.go index 35f4f6846..269c80ae6 100644 --- a/pkg/duckbuilder/informer_test.go +++ b/pkg/duckbuilder/informer_test.go @@ -40,7 +40,7 @@ func testDuckBuilderInformer(t *testing.T, when spec.G, it spec.S) { }, Spec: buildapi.NamespacedBuilderSpec{}, Status: buildapi.BuilderStatus{ - BuilderMetadata: corev1alpha1.BuildpackMetadataList{ + BuilderMetadataBuildpacks: corev1alpha1.BuildpackMetadataList{ { Id: "another-buildpack", Version: "another-version", @@ -57,7 +57,7 @@ func testDuckBuilderInformer(t *testing.T, when spec.G, it spec.S) { }, Spec: buildapi.ClusterBuilderSpec{}, Status: buildapi.BuilderStatus{ - BuilderMetadata: corev1alpha1.BuildpackMetadataList{ + BuilderMetadataBuildpacks: corev1alpha1.BuildpackMetadataList{ { Id: "another-buildpack", Version: "another-version", diff --git a/pkg/reconciler/builder/builder_test.go b/pkg/reconciler/builder/builder_test.go index 13cbcab7a..eb21d0326 100644 --- a/pkg/reconciler/builder/builder_test.go +++ b/pkg/reconciler/builder/builder_test.go @@ -236,7 +236,7 @@ func testBuilderReconciler(t *testing.T, when spec.G, it spec.S) { }, }, }, - BuilderMetadata: []corev1alpha1.BuildpackMetadata{ + BuilderMetadataBuildpacks: []corev1alpha1.BuildpackMetadata{ { Id: "buildpack.id.1", Version: "1.0.0", @@ -317,7 +317,7 @@ func testBuilderReconciler(t *testing.T, when spec.G, it spec.S) { }, }, }, - BuilderMetadata: []corev1alpha1.BuildpackMetadata{}, + BuilderMetadataBuildpacks: []corev1alpha1.BuildpackMetadata{}, Stack: corev1alpha1.BuildStack{ RunImage: "example.com/run-image@sha256:123456", ID: "fake.stack.id", @@ -384,7 +384,7 @@ func testBuilderReconciler(t *testing.T, when spec.G, it spec.S) { }, }, }, - BuilderMetadata: []corev1alpha1.BuildpackMetadata{ + BuilderMetadataBuildpacks: []corev1alpha1.BuildpackMetadata{ { Id: "buildpack.id.1", Version: "1.0.0", diff --git a/pkg/reconciler/buildpack/buildpack.go b/pkg/reconciler/buildpack/buildpack.go index b818d6059..b29fb4f13 100644 --- a/pkg/reconciler/buildpack/buildpack.go +++ b/pkg/reconciler/buildpack/buildpack.go @@ -21,7 +21,6 @@ import ( "github.com/pivotal/kpack/pkg/registry" ) -// TODO: add for extensions const ( ReconcilerName = "Buildpacks" Kind = "Buildpack" diff --git a/pkg/reconciler/clusterbuilder/clusterbuilder_test.go b/pkg/reconciler/clusterbuilder/clusterbuilder_test.go index 6fd76bbe8..b8b11581a 100644 --- a/pkg/reconciler/clusterbuilder/clusterbuilder_test.go +++ b/pkg/reconciler/clusterbuilder/clusterbuilder_test.go @@ -221,7 +221,7 @@ func testClusterBuilderReconciler(t *testing.T, when spec.G, it spec.S) { }, }, }, - BuilderMetadata: []corev1alpha1.BuildpackMetadata{ + BuilderMetadataBuildpacks: []corev1alpha1.BuildpackMetadata{ { Id: "buildpack.id.1", Version: "1.0.0", @@ -301,7 +301,7 @@ func testClusterBuilderReconciler(t *testing.T, when spec.G, it spec.S) { }, }, }, - BuilderMetadata: []corev1alpha1.BuildpackMetadata{}, + BuilderMetadataBuildpacks: []corev1alpha1.BuildpackMetadata{}, Stack: corev1alpha1.BuildStack{ RunImage: "example.com/run-image@sha256:123456", ID: "fake.stack.id", @@ -356,7 +356,7 @@ func testClusterBuilderReconciler(t *testing.T, when spec.G, it spec.S) { }, }, }, - BuilderMetadata: []corev1alpha1.BuildpackMetadata{ + BuilderMetadataBuildpacks: []corev1alpha1.BuildpackMetadata{ { Id: "buildpack.id.1", Version: "1.0.0", diff --git a/pkg/reconciler/clusterbuildpack/clusterbuildpack.go b/pkg/reconciler/clusterbuildpack/clusterbuildpack.go index 300539b35..8d11742ec 100644 --- a/pkg/reconciler/clusterbuildpack/clusterbuildpack.go +++ b/pkg/reconciler/clusterbuildpack/clusterbuildpack.go @@ -21,7 +21,6 @@ import ( "github.com/pivotal/kpack/pkg/registry" ) -// TODO: add for extensions const ( ReconcilerName = "ClusterBuildpacks" Kind = "ClusterBuildpack" diff --git a/pkg/reconciler/clusterextension/clusterextension.go b/pkg/reconciler/clusterextension/clusterextension.go new file mode 100644 index 000000000..b73febb3c --- /dev/null +++ b/pkg/reconciler/clusterextension/clusterextension.go @@ -0,0 +1,143 @@ +package clusterextension + +import ( + "context" + + "github.com/google/go-containerregistry/pkg/authn" + "go.uber.org/zap" + "k8s.io/apimachinery/pkg/api/equality" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/cache" + "knative.dev/pkg/controller" + "knative.dev/pkg/logging/logkey" + + buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" + corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" + "github.com/pivotal/kpack/pkg/client/clientset/versioned" + buildinformers "github.com/pivotal/kpack/pkg/client/informers/externalversions/build/v1alpha2" + buildlisters "github.com/pivotal/kpack/pkg/client/listers/build/v1alpha2" + "github.com/pivotal/kpack/pkg/reconciler" + "github.com/pivotal/kpack/pkg/registry" +) + +const ( + ReconcilerName = "ClusterExtensions" +) + +//go:generate counterfeiter . StoreReader +type StoreReader interface { + ReadExtension(keychain authn.Keychain, storeImages []corev1alpha1.ImageSource) ([]corev1alpha1.BuildpackStatus, error) +} + +func NewController( + ctx context.Context, + opt reconciler.Options, + keychainFactory registry.KeychainFactory, + informer buildinformers.ClusterExtensionInformer, + storeReader StoreReader) *controller.Impl { + c := &Reconciler{ + Client: opt.Client, + Lister: informer.Lister(), + StoreReader: storeReader, + KeychainFactory: keychainFactory, + } + + logger := opt.Logger.With( + zap.String(logkey.Kind, buildapi.ClusterExtensionCRName), + ) + + impl := controller.NewContext( + ctx, + &reconciler.NetworkErrorReconciler{ + Reconciler: c, + }, + controller.ControllerOptions{WorkQueueName: ReconcilerName, Logger: logger}, + ) + informer.Informer().AddEventHandler(controller.HandleAll(impl.Enqueue)) + return impl +} + +type Reconciler struct { + Client versioned.Interface + StoreReader StoreReader + Lister buildlisters.ClusterExtensionLister + KeychainFactory registry.KeychainFactory +} + +func (c *Reconciler) Reconcile(ctx context.Context, key string) error { + _, moduleName, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return err + } + + module, err := c.Lister.Get(moduleName) + if k8serrors.IsNotFound(err) { + return nil + } else if err != nil { + return err + } + + module = module.DeepCopy() + + module, err = c.reconcileStatus(ctx, module) + + updateErr := c.updateStatus(ctx, module) + if updateErr != nil { + return updateErr + } + + if err != nil { + return err + } + return nil +} + +func (c *Reconciler) updateStatus(ctx context.Context, desired *buildapi.ClusterExtension) error { + desired.Status.ObservedGeneration = desired.Generation + + original, err := c.Lister.Get(desired.Name) + if err != nil { + return err + } + + if equality.Semantic.DeepEqual(desired.Status, original.Status) { + return nil + } + + _, err = c.Client.KpackV1alpha2().ClusterExtensions().UpdateStatus(ctx, desired, metav1.UpdateOptions{}) + return err +} + +func (c *Reconciler) reconcileStatus(ctx context.Context, module *buildapi.ClusterExtension) (*buildapi.ClusterExtension, error) { + secretRef := registry.SecretRef{} + + if module.Spec.ServiceAccountRef != nil { + secretRef = registry.SecretRef{ + ServiceAccount: module.Spec.ServiceAccountRef.Name, + Namespace: module.Spec.ServiceAccountRef.Namespace, + } + } + + keychain, err := c.KeychainFactory.KeychainForSecretRef(ctx, secretRef) + if err != nil { + module.Status = buildapi.ClusterExtensionStatus{ + Status: corev1alpha1.CreateStatusWithReadyCondition(module.Generation, err), + } + return module, err + } + + modules, err := c.StoreReader.ReadExtension(keychain, []corev1alpha1.ImageSource{module.Spec.ImageSource}) + if err != nil { + module.Status = buildapi.ClusterExtensionStatus{ + Status: corev1alpha1.CreateStatusWithReadyCondition(module.Generation, err), + } + return module, err + } + + module.Status = buildapi.ClusterExtensionStatus{ + Extensions: modules, + Status: corev1alpha1.CreateStatusWithReadyCondition(module.Generation, nil), + } + return module, nil +} diff --git a/pkg/reconciler/clusterextension/clusterextensionfakes/fake_store_reader.go b/pkg/reconciler/clusterextension/clusterextensionfakes/fake_store_reader.go new file mode 100644 index 000000000..775e2df32 --- /dev/null +++ b/pkg/reconciler/clusterextension/clusterextensionfakes/fake_store_reader.go @@ -0,0 +1,125 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package clusterextensionfakes + +import ( + "sync" + + "github.com/google/go-containerregistry/pkg/authn" + "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" + clusterbuildpack "github.com/pivotal/kpack/pkg/reconciler/clusterextension" +) + +type FakeStoreReader struct { + ReadExtensionStub func(authn.Keychain, []v1alpha1.ImageSource) ([]v1alpha1.BuildpackStatus, error) + readExtensionMutex sync.RWMutex + readExtensionArgsForCall []struct { + arg1 authn.Keychain + arg2 []v1alpha1.ImageSource + } + readExtensionReturns struct { + result1 []v1alpha1.BuildpackStatus + result2 error + } + readExtensionReturnsOnCall map[int]struct { + result1 []v1alpha1.BuildpackStatus + result2 error + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeStoreReader) ReadExtension(arg1 authn.Keychain, arg2 []v1alpha1.ImageSource) ([]v1alpha1.BuildpackStatus, error) { + var arg2Copy []v1alpha1.ImageSource + if arg2 != nil { + arg2Copy = make([]v1alpha1.ImageSource, len(arg2)) + copy(arg2Copy, arg2) + } + fake.readExtensionMutex.Lock() + ret, specificReturn := fake.readExtensionReturnsOnCall[len(fake.readExtensionArgsForCall)] + fake.readExtensionArgsForCall = append(fake.readExtensionArgsForCall, struct { + arg1 authn.Keychain + arg2 []v1alpha1.ImageSource + }{arg1, arg2Copy}) + stub := fake.ReadExtensionStub + fakeReturns := fake.readExtensionReturns + fake.recordInvocation("ReadExtension", []interface{}{arg1, arg2Copy}) + fake.readExtensionMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeStoreReader) ReadExtensionCallCount() int { + fake.readExtensionMutex.RLock() + defer fake.readExtensionMutex.RUnlock() + return len(fake.readExtensionArgsForCall) +} + +func (fake *FakeStoreReader) ReadExtensionCalls(stub func(authn.Keychain, []v1alpha1.ImageSource) ([]v1alpha1.BuildpackStatus, error)) { + fake.readExtensionMutex.Lock() + defer fake.readExtensionMutex.Unlock() + fake.ReadExtensionStub = stub +} + +func (fake *FakeStoreReader) ReadExtensionArgsForCall(i int) (authn.Keychain, []v1alpha1.ImageSource) { + fake.readExtensionMutex.RLock() + defer fake.readExtensionMutex.RUnlock() + argsForCall := fake.readExtensionArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeStoreReader) ReadExtensionReturns(result1 []v1alpha1.BuildpackStatus, result2 error) { + fake.readExtensionMutex.Lock() + defer fake.readExtensionMutex.Unlock() + fake.ReadExtensionStub = nil + fake.readExtensionReturns = struct { + result1 []v1alpha1.BuildpackStatus + result2 error + }{result1, result2} +} + +func (fake *FakeStoreReader) ReadExtensionReturnsOnCall(i int, result1 []v1alpha1.BuildpackStatus, result2 error) { + fake.readExtensionMutex.Lock() + defer fake.readExtensionMutex.Unlock() + fake.ReadExtensionStub = nil + if fake.readExtensionReturnsOnCall == nil { + fake.readExtensionReturnsOnCall = make(map[int]struct { + result1 []v1alpha1.BuildpackStatus + result2 error + }) + } + fake.readExtensionReturnsOnCall[i] = struct { + result1 []v1alpha1.BuildpackStatus + result2 error + }{result1, result2} +} + +func (fake *FakeStoreReader) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.readExtensionMutex.RLock() + defer fake.readExtensionMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeStoreReader) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} + +var _ clusterbuildpack.StoreReader = new(FakeStoreReader) diff --git a/pkg/reconciler/extension/extension.go b/pkg/reconciler/extension/extension.go new file mode 100644 index 000000000..38f917e69 --- /dev/null +++ b/pkg/reconciler/extension/extension.go @@ -0,0 +1,144 @@ +package extension + +import ( + "context" + + "github.com/google/go-containerregistry/pkg/authn" + "go.uber.org/zap" + "k8s.io/apimachinery/pkg/api/equality" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/cache" + "knative.dev/pkg/controller" + "knative.dev/pkg/logging/logkey" + + buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" + corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" + "github.com/pivotal/kpack/pkg/client/clientset/versioned" + buildinformers "github.com/pivotal/kpack/pkg/client/informers/externalversions/build/v1alpha2" + buildlisters "github.com/pivotal/kpack/pkg/client/listers/build/v1alpha2" + "github.com/pivotal/kpack/pkg/reconciler" + "github.com/pivotal/kpack/pkg/registry" +) + +const ( + ReconcilerName = "Extensions" +) + +//go:generate counterfeiter . StoreReader +type StoreReader interface { + ReadExtension(keychain authn.Keychain, storeImages []corev1alpha1.ImageSource) ([]corev1alpha1.BuildpackStatus, error) +} + +func NewController( + ctx context.Context, + opt reconciler.Options, + keychainFactory registry.KeychainFactory, + informer buildinformers.ExtensionInformer, + storeReader StoreReader, +) *controller.Impl { + c := &Reconciler{ + Client: opt.Client, + Lister: informer.Lister(), + StoreReader: storeReader, + KeychainFactory: keychainFactory, + } + + logger := opt.Logger.With( + zap.String(logkey.Kind, buildapi.ExtensionCRName), + ) + + impl := controller.NewContext( + ctx, + &reconciler.NetworkErrorReconciler{ + Reconciler: c, + }, + controller.ControllerOptions{WorkQueueName: ReconcilerName, Logger: logger}, + ) + informer.Informer().AddEventHandler(controller.HandleAll(impl.Enqueue)) + return impl +} + +type Reconciler struct { + Client versioned.Interface + StoreReader StoreReader + Lister buildlisters.ExtensionLister + KeychainFactory registry.KeychainFactory +} + +func (c *Reconciler) Reconcile(ctx context.Context, key string) error { + namespace, moduleName, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return err + } + + module, err := c.Lister.Extensions(namespace).Get(moduleName) + if k8serrors.IsNotFound(err) { + return nil + } else if err != nil { + return err + } + + module = module.DeepCopy() + + module, err = c.reconcileExtensionStatus(ctx, module) + + updateErr := c.updateModuleStatus(ctx, module) + if updateErr != nil { + return updateErr + } + + if err != nil { + return err + } + return nil +} + +func (c *Reconciler) updateModuleStatus(ctx context.Context, desired *buildapi.Extension) error { + desired.Status.ObservedGeneration = desired.Generation + + original, err := c.Lister.Extensions(desired.Namespace).Get(desired.Name) + if err != nil { + return err + } + + if equality.Semantic.DeepEqual(desired.Status, original.Status) { + return nil + } + + _, err = c.Client.KpackV1alpha2().Extensions(desired.Namespace).UpdateStatus(ctx, desired, metav1.UpdateOptions{}) + return err +} + +func (c *Reconciler) reconcileExtensionStatus(ctx context.Context, module *buildapi.Extension) (*buildapi.Extension, error) { + secretRef := registry.SecretRef{} + + if module.Spec.ServiceAccountName != "" { + secretRef = registry.SecretRef{ + ServiceAccount: module.Spec.ServiceAccountName, + Namespace: module.Namespace, + } + } + + keychain, err := c.KeychainFactory.KeychainForSecretRef(ctx, secretRef) + if err != nil { + module.Status = buildapi.ExtensionStatus{ + Status: corev1alpha1.CreateStatusWithReadyCondition(module.Generation, err), + } + return module, err + } + + modules, err := c.StoreReader.ReadExtension(keychain, []corev1alpha1.ImageSource{module.Spec.ImageSource}) + if err != nil { + module.Status = buildapi.ExtensionStatus{ + Status: corev1alpha1.CreateStatusWithReadyCondition(module.Generation, err), + } + return module, err + } + + module.Status = buildapi.ExtensionStatus{ + Extensions: modules, + Status: corev1alpha1.CreateStatusWithReadyCondition(module.Generation, nil), + } + return module, nil +} diff --git a/pkg/reconciler/extension/extensionfakes/fake_store_reader.go b/pkg/reconciler/extension/extensionfakes/fake_store_reader.go new file mode 100644 index 000000000..098b5d020 --- /dev/null +++ b/pkg/reconciler/extension/extensionfakes/fake_store_reader.go @@ -0,0 +1,125 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package extensionfakes + +import ( + "sync" + + "github.com/google/go-containerregistry/pkg/authn" + "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" + "github.com/pivotal/kpack/pkg/reconciler/extension" +) + +type FakeStoreReader struct { + ReadExtensionStub func(authn.Keychain, []v1alpha1.ImageSource) ([]v1alpha1.BuildpackStatus, error) + readExtensionMutex sync.RWMutex + readExtensionArgsForCall []struct { + arg1 authn.Keychain + arg2 []v1alpha1.ImageSource + } + readExtensionReturns struct { + result1 []v1alpha1.BuildpackStatus + result2 error + } + readExtensionReturnsOnCall map[int]struct { + result1 []v1alpha1.BuildpackStatus + result2 error + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeStoreReader) ReadExtension(arg1 authn.Keychain, arg2 []v1alpha1.ImageSource) ([]v1alpha1.BuildpackStatus, error) { + var arg2Copy []v1alpha1.ImageSource + if arg2 != nil { + arg2Copy = make([]v1alpha1.ImageSource, len(arg2)) + copy(arg2Copy, arg2) + } + fake.readExtensionMutex.Lock() + ret, specificReturn := fake.readExtensionReturnsOnCall[len(fake.readExtensionArgsForCall)] + fake.readExtensionArgsForCall = append(fake.readExtensionArgsForCall, struct { + arg1 authn.Keychain + arg2 []v1alpha1.ImageSource + }{arg1, arg2Copy}) + stub := fake.ReadExtensionStub + fakeReturns := fake.readExtensionReturns + fake.recordInvocation("ReadExtension", []interface{}{arg1, arg2Copy}) + fake.readExtensionMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeStoreReader) ReadExtensionCallCount() int { + fake.readExtensionMutex.RLock() + defer fake.readExtensionMutex.RUnlock() + return len(fake.readExtensionArgsForCall) +} + +func (fake *FakeStoreReader) ReadExtensionCalls(stub func(authn.Keychain, []v1alpha1.ImageSource) ([]v1alpha1.BuildpackStatus, error)) { + fake.readExtensionMutex.Lock() + defer fake.readExtensionMutex.Unlock() + fake.ReadExtensionStub = stub +} + +func (fake *FakeStoreReader) ReadExtensionArgsForCall(i int) (authn.Keychain, []v1alpha1.ImageSource) { + fake.readExtensionMutex.RLock() + defer fake.readExtensionMutex.RUnlock() + argsForCall := fake.readExtensionArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeStoreReader) ReadExtensionReturns(result1 []v1alpha1.BuildpackStatus, result2 error) { + fake.readExtensionMutex.Lock() + defer fake.readExtensionMutex.Unlock() + fake.ReadExtensionStub = nil + fake.readExtensionReturns = struct { + result1 []v1alpha1.BuildpackStatus + result2 error + }{result1, result2} +} + +func (fake *FakeStoreReader) ReadExtensionReturnsOnCall(i int, result1 []v1alpha1.BuildpackStatus, result2 error) { + fake.readExtensionMutex.Lock() + defer fake.readExtensionMutex.Unlock() + fake.ReadExtensionStub = nil + if fake.readExtensionReturnsOnCall == nil { + fake.readExtensionReturnsOnCall = make(map[int]struct { + result1 []v1alpha1.BuildpackStatus + result2 error + }) + } + fake.readExtensionReturnsOnCall[i] = struct { + result1 []v1alpha1.BuildpackStatus + result2 error + }{result1, result2} +} + +func (fake *FakeStoreReader) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.readExtensionMutex.RLock() + defer fake.readExtensionMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeStoreReader) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} + +var _ extension.StoreReader = new(FakeStoreReader) diff --git a/pkg/reconciler/image/image_test.go b/pkg/reconciler/image/image_test.go index 0343e53ee..679642d56 100644 --- a/pkg/reconciler/image/image_test.go +++ b/pkg/reconciler/image/image_test.go @@ -156,7 +156,7 @@ func testImageReconciler(t *testing.T, when spec.G, it spec.S) { }, Status: buildapi.BuilderStatus{ LatestImage: "some/builder@sha256:acf123", - BuilderMetadata: corev1alpha1.BuildpackMetadataList{ + BuilderMetadataBuildpacks: corev1alpha1.BuildpackMetadataList{ { Id: "buildpack.version", Version: "version", @@ -191,7 +191,7 @@ func testImageReconciler(t *testing.T, when spec.G, it spec.S) { }, Status: buildapi.BuilderStatus{ LatestImage: "some/clusterbuilder@sha256:acf123", - BuilderMetadata: corev1alpha1.BuildpackMetadataList{ + BuilderMetadataBuildpacks: corev1alpha1.BuildpackMetadataList{ { Id: "buildpack.version", Version: "version", @@ -1594,7 +1594,7 @@ func testImageReconciler(t *testing.T, when spec.G, it spec.S) { RunImage: "some/run@sha256:67e3de2af270bf09c02e9a644aeb7e87e6b3c049abe6766bf6b6c3728a83e7fb", ID: "io.buildpacks.stacks.bionic", }, - BuilderMetadata: corev1alpha1.BuildpackMetadataList{ + BuilderMetadataBuildpacks: corev1alpha1.BuildpackMetadataList{ { Id: "io.buildpack", Version: "new-version", @@ -1764,7 +1764,7 @@ func testImageReconciler(t *testing.T, when spec.G, it spec.S) { RunImage: updatedBuilderRunImage, ID: "io.buildpacks.stacks.bionic", }, - BuilderMetadata: corev1alpha1.BuildpackMetadataList{ + BuilderMetadataBuildpacks: corev1alpha1.BuildpackMetadataList{ { Id: "io.buildpack", Version: "version", diff --git a/test/execute_build_test.go b/test/execute_build_test.go index 6be2d8452..3cad5ddf6 100644 --- a/test/execute_build_test.go +++ b/test/execute_build_test.go @@ -11,8 +11,10 @@ import ( "testing" "time" + "github.com/buildpacks/lifecycle/platform/files" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/sclevine/spec" "github.com/stretchr/testify/require" @@ -43,17 +45,21 @@ func TestCreateImage(t *testing.T) { func testCreateImage(t *testing.T, when spec.G, it spec.S) { const ( - testNamespace = "test" - dockerSecret = "docker-secret" - gitBasicSecret = "git-basic-secret" - gitSSHSecret = "git-ssh-secret" - serviceAccountName = "image-service-account" - clusterStoreName = "store" - buildpackName = "buildpack" - clusterBuildpackName = "cluster-buildpack" - clusterStackName = "stack" - builderName = "custom-builder" - clusterBuilderName = "custom-cluster-builder" + testNamespace = "test" + dockerSecret = "docker-secret" + gitBasicSecret = "git-basic-secret" + gitSSHSecret = "git-ssh-secret" + serviceAccountName = "image-service-account" + clusterStoreName = "store" + buildpackName = "buildpack" + extensionName = "extension" + clusterBuildpackName = "cluster-buildpack" + clusterExtensionName = "cluster-extension" + clusterStackName = "stack" + builderName = "custom-builder" + builderWithExtensionsName = "custom-builder-with-extensions" + clusterBuilderName = "custom-cluster-builder" + clusterBuilderWithExtensionsName = "custom-cluster-builder-with-extensions" ) var ( @@ -78,10 +84,18 @@ func testCreateImage(t *testing.T, when spec.G, it spec.S) { Kind: buildapi.BuilderKind, Name: builderName, }, + "custom-builder-with-extensions": { + Kind: buildapi.BuilderKind, + Name: builderWithExtensionsName, + }, "custom-cluster-builder": { Kind: buildapi.ClusterBuilderKind, Name: clusterBuilderName, }, + "custom-cluster-builder-with-extensions": { + Kind: buildapi.ClusterBuilderKind, + Name: clusterBuilderWithExtensionsName, + }, } ) @@ -89,6 +103,10 @@ func testCreateImage(t *testing.T, when spec.G, it spec.S) { for builderType := range builderConfigs { imageName := fmt.Sprintf("%s-%s", name, builderType) builder := builderConfigs[builderType] + var builderHasExtensions bool + if strings.Contains(builder.Name, "extensions") { // TODO: this is a bit hacky, maybe we can improve it + builderHasExtensions = true + } t.Run(imageName, func(t *testing.T) { t.Parallel() @@ -116,7 +134,20 @@ func testCreateImage(t *testing.T, when spec.G, it spec.S) { }, metav1.CreateOptions{}) require.NoError(t, err) - builtImages[validateImageCreate(t, clients, image, expectedResources)] = struct{}{} + expectImage := func(t *testing.T, image v1.Image) {} + if builderHasExtensions { + expectImage = func(t *testing.T, image v1.Image) { + cfg, err := image.ConfigFile() + require.NoError(t, err) + lifecycleMDLabel, ok := cfg.Config.Labels["io.buildpacks.lifecycle.metadata"] + require.True(t, ok) + var lifecycleMD files.LayersMetadata + require.NoError(t, json.Unmarshal([]byte(lifecycleMDLabel), &lifecycleMD)) + require.Equal(t, "gcr.io/paketo-buildpacks/run-jammy-tiny", lifecycleMD.Stack.RunImage.Image) + } + } + + builtImages[validateImageCreate(t, clients, image, expectedResources, expectImage)] = struct{}{} validateRebase(t, ctx, clients, image.Name, testNamespace) }) } @@ -147,11 +178,21 @@ func testCreateImage(t *testing.T, when spec.G, it spec.S) { require.NoError(t, err) } + err = clients.client.KpackV1alpha2().Extensions(testNamespace).Delete(ctx, extensionName, metav1.DeleteOptions{}) + if !errors.IsNotFound(err) { + require.NoError(t, err) + } + err = clients.client.KpackV1alpha2().ClusterBuildpacks().Delete(ctx, clusterBuildpackName, metav1.DeleteOptions{}) if !errors.IsNotFound(err) { require.NoError(t, err) } + err = clients.client.KpackV1alpha2().ClusterExtensions().Delete(ctx, clusterExtensionName, metav1.DeleteOptions{}) + if !errors.IsNotFound(err) { + require.NoError(t, err) + } + err = clients.client.KpackV1alpha2().ClusterStacks().Delete(ctx, clusterStackName, metav1.DeleteOptions{}) if !errors.IsNotFound(err) { require.NoError(t, err) @@ -162,6 +203,11 @@ func testCreateImage(t *testing.T, when spec.G, it spec.S) { require.NoError(t, err) } + err = clients.client.KpackV1alpha2().ClusterBuilders().Delete(ctx, clusterBuilderWithExtensionsName, metav1.DeleteOptions{}) + if !errors.IsNotFound(err) { + require.NoError(t, err) + } + deleteNamespace(t, ctx, clients, testNamespace) _, err = clients.k8sClient.CoreV1().Namespaces().Create(ctx, &corev1.Namespace{ @@ -233,6 +279,18 @@ func testCreateImage(t *testing.T, when spec.G, it spec.S) { }, metav1.CreateOptions{}) require.NoError(t, err) + _, err = clients.client.KpackV1alpha2().Extensions(testNamespace).Create(ctx, &buildapi.Extension{ + ObjectMeta: metav1.ObjectMeta{ + Name: extensionName, + }, + Spec: buildapi.ExtensionSpec{ + ImageSource: corev1alpha1.ImageSource{ + Image: "natalieparellano/sample-extension", // FIXME + }, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + _, err = clients.client.KpackV1alpha2().ClusterBuildpacks().Create(ctx, &buildapi.ClusterBuildpack{ ObjectMeta: metav1.ObjectMeta{ Name: clusterBuildpackName, @@ -245,6 +303,18 @@ func testCreateImage(t *testing.T, when spec.G, it spec.S) { }, metav1.CreateOptions{}) require.NoError(t, err) + _, err = clients.client.KpackV1alpha2().ClusterExtensions().Create(ctx, &buildapi.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterExtensionName, + }, + Spec: buildapi.ClusterExtensionSpec{ + ImageSource: corev1alpha1.ImageSource{ + Image: "natalieparellano/sample-extension", // FIXME + }, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + _, err = clients.client.KpackV1alpha2().ClusterStacks().Create(ctx, &buildapi.ClusterStack{ ObjectMeta: metav1.ObjectMeta{ Name: clusterStackName, @@ -358,6 +428,116 @@ func testCreateImage(t *testing.T, when spec.G, it spec.S) { }, metav1.CreateOptions{}) require.NoError(t, err) + builderWithExtensions, err := clients.client.KpackV1alpha2().Builders(testNamespace).Create(ctx, &buildapi.Builder{ + ObjectMeta: metav1.ObjectMeta{ + Name: builderWithExtensionsName, + Namespace: testNamespace, + }, + Spec: buildapi.NamespacedBuilderSpec{ + BuilderSpec: buildapi.BuilderSpec{ + Tag: cfg.newImageTag(), + Stack: corev1.ObjectReference{ + Name: clusterStackName, + Kind: "ClusterStack", + }, + Store: corev1.ObjectReference{ + Name: clusterStoreName, + Kind: "ClusterStore", + }, + Order: []buildapi.BuilderOrderEntry{ + { + Group: []buildapi.BuilderBuildpackRef{ + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/go", + }, + }, + }, + }, + }, + { + Group: []buildapi.BuilderBuildpackRef{ + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/nodejs", + }, + }, + }, + }, + }, + { + Group: []buildapi.BuilderBuildpackRef{ + { + ObjectReference: corev1.ObjectReference{ + Name: buildpackName, + Kind: "Buildpack", + }, + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/bellsoft-liberica", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/gradle", + }, + Optional: true, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/syft", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/executable-jar", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/dist-zip", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/spring-boot", + }, + }, + }, + }, + }, + }, + OrderExtensions: []buildapi.BuilderOrderEntry{ + { + Group: []buildapi.BuilderBuildpackRef{ + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "samples/curl", // FIXME + }, + }, + }, + }, + }, + }, + }, + ServiceAccountName: serviceAccountName, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + clusterBuilder, err := clients.client.KpackV1alpha2().ClusterBuilders().Create(ctx, &buildapi.ClusterBuilder{ ObjectMeta: metav1.ObjectMeta{ Name: clusterBuilderName, @@ -452,10 +632,117 @@ func testCreateImage(t *testing.T, when spec.G, it spec.S) { }, metav1.CreateOptions{}) require.NoError(t, err) - waitUntilReady(t, ctx, clients, builder, clusterBuilder) + clusterBuilderWithExtensions, err := clients.client.KpackV1alpha2().ClusterBuilders().Create(ctx, &buildapi.ClusterBuilder{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterBuilderWithExtensionsName, + }, + Spec: buildapi.ClusterBuilderSpec{ + BuilderSpec: buildapi.BuilderSpec{ + Tag: cfg.newImageTag(), + Stack: corev1.ObjectReference{ + Name: clusterStackName, + Kind: "ClusterStack", + }, + Store: corev1.ObjectReference{ + Name: clusterStoreName, + Kind: "ClusterStore", + }, + Order: []buildapi.BuilderOrderEntry{ + { + Group: []buildapi.BuilderBuildpackRef{ + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/go", + }, + }, + }, + }, + }, + { + Group: []buildapi.BuilderBuildpackRef{ + { + ObjectReference: corev1.ObjectReference{ + Name: clusterBuildpackName, + Kind: "ClusterBuildpack", + }, + }, + }, + }, + { + Group: []buildapi.BuilderBuildpackRef{ + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/bellsoft-liberica", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/gradle", + }, + Optional: true, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/syft", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/executable-jar", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/dist-zip", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/spring-boot", + }, + }, + }, + }, + }, + }, + OrderExtensions: []buildapi.BuilderOrderEntry{ + { + Group: []buildapi.BuilderBuildpackRef{ + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "samples/curl", // FIXME + }, + }, + }, + }, + }, + }, + }, + ServiceAccountRef: corev1.ObjectReference{ + Namespace: testNamespace, + Name: serviceAccountName, + }, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + + waitUntilReady(t, ctx, clients, builder, clusterBuilder, builderWithExtensions, clusterBuilderWithExtensions) }) - it("builds and rebases git, blob, and registry images from unauthenticated sources", func() { + it.Focus("builds and rebases git, blob, and registry images from unauthenticated sources", func() { imageSources := map[string]corev1alpha1.SourceConfig{ "git-image": { Git: &corev1alpha1.Git{ @@ -594,7 +881,7 @@ func generateRebuild(ctx *context.Context, t *testing.T, cfg config, clients *cl }, metav1.CreateOptions{}) require.NoError(t, err) - originalImageTag := validateImageCreate(t, clients, image, expectedResources) + originalImageTag := validateImageCreate(t, clients, image, expectedResources, func(t *testing.T, image v1.Image) {}) list, err := clients.client.KpackV1alpha2().Builds(testNamespace).List(*ctx, metav1.ListOptions{ LabelSelector: fmt.Sprintf("image.kpack.io/image=%s", imageName), @@ -615,7 +902,7 @@ func generateRebuild(ctx *context.Context, t *testing.T, cfg config, clients *cl return len(list.Items) == 2 }, 5*time.Second, 1*time.Minute) - rebuiltImageTag := validateImageCreate(t, clients, image, expectedResources) + rebuiltImageTag := validateImageCreate(t, clients, image, expectedResources, func(t *testing.T, image v1.Image) {}) require.Equal(t, originalImageTag, rebuiltImageTag) return originalImageTag @@ -657,7 +944,13 @@ func waitUntilReady(t *testing.T, ctx context.Context, clients *clients, objects } } -func validateImageCreate(t *testing.T, clients *clients, image *buildapi.Image, expectedResources corev1.ResourceRequirements) string { +func validateImageCreate( + t *testing.T, + clients *clients, + image *buildapi.Image, + expectedResources corev1.ResourceRequirements, // TODO: this seems to no longer be used? + expectImage func(*testing.T, v1.Image), +) string { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -671,13 +964,16 @@ func validateImageCreate(t *testing.T, clients *clients, image *buildapi.Image, waitUntilReady(t, ctx, clients, image) registryClient := ®istry.Client{} - _, identifier, err := registryClient.Fetch(authn.DefaultKeychain, image.Spec.Tag) + builtImage, identifier, err := registryClient.Fetch(authn.DefaultKeychain, image.Spec.Tag) require.NoError(t, err) eventually(t, func() bool { return strings.Contains(logTail.String(), "Build successful") }, 1*time.Second, 10*time.Second) + // TODO: expect extend build image with kaniko + expectImage(t, builtImage) + buildList, err := clients.client.KpackV1alpha2().Builds(image.Namespace).List(ctx, metav1.ListOptions{ LabelSelector: fmt.Sprintf("image.kpack.io/image=%s", image.Name), }) From dbaa66d9f57e35114f756603562049ba4c87af0c Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Fri, 13 Oct 2023 15:42:09 -0400 Subject: [PATCH 17/20] WIP: add integration test for build with extensions Signed-off-by: Natalie Arellano --- pkg/apis/build/v1alpha2/build_pod.go | 13 ++++++----- pkg/apis/build/v1alpha2/build_pod_test.go | 9 +++++++- test/execute_build_test.go | 27 +++++++++++------------ 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/pkg/apis/build/v1alpha2/build_pod.go b/pkg/apis/build/v1alpha2/build_pod.go index 3a0ead662..3a2ab2acb 100644 --- a/pkg/apis/build/v1alpha2/build_pod.go +++ b/pkg/apis/build/v1alpha2/build_pod.go @@ -645,6 +645,10 @@ func boolPointer(b bool) *bool { return &b } +func intPointer(i int64) *int64 { + return &i +} + func containerSecurityContext(config BuildPodBuilderConfig) *corev1.SecurityContext { if config.OS == "windows" { return nil @@ -739,17 +743,16 @@ func (b *Build) useImageExtensions(pod *corev1.Pod) { container.VolumeMounts = append(container.VolumeMounts, kanikoMount) container.Args = append(container.Args, fmt.Sprintf("-build-image=%s", b.Spec.Builder.Image)) case BuildContainerName: - runAsNonRoot := false - rootUser := int64(0) container.Name = ExtendContainerName container.Command = []string{"/cnb/lifecycle/extender"} container.VolumeMounts = append(container.VolumeMounts, kanikoMount) - container.SecurityContext.RunAsNonRoot = &runAsNonRoot - container.SecurityContext.RunAsUser = &rootUser + container.SecurityContext.RunAsUser = intPointer(0) + container.SecurityContext.RunAsGroup = intPointer(0) + container.SecurityContext.RunAsNonRoot = boolPointer(false) + container.SecurityContext.Capabilities = &corev1.Capabilities{Add: []corev1.Capability{"SETGID", "SETUID"}} // TODO: check if this is needed if not using kind } pod.Spec.InitContainers[idx] = container } - } func (b *Build) useStandardContainers(buildWaiterImage string, pod *corev1.Pod) *corev1.Pod { diff --git a/pkg/apis/build/v1alpha2/build_pod_test.go b/pkg/apis/build/v1alpha2/build_pod_test.go index e89ee8d10..9044da036 100644 --- a/pkg/apis/build/v1alpha2/build_pod_test.go +++ b/pkg/apis/build/v1alpha2/build_pod_test.go @@ -2520,15 +2520,22 @@ func testBuildPod(t *testing.T, when spec.G, it spec.S) { assert.Equal(t, []string{"/cnb/lifecycle/extender"}, pod.Spec.InitContainers[4].Command) for _, container := range pod.Spec.InitContainers { + // every phase should be unprivileged + actualPrivileged := container.SecurityContext.Privileged + assert.Equal(t, false, *actualPrivileged) + // extend phase should run as root actualRunAsNonRoot := container.SecurityContext.RunAsNonRoot actualRunAsUser := container.SecurityContext.RunAsUser + actualRunAsGroup := container.SecurityContext.RunAsGroup switch container.Name { case buildapi.ExtendContainerName: assert.Equal(t, false, *actualRunAsNonRoot) assert.Equal(t, int64(0), *actualRunAsUser) + assert.Equal(t, int64(0), *actualRunAsGroup) default: assert.Equal(t, true, *actualRunAsNonRoot) - assert.NotEqual(t, nil, actualRunAsUser) // in real life this would be the user from the builder + assert.NotEqual(t, nil, actualRunAsUser) // in real life this would be the uid from the builder + assert.NotEqual(t, nil, actualRunAsGroup) // in real life this would be the gid from the builder } } }) diff --git a/test/execute_build_test.go b/test/execute_build_test.go index 3cad5ddf6..4da628868 100644 --- a/test/execute_build_test.go +++ b/test/execute_build_test.go @@ -135,6 +135,7 @@ func testCreateImage(t *testing.T, when spec.G, it spec.S) { require.NoError(t, err) expectImage := func(t *testing.T, image v1.Image) {} + expectLogs := func(t *testing.T, logs string) {} if builderHasExtensions { expectImage = func(t *testing.T, image v1.Image) { cfg, err := image.ConfigFile() @@ -143,11 +144,15 @@ func testCreateImage(t *testing.T, when spec.G, it spec.S) { require.True(t, ok) var lifecycleMD files.LayersMetadata require.NoError(t, json.Unmarshal([]byte(lifecycleMDLabel), &lifecycleMD)) - require.Equal(t, "gcr.io/paketo-buildpacks/run-jammy-tiny", lifecycleMD.Stack.RunImage.Image) + runImageReference := lifecycleMD.RunImage.Reference + require.Contains(t, runImageReference, "gcr.io/paketo-buildpacks/run-jammy-tiny") + } + expectLogs = func(t *testing.T, logs string) { + require.Contains(t, logs, "Setting up curl") } } - builtImages[validateImageCreate(t, clients, image, expectedResources, expectImage)] = struct{}{} + builtImages[validateImageCreate(t, clients, image, expectedResources, expectImage, expectLogs)] = struct{}{} validateRebase(t, ctx, clients, image.Name, testNamespace) }) } @@ -881,7 +886,7 @@ func generateRebuild(ctx *context.Context, t *testing.T, cfg config, clients *cl }, metav1.CreateOptions{}) require.NoError(t, err) - originalImageTag := validateImageCreate(t, clients, image, expectedResources, func(t *testing.T, image v1.Image) {}) + originalImageTag := validateImageCreate(t, clients, image, expectedResources, func(t *testing.T, image v1.Image) {}, func(t *testing.T, logs string) {}) list, err := clients.client.KpackV1alpha2().Builds(testNamespace).List(*ctx, metav1.ListOptions{ LabelSelector: fmt.Sprintf("image.kpack.io/image=%s", imageName), @@ -902,7 +907,7 @@ func generateRebuild(ctx *context.Context, t *testing.T, cfg config, clients *cl return len(list.Items) == 2 }, 5*time.Second, 1*time.Minute) - rebuiltImageTag := validateImageCreate(t, clients, image, expectedResources, func(t *testing.T, image v1.Image) {}) + rebuiltImageTag := validateImageCreate(t, clients, image, expectedResources, func(t *testing.T, image v1.Image) {}, func(t *testing.T, logs string) {}) require.Equal(t, originalImageTag, rebuiltImageTag) return originalImageTag @@ -928,11 +933,11 @@ func readNamespaceLabelsFromEnv() map[string]string { func waitUntilReady(t *testing.T, ctx context.Context, clients *clients, objects ...kmeta.OwnerRefable) { for _, ob := range objects { namespace := ob.GetObjectMeta().GetNamespace() - name := ob.GetObjectMeta().GetName() + imageName := ob.GetObjectMeta().GetName() gvr, _ := meta.UnsafeGuessKindToResource(ob.GetGroupVersionKind()) eventually(t, func() bool { - unstructured, err := clients.dynamicClient.Resource(gvr).Namespace(namespace).Get(ctx, name, metav1.GetOptions{}) + unstructured, err := clients.dynamicClient.Resource(gvr).Namespace(namespace).Get(ctx, imageName, metav1.GetOptions{}) require.NoError(t, err) kResource := &duckv1.KResource{} @@ -944,13 +949,7 @@ func waitUntilReady(t *testing.T, ctx context.Context, clients *clients, objects } } -func validateImageCreate( - t *testing.T, - clients *clients, - image *buildapi.Image, - expectedResources corev1.ResourceRequirements, // TODO: this seems to no longer be used? - expectImage func(*testing.T, v1.Image), -) string { +func validateImageCreate(t *testing.T, clients *clients, image *buildapi.Image, expectedResources corev1.ResourceRequirements, expectImage func(*testing.T, v1.Image), expectLogs func(*testing.T, string)) string { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -971,8 +970,8 @@ func validateImageCreate( return strings.Contains(logTail.String(), "Build successful") }, 1*time.Second, 10*time.Second) - // TODO: expect extend build image with kaniko expectImage(t, builtImage) + expectLogs(t, logTail.String()) buildList, err := clients.client.KpackV1alpha2().Builds(image.Namespace).List(ctx, metav1.ListOptions{ LabelSelector: fmt.Sprintf("image.kpack.io/image=%s", image.Name), From a029735c45642bb9222dfe46c8b03a3901ca9719 Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Mon, 16 Oct 2023 13:43:18 -0400 Subject: [PATCH 18/20] Update go.sum Signed-off-by: Natalie Arellano --- go.sum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.sum b/go.sum index 9bc4f767f..4d6aa3c95 100644 --- a/go.sum +++ b/go.sum @@ -58,7 +58,7 @@ dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= -github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d h1:zjqpY4C7H15HjRPEenkS4SAn3Jy2eRRjkjZbGR30TOg= +github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230618160516-e936619f9f18 h1:rd389Q26LMy03gG4anandGFC2LW/xvjga5GezeeaxQk= github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0 h1:8+4G8JaejP8Xa6W46PzJEwisNgBXMvFcz78N6zG/ARw= github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0/go.mod h1:GgeIE+1be8Ivm7Sh4RgwI42aTtC9qrcj+Y9Y6CjJhJs= github.com/Azure/azure-sdk-for-go v55.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= From d6036e45e5efba0b7b7c8e08cf310895e357b47c Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Mon, 16 Oct 2023 16:54:48 -0400 Subject: [PATCH 19/20] Tests seem to pass Signed-off-by: Natalie Arellano --- cmd/controller/main.go | 8 +- go.mod | 2 - pkg/apis/build/v1alpha2/build.go | 10 - pkg/apis/build/v1alpha2/build_conversion.go | 4 +- .../build/v1alpha2/build_conversion_test.go | 2 +- pkg/apis/build/v1alpha2/build_pod.go | 12 +- pkg/apis/build/v1alpha2/build_types.go | 13 +- pkg/apis/build/v1alpha2/builder_resource.go | 1 + pkg/apis/build/v1alpha2/image_builds.go | 1 + pkg/apis/build/v1alpha2/image_builds_test.go | 27 ++- .../build/v1alpha2/zz_generated.deepcopy.go | 4 +- pkg/buildchange/buildpack_change.go | 6 +- pkg/buildchange/extension_change.go | 40 ++++ pkg/cnb/build_metadata.go | 34 ++- pkg/duckbuilder/duck_builder.go | 5 +- pkg/reconciler/build/build.go | 25 +- pkg/reconciler/build/build_test.go | 19 +- pkg/reconciler/image/build_required.go | 37 ++- pkg/reconciler/image/build_required_test.go | 224 +++++++++++++----- pkg/reconciler/image/image_test.go | 4 +- test/execute_build_test.go | 22 +- 21 files changed, 343 insertions(+), 157 deletions(-) create mode 100644 pkg/buildchange/extension_change.go diff --git a/cmd/controller/main.go b/cmd/controller/main.go index f1ed25948..867c17a11 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -9,13 +9,9 @@ import ( "os" "time" - "github.com/pivotal/kpack/pkg/secret" - + "github.com/Masterminds/semver/v3" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" - - "github.com/pivotal/kpack/pkg/cosign" - "go.uber.org/zap" "golang.org/x/sync/errgroup" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -46,6 +42,7 @@ import ( "github.com/pivotal/kpack/pkg/client/informers/externalversions" "github.com/pivotal/kpack/pkg/cnb" "github.com/pivotal/kpack/pkg/config" + "github.com/pivotal/kpack/pkg/cosign" "github.com/pivotal/kpack/pkg/dockercreds/k8sdockercreds" "github.com/pivotal/kpack/pkg/duckbuilder" "github.com/pivotal/kpack/pkg/flaghelpers" @@ -64,6 +61,7 @@ import ( "github.com/pivotal/kpack/pkg/reconciler/lifecycle" "github.com/pivotal/kpack/pkg/reconciler/sourceresolver" "github.com/pivotal/kpack/pkg/registry" + "github.com/pivotal/kpack/pkg/secret" ) const ( diff --git a/go.mod b/go.mod index b5c8f4c17..e5439f45e 100644 --- a/go.mod +++ b/go.mod @@ -324,5 +324,3 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) - -replace github.com/AdamKorcz/go-fuzz-headers-1 => github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d diff --git a/pkg/apis/build/v1alpha2/build.go b/pkg/apis/build/v1alpha2/build.go index 0714cc7e4..845685cf8 100644 --- a/pkg/apis/build/v1alpha2/build.go +++ b/pkg/apis/build/v1alpha2/build.go @@ -200,16 +200,6 @@ func (b *Build) builtWithStack(runImage string) bool { return lastBuildRunImageRef.Identifier() == builderRunImageRef.Identifier() } -func (b *Build) builtWithBuildpacks(buildpacks corev1alpha1.BuildpackMetadataList) bool { - for _, bp := range b.Status.BuildMetadata { - if !buildpacks.Include(bp) { - return false - } - } - - return true -} - func (b *Build) additionalBuildNeeded() bool { _, ok := b.Annotations[BuildNeededAnnotation] return ok diff --git a/pkg/apis/build/v1alpha2/build_conversion.go b/pkg/apis/build/v1alpha2/build_conversion.go index 00dd29cb3..2ae70fdd1 100644 --- a/pkg/apis/build/v1alpha2/build_conversion.go +++ b/pkg/apis/build/v1alpha2/build_conversion.go @@ -80,7 +80,7 @@ func (bs *BuildSpec) convertFrom(from *v1alpha1.BuildSpec) { func (bs *BuildStatus) convertFrom(from *v1alpha1.BuildStatus) { bs.Status = from.Status - bs.BuildMetadata = from.BuildMetadata + bs.BuildMetadataBuildpacks = from.BuildMetadata bs.Stack = from.Stack bs.LatestImage = from.LatestImage bs.PodName = from.PodName @@ -90,7 +90,7 @@ func (bs *BuildStatus) convertFrom(from *v1alpha1.BuildStatus) { func (bs *BuildStatus) convertTo(to *v1alpha1.BuildStatus) { to.Status = bs.Status - to.BuildMetadata = bs.BuildMetadata + to.BuildMetadata = bs.BuildMetadataBuildpacks to.Stack = bs.Stack to.LatestImage = bs.LatestImage to.PodName = bs.PodName diff --git a/pkg/apis/build/v1alpha2/build_conversion_test.go b/pkg/apis/build/v1alpha2/build_conversion_test.go index 2a719b112..479c06c56 100644 --- a/pkg/apis/build/v1alpha2/build_conversion_test.go +++ b/pkg/apis/build/v1alpha2/build_conversion_test.go @@ -68,7 +68,7 @@ func testBuildConversion(t *testing.T, when spec.G, it spec.S) { ObservedGeneration: 0, Conditions: nil, }, - BuildMetadata: corev1alpha1.BuildpackMetadataList{}, + BuildMetadataBuildpacks: corev1alpha1.BuildpackMetadataList{}, Stack: corev1alpha1.BuildStack{ RunImage: "some-run", ID: "some-id", diff --git a/pkg/apis/build/v1alpha2/build_pod.go b/pkg/apis/build/v1alpha2/build_pod.go index eabf5243c..617b4592f 100644 --- a/pkg/apis/build/v1alpha2/build_pod.go +++ b/pkg/apis/build/v1alpha2/build_pod.go @@ -72,11 +72,11 @@ const ( var ( PrepareCommand = "/cnb/process/build-init" - AnalyzeCommand = "/cnb/lifecycle/analyzer" - DetectCommand = "/cnb/lifecycle/detector" - RestoreCommand = "/cnb/lifecycle/restorer" - BuildCommand = "/cnb/lifecycle/builder" - ExportCommand = "/cnb/lifecycle/exporter" + AnalyzeCommand = "/cnb/lifecycle/analyzer" + DetectCommand = "/cnb/lifecycle/detector" + RestoreCommand = "/cnb/lifecycle/restorer" + BuildCommand = "/cnb/lifecycle/builder" + ExportCommand = "/cnb/lifecycle/exporter" CompletionCommand = "/cnb/process/completion" RebaseCommand = "/cnb/process/rebase" ) @@ -760,7 +760,7 @@ func (b *Build) useImageExtensions(pod *corev1.Pod) { container.SecurityContext.RunAsUser = intPointer(0) container.SecurityContext.RunAsGroup = intPointer(0) container.SecurityContext.RunAsNonRoot = boolPointer(false) - container.SecurityContext.Capabilities = &corev1.Capabilities{Add: []corev1.Capability{"SETGID", "SETUID"}} // TODO: check if this is needed if not using kind + container.SecurityContext.Capabilities = &corev1.Capabilities{Add: []corev1.Capability{"SETGID", "SETUID"}} } pod.Spec.InitContainers[idx] = container } diff --git a/pkg/apis/build/v1alpha2/build_types.go b/pkg/apis/build/v1alpha2/build_types.go index 83b54e4cc..e4c30324e 100644 --- a/pkg/apis/build/v1alpha2/build_types.go +++ b/pkg/apis/build/v1alpha2/build_types.go @@ -129,12 +129,13 @@ type BuildStack struct { // +k8s:openapi-gen=true type BuildStatus struct { - corev1alpha1.Status `json:",inline"` - BuildMetadata corev1alpha1.BuildpackMetadataList `json:"buildMetadata,omitempty"` - Stack corev1alpha1.BuildStack `json:"stack,omitempty"` - LatestImage string `json:"latestImage,omitempty"` - LatestCacheImage string `json:"latestCacheImage,omitempty"` - PodName string `json:"podName,omitempty"` + corev1alpha1.Status `json:",inline"` + BuildMetadataBuildpacks corev1alpha1.BuildpackMetadataList `json:"buildMetadata,omitempty"` + BuildMetadataExtensions corev1alpha1.BuildpackMetadataList `json:"buildMetadataExtensions,omitempty"` + Stack corev1alpha1.BuildStack `json:"stack,omitempty"` + LatestImage string `json:"latestImage,omitempty"` + LatestCacheImage string `json:"latestCacheImage,omitempty"` + PodName string `json:"podName,omitempty"` // +listType StepStates []corev1.ContainerState `json:"stepStates,omitempty"` // +listType diff --git a/pkg/apis/build/v1alpha2/builder_resource.go b/pkg/apis/build/v1alpha2/builder_resource.go index ea280d683..f340417ef 100644 --- a/pkg/apis/build/v1alpha2/builder_resource.go +++ b/pkg/apis/build/v1alpha2/builder_resource.go @@ -8,6 +8,7 @@ type BuilderResource interface { BuildBuilderSpec() corev1alpha1.BuildBuilderSpec Ready() bool BuildpackMetadata() corev1alpha1.BuildpackMetadataList + ExtensionMetadata() corev1alpha1.BuildpackMetadataList RunImage() string GetKind() string ConditionReadyMessage() string diff --git a/pkg/apis/build/v1alpha2/image_builds.go b/pkg/apis/build/v1alpha2/image_builds.go index 786bf06f6..7b5a0eb4e 100644 --- a/pkg/apis/build/v1alpha2/image_builds.go +++ b/pkg/apis/build/v1alpha2/image_builds.go @@ -28,6 +28,7 @@ const ( BuildReasonConfig = "CONFIG" BuildReasonCommit = "COMMIT" BuildReasonBuildpack = "BUILDPACK" + BuildReasonExtension = "EXTENSION" BuildReasonStack = "STACK" BuildReasonTrigger = "TRIGGER" ) diff --git a/pkg/apis/build/v1alpha2/image_builds_test.go b/pkg/apis/build/v1alpha2/image_builds_test.go index c33233df2..f8c63ee4b 100644 --- a/pkg/apis/build/v1alpha2/image_builds_test.go +++ b/pkg/apis/build/v1alpha2/image_builds_test.go @@ -60,7 +60,7 @@ func testImageBuilds(t *testing.T, when spec.G, it spec.S) { LatestImage: "some/builder@sha256:builder-digest", Kind: BuilderKind, BuilderReady: true, - BuilderMetadata: []corev1alpha1.BuildpackMetadata{ + BuilderMetadataBuildpacks: []corev1alpha1.BuildpackMetadata{ {Id: "buildpack.matches", Version: "1"}, }, LatestRunImage: "some.registry.io/run-image@sha256:67e3de2af270bf09c02e9a644aeb7e87e6b3c049abe6766bf6b6c3728a83e7fb", @@ -84,7 +84,7 @@ func testImageBuilds(t *testing.T, when spec.G, it spec.S) { }, }, }, - BuildMetadata: []corev1alpha1.BuildpackMetadata{ + BuildMetadataBuildpacks: []corev1alpha1.BuildpackMetadata{ {Id: "buildpack.matches", Version: "1"}, }, Stack: corev1alpha1.BuildStack{ @@ -371,14 +371,15 @@ func testImageBuilds(t *testing.T, when spec.G, it spec.S) { } type TestBuilderResource struct { - BuilderReady bool - BuilderMetadata []corev1alpha1.BuildpackMetadata - ImagePullSecrets []corev1.LocalObjectReference - Kind string - LatestImage string - LatestRunImage string - Name string - Namespace string + BuilderReady bool + BuilderMetadataBuildpacks []corev1alpha1.BuildpackMetadata + BuilderMetadataExtensions []corev1alpha1.BuildpackMetadata + ImagePullSecrets []corev1.LocalObjectReference + Kind string + LatestImage string + LatestRunImage string + Name string + Namespace string } func (t TestBuilderResource) ConditionReadyMessage() string { @@ -397,7 +398,11 @@ func (t TestBuilderResource) Ready() bool { } func (t TestBuilderResource) BuildpackMetadata() corev1alpha1.BuildpackMetadataList { - return t.BuilderMetadata + return t.BuilderMetadataBuildpacks +} + +func (t TestBuilderResource) ExtensionMetadata() corev1alpha1.BuildpackMetadataList { + return t.BuilderMetadataExtensions } func (t TestBuilderResource) RunImage() string { diff --git a/pkg/apis/build/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/build/v1alpha2/zz_generated.deepcopy.go index 52001390b..9843afddc 100644 --- a/pkg/apis/build/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/build/v1alpha2/zz_generated.deepcopy.go @@ -313,8 +313,8 @@ func (in *BuildStack) DeepCopy() *BuildStack { func (in *BuildStatus) DeepCopyInto(out *BuildStatus) { *out = *in in.Status.DeepCopyInto(&out.Status) - if in.BuildMetadata != nil { - in, out := &in.BuildMetadata, &out.BuildMetadata + if in.BuildMetadataBuildpacks != nil { + in, out := &in.BuildMetadataBuildpacks, &out.BuildMetadataBuildpacks *out = make(v1alpha1.BuildpackMetadataList, len(*in)) copy(*out, *in) } diff --git a/pkg/buildchange/buildpack_change.go b/pkg/buildchange/buildpack_change.go index c72fc9fb5..339b6080b 100644 --- a/pkg/buildchange/buildpack_change.go +++ b/pkg/buildchange/buildpack_change.go @@ -9,10 +9,10 @@ import ( corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" ) -func NewBuildpackChange(oldBuildpacks, newBuildpacks []corev1alpha1.BuildpackInfo) Change { +func NewBuildpackChange(oldInfos, newInfos []corev1alpha1.BuildpackInfo) Change { return buildpackChange{ - old: oldBuildpacks, - new: newBuildpacks, + old: oldInfos, + new: newInfos, } } diff --git a/pkg/buildchange/extension_change.go b/pkg/buildchange/extension_change.go new file mode 100644 index 000000000..5ed8197ef --- /dev/null +++ b/pkg/buildchange/extension_change.go @@ -0,0 +1,40 @@ +package buildchange + +import ( + "sort" + + "github.com/google/go-cmp/cmp" + + buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" + corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" +) + +func NewExtensionChange(oldInfos, newInfos []corev1alpha1.BuildpackInfo) Change { + return extensionChange{ + old: oldInfos, + new: newInfos, + } +} + +type extensionChange struct { + old []corev1alpha1.BuildpackInfo + new []corev1alpha1.BuildpackInfo +} + +func (b extensionChange) Reason() buildapi.BuildReason { return buildapi.BuildReasonExtension } + +func (b extensionChange) IsBuildRequired() (bool, error) { + sort.Slice(b.old, func(i, j int) bool { + return b.old[i].Id < b.old[j].Id + }) + sort.Slice(b.new, func(i, j int) bool { + return b.new[i].Id < b.new[j].Id + }) + return !cmp.Equal(b.old, b.new), nil +} + +func (b extensionChange) Old() interface{} { return b.old } + +func (b extensionChange) New() interface{} { return b.new } + +func (b extensionChange) Priority() buildapi.BuildPriority { return buildapi.BuildPriorityLow } diff --git a/pkg/cnb/build_metadata.go b/pkg/cnb/build_metadata.go index 44cd4d2c3..a71138208 100644 --- a/pkg/cnb/build_metadata.go +++ b/pkg/cnb/build_metadata.go @@ -21,6 +21,7 @@ import ( type BuildMetadata struct { BuildpackMetadata corev1alpha1.BuildpackMetadataList `json:"buildpackMetadata"` + ExtensionMetadata corev1alpha1.BuildpackMetadataList `json:"extensionMetadata"` LatestCacheImage string `json:"latestCacheImage"` LatestImage string `json:"latestImage"` StackID string `json:"stackID"` @@ -43,7 +44,8 @@ func (r *RemoteMetadataRetriever) GetBuildMetadata(builtImageRef, cacheTag strin cacheImageRef, _ := r.getCacheImage(cacheTag, keychain) // if getting cache fails, use empty cache return &BuildMetadata{ - BuildpackMetadata: buildMetadataFromBuiltImage(buildImg), + BuildpackMetadata: buildpackMetadataFromBuiltImage(buildImg), + ExtensionMetadata: extensionMetadataFromBuiltImage(buildImg), LatestImage: buildImg.identifier, LatestCacheImage: cacheImageRef, StackRunImage: buildImg.stack.RunImage, @@ -103,6 +105,7 @@ func readBuiltImage(appImage ggcrv1.Image, appImageId string) (builtImage, error return builtImage{ identifier: appImageId, buildpackMetadata: buildMetadata.Buildpacks, + extensionMetadata: buildMetadata.Extensions, stack: builtImageStack{ RunImage: baseImageRef.Context().String() + "@" + runImageRef.Identifier(), ID: stackId, @@ -113,6 +116,7 @@ func readBuiltImage(appImage ggcrv1.Image, appImageId string) (builtImage, error type builtImage struct { identifier string buildpackMetadata []lifecyclebuildpack.GroupElement + extensionMetadata []lifecyclebuildpack.GroupElement stack builtImageStack } @@ -126,16 +130,28 @@ type RunImageAppMetadata struct { Reference string `json:"reference" toml:"reference"` } -func buildMetadataFromBuiltImage(image builtImage) corev1alpha1.BuildpackMetadataList { - bpMetadata := make([]corev1alpha1.BuildpackMetadata, 0, len(image.buildpackMetadata)) - for _, metadata := range image.buildpackMetadata { - bpMetadata = append(bpMetadata, corev1alpha1.BuildpackMetadata{ - Id: metadata.ID, - Version: metadata.Version, - Homepage: metadata.Homepage, +func buildpackMetadataFromBuiltImage(image builtImage) corev1alpha1.BuildpackMetadataList { + ret := make([]corev1alpha1.BuildpackMetadata, 0, len(image.buildpackMetadata)) + for _, m := range image.buildpackMetadata { + ret = append(ret, corev1alpha1.BuildpackMetadata{ + Id: m.ID, + Version: m.Version, + Homepage: m.Homepage, }) } - return bpMetadata + return ret +} + +func extensionMetadataFromBuiltImage(image builtImage) corev1alpha1.BuildpackMetadataList { + ret := make([]corev1alpha1.BuildpackMetadata, 0, len(image.buildpackMetadata)) + for _, m := range image.buildpackMetadata { + ret = append(ret, corev1alpha1.BuildpackMetadata{ + Id: m.ID, + Version: m.Version, + Homepage: m.Homepage, + }) + } + return ret } func CompressBuildMetadata(metadata *BuildMetadata) ([]byte, error) { diff --git a/pkg/duckbuilder/duck_builder.go b/pkg/duckbuilder/duck_builder.go index 3a7942ee4..00c48b420 100644 --- a/pkg/duckbuilder/duck_builder.go +++ b/pkg/duckbuilder/duck_builder.go @@ -44,11 +44,14 @@ func (b *DuckBuilder) BuildBuilderSpec() corev1alpha1.BuildBuilderSpec { } } -// TODO: add for extensions? func (b *DuckBuilder) BuildpackMetadata() corev1alpha1.BuildpackMetadataList { return b.Status.BuilderMetadataBuildpacks } +func (b *DuckBuilder) ExtensionMetadata() corev1alpha1.BuildpackMetadataList { + return b.Status.BuilderMetadataExtensions +} + func (b *DuckBuilder) RunImage() string { return b.Status.Stack.RunImage } diff --git a/pkg/reconciler/build/build.go b/pkg/reconciler/build/build.go index b02cfc0c5..beb6fbeee 100644 --- a/pkg/reconciler/build/build.go +++ b/pkg/reconciler/build/build.go @@ -3,17 +3,8 @@ package build import ( "context" "encoding/json" + "github.com/google/go-containerregistry/pkg/authn" - buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" - corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" - "github.com/pivotal/kpack/pkg/buildchange" - "github.com/pivotal/kpack/pkg/buildpod" - "github.com/pivotal/kpack/pkg/client/clientset/versioned" - buildinformers "github.com/pivotal/kpack/pkg/client/informers/externalversions/build/v1alpha2" - buildlisters "github.com/pivotal/kpack/pkg/client/listers/build/v1alpha2" - "github.com/pivotal/kpack/pkg/cnb" - "github.com/pivotal/kpack/pkg/reconciler" - "github.com/pivotal/kpack/pkg/registry" "github.com/pkg/errors" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" @@ -27,6 +18,17 @@ import ( "k8s.io/client-go/tools/cache" "knative.dev/pkg/controller" "knative.dev/pkg/logging/logkey" + + buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" + corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" + "github.com/pivotal/kpack/pkg/buildchange" + "github.com/pivotal/kpack/pkg/buildpod" + "github.com/pivotal/kpack/pkg/client/clientset/versioned" + buildinformers "github.com/pivotal/kpack/pkg/client/informers/externalversions/build/v1alpha2" + buildlisters "github.com/pivotal/kpack/pkg/client/listers/build/v1alpha2" + "github.com/pivotal/kpack/pkg/cnb" + "github.com/pivotal/kpack/pkg/reconciler" + "github.com/pivotal/kpack/pkg/registry" ) const ( @@ -167,7 +169,8 @@ func (c *Reconciler) reconcile(ctx context.Context, build *buildapi.Build) error return errors.Wrap(err, "failed to get build metadata from build pod") } } - build.Status.BuildMetadata = buildMetadata.BuildpackMetadata + build.Status.BuildMetadataBuildpacks = buildMetadata.BuildpackMetadata + build.Status.BuildMetadataExtensions = buildMetadata.ExtensionMetadata build.Status.LatestImage = buildMetadata.LatestImage build.Status.LatestCacheImage = buildMetadata.LatestCacheImage build.Status.Stack.RunImage = buildMetadata.StackRunImage diff --git a/pkg/reconciler/build/build_test.go b/pkg/reconciler/build/build_test.go index 47657cec9..0b2809b73 100644 --- a/pkg/reconciler/build/build_test.go +++ b/pkg/reconciler/build/build_test.go @@ -5,6 +5,11 @@ import ( "encoding/json" "errors" "fmt" + "os" + "path/filepath" + "testing" + "time" + "github.com/sclevine/spec" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -21,10 +26,6 @@ import ( "k8s.io/client-go/tools/record" "knative.dev/pkg/controller" rtesting "knative.dev/pkg/reconciler/testing" - "os" - "path/filepath" - "testing" - "time" buildapi "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1" @@ -578,7 +579,7 @@ func testBuildReconciler(t *testing.T, when spec.G, it spec.S) { }, }, PodName: "build-name-build-pod", - BuildMetadata: corev1alpha1.BuildpackMetadataList{ + BuildMetadataBuildpacks: corev1alpha1.BuildpackMetadataList{ { Id: "some-id", Version: "some-version", @@ -647,7 +648,7 @@ func testBuildReconciler(t *testing.T, when spec.G, it spec.S) { }, }, }, - BuildMetadata: corev1alpha1.BuildpackMetadataList{{ + BuildMetadataBuildpacks: corev1alpha1.BuildpackMetadataList{{ Id: "io.buildpack.previouslyfetched", Version: "1.1", }}, @@ -759,7 +760,7 @@ func testBuildReconciler(t *testing.T, when spec.G, it spec.S) { }, }, PodName: "build-name-build-pod", - BuildMetadata: corev1alpha1.BuildpackMetadataList{{ + BuildMetadataBuildpacks: corev1alpha1.BuildpackMetadataList{{ Id: "io.buildpack.executed", Version: "1.1", Homepage: "mysupercoolsite.com", @@ -818,7 +819,7 @@ func testBuildReconciler(t *testing.T, when spec.G, it spec.S) { }, }, }, - BuildMetadata: corev1alpha1.BuildpackMetadataList{{ + BuildMetadataBuildpacks: corev1alpha1.BuildpackMetadataList{{ Id: "io.buildpack.previouslyfetched", Version: "1.1", }}, @@ -1317,7 +1318,7 @@ func testBuildReconciler(t *testing.T, when spec.G, it spec.S) { }, }, PodName: "build-name-build-pod", - BuildMetadata: corev1alpha1.BuildpackMetadataList{ + BuildMetadataBuildpacks: corev1alpha1.BuildpackMetadataList{ { Id: "some-id", Version: "some-version", diff --git a/pkg/reconciler/image/build_required.go b/pkg/reconciler/image/build_required.go index a01841164..73761db35 100644 --- a/pkg/reconciler/image/build_required.go +++ b/pkg/reconciler/image/build_required.go @@ -45,6 +45,7 @@ func isBuildRequired(img *buildapi.Image, Process(commitChange(lastBuild, srcResolver)). Process(configChange(img, lastBuild, srcResolver)). Process(buildpackChange(lastBuild, builder)). + Process(extensionChange(lastBuild, builder)). Process(stackChange(lastBuild, builder)). Summarize() if err != nil { @@ -109,17 +110,35 @@ func buildpackChange(lastBuild *buildapi.Build, builder buildapi.BuilderResource return nil } - var old []corev1alpha1.BuildpackInfo - var new []corev1alpha1.BuildpackInfo + var oldInfo []corev1alpha1.BuildpackInfo + var newInfo []corev1alpha1.BuildpackInfo - builderBuildpacks := builder.BuildpackMetadata() - for _, lastBuildBp := range lastBuild.Status.BuildMetadata { - if !builderBuildpacks.Include(lastBuildBp) { - old = append(old, corev1alpha1.BuildpackInfo{Id: lastBuildBp.Id, Version: lastBuildBp.Version}) + fromBuilder := builder.BuildpackMetadata() + for _, fromLastBuild := range lastBuild.Status.BuildMetadataBuildpacks { + if !fromBuilder.Include(fromLastBuild) { + oldInfo = append(oldInfo, corev1alpha1.BuildpackInfo{Id: fromLastBuild.Id, Version: fromLastBuild.Version}) } } - return buildchange.NewBuildpackChange(old, new) + return buildchange.NewBuildpackChange(oldInfo, newInfo) +} + +func extensionChange(lastBuild *buildapi.Build, builder buildapi.BuilderResource) buildchange.Change { + if lastBuild == nil || !lastBuild.IsSuccess() { + return nil + } + + var oldInfo []corev1alpha1.BuildpackInfo + var newInfo []corev1alpha1.BuildpackInfo + + fromBuilder := builder.ExtensionMetadata() + for _, fromLastBuild := range lastBuild.Status.BuildMetadataExtensions { + if !fromBuilder.Include(fromLastBuild) { + oldInfo = append(oldInfo, corev1alpha1.BuildpackInfo{Id: fromLastBuild.Id, Version: fromLastBuild.Version}) + } + } + + return buildchange.NewExtensionChange(oldInfo, newInfo) } func stackChange(lastBuild *buildapi.Build, builder buildapi.BuilderResource) buildchange.Change { @@ -127,6 +146,10 @@ func stackChange(lastBuild *buildapi.Build, builder buildapi.BuilderResource) bu return nil } + if len(builder.ExtensionMetadata()) > 0 { + return nil + } + oldRunImageRefStr := lastBuild.Status.Stack.RunImage newRunImageRefStr := builder.RunImage() return buildchange.NewStackChange(oldRunImageRefStr, newRunImageRefStr) diff --git a/pkg/reconciler/image/build_required_test.go b/pkg/reconciler/image/build_required_test.go index 617c44eed..c7bafbf5e 100644 --- a/pkg/reconciler/image/build_required_test.go +++ b/pkg/reconciler/image/build_required_test.go @@ -60,7 +60,7 @@ func testImageBuilds(t *testing.T, when spec.G, it spec.S) { Namespace: "test-ns", LatestImage: "some/builder@sha256:builder-digest", BuilderReady: true, - BuilderMetadata: []corev1alpha1.BuildpackMetadata{ + BuilderMetadataBuildpacks: []corev1alpha1.BuildpackMetadata{ {Id: "buildpack.matches", Version: "1"}, }, LatestRunImage: "some.registry.io/run-image@sha256:67e3de2af270bf09c02e9a644aeb7e87e6b3c049abe6766bf6b6c3728a83e7fb", @@ -85,7 +85,7 @@ func testImageBuilds(t *testing.T, when spec.G, it spec.S) { }, }, }, - BuildMetadata: []corev1alpha1.BuildpackMetadata{ + BuildMetadataBuildpacks: []corev1alpha1.BuildpackMetadata{ {Id: "buildpack.matches", Version: "1"}, }, Stack: corev1alpha1.BuildStack{ @@ -308,27 +308,56 @@ func testImageBuilds(t *testing.T, when spec.G, it spec.S) { }) when("Builder Metadata changes", func() { - it("false if builder has additional unused buildpacks", func() { - builder.BuilderMetadata = []corev1alpha1.BuildpackMetadata{ - {Id: "buildpack.matches", Version: "1"}, - {Id: "buildpack.unused", Version: "unused"}, - } - - result, err := isBuildRequired(image, latestBuild, sourceResolver, builder) - assert.NoError(t, err) - assert.Equal(t, corev1.ConditionFalse, result.ConditionStatus) - assert.Equal(t, "", result.PriorityClass) - assert.Equal(t, "", result.ReasonsStr) - assert.Equal(t, "", result.ChangesStr) - }) - - it("true if builder metadata has different buildpack version from used buildpack version", func() { - builder.BuilderMetadata = []corev1alpha1.BuildpackMetadata{ - {Id: "buildpack.matches", Version: "NEW_VERSION"}, - {Id: "buildpack.different", Version: "different"}, - } + when("buildpacks", func() { + it("false if builder has additional unused buildpacks", func() { + builder.BuilderMetadataBuildpacks = []corev1alpha1.BuildpackMetadata{ + {Id: "buildpack.matches", Version: "1"}, + {Id: "buildpack.unused", Version: "unused"}, + } + + result, err := isBuildRequired(image, latestBuild, sourceResolver, builder) + assert.NoError(t, err) + assert.Equal(t, corev1.ConditionFalse, result.ConditionStatus) + assert.Equal(t, "", result.PriorityClass) + assert.Equal(t, "", result.ReasonsStr) + assert.Equal(t, "", result.ChangesStr) + }) + + it("true if builder metadata has different buildpack version from used buildpack version", func() { + builder.BuilderMetadataBuildpacks = []corev1alpha1.BuildpackMetadata{ + {Id: "buildpack.matches", Version: "NEW_VERSION"}, + {Id: "buildpack.different", Version: "different"}, + } + + expectedChanges := testhelpers.CompactJSON(` +[ + { + "reason": "BUILDPACK", + "old": [ + { + "id": "buildpack.matches", + "version": "1" + } + ], + "new": null + } +]`) - expectedChanges := testhelpers.CompactJSON(` + result, err := isBuildRequired(image, latestBuild, sourceResolver, builder) + assert.NoError(t, err) + assert.Equal(t, corev1.ConditionTrue, result.ConditionStatus) + assert.Equal(t, buildapi.BuildReasonBuildpack, result.ReasonsStr) + assert.Equal(t, buildapi.BuildPriorityClassLow, result.PriorityClass) + assert.Equal(t, expectedChanges, result.ChangesStr) + }) + + it("true if builder does not have all most recent used buildpacks", func() { + builder.BuilderMetadataBuildpacks = []corev1alpha1.BuildpackMetadata{ + {Id: "buildpack.only.new.buildpacks", Version: "1"}, + {Id: "buildpack.only.new.or.unused.buildpacks", Version: "1"}, + } + + expectedChanges := testhelpers.CompactJSON(` [ { "reason": "BUILDPACK", @@ -342,27 +371,77 @@ func testImageBuilds(t *testing.T, when spec.G, it spec.S) { } ]`) - result, err := isBuildRequired(image, latestBuild, sourceResolver, builder) - assert.NoError(t, err) - assert.Equal(t, corev1.ConditionTrue, result.ConditionStatus) - assert.Equal(t, buildapi.BuildReasonBuildpack, result.ReasonsStr) - assert.Equal(t, buildapi.BuildPriorityClassLow, result.PriorityClass) - assert.Equal(t, expectedChanges, result.ChangesStr) + result, err := isBuildRequired(image, latestBuild, sourceResolver, builder) + assert.NoError(t, err) + assert.Equal(t, corev1.ConditionTrue, result.ConditionStatus) + assert.Equal(t, buildapi.BuildReasonBuildpack, result.ReasonsStr) + assert.Equal(t, buildapi.BuildPriorityClassLow, result.PriorityClass) + assert.Equal(t, expectedChanges, result.ChangesStr) + }) }) - it("true if builder does not have all most recent used buildpacks", func() { - builder.BuilderMetadata = []corev1alpha1.BuildpackMetadata{ - {Id: "buildpack.only.new.buildpacks", Version: "1"}, - {Id: "buildpack.only.new.or.unused.buildpacks", Version: "1"}, - } + when("extensions", func() { + it.Before(func() { + latestBuild.Status.BuildMetadataExtensions = []corev1alpha1.BuildpackMetadata{ + {Id: "extension.matches", Version: "1"}, + } + }) + + it("false if builder has additional unused extensions", func() { + builder.BuilderMetadataExtensions = []corev1alpha1.BuildpackMetadata{ + {Id: "extension.matches", Version: "1"}, + {Id: "extension.unused", Version: "unused"}, + } + + result, err := isBuildRequired(image, latestBuild, sourceResolver, builder) + assert.NoError(t, err) + assert.Equal(t, corev1.ConditionFalse, result.ConditionStatus) + assert.Equal(t, "", result.PriorityClass) + assert.Equal(t, "", result.ReasonsStr) + assert.Equal(t, "", result.ChangesStr) + }) + + it("true if builder metadata has different extension version from used extension version", func() { + builder.BuilderMetadataExtensions = []corev1alpha1.BuildpackMetadata{ + {Id: "extension.matches", Version: "NEW_VERSION"}, + {Id: "extension.different", Version: "different"}, + } + + expectedChanges := testhelpers.CompactJSON(` +[ + { + "reason": "EXTENSION", + "old": [ + { + "id": "extension.matches", + "version": "1" + } + ], + "new": null + } +]`) - expectedChanges := testhelpers.CompactJSON(` + result, err := isBuildRequired(image, latestBuild, sourceResolver, builder) + assert.NoError(t, err) + assert.Equal(t, corev1.ConditionTrue, result.ConditionStatus) + assert.Equal(t, buildapi.BuildReasonExtension, result.ReasonsStr) + assert.Equal(t, buildapi.BuildPriorityClassLow, result.PriorityClass) + assert.Equal(t, expectedChanges, result.ChangesStr) + }) + + it("true if builder does not have all most recent used extensions", func() { + builder.BuilderMetadataExtensions = []corev1alpha1.BuildpackMetadata{ + {Id: "extension.only.new.extensions", Version: "1"}, + {Id: "extension.only.new.or.unused.extensions", Version: "1"}, + } + + expectedChanges := testhelpers.CompactJSON(` [ { - "reason": "BUILDPACK", + "reason": "EXTENSION", "old": [ { - "id": "buildpack.matches", + "id": "extension.matches", "version": "1" } ], @@ -370,18 +449,22 @@ func testImageBuilds(t *testing.T, when spec.G, it spec.S) { } ]`) - result, err := isBuildRequired(image, latestBuild, sourceResolver, builder) - assert.NoError(t, err) - assert.Equal(t, corev1.ConditionTrue, result.ConditionStatus) - assert.Equal(t, buildapi.BuildReasonBuildpack, result.ReasonsStr) - assert.Equal(t, buildapi.BuildPriorityClassLow, result.PriorityClass) - assert.Equal(t, expectedChanges, result.ChangesStr) + result, err := isBuildRequired(image, latestBuild, sourceResolver, builder) + assert.NoError(t, err) + assert.Equal(t, corev1.ConditionTrue, result.ConditionStatus) + assert.Equal(t, buildapi.BuildReasonExtension, result.ReasonsStr) + assert.Equal(t, buildapi.BuildPriorityClassLow, result.PriorityClass) + assert.Equal(t, expectedChanges, result.ChangesStr) + }) }) - it("true if builder has a different run image", func() { - builder.LatestRunImage = "some.registry.io/run-image@sha256:a1aa3da2a80a775df55e880b094a1a8de19b919435ad0c71c29a0983d64e65db" + when("builder has a different run image", func() { + it.Before(func() { + builder.LatestRunImage = "some.registry.io/run-image@sha256:a1aa3da2a80a775df55e880b094a1a8de19b919435ad0c71c29a0983d64e65db" + }) - expectedChanges := testhelpers.CompactJSON(` + it("true", func() { + expectedChanges := testhelpers.CompactJSON(` [ { "reason": "STACK", @@ -390,12 +473,28 @@ func testImageBuilds(t *testing.T, when spec.G, it spec.S) { } ]`) - result, err := isBuildRequired(image, latestBuild, sourceResolver, builder) - assert.NoError(t, err) - assert.Equal(t, corev1.ConditionTrue, result.ConditionStatus) - assert.Equal(t, buildapi.BuildReasonStack, result.ReasonsStr) - assert.Equal(t, buildapi.BuildPriorityClassLow, result.PriorityClass) - assert.Equal(t, expectedChanges, result.ChangesStr) + result, err := isBuildRequired(image, latestBuild, sourceResolver, builder) + assert.NoError(t, err) + assert.Equal(t, corev1.ConditionTrue, result.ConditionStatus) + assert.Equal(t, buildapi.BuildReasonStack, result.ReasonsStr) + assert.Equal(t, buildapi.BuildPriorityClassLow, result.PriorityClass) + assert.Equal(t, expectedChanges, result.ChangesStr) + }) + + when("there are extensions", func() { + it.Before(func() { + builder.BuilderMetadataExtensions = []corev1alpha1.BuildpackMetadata{ + {Id: "some-extension-id", Version: "some-extension-version"}, + } + }) + it("false", func() { + expectedChanges := testhelpers.CompactJSON(``) + + result, err := isBuildRequired(image, latestBuild, sourceResolver, builder) + assert.NoError(t, err) + assert.Equal(t, expectedChanges, result.ChangesStr) + }) + }) }) }) @@ -799,14 +898,15 @@ func testImageBuilds(t *testing.T, when spec.G, it spec.S) { } type TestBuilderResource struct { - BuilderReady bool - BuilderMetadata []corev1alpha1.BuildpackMetadata - ImagePullSecrets []corev1.LocalObjectReference - LatestImage string - LatestRunImage string - Name string - Namespace string - Kind string + BuilderReady bool + BuilderMetadataBuildpacks []corev1alpha1.BuildpackMetadata + BuilderMetadataExtensions []corev1alpha1.BuildpackMetadata + ImagePullSecrets []corev1.LocalObjectReference + LatestImage string + LatestRunImage string + Name string + Namespace string + Kind string } func (t TestBuilderResource) BuildBuilderSpec() corev1alpha1.BuildBuilderSpec { @@ -821,7 +921,11 @@ func (t TestBuilderResource) Ready() bool { } func (t TestBuilderResource) BuildpackMetadata() corev1alpha1.BuildpackMetadataList { - return t.BuilderMetadata + return t.BuilderMetadataBuildpacks +} + +func (t TestBuilderResource) ExtensionMetadata() corev1alpha1.BuildpackMetadataList { + return t.BuilderMetadataExtensions } func (t TestBuilderResource) RunImage() string { diff --git a/pkg/reconciler/image/image_test.go b/pkg/reconciler/image/image_test.go index 679642d56..384cd6f9e 100644 --- a/pkg/reconciler/image/image_test.go +++ b/pkg/reconciler/image/image_test.go @@ -1642,7 +1642,7 @@ func testImageReconciler(t *testing.T, when spec.G, it spec.S) { RunImage: "some/run@sha256:67e3de2af270bf09c02e9a644aeb7e87e6b3c049abe6766bf6b6c3728a83e7fb", ID: "io.buildpacks.stacks.bionic", }, - BuildMetadata: corev1alpha1.BuildpackMetadataList{ + BuildMetadataBuildpacks: corev1alpha1.BuildpackMetadataList{ { Id: "io.buildpack", Version: "old-version", @@ -1812,7 +1812,7 @@ func testImageReconciler(t *testing.T, when spec.G, it spec.S) { RunImage: "gcr.io/test-project/install/run@sha256:42841631725942db48b7ba8b788b97374a2ada34c84ee02ca5e02ef3d4b0dfca", ID: "io.buildpacks.stacks.bionic", }, - BuildMetadata: corev1alpha1.BuildpackMetadataList{ + BuildMetadataBuildpacks: corev1alpha1.BuildpackMetadataList{ { Id: "io.buildpack", Version: "version", diff --git a/test/execute_build_test.go b/test/execute_build_test.go index d00617f38..fd82b15ac 100644 --- a/test/execute_build_test.go +++ b/test/execute_build_test.go @@ -105,7 +105,7 @@ func testCreateImage(t *testing.T, _ spec.G, it spec.S) { imageName := fmt.Sprintf("%s-%s", name, builderType) builder := builderConfigs[builderType] var builderHasExtensions bool - if strings.Contains(builder.Name, "extensions") { // TODO: this is a bit hacky, maybe we can improve it + if strings.Contains(builder.Name, "extensions") { // FIXME: this is a bit hacky, maybe we can improve it builderHasExtensions = true } @@ -146,15 +146,17 @@ func testCreateImage(t *testing.T, _ spec.G, it spec.S) { var lifecycleMD files.LayersMetadata require.NoError(t, json.Unmarshal([]byte(lifecycleMDLabel), &lifecycleMD)) runImageReference := lifecycleMD.RunImage.Reference - require.Contains(t, runImageReference, "gcr.io/paketo-buildpacks/run-jammy-tiny") + require.Contains(t, runImageReference, "paketobuildpacks/run-jammy-tiny") } expectLogs = func(t *testing.T, logs string) { - require.Contains(t, logs, "Setting up curl") + require.Contains(t, logs, "curl --version") } } builtImages[validateImageCreate(t, clients, image, expectedResources, expectImage, expectLogs)] = struct{}{} - validateRebase(t, ctx, clients, image.Name, testNamespace) + if !builderHasExtensions { + validateRebase(t, ctx, clients, image.Name, testNamespace) + } }) } } @@ -531,7 +533,7 @@ func testCreateImage(t *testing.T, _ spec.G, it spec.S) { { BuildpackRef: corev1alpha1.BuildpackRef{ BuildpackInfo: corev1alpha1.BuildpackInfo{ - Id: "samples/curl", // FIXME + Id: "samples/curl", }, }, }, @@ -729,7 +731,7 @@ func testCreateImage(t *testing.T, _ spec.G, it spec.S) { { BuildpackRef: corev1alpha1.BuildpackRef{ BuildpackInfo: corev1alpha1.BuildpackInfo{ - Id: "samples/curl", // FIXME + Id: "samples/curl", }, }, }, @@ -748,7 +750,7 @@ func testCreateImage(t *testing.T, _ spec.G, it spec.S) { waitUntilReady(t, ctx, clients, builder, clusterBuilder, builderWithExtensions, clusterBuilderWithExtensions) }) - it.Focus("builds and rebases git, blob, and registry images from unauthenticated sources", func() { + it("builds and rebases git, blob, and registry images from unauthenticated sources", func() { imageSources := map[string]corev1alpha1.SourceConfig{ "git-image": { Git: &corev1alpha1.Git{ @@ -946,7 +948,7 @@ func waitUntilReady(t *testing.T, ctx context.Context, clients *clients, objects require.NoError(t, err) return kResource.Status.GetCondition(apis.ConditionReady).IsTrue() - }, 1*time.Second, 8*time.Minute) + }, 1*time.Second, 16*time.Minute) } } @@ -966,7 +968,7 @@ func waitUntilFailed(t *testing.T, ctx context.Context, clients *clients, expect condition := kResource.Status.GetCondition(apis.ConditionReady) return condition.IsFalse() && "" != condition.Message && strings.Contains(condition.Message, expectedMessage) - }, 1*time.Second, 8*time.Minute) + }, 1*time.Second, 16*time.Minute) } } @@ -1039,7 +1041,7 @@ func validateRebase(t *testing.T, ctx context.Context, clients *clients, imageNa build, err := clients.client.KpackV1alpha2().Builds(testNamespace).Get(ctx, rebaseBuildName, metav1.GetOptions{}) require.NoError(t, err) - //rebase and completion + // rebase and completion require.LessOrEqual(t, len(build.Status.StepsCompleted), 2) return build.Status.GetCondition(corev1alpha1.ConditionSucceeded).IsTrue() From b14fce92cada91c214661314041b18b1e72ee638 Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Mon, 16 Oct 2023 17:15:24 -0400 Subject: [PATCH 20/20] Fix reconciler Signed-off-by: Natalie Arellano --- pkg/cnb/build_metadata.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cnb/build_metadata.go b/pkg/cnb/build_metadata.go index a71138208..1aaa691d6 100644 --- a/pkg/cnb/build_metadata.go +++ b/pkg/cnb/build_metadata.go @@ -144,7 +144,7 @@ func buildpackMetadataFromBuiltImage(image builtImage) corev1alpha1.BuildpackMet func extensionMetadataFromBuiltImage(image builtImage) corev1alpha1.BuildpackMetadataList { ret := make([]corev1alpha1.BuildpackMetadata, 0, len(image.buildpackMetadata)) - for _, m := range image.buildpackMetadata { + for _, m := range image.extensionMetadata { ret = append(ret, corev1alpha1.BuildpackMetadata{ Id: m.ID, Version: m.Version,