Skip to content

Commit

Permalink
Trigger a kpack build if requested buildpack changes
Browse files Browse the repository at this point in the history
Kpack would not trigger a build if the builder in the kpack image
changes and latest build had failed (e.g. push with buildpack A that
fails and then try to push with buildpack B) - see
buildpacks-community/kpack#1198

In order to work around this kpack limitation we annotate the latest
build with `image.kpack.io/additionalBuildNeeded` annotation that forces
kpack into creating a new build thus picking up the builder specified in
the kpack image.

Co-authored-by: Kieron Browne <kbrowne@vmware.com>
  • Loading branch information
danail-branekov and Kieron Browne committed May 10, 2023
1 parent c441f26 commit 9cfcebb
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 15 deletions.
28 changes: 20 additions & 8 deletions kpack-image-builder/controllers/buildworkload_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (r *BuildWorkloadReconciler) ReconcileResource(ctx context.Context, buildWo
return ctrl.Result{}, fmt.Errorf("failed getting kpack Image: %w", err)
}

err = r.ensureBuildCreationNotSkipped(ctx, buildWorkload, kpackImage)
err = r.recoverIfBuildCreationHasBeenSkipped(ctx, buildWorkload, kpackImage)
if err != nil {
r.log.Error(err, "ensuring kpack build was generated failed")
return ctrl.Result{}, ignoreDoNotRetryError(err)
Expand Down Expand Up @@ -248,7 +248,7 @@ func (r *BuildWorkloadReconciler) ReconcileResource(ctx context.Context, buildWo
return ctrl.Result{}, nil
}

func (r *BuildWorkloadReconciler) ensureBuildCreationNotSkipped(ctx context.Context, buildWorkload *korifiv1alpha1.BuildWorkload, kpackImage *buildv1alpha2.Image) error {
func (r *BuildWorkloadReconciler) recoverIfBuildCreationHasBeenSkipped(ctx context.Context, buildWorkload *korifiv1alpha1.BuildWorkload, kpackImage *buildv1alpha2.Image) error {
workloadImageGeneration, err := strconv.ParseInt(buildWorkload.Labels[ImageGenerationKey], 10, 64)
if err != nil {
r.log.Error(err, "couldn't parse image generation on buildworkload label")
Expand All @@ -260,13 +260,25 @@ func (r *BuildWorkloadReconciler) ensureBuildCreationNotSkipped(ctx context.Cont
imageReady.Status != corev1.ConditionUnknown &&
kpackImage.Status.ObservedGeneration >= workloadImageGeneration &&
kpackImage.Status.LatestBuildImageGeneration < workloadImageGeneration {
meta.SetStatusCondition(&buildWorkload.Status.Conditions, metav1.Condition{
Type: korifiv1alpha1.SucceededConditionType,
Status: metav1.ConditionFalse,
Reason: "NoKpackBuildCreated",
Message: "This may happen when only a specified buildpack is changed. You can modify the sources to force a new Kpack Build",
latestKpackBuild := &buildv1alpha2.Build{
ObjectMeta: metav1.ObjectMeta{
Namespace: kpackImage.Namespace,
Name: kpackImage.Status.LatestBuildRef,
},
}
err = r.k8sClient.Get(ctx, client.ObjectKeyFromObject(latestKpackBuild), latestKpackBuild)
if err != nil {
return fmt.Errorf("failed to get latest kpack build %q: %w", kpackImage.Status.LatestBuildRef, err)
}
err = k8s.Patch(ctx, r.k8sClient, latestKpackBuild, func() {
if latestKpackBuild.Annotations == nil {
latestKpackBuild.Annotations = map[string]string{}
}
latestKpackBuild.Annotations[buildv1alpha2.BuildNeededAnnotation] = "true"
})
return newDoNotRetryError(errors.New("kpack build was not created"))
if err != nil {
return fmt.Errorf("failed to request additional build for build %q: %w", latestKpackBuild.Name, err)
}
}

return nil
Expand Down
38 changes: 31 additions & 7 deletions kpack-image-builder/controllers/buildworkload_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,18 @@ var _ = Describe("BuildWorkloadReconciler", func() {
}
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(image), image)).To(Succeed())

Expect(k8sClient.Create(ctx, &buildv1alpha2.Build{
ObjectMeta: metav1.ObjectMeta{
Name: "first-build",
Namespace: namespaceGUID,
Labels: map[string]string{
buildv1alpha2.ImageLabel: "app-guid",
buildv1alpha2.ImageGenerationLabel: buildWorkload.Labels[controllers.ImageGenerationKey],
buildv1alpha2.BuildNumberLabel: "1",
},
},
})).To(Succeed())

Expect(k8s.Patch(ctx, k8sClient, image, func() {
gen, err := strconv.ParseInt(buildWorkload2.Labels[controllers.ImageGenerationKey], 10, 64)
Expect(err).NotTo(HaveOccurred())
Expand All @@ -832,14 +844,20 @@ var _ = Describe("BuildWorkloadReconciler", func() {
image.Status.Conditions = corev1alpha1.Conditions{
{Type: "Ready", Status: readyStatus},
}
image.Status.LatestBuildRef = "first-build"
})).To(Succeed())
})

It("should fail the second buildworkload", func() {
It("should annotate the latest build as re-build needed", func() {
Eventually(func(g Gomega) {
g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(buildWorkload2), buildWorkload2)).To(Succeed())
g.Expect(mustHaveCondition(g, buildWorkload2.Status.Conditions, korifiv1alpha1.SucceededConditionType).Status).To(Equal(metav1.ConditionFalse))
g.Expect(mustHaveCondition(g, buildWorkload2.Status.Conditions, korifiv1alpha1.SucceededConditionType).Reason).To(Equal("NoKpackBuildCreated"))
latestBuild := &buildv1alpha2.Build{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespaceGUID,
Name: "first-build",
},
}
g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(latestBuild), latestBuild)).To(Succeed())
g.Expect(latestBuild.Annotations).To(HaveKey(buildv1alpha2.BuildNeededAnnotation))
}).Should(Succeed())
})

Expand All @@ -848,10 +866,16 @@ var _ = Describe("BuildWorkloadReconciler", func() {
readyStatus = "Unknown"
})

It("does not fail the build workload", func() {
It("does not annotate the latest build as re-build needed", func() {
Consistently(func(g Gomega) {
g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(buildWorkload2), buildWorkload2)).To(Succeed())
g.Expect(mustHaveCondition(g, buildWorkload2.Status.Conditions, korifiv1alpha1.SucceededConditionType).Status).To(Equal(metav1.ConditionUnknown))
latestBuild := &buildv1alpha2.Build{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespaceGUID,
Name: "first-build",
},
}
g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(latestBuild), latestBuild)).To(Succeed())
g.Expect(latestBuild.Annotations).NotTo(HaveKey(buildv1alpha2.BuildNeededAnnotation))
}).Should(Succeed())
})
})
Expand Down

0 comments on commit 9cfcebb

Please sign in to comment.