Skip to content

Commit da640cc

Browse files
authored
Extends builds.kpack.io with a new optional field spec.builder.ref (buildpacks-community#1857)
Extends builds.kpack.io with a new optional field spec.builder.ref. This allows build resources to reference resource and cluster builders directly. Few cosmetic cleanup. Added unit test for the new field validation and implementation Signed-off-by: Rashed Kamal <rashed.kamal@broadcom.com>
1 parent 314fc06 commit da640cc

15 files changed

Lines changed: 347 additions & 57 deletions

File tree

api/openapi-spec/swagger.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7187,6 +7187,10 @@
71877187
"x-kubernetes-list-type": "",
71887188
"x-kubernetes-patch-merge-key": "name",
71897189
"x-kubernetes-patch-strategy": "merge"
7190+
},
7191+
"ref": {
7192+
"description": "Ref reference to an existing Builder/ClusterBuilder",
7193+
"$ref": "#/definitions/io.k8s.api.core.v1.ObjectReference"
71907194
}
71917195
}
71927196
},

cmd/controller/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ func main() {
169169
MaximumPlatformApiVersion: maxPlatformApi,
170170
InjectedSidecarSupport: featureFlags.InjectedSidecarSupport,
171171
SSHTrustUnknownHost: cfg.SshTrustUnknownHosts,
172+
KpackClient: client,
172173
}
173174

174175
gitResolver := git.NewResolver(k8sClient, cfg.SshTrustUnknownHosts)

