Skip to content

Commit 9d242c5

Browse files
author
Paulo Gomes
committed
Add feature gate GitAllBranchReferences
The new feature gate enables users to toggle the download of all branch head references when push branches are configured. Tests were refactored to ensure that they are feature gate sensitive. Signed-off-by: Paulo Gomes <[email protected]>
1 parent 922b4f6 commit 9d242c5

File tree

4 files changed

+98
-34
lines changed

4 files changed

+98
-34
lines changed

controllers/imageupdateautomation_controller.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,15 +261,24 @@ func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctr
261261
}
262262

263263
clientOpts := []gogit.ClientOption{gogit.WithDiskStorage()}
264-
forcePush, _ := features.Enabled(features.GitForcePushBranch)
264+
forcePush := r.features[features.GitForcePushBranch]
265265
if forcePush && pushBranch != ref.Branch {
266266
clientOpts = append(clientOpts, gogit.WithForcePush())
267267
}
268268
if authOpts.Transport == git.HTTP {
269269
clientOpts = append(clientOpts, gogit.WithInsecureCredentialsOverHTTP())
270270
}
271+
272+
// If the push branch is different from the checkout ref, we need to
273+
// have all the references downloaded at clone time, to ensure that
274+
// SwitchBranch will have access to the target branch state. fluxcd/flux2#3384
275+
//
276+
// To always overwrite the push branch, the feature gate
277+
// GitAllBranchReferences can be set to false, which will cause
278+
// the SwitchBranch operation to ignore the remote branch state.
279+
allReferences := r.features[features.GitAllBranchReferences]
271280
if pushBranch != ref.Branch {
272-
clientOpts = append(clientOpts, gogit.WithSingleBranch(false))
281+
clientOpts = append(clientOpts, gogit.WithSingleBranch(!allReferences))
273282
}
274283

275284
gitClient, err := gogit.NewClient(tmp, authOpts, clientOpts...)

controllers/suite_test.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import (
3333
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
3434

3535
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
36-
"github.com/fluxcd/image-automation-controller/internal/features"
3736
// +kubebuilder:scaffold:imports
3837
)
3938

@@ -60,13 +59,8 @@ func TestMain(m *testing.M) {
6059
code := runTestsWithFeatures(m, nil)
6160
if code != 0 {
6261
fmt.Println("failed with default feature values")
63-
os.Exit(code)
6462
}
6563

66-
code = runTestsWithFeatures(m, map[string]bool{
67-
features.GitShallowClone: true,
68-
})
69-
7064
os.Exit(code)
7165
}
7266

controllers/update_test.go

