Skip to content

Commit a47cba7

Browse files
authored
feat: Migrate kp import to v1 API with ClusterLifecycle and ClusterBuildpack support (#434)
* feat: Migrate kp import to v1 API with ClusterLifecycle and ClusterBuildpack support This PR migrates the kp import command to use the new v1 API (kp.kpack.io/v1) with support for ClusterLifecycle and ClusterBuildpack CRDs. API Version Migration: - Update dependency descriptor to support kp.kpack.io/v1 API version - Add automatic conversion from v1alpha1 and v1alpha3 descriptors to v1 - Replace ConfigMap-based lifecycle management with ClusterLifecycle CRDs ClusterLifecycle Support: - Import creates ClusterLifecycle resources instead of updating the lifecycle ConfigMap - Support for multiple named lifecycles in a single descriptor - Add defaultClusterLifecycle field for aliasing (creates a 'default-lifecycle' alias) - Lifecycle images are pre-loaded (relocated) to the default repository ClusterBuildpack Support: - Add new clusterBuildpacks section to dependency descriptor - Support for standalone buildpack images (not part of a store) - Add defaultClusterBuildpack field for aliasing (creates a 'default' alias) - Buildpack images are pre-loaded (relocated) to the default repository - Buildpack image validation (checks for io.buildpacks.buildpackage.metadata label) ClusterBuilder Improvements: - Skip store reference in ClusterBuilder when clusterStore field is empty - Allows builders that only use ClusterBuildpacks without a ClusterStore Code Organization: - Move descriptor types and conversion logic to pkg/import/descriptor/ - Separate files for each API version (v1alpha1.go, v1alpha3.go, v1.go) - Add LastAppliedConfiguration annotation to ClusterLifecycle and ClusterBuildpack Backward Compatibility: - v1alpha1 and v1alpha3 descriptors are automatically converted to v1 format - Existing descriptors continue to work without modification - The lifecycle field in v1alpha3 is converted to a ClusterLifecycle named 'default-lifecycle' * Fix: comment update to reflect new pkg name --------- Co-authored-by: Neil Hickey <neil-hickey@users.noreply.github.com>
1 parent 20fcded commit a47cba7

23 files changed

Lines changed: 1446 additions & 336 deletions

pkg/buildpackage/uploader.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,23 @@
44
package buildpackage
55

66
import (
7+
"fmt"
78
"io/ioutil"
89
"os"
910

1011
"github.com/google/go-containerregistry/pkg/authn"
1112
v1 "github.com/google/go-containerregistry/pkg/v1"
1213
"github.com/google/go-containerregistry/pkg/v1/layout"
14+
"github.com/pivotal/kpack/pkg/registry/imagehelpers"
1315
"github.com/pkg/errors"
1416

1517
"github.com/buildpacks-community/kpack-cli/pkg/archive"
1618
)
1719

20+
const (
21+
buildpackageMetadataLabel = "io.buildpacks.buildpackage.metadata"
22+
)
23+
1824
type Relocator interface {
1925
Relocate(keychain authn.Keychain, image v1.Image, dest string) (string, error)
2026
}
@@ -84,3 +90,21 @@ func readCNB(buildPackage, tempDir string) (v1.Image, error) {
8490

8591
return image, nil
8692
}
93+
94+
// ValidateBuildpackImage checks that the image has the required buildpackage metadata label
95+
func (u *Uploader) ValidateBuildpackImage(keychain authn.Keychain, imageTag string) error {
96+
image, err := u.Fetcher.Fetch(keychain, imageTag)
97+
if err != nil {
98+
return err
99+
}
100+
101+
hasMetadataLabel, err := imagehelpers.HasLabel(image, buildpackageMetadataLabel)
102+
if err != nil {
103+
return fmt.Errorf("could not get label %s: %w", buildpackageMetadataLabel, err)
104+
}
105+
if !hasMetadataLabel {
106+
return fmt.Errorf("missing label %s", buildpackageMetadataLabel)
107+
}
108+
109+
return nil
110+
}

pkg/clusterbuildpack/factory.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Copyright 2020-Present VMware, Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package clusterbuildpack
5+
6+
import (
7+
"fmt"
8+
9+
"github.com/google/go-containerregistry/pkg/authn"
10+
"github.com/pivotal/kpack/pkg/apis/build/v1alpha2"
11+
corev1alpha1 "github.com/pivotal/kpack/pkg/apis/core/v1alpha1"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
14+
"github.com/buildpacks-community/kpack-cli/pkg/buildpackage"
15+
"github.com/buildpacks-community/kpack-cli/pkg/config"
16+
"github.com/buildpacks-community/kpack-cli/pkg/k8s"
17+
"github.com/buildpacks-community/kpack-cli/pkg/registry"
18+
)
19+
20+
type BuildpackageUploader interface {
21+
UploadBuildpackage(keychain authn.Keychain, buildPackage, repository string) (string, error)
22+
ValidateBuildpackImage(keychain authn.Keychain, imageTag string) error
23+
}
24+
25+
type Printer interface {
26+
Printlnf(format string, args ...interface{}) error
27+
PrintStatus(format string, args ...interface{}) error
28+
}
29+
30+
type Factory struct {
31+
Uploader BuildpackageUploader
32+
Printer Printer
33+
}
34+
35+
func NewFactory(printer Printer, relocator registry.Relocator, fetcher registry.Fetcher) *Factory {
36+
return &Factory{
37+
Uploader: &buildpackage.Uploader{
38+
Fetcher: fetcher,
39+
Relocator: relocator,
40+
},
41+
Printer: printer,
42+
}
43+
}
44+
45+
func (f *Factory) MakeBuildpack(keychain authn.Keychain, name, imageTag string, kpConfig config.KpConfig) (*v1alpha2.ClusterBuildpack, error) {
46+
err := f.validate(keychain, imageTag)
47+
if err != nil {
48+
return nil, fmt.Errorf("invalid buildpack image: %w", err)
49+
}
50+
51+
defaultRepo, err := kpConfig.DefaultRepository()
52+
if err != nil {
53+
return nil, err
54+
}
55+
56+
if err := f.Printer.PrintStatus("Uploading to '%s'...", defaultRepo); err != nil {
57+
return nil, err
58+
}
59+
60+
relocatedImageRef, err := f.Uploader.UploadBuildpackage(keychain, imageTag, defaultRepo)
61+
if err != nil {
62+
return nil, err
63+
}
64+
65+
sa := kpConfig.ServiceAccount()
66+
67+
buildpack := &v1alpha2.ClusterBuildpack{
68+
TypeMeta: metav1.TypeMeta{
69+
Kind: v1alpha2.ClusterBuildpackKind,
70+
APIVersion: "kpack.io/v1alpha2",
71+
},
72+
ObjectMeta: metav1.ObjectMeta{
73+
Name: name,
74+
Annotations: map[string]string{},
75+
},
76+
Spec: v1alpha2.ClusterBuildpackSpec{
77+
ImageSource: corev1alpha1.ImageSource{
78+
Image: relocatedImageRef,
79+
},
80+
ServiceAccountRef: &sa,
81+
},
82+
}
83+
84+
return buildpack, k8s.SetLastAppliedCfg(buildpack)
85+
}
86+
87+
func (f *Factory) UpdateBuildpack(keychain authn.Keychain, buildpack *v1alpha2.ClusterBuildpack, imageTag string, kpConfig config.KpConfig) (*v1alpha2.ClusterBuildpack, error) {
88+
err := f.validate(keychain, imageTag)
89+
if err != nil {
90+
return nil, fmt.Errorf("invalid buildpack image: %w", err)
91+
}
92+
93+
defaultRepo, err := kpConfig.DefaultRepository()
94+
if err != nil {
95+
return nil, err
96+
}
97+
98+
if err := f.Printer.PrintStatus("Uploading to '%s'...", defaultRepo); err != nil {
99+
return nil, err
100+
}
101+
102+
relocatedImageRef, err := f.Uploader.UploadBuildpackage(keychain, imageTag, defaultRepo)
103+
if err != nil {
104+
return nil, err
105+
}
106+
107+
newBuildpack := buildpack.DeepCopy()
108+
newBuildpack.Spec.ImageSource.Image = relocatedImageRef
109+
return newBuildpack, nil
110+
}
111+
112+
func (f *Factory) validate(keychain authn.Keychain, imageTag string) error {
113+
return f.Uploader.ValidateBuildpackImage(keychain, imageTag)
114+
}

pkg/clusterlifecycle/factory.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1313

1414
"github.com/buildpacks-community/kpack-cli/pkg/config"
15+
"github.com/buildpacks-community/kpack-cli/pkg/k8s"
1516
"github.com/buildpacks-community/kpack-cli/pkg/lifecycleimage"
1617
"github.com/buildpacks-community/kpack-cli/pkg/registry"
1718
)
@@ -63,7 +64,7 @@ func (f *Factory) MakeLifecycle(keychain authn.Keychain, name, imageTag string,
6364

6465
sa := kpConfig.ServiceAccount()
6566

66-
return &v1alpha2.ClusterLifecycle{
67+
lifecycle := &v1alpha2.ClusterLifecycle{
6768
TypeMeta: metav1.TypeMeta{
6869
Kind: v1alpha2.ClusterLifecycleKind,
6970
APIVersion: "kpack.io/v1alpha2",
@@ -78,7 +79,9 @@ func (f *Factory) MakeLifecycle(keychain authn.Keychain, name, imageTag string,
7879
},
7980
ServiceAccountRef: &sa,
8081
},
81-
}, nil
82+
}
83+
84+
return lifecycle, k8s.SetLastAppliedCfg(lifecycle)
8285
}
8386