hack/local.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ Builds and generates a deployment yaml for kpack. This only builds linux images.
1313
1414
Prerequisites:
1515
- pack or ko installed
16-
- kubectl installed
16+
- kubectl installed (recommended)
1717
- docker login to your registry
18+
- kbld and kapp (https://carvel.dev/#install)
1819
1920
OPTIONS
2021
--help -h prints the command usage
@@ -102,7 +103,7 @@ function main() {
102103

103104
if "${apply}"; then
104105
echo "Applying $output to cluster"
105-
kubectl apply -f $output
106+
kapp deploy -a kpack -f $output -y
106107
fi
107108

108109
}

pkg/apis/build/v1alpha1/build_validation_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func testBuildValidation(t *testing.T, when spec.G, it spec.S) {
8080

8181
it("missing builder name", func() {
8282
build.Spec.Builder.Image = ""
83-
assertValidationError(build, apis.ErrMissingField("image").ViaField("spec", "builder"))
83+
assertValidationError(build, apis.ErrMissingField("image", "ref").ViaField("spec", "builder"))
8484
})
8585

8686
it("invalid builder name", func() {
@@ -217,7 +217,7 @@ func testBuildValidation(t *testing.T, when spec.G, it spec.S) {
217217
build.Spec.Builder.Image = ""
218218
assertValidationError(build,
219219
apis.ErrMissingField("tags").ViaField("spec").
220-
Also(apis.ErrMissingField("image").ViaField("spec", "builder")))
220+
Also(apis.ErrMissingField("image", "ref").ViaField("spec", "builder")))
221221
})
222222

223223
it("validates spec is immutable", func() {

pkg/apis/build/v1alpha2/build_pod.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,12 @@ type BuildContext struct {
104104
}
105105

106106
type BuildPodBuilderConfig struct {
107-
StackID string
108-
RunImage string
109-
Uid int64
110-
Gid int64
111-
PlatformAPIs []string
107+
StackID string
108+
RunImage string
109+
Uid int64
110+
Gid int64
111+
PlatformAPIs []string
112+
ResolvedImage string
112113
}
113114

114115
var (
@@ -240,7 +241,7 @@ func (b *Build) BuildPod(images BuildPodImages, buildContext BuildContext) (*cor
240241

241242
analyzeContainer := corev1.Container{
242243
Name: AnalyzeContainerName,
243-
Image: b.Spec.Builder.Image,
244+
Image: buildContext.BuildPodBuilderConfig.ResolvedImage,
244245
Command: []string{AnalyzeCommand},
245246
Resources: b.Spec.Resources,
246247
Args: args(
@@ -282,7 +283,7 @@ func (b *Build) BuildPod(images BuildPodImages, buildContext BuildContext) (*cor
282283

283284
detectContainer := corev1.Container{
284285
Name: DetectContainerName,
285-
Image: b.Spec.Builder.Image,
286+
Image: buildContext.BuildPodBuilderConfig.ResolvedImage,
286287
Command: []string{DetectCommand},
287288
Resources: b.Spec.Resources,
288289
Args: []string{
@@ -432,7 +433,7 @@ func (b *Build) BuildPod(images BuildPodImages, buildContext BuildContext) (*cor
432433
step(
433434
corev1.Container{
434435
Name: RestoreContainerName,
435-
Image: b.Spec.Builder.Image,
436+
Image: buildContext.BuildPodBuilderConfig.ResolvedImage,
436437
Command: []string{RestoreCommand},
437438
Resources: b.Spec.Resources,
438439
SecurityContext: containerSecurityContext(),
@@ -456,7 +457,7 @@ func (b *Build) BuildPod(images BuildPodImages, buildContext BuildContext) (*cor
456457
step(
457458
corev1.Container{
458459
Name: BuildContainerName,
459-
Image: b.Spec.Builder.Image,
460+
Image: buildContext.BuildPodBuilderConfig.ResolvedImage,
460461
Command: []string{BuildCommand},
461462
Resources: b.Spec.Resources,
462463
SecurityContext: containerSecurityContext(),
@@ -481,7 +482,7 @@ func (b *Build) BuildPod(images BuildPodImages, buildContext BuildContext) (*cor
481482
step(
482483
corev1.Container{
483484
Name: ExportContainerName,
484-
Image: b.Spec.Builder.Image,
485+
Image: buildContext.BuildPodBuilderConfig.ResolvedImage,
485486
Command: []string{ExportCommand},
486487
Resources: b.Spec.Resources,
487488
SecurityContext: containerSecurityContext(),
@@ -919,7 +920,7 @@ func (b *Build) setupSecretVolumesAndArgs(secrets []corev1.Secret, filter func(s
919920
annotatedUrl := secret.Annotations[BlobSecretAnnotationPrefix]
920921
args = append(args, fmt.Sprintf("-blob=%s=%s", secret.Name, annotatedUrl))
921922
default:
922-
//ignoring secret
923+
// ignoring secret
923924
continue
924925
}
925926

pkg/apis/build/v1alpha2/build_pod_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -249,11 +249,12 @@ func testBuildPod(t *testing.T, when spec.G, it spec.S) {
249249

250250
buildContext := buildapi.BuildContext{
251251
BuildPodBuilderConfig: buildapi.BuildPodBuilderConfig{
252-
StackID: "com.builder.stack.io",
253-
RunImage: "builderregistry.io/run",
254-
Uid: 2000,
255-
Gid: 3000,
256-
PlatformAPIs: []string{"0.7", "0.8", "0.9"},
252+
StackID: "com.builder.stack.io",
253+
RunImage: "builderregistry.io/run",
254+
Uid: 2000,
255+
Gid: 3000,
256+
PlatformAPIs: []string{"0.7", "0.8", "0.9"},
257+
ResolvedImage: builderImage,
257258
},
258259
Secrets: secrets,
259260
Bindings: serviceBindings,

pkg/apis/build/v1alpha2/build_validation_test.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,27 @@ func testBuildValidation(t *testing.T, when spec.G, it spec.S) {
3838
},
3939
},
4040
}
41+
buildUsingRef := &Build{
42+
ObjectMeta: metav1.ObjectMeta{
43+
Name: "buildWithRef-name",
44+
},
45+
Spec: BuildSpec{
46+
Tags: []string{"some/image"},
47+
Builder: corev1alpha1.BuildBuilderSpec{
48+
Ref: &corev1.ObjectReference{
49+
Name: "default",
50+
Kind: "ClusterBuilder",
51+
},
52+
},
53+
ServiceAccountName: "some/service-account",
54+
Source: corev1alpha1.SourceConfig{
55+
Git: &corev1alpha1.Git{
56+
URL: "http://github.com/repo",
57+
Revision: "master",
58+
},
59+
},
60+
},
61+
}
4162
when("Default", func() {
4263
it("does not modify already set fields", func() {
4364
oldBuild := build.DeepCopy()
@@ -81,14 +102,32 @@ func testBuildValidation(t *testing.T, when spec.G, it spec.S) {
81102

82103
it("missing builder name", func() {
83104
build.Spec.Builder.Image = ""
84-
assertValidationError(build, context.TODO(), apis.ErrMissingField("image").ViaField("spec", "builder"))
105+
assertValidationError(build, context.TODO(), apis.ErrMissingField("image", "ref").ViaField("spec", "builder"))
85106
})
86107

87108
it("invalid builder name", func() {
88109
build.Spec.Builder.Image = "foo.ioo/builder-but-not-a-builder@sha256:alksdifhjalsouidfh"
89110
assertValidationError(build, context.TODO(), apis.ErrInvalidValue("foo.ioo/builder-but-not-a-builder@sha256:alksdifhjalsouidfh", "image").ViaField("spec", "builder"))
90111
})
91112

113+
it("valid cluster builder by ref", func() {
114+
assert.Nil(t, buildUsingRef.Validate(context.TODO()))
115+
})
116+
117+
it("both image and builder by ref present", func() {
118+
buildUsingRef.Spec.Builder.Image = "builder/bionic-builder@sha256:e431a4f94fb84854fd081da62762192f36fd093fdfb85ad3bc009b9309524e2d"
119+
assertValidationError(buildUsingRef, context.TODO(), apis.ErrMultipleOneOf("image", "ref").ViaField("spec", "builder"))
120+
})
121+
122+
it("validate namespaced builder by ref", func() {
123+
buildUsingRef.Spec.Builder.Ref = &corev1.ObjectReference{
124+
Kind: "Builder",
125+
Name: "namespaced-builder",
126+
Namespace: "default",
127+
}
128+
assert.Nil(t, buildUsingRef.Validate(context.TODO()))
129+
})
130+
92131
it("multiple sources", func() {
93132
build.Spec.Source.Git = &corev1alpha1.Git{
94133
URL: "http://github.com/repo",
@@ -331,7 +370,7 @@ func testBuildValidation(t *testing.T, when spec.G, it spec.S) {
331370
build.Spec.Builder.Image = ""
332371
assertValidationError(build, context.TODO(),
333372
apis.ErrMissingField("tags").ViaField("spec").
334-
Also(apis.ErrMissingField("image").ViaField("spec", "builder")))
373+
Also(apis.ErrMissingField("image", "ref").ViaField("spec", "builder")))
335374
})
336375

337376
it("validates spec is immutable", func() {

pkg/apis/build/v1alpha2/cluster_store_conversion_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func testClusterStoreConversion(t *testing.T, when spec.G, it spec.S) {
2424
Annotations: map[string]string{"some-key": "some-value"},
2525
},
2626
Spec: ClusterStoreSpec{
27-
Sources: []corev1alpha1.ImageSource{{"some-image"}, {"another-image"}},
27+
Sources: []corev1alpha1.ImageSource{{Image: "some-image"}, {Image: "another-image"}},
2828
ServiceAccountRef: &corev1.ObjectReference{
2929
Namespace: "some-namespace",
3030
Name: "some-service-account",
@@ -65,7 +65,7 @@ func testClusterStoreConversion(t *testing.T, when spec.G, it spec.S) {
6565
},
6666
},
6767
Spec: v1alpha1.ClusterStoreSpec{
68-
Sources: []corev1alpha1.ImageSource{{"some-image"}, {"another-image"}},
68+
Sources: []corev1alpha1.ImageSource{{Image: "some-image"}, {Image: "another-image"}},
6969
},
7070
Status: v1alpha1.ClusterStoreStatus{
7171
Status: corev1alpha1.Status{},

pkg/apis/core/v1alpha1/build_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ type BuildBuilderSpec struct {
1919
// +patchStrategy=merge
2020
// +listType
2121
ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,15,rep,name=imagePullSecrets"`
22+
23+
// Ref reference to an existing Builder/ClusterBuilder
24+
Ref *corev1.ObjectReference `json:"ref,omitempty"`
2225
}
2326

2427
// +k8s:openapi-gen=true

pkg/apis/core/v1alpha1/build_validation.go

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,48 @@ import (
55
"fmt"
66
"regexp"
77

8-
"knative.dev/pkg/apis"
9-
8+
"github.com/google/go-containerregistry/pkg/name"
109
"github.com/pivotal/kpack/pkg/apis/validate"
10+
corev1 "k8s.io/api/core/v1"
11+
"knative.dev/pkg/apis"
1112
)
1213

1314
func (bbs *BuildBuilderSpec) Validate(ctx context.Context) *apis.FieldError {
14-
return validate.Image(bbs.Image)
15+
return validateBuilderOrImagePresent(bbs).
16+
Also(validateBuilderRefAndImageMutuallyExclusive(bbs)).
17+
Also(validateBuilderRef(bbs.Ref).ViaField("ref")).
18+
Also(validateImage(bbs.Image))
19+
}
20+
21+
func validateImage(value string) *apis.FieldError {
22+
if value != "" {
23+
_, err := name.ParseReference(value, name.WeakValidation)
24+
if err != nil {
25+
return apis.ErrInvalidValue(value, "image")
26+
}
27+
}
28+
29+
return nil
30+
}
31+
32+
func validateBuilderRef(ref *corev1.ObjectReference) *apis.FieldError {
33+
if ref != nil {
34+
return validate.FieldNotEmpty(ref.Name, "name").Also(validate.FieldNotEmpty(ref.Kind, "kind"))
35+
}
36+
return nil
37+
}
38+
39+
func validateBuilderRefAndImageMutuallyExclusive(bbs *BuildBuilderSpec) *apis.FieldError {
40+
if bbs.Image != "" && bbs.Ref != nil {
41+
return apis.ErrMultipleOneOf("image", "ref")
42+
}
43+
return nil
44+
}
45+
func validateBuilderOrImagePresent(bbs *BuildBuilderSpec) *apis.FieldError {
46+
if bbs.Image == "" && bbs.Ref == nil {
47+
return apis.ErrMissingField("image", "ref")
48+
}
49+
return nil
1550
}
1651

1752
func (bs CNBBindings) Validate(ctx context.Context) *apis.FieldError {

0 commit comments

Comments
 (0)