Lines changed: 80 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import (
6262
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
6363

6464
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
65+
"github.com/fluxcd/image-automation-controller/internal/features"
6566
"github.com/fluxcd/image-automation-controller/pkg/test"
6667
"github.com/fluxcd/image-automation-controller/pkg/update"
6768
)
@@ -426,7 +427,7 @@ func TestImageAutomationReconciler_signedCommit(t *testing.T) {
426427
func TestImageAutomationReconciler_e2e(t *testing.T) {
427428
protos := []string{"http", "ssh"}
428429

429-
testFunc := func(t *testing.T, proto, clientName string) {
430+
testFunc := func(t *testing.T, proto string, feats map[string]bool) {
430431
g := NewWithT(t)
431432

432433
const latestImage = "helloworld:1.0.1"
@@ -441,8 +442,18 @@ func TestImageAutomationReconciler_e2e(t *testing.T) {
441442
Strategy: imagev1.UpdateStrategySetters,
442443
}
443444

445+
controllerName := "image-automation-controller"
446+
// Create ImagePolicy and ImageUpdateAutomation resource for each of the
447+
// test cases and cleanup at the end.
448+
builder := fakeclient.NewClientBuilder().WithScheme(testEnv.Scheme())
449+
r := &ImageUpdateAutomationReconciler{
450+
Client: builder.Build(),
451+
EventRecorder: testEnv.GetEventRecorderFor(controllerName),
452+
features: feats,
453+
}
454+
444455
// Create a test namespace.
445-
nsCleanup, err := createNamespace(testEnv, namespace)
456+
nsCleanup, err := createNamespace(r.Client, namespace)
446457
g.Expect(err).ToNot(HaveOccurred(), "failed to create test namespace")
447458
defer func() {
448459
g.Expect(nsCleanup()).To(Succeed())
@@ -478,12 +489,12 @@ func TestImageAutomationReconciler_e2e(t *testing.T) {
478489
if proto == "ssh" {
479490
// SSH requires an identity (private key) and known_hosts file
480491
// in a secret.
481-
err = createSSHIdentitySecret(testEnv, gitSecretName, namespace, repoURL)
492+
err = createSSHIdentitySecret(r.Client, gitSecretName, namespace, repoURL)
482493
g.Expect(err).ToNot(HaveOccurred())
483-
err = createGitRepository(testEnv, gitRepoName, namespace, repoURL, gitSecretName, clientName)
494+
err = createGitRepository(r.Client, gitRepoName, namespace, repoURL, gitSecretName)
484495
g.Expect(err).ToNot(HaveOccurred())
485496
} else {
486-
err = createGitRepository(testEnv, gitRepoName, namespace, repoURL, "", clientName)
497+
err = createGitRepository(r.Client, gitRepoName, namespace, repoURL, "")
487498
g.Expect(err).ToNot(HaveOccurred())
488499
}
489500

@@ -493,9 +504,6 @@ func TestImageAutomationReconciler_e2e(t *testing.T) {
493504
Namespace: namespace,
494505
}
495506

496-
// Create ImagePolicy and ImageUpdateAutomation resource for each of the
497-
// test cases and cleanup at the end.
498-
499507
t.Run("PushSpec", func(t *testing.T) {
500508
g := NewWithT(t)
501509

@@ -507,16 +515,21 @@ func TestImageAutomationReconciler_e2e(t *testing.T) {
507515

508516
// NB not testing the image reflector controller; this
509517
// will make a "fully formed" ImagePolicy object.
510-
err = createImagePolicyWithLatestImage(testEnv, imagePolicyName, namespace, "not-expected-to-exist", "1.x", latestImage)
518+
err = createImagePolicyWithLatestImage(r.Client, imagePolicyName, namespace, "not-expected-to-exist", "1.x", latestImage)
511519
g.Expect(err).ToNot(HaveOccurred(), "failed to create ImagePolicy resource")
512520

513521
defer func() {
514-
g.Expect(deleteImagePolicy(testEnv, imagePolicyName, namespace)).ToNot(HaveOccurred())
522+
g.Expect(deleteImagePolicy(r.Client, imagePolicyName, namespace)).ToNot(HaveOccurred())
515523
}()
516524

517525
imageUpdateAutomationName := "update-" + randStringRunes(5)
518526
pushBranch := "pr-" + randStringRunes(5)
519527

528+
automationKey := types.NamespacedName{
529+
Name: imageUpdateAutomationName,
530+
Namespace: namespace,
531+
}
532+
520533
t.Run("update with PushSpec", func(t *testing.T) {
521534
g := NewWithT(t)
522535

@@ -531,9 +544,12 @@ func TestImageAutomationReconciler_e2e(t *testing.T) {
531544

532545
// Now create the automation object, and let it (one
533546
// hopes!) make a commit itself.
534-
err = createImageUpdateAutomation(testEnv, imageUpdateAutomationName, namespace, gitRepoName, namespace, branch, pushBranch, commitMessage, "", updateStrategy)
547+
err = createImageUpdateAutomation(r.Client, imageUpdateAutomationName, namespace, gitRepoName, namespace, branch, pushBranch, commitMessage, "", updateStrategy)
535548
g.Expect(err).ToNot(HaveOccurred())
536549

550+
_, err = r.Reconcile(context.TODO(), ctrl.Request{NamespacedName: automationKey})
551+
g.Expect(err).To(BeNil())
552+
537553
initialHead, err := headFromBranch(localRepo, branch)
538554
g.Expect(err).ToNot(HaveOccurred())
539555

@@ -555,6 +571,10 @@ func TestImageAutomationReconciler_e2e(t *testing.T) {
555571
})
556572

557573
t.Run("push branch gets updated", func(t *testing.T) {
574+
if !feats[features.GitAllBranchReferences] {
575+
t.Skip("GitAllBranchReferences feature not enabled")
576+
}
577+
558578
g := NewWithT(t)
559579

560580
initialHead, err := headFromBranch(localRepo, branch)
@@ -569,9 +589,12 @@ func TestImageAutomationReconciler_e2e(t *testing.T) {
569589

570590
// Update the policy and expect another commit in the push
571591
// branch.
572-
err = updateImagePolicyWithLatestImage(testEnv, imagePolicyName, namespace, "helloworld:v1.3.0")
592+
err = updateImagePolicyWithLatestImage(r.Client, imagePolicyName, namespace, "helloworld:v1.3.0")
573593
g.Expect(err).ToNot(HaveOccurred())
574594

595+
_, err = r.Reconcile(context.TODO(), ctrl.Request{NamespacedName: automationKey})
596+
g.Expect(err).To(BeNil())
597+
575598
waitForNewHead(g, localRepo, pushBranch, preChangeCommitId)
576599

577600
head, err = getRemoteHead(localRepo, pushBranch)
@@ -586,6 +609,10 @@ func TestImageAutomationReconciler_e2e(t *testing.T) {
586609
})
587610

588611
t.Run("still pushes to the push branch after it's merged", func(t *testing.T) {
612+
if !feats[features.GitAllBranchReferences] {
613+
t.Skip("GitAllBranchReferences feature not enabled")
614+
}
615+
589616
g := NewWithT(t)
590617

591618
initialHead, err := headFromBranch(localRepo, branch)
@@ -617,9 +644,12 @@ func TestImageAutomationReconciler_e2e(t *testing.T) {
617644

618645
// Update the policy and expect another commit in the push
619646
// branch.
620-
err = updateImagePolicyWithLatestImage(testEnv, imagePolicyName, namespace, "helloworld:v1.3.1")
647+
err = updateImagePolicyWithLatestImage(r.Client, imagePolicyName, namespace, "helloworld:v1.3.1")
621648
g.Expect(err).ToNot(HaveOccurred())
622649

650+
_, err = r.Reconcile(context.TODO(), ctrl.Request{NamespacedName: automationKey})
651+
g.Expect(err).To(BeNil())
652+
623653
waitForNewHead(g, localRepo, pushBranch, preChangeCommitId)
624654

625655
head, err = getRemoteHead(localRepo, pushBranch)
@@ -634,7 +664,7 @@ func TestImageAutomationReconciler_e2e(t *testing.T) {
634664
})
635665

636666
// Cleanup the image update automation used above.
637-
g.Expect(deleteImageUpdateAutomation(testEnv, imageUpdateAutomationName, namespace)).To(Succeed())
667+
g.Expect(deleteImageUpdateAutomation(r.Client, imageUpdateAutomationName, namespace)).To(Succeed())
638668
})
639669

640670
t.Run("with update strategy setters", func(t *testing.T) {
@@ -652,11 +682,11 @@ func TestImageAutomationReconciler_e2e(t *testing.T) {
652682
g.Expect(err).ToNot(HaveOccurred(), "failed to clone git repo")
653683

654684
g.Expect(checkoutBranch(localRepo, branch)).ToNot(HaveOccurred())
655-
err = createImagePolicyWithLatestImage(testEnv, imagePolicyName, namespace, "not-expected-to-exist", "1.x", latestImage)
685+
err = createImagePolicyWithLatestImage(r.Client, imagePolicyName, namespace, "not-expected-to-exist", "1.x", latestImage)
656686
g.Expect(err).ToNot(HaveOccurred(), "failed to create ImagePolicy resource")
657687

658688
defer func() {
659-
g.Expect(deleteImagePolicy(testEnv, imagePolicyName, namespace)).ToNot(HaveOccurred())
689+
g.Expect(deleteImagePolicy(r.Client, imagePolicyName, namespace)).ToNot(HaveOccurred())
660690
}()
661691

662692
preChangeCommitId := commitIdFromBranch(localRepo, branch)
@@ -679,12 +709,15 @@ func TestImageAutomationReconciler_e2e(t *testing.T) {
679709
Namespace: namespace,
680710
Name: "update-" + randStringRunes(5),
681711
}
682-
err = createImageUpdateAutomation(testEnv, updateKey.Name, namespace, gitRepoName, namespace, branch, "", commitMessage, "", updateStrategy)
712+
err = createImageUpdateAutomation(r.Client, updateKey.Name, namespace, gitRepoName, namespace, branch, "", commitMessage, "", updateStrategy)
683713
g.Expect(err).ToNot(HaveOccurred())
684714
defer func() {
685-
g.Expect(deleteImageUpdateAutomation(testEnv, updateKey.Name, namespace)).To(Succeed())
715+
g.Expect(deleteImageUpdateAutomation(r.Client, updateKey.Name, namespace)).To(Succeed())
686716
}()
687717

718+
_, err = r.Reconcile(context.TODO(), ctrl.Request{NamespacedName: updateKey})
719+
g.Expect(err).To(BeNil())
720+
688721
// Wait for a new commit to be made by the controller.
689722
waitForNewHead(g, localRepo, branch, preChangeCommitId)
690723

@@ -696,7 +729,7 @@ func TestImageAutomationReconciler_e2e(t *testing.T) {
696729
g.Expect(commit.Message).To(Equal(commitMessage))
697730

698731
var newObj imagev1.ImageUpdateAutomation
699-
g.Expect(testEnv.Get(context.Background(), updateKey, &newObj)).To(Succeed())
732+
g.Expect(r.Client.Get(context.Background(), updateKey, &newObj)).To(Succeed())
700733
g.Expect(newObj.Status.LastPushCommit).To(Equal(commit.Hash.String()))
701734
g.Expect(newObj.Status.LastPushTime).ToNot(BeNil())
702735

@@ -708,6 +741,12 @@ func TestImageAutomationReconciler_e2e(t *testing.T) {
708741
t.Run("no reconciliation when object is suspended", func(t *testing.T) {
709742
g := NewWithT(t)
710743

744+
nsCleanup, err := createNamespace(testEnv, namespace)
745+
g.Expect(err).ToNot(HaveOccurred(), "failed to create test namespace")
746+
defer func() {
747+
g.Expect(nsCleanup()).To(Succeed())
748+
}()
749+
711750
err = createImagePolicyWithLatestImage(testEnv, imagePolicyName, namespace, "not-expected-to-exist", "1.x", latestImage)
712751
g.Expect(err).ToNot(HaveOccurred(), "failed to create ImagePolicy resource")
713752

@@ -726,6 +765,9 @@ func TestImageAutomationReconciler_e2e(t *testing.T) {
726765
g.Expect(deleteImageUpdateAutomation(testEnv, updateKey.Name, namespace)).To(Succeed())
727766
}()
728767

768+
_, err = r.Reconcile(context.TODO(), ctrl.Request{NamespacedName: updateKey})
769+
g.Expect(err).To(BeNil())
770+
729771
// Wait for the object to be available in the cache before
730772
// attempting update.
731773
g.Eventually(func() bool {
@@ -775,10 +817,16 @@ func TestImageAutomationReconciler_e2e(t *testing.T) {
775817
})
776818
}
777819

778-
for _, proto := range protos {
779-
t.Run(fmt.Sprintf("%s/%s", gogit.ClientName, proto), func(t *testing.T) {
780-
testFunc(t, proto, gogit.ClientName)
781-
})
820+
for _, enabled := range []bool{true, false} {
821+
feats := features.FeatureGates()
822+
for k := range feats {
823+
feats[k] = enabled
824+
}
825+
for _, proto := range protos {
826+
t.Run(fmt.Sprintf("%s/features=%t", proto, enabled), func(t *testing.T) {
827+
testFunc(t, proto, feats)
828+
})
829+
}
782830
}
783831
}
784832

@@ -1361,7 +1409,7 @@ func testWithCustomRepoAndImagePolicy(
13611409
g.Expect(err).ToNot(HaveOccurred(), "failed to create new remote origin")
13621410

13631411
// Create GitRepository resource for the above repo.
1364-
err = createGitRepository(kClient, args.gitRepoName, args.gitRepoNamespace, repoURL, "", gitImpl)
1412+
err = createGitRepository(kClient, args.gitRepoName, args.gitRepoNamespace, repoURL, "")
13651413
g.Expect(err).ToNot(HaveOccurred(), "failed to create GitRepository resource")
13661414

13671415
// Create ImagePolicy with populated latest image in the status.
@@ -1412,19 +1460,19 @@ func createNamespace(kClient client.Client, name string) (cleanup, error) {
14121460
return cleanup, nil
14131461
}
14141462

1415-
func createGitRepository(kClient client.Client, name, namespace, repoURL, secretRef, gitImpl string) error {
1463+
func createGitRepository(kClient client.Client, name, namespace, repoURL, secretRef string) error {
14161464
gitRepo := &sourcev1.GitRepository{
14171465
Spec: sourcev1.GitRepositorySpec{
14181466
URL: repoURL,
14191467
Interval: metav1.Duration{Duration: time.Minute},
1468+
Timeout: &metav1.Duration{Duration: time.Minute},
14201469
},
14211470
}
14221471
gitRepo.Name = name
14231472
gitRepo.Namespace = namespace
14241473
if secretRef != "" {
14251474
gitRepo.Spec.SecretRef = &meta.LocalObjectReference{Name: secretRef}
14261475
}
1427-
gitRepo.Spec.GitImplementation = gitImpl
14281476
return kClient.Create(context.Background(), gitRepo)
14291477
}
14301478

@@ -1580,6 +1628,12 @@ func createSSHIdentitySecret(kClient client.Client, name, namespace, repoURL str
15801628
"identity": string(pair.PrivateKey),
15811629
"identity.pub": string(pair.PublicKey),
15821630
},
1631+
// Without KAS, StringData and Data must be kept in sync manually.
1632+
Data: map[string][]byte{
1633+
"known_hosts": knownhosts,
1634+
"identity": pair.PrivateKey,
1635+
"identity.pub": pair.PublicKey,
1636+
},
15831637
}
15841638
sec.Name = name
15851639
sec.Namespace = namespace

internal/features/features.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ const (
2828
// GitShallowClone enables the use of shallow clones when pulling source from
2929
// Git repositories.
3030
GitShallowClone = "GitShallowClone"
31+
// GitAllBranchReferences enables the download of all branch head references
32+
// when push branches are configured. When enabled fixes fluxcd/flux2#3384.
33+
GitAllBranchReferences = "GitAllBranchReferences"
3134
)
3235

3336
var features = map[string]bool{
@@ -38,6 +41,10 @@ var features = map[string]bool{
3841
// GitShallowClone
3942
// opt-out from v0.28
4043
GitShallowClone: true,
44+
45+
// GitAllBranchReferences
46+
// opt-out from v0.28
47+
GitAllBranchReferences: true,
4148
}
4249

4350
// DefaultFeatureGates contains a list of all supported feature gates and

0 commit comments

Comments
 (0)