Skip to content

Commit 2922cce

Browse files
author
Paulo Gomes
authored
Merge pull request #469 from fluxcd/single-branch
Add feature gate `GitAllBranchReferences`
2 parents 922b4f6 + 9d242c5 commit 2922cce

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)