8487
func (f *Factory) UpdateLifecycle(keychain authn.Keychain, lifecycle *v1alpha2.ClusterLifecycle, imageTag string, kpConfig config.KpConfig) (*v1alpha2.ClusterLifecycle, error) {

pkg/commands/clusterlifecycle/create_test.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,10 @@ func testCreateCommand(t *testing.T, when spec.G, it spec.S) {
7070
APIVersion: "kpack.io/v1alpha2",
7171
},
7272
ObjectMeta: metav1.ObjectMeta{
73-
Name: "my-lifecycle",
74-
Annotations: map[string]string{},
73+
Name: "my-lifecycle",
74+
Annotations: map[string]string{
75+
"kubectl.kubernetes.io/last-applied-configuration": `{"kind":"ClusterLifecycle","apiVersion":"kpack.io/v1alpha2","metadata":{"name":"my-lifecycle","creationTimestamp":null},"spec":{"image":"default-registry.io/default-repo@sha256:lifecycle-image-digest","serviceAccountRef":{"namespace":"some-namespace","name":"some-serviceaccount"}},"status":{"image":{},"api":{},"apis":{"buildpack":{"deprecated":null,"supported":null},"platform":{"deprecated":null,"supported":null}}}}`,
76+
},
7577
},
7678
Spec: v1alpha2.ClusterLifecycleSpec{
7779
ImageSource: v1alpha1.ImageSource{
@@ -165,6 +167,8 @@ ClusterLifecycle "my-lifecycle" created
165167
const resourceYAML = `apiVersion: kpack.io/v1alpha2
166168
kind: ClusterLifecycle
167169
metadata:
170+
annotations:
171+
kubectl.kubernetes.io/last-applied-configuration: '{"kind":"ClusterLifecycle","apiVersion":"kpack.io/v1alpha2","metadata":{"name":"my-lifecycle","creationTimestamp":null},"spec":{"image":"default-registry.io/default-repo@sha256:lifecycle-image-digest","serviceAccountRef":{"namespace":"some-namespace","name":"some-serviceaccount"}},"status":{"image":{},"api":{},"apis":{"buildpack":{"deprecated":null,"supported":null},"platform":{"deprecated":null,"supported":null}}}}'
168172
creationTimestamp: null
169173
name: my-lifecycle
170174
spec:
@@ -210,7 +214,10 @@ Uploading to 'default-registry.io/default-repo'...
210214
"apiVersion": "kpack.io/v1alpha2",
211215
"metadata": {
212216
"name": "my-lifecycle",
213-
"creationTimestamp": null
217+
"creationTimestamp": null,
218+
"annotations": {
219+
"kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"ClusterLifecycle\",\"apiVersion\":\"kpack.io/v1alpha2\",\"metadata\":{\"name\":\"my-lifecycle\",\"creationTimestamp\":null},\"spec\":{\"image\":\"default-registry.io/default-repo@sha256:lifecycle-image-digest\",\"serviceAccountRef\":{\"namespace\":\"some-namespace\",\"name\":\"some-serviceaccount\"}},\"status\":{\"image\":{},\"api\":{},\"apis\":{\"buildpack\":{\"deprecated\":null,\"supported\":null},\"platform\":{\"deprecated\":null,\"supported\":null}}}}"
220+
}
214221
},
215222
"spec": {
216223
"image": "default-registry.io/default-repo@sha256:lifecycle-image-digest",
@@ -282,6 +289,8 @@ ClusterLifecycle "my-lifecycle" created (dry run)
282289
const resourceYAML = `apiVersion: kpack.io/v1alpha2
283290
kind: ClusterLifecycle
284291
metadata:
292+
annotations:
293+
kubectl.kubernetes.io/last-applied-configuration: '{"kind":"ClusterLifecycle","apiVersion":"kpack.io/v1alpha2","metadata":{"name":"my-lifecycle","creationTimestamp":null},"spec":{"image":"default-registry.io/default-repo@sha256:lifecycle-image-digest","serviceAccountRef":{"namespace":"some-namespace","name":"some-serviceaccount"}},"status":{"image":{},"api":{},"apis":{"buildpack":{"deprecated":null,"supported":null},"platform":{"deprecated":null,"supported":null}}}}'
285294
creationTimestamp: null
286295
name: my-lifecycle
287296
spec:
@@ -345,6 +354,8 @@ ClusterLifecycle "my-lifecycle" created (dry run with image upload)
345354
const resourceYAML = `apiVersion: kpack.io/v1alpha2
346355
kind: ClusterLifecycle
347356
metadata:
357+
annotations:
358+
kubectl.kubernetes.io/last-applied-configuration: '{"kind":"ClusterLifecycle","apiVersion":"kpack.io/v1alpha2","metadata":{"name":"my-lifecycle","creationTimestamp":null},"spec":{"image":"default-registry.io/default-repo@sha256:lifecycle-image-digest","serviceAccountRef":{"namespace":"some-namespace","name":"some-serviceaccount"}},"status":{"image":{},"api":{},"apis":{"buildpack":{"deprecated":null,"supported":null},"platform":{"deprecated":null,"supported":null}}}}'
348359
creationTimestamp: null
349360
name: my-lifecycle
350361
spec:

pkg/commands/clusterlifecycle/save_test.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,10 @@ func testSaveCreateCommand(t *testing.T, when spec.G, it spec.S) {
7171
APIVersion: "kpack.io/v1alpha2",
7272
},
7373
ObjectMeta: metav1.ObjectMeta{
74-
Name: "my-lifecycle",
75-
Annotations: map[string]string{},
74+
Name: "my-lifecycle",
75+
Annotations: map[string]string{
76+
"kubectl.kubernetes.io/last-applied-configuration": `{"kind":"ClusterLifecycle","apiVersion":"kpack.io/v1alpha2","metadata":{"name":"my-lifecycle","creationTimestamp":null},"spec":{"image":"default-registry.io/default-repo@sha256:lifecycle-image-digest","serviceAccountRef":{"namespace":"some-namespace","name":"some-serviceaccount"}},"status":{"image":{},"api":{},"apis":{"buildpack":{"deprecated":null,"supported":null},"platform":{"deprecated":null,"supported":null}}}}`,
77+
},
7678
},
7779
Spec: v1alpha2.ClusterLifecycleSpec{
7880
ImageSource: v1alpha1.ImageSource{
@@ -136,6 +138,8 @@ ClusterLifecycle "my-lifecycle" created
136138
const resourceYAML = `apiVersion: kpack.io/v1alpha2
137139
kind: ClusterLifecycle
138140
metadata:
141+
annotations:
142+
kubectl.kubernetes.io/last-applied-configuration: '{"kind":"ClusterLifecycle","apiVersion":"kpack.io/v1alpha2","metadata":{"name":"my-lifecycle","creationTimestamp":null},"spec":{"image":"default-registry.io/default-repo@sha256:lifecycle-image-digest","serviceAccountRef":{"namespace":"some-namespace","name":"some-serviceaccount"}},"status":{"image":{},"api":{},"apis":{"buildpack":{"deprecated":null,"supported":null},"platform":{"deprecated":null,"supported":null}}}}'
139143
creationTimestamp: null
140144
name: my-lifecycle
141145
spec:
@@ -181,7 +185,10 @@ Uploading to 'default-registry.io/default-repo'...
181185
"apiVersion": "kpack.io/v1alpha2",
182186
"metadata": {
183187
"name": "my-lifecycle",
184-
"creationTimestamp": null
188+
"creationTimestamp": null,
189+
"annotations": {
190+
"kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"ClusterLifecycle\",\"apiVersion\":\"kpack.io/v1alpha2\",\"metadata\":{\"name\":\"my-lifecycle\",\"creationTimestamp\":null},\"spec\":{\"image\":\"default-registry.io/default-repo@sha256:lifecycle-image-digest\",\"serviceAccountRef\":{\"namespace\":\"some-namespace\",\"name\":\"some-serviceaccount\"}},\"status\":{\"image\":{},\"api\":{},\"apis\":{\"buildpack\":{\"deprecated\":null,\"supported\":null},\"platform\":{\"deprecated\":null,\"supported\":null}}}}"
191+
}
185192
},
186193
"spec": {
187194
"image": "default-registry.io/default-repo@sha256:lifecycle-image-digest",
@@ -253,6 +260,8 @@ ClusterLifecycle "my-lifecycle" created (dry run)
253260
const resourceYAML = `apiVersion: kpack.io/v1alpha2
254261
kind: ClusterLifecycle
255262
metadata:
263+
annotations:
264+
kubectl.kubernetes.io/last-applied-configuration: '{"kind":"ClusterLifecycle","apiVersion":"kpack.io/v1alpha2","metadata":{"name":"my-lifecycle","creationTimestamp":null},"spec":{"image":"default-registry.io/default-repo@sha256:lifecycle-image-digest","serviceAccountRef":{"namespace":"some-namespace","name":"some-serviceaccount"}},"status":{"image":{},"api":{},"apis":{"buildpack":{"deprecated":null,"supported":null},"platform":{"deprecated":null,"supported":null}}}}'
256265
creationTimestamp: null
257266
name: my-lifecycle
258267
spec:
@@ -316,6 +325,8 @@ ClusterLifecycle "my-lifecycle" created (dry run with image upload)
316325
const resourceYAML = `apiVersion: kpack.io/v1alpha2
317326
kind: ClusterLifecycle
318327
metadata:
328+
annotations:
329+
kubectl.kubernetes.io/last-applied-configuration: '{"kind":"ClusterLifecycle","apiVersion":"kpack.io/v1alpha2","metadata":{"name":"my-lifecycle","creationTimestamp":null},"spec":{"image":"default-registry.io/default-repo@sha256:lifecycle-image-digest","serviceAccountRef":{"namespace":"some-namespace","name":"some-serviceaccount"}},"status":{"image":{},"api":{},"apis":{"buildpack":{"deprecated":null,"supported":null},"platform":{"deprecated":null,"supported":null}}}}'
319330
creationTimestamp: null
320331
name: my-lifecycle
321332
spec:

0 commit comments

Comments
 (0)