Skip to content

Commit c0962f7

Browse files
authored
Merge pull request #373 from aryan9600/managed-transport-default
2 parents d2174b4 + 175f91e commit c0962f7

File tree

4 files changed

+265
-67
lines changed

4 files changed

+265
-67
lines changed

controllers/git_test.go

Lines changed: 116 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010

1111
"github.com/go-logr/logr"
1212
libgit2 "github.com/libgit2/git2go/v33"
13+
. "github.com/onsi/gomega"
14+
"k8s.io/apimachinery/pkg/types"
1315

1416
"github.com/fluxcd/pkg/gittestserver"
1517
)
@@ -127,22 +129,32 @@ func TestPushRejected(t *testing.T) {
127129
t.Fatal(err)
128130
}
129131

130-
// this is currently defined in update_test.go, but handy right here ..
131-
if err = initGitRepo(gitServer, "testdata/appconfig", "main", "/appconfig.git"); err != nil {
132+
// We use "test" as the branch to init repo, to avoid potential conflicts
133+
// with the default branch(main/master) of the system this test is running
134+
// on. If, for e.g., we used main as the branch and the default branch is
135+
// supposed to be main, this will fail as this would try to create a branch
136+
// named main explicitly.
137+
if err = initGitRepo(gitServer, "testdata/appconfig", "test", "/appconfig.git"); err != nil {
132138
t.Fatal(err)
133139
}
134140

135141
repoURL := gitServer.HTTPAddressWithCredentials() + "/appconfig.git"
136-
repo, err := clone(repoURL, "origin", "main")
142+
cloneCtx, cancel := context.WithTimeout(ctx, time.Second*10)
143+
defer cancel()
144+
repo, err := clone(cloneCtx, repoURL, "test")
137145
if err != nil {
138146
t.Fatal(err)
139147
}
148+
defer repo.Free()
149+
150+
cleanup, err := configureTransportOptsForRepo(repo)
151+
if err != nil {
152+
t.Fatal(err)
153+
}
154+
defer cleanup()
140155

141156
// This is here to guard against push in general being broken
142-
err = push(context.TODO(), repo.Workdir(), "main", repoAccess{
143-
url: repoURL,
144-
auth: nil,
145-
})
157+
err = push(context.TODO(), repo.Workdir(), "test", repoAccess{})
146158
if err != nil {
147159
t.Fatal(err)
148160
}
@@ -154,11 +166,104 @@ func TestPushRejected(t *testing.T) {
154166

155167
// This is supposed to fail, because the hook rejects the branch
156168
// pushed to.
157-
err = push(context.TODO(), repo.Workdir(), branch, repoAccess{
158-
url: repoURL,
159-
auth: nil,
160-
})
169+
err = push(context.TODO(), repo.Workdir(), branch, repoAccess{})
161170
if err == nil {
162171
t.Error("push to a forbidden branch is expected to fail, but succeeded")
163172
}
164173
}
174+
175+
func Test_switchToBranch(t *testing.T) {
176+
g := NewWithT(t)
177+
gitServer, err := gittestserver.NewTempGitServer()
178+
g.Expect(err).ToNot(HaveOccurred())
179+
gitServer.AutoCreate()
180+
g.Expect(gitServer.StartHTTP()).To(Succeed())
181+
182+
// We use "test" as the branch to init repo, to avoid potential conflicts
183+
// with the default branch(main/master) of the system this test is running
184+
// on. If, for e.g., we used main as the branch and the default branch is
185+
// supposed to be main, this will fail as this would try to create a branch
186+
// named main explicitly.
187+
branch := "test"
188+
g.Expect(initGitRepo(gitServer, "testdata/appconfig", branch, "/appconfig.git")).To(Succeed())
189+
190+
repoURL := gitServer.HTTPAddressWithCredentials() + "/appconfig.git"
191+
cloneCtx, cancel := context.WithTimeout(ctx, time.Second*10)
192+
defer cancel()
193+
repo, err := clone(cloneCtx, repoURL, branch)
194+
g.Expect(err).ToNot(HaveOccurred())
195+
defer repo.Free()
196+
197+
head, err := repo.Head()
198+
g.Expect(err).ToNot(HaveOccurred())
199+
defer head.Free()
200+
target := head.Target()
201+
202+
// register transport options and update remote to transport url
203+
cleanup, err := configureTransportOptsForRepo(repo)
204+
if err != nil {
205+
t.Fatal(err)
206+
}
207+
defer cleanup()
208+
209+
// calling switchToBranch with a branch that doesn't exist on origin
210+
// should result in the branch being created and switched to.
211+
branch = "not-on-origin"
212+
switchToBranch(repo, context.TODO(), branch, repoAccess{})
213+
214+
head, err = repo.Head()
215+
g.Expect(err).ToNot(HaveOccurred())
216+
name, err := head.Branch().Name()
217+
g.Expect(err).ToNot(HaveOccurred())
218+
g.Expect(name).To(Equal(branch))
219+
220+
cc, err := repo.LookupCommit(head.Target())
221+
g.Expect(err).ToNot(HaveOccurred())
222+
defer cc.Free()
223+
g.Expect(cc.Id().String()).To(Equal(target.String()))
224+
225+
// create a branch with the HEAD commit and push it to origin
226+
branch = "exists-on-origin"
227+
_, err = repo.CreateBranch(branch, cc, false)
228+
g.Expect(err).ToNot(HaveOccurred())
229+
origin, err := repo.Remotes.Lookup(originRemote)
230+
g.Expect(err).ToNot(HaveOccurred())
231+
defer origin.Free()
232+
233+
g.Expect(origin.Push(
234+
[]string{fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)}, &libgit2.PushOptions{},
235+
)).To(Succeed())
236+
237+
// push a new commit to the branch. this is done to test whether we properly
238+
// sync our local branch with the remote branch, before switching.
239+
policyKey := types.NamespacedName{
240+
Name: "policy",
241+
Namespace: "ns",
242+
}
243+
commitID := commitInRepo(g, repoURL, branch, "Install setter marker", func(tmp string) {
244+
g.Expect(replaceMarker(tmp, policyKey)).To(Succeed())
245+
})
246+
247+
// calling switchToBranch with a branch that exists should make sure to fetch latest
248+
// for that branch from origin, and then switch to it.
249+
switchToBranch(repo, context.TODO(), branch, repoAccess{})
250+
head, err = repo.Head()
251+
g.Expect(err).ToNot(HaveOccurred())
252+
name, err = head.Branch().Name()
253+
254+
g.Expect(err).ToNot(HaveOccurred())
255+
g.Expect(name).To(Equal(branch))
256+
g.Expect(head.Target().String()).To(Equal(commitID.String()))
257+
258+
// push a commit after switching to the branch, to check if the local
259+
// branch is synced with origin.
260+
replaceMarker(repo.Workdir(), policyKey)
261+
sig := &libgit2.Signature{
262+
Name: "Testbot",
263+
264+
When: time.Now(),
265+
}
266+
_, err = commitWorkDir(repo, branch, "update policy", sig)
267+
g.Expect(err).ToNot(HaveOccurred())
268+
g.Expect(push(context.TODO(), repo.Workdir(), branch, repoAccess{})).To(Succeed())
269+
}

controllers/imageupdateautomation_controller.go

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ type repoAccess struct {
510510
func (r *ImageUpdateAutomationReconciler) getRepoAccess(ctx context.Context, repository *sourcev1.GitRepository) (repoAccess, error) {
511511
var access repoAccess
512512
access.url = repository.Spec.URL
513+
access.auth = &git.AuthOptions{}
513514

514515
if repository.Spec.SecretRef != nil {
515516
name := types.NamespacedName{
@@ -540,12 +541,15 @@ func (r repoAccess) remoteCallbacks(ctx context.Context) libgit2.RemoteCallbacks
540541
// cloneInto clones the upstream repository at the `ref` given (which
541542
// can be `nil`). It returns a `*libgit2.Repository` since that is used
542543
// for committing changes.
543-
func cloneInto(ctx context.Context, access repoAccess, ref *sourcev1.GitRepositoryRef, path string) (*libgit2.Repository, error) {
544+
func cloneInto(ctx context.Context, access repoAccess, ref *sourcev1.GitRepositoryRef,
545+
path string) (_ *libgit2.Repository, err error) {
546+
defer recoverPanic(&err)
547+
544548
opts := git.CheckoutOptions{}
545549
if ref != nil {
546550
opts.Tag = ref.Tag
547551
opts.SemVer = ref.SemVer
548-
opts.Tag = ref.Tag
552+
opts.Commit = ref.Commit
549553
opts.Branch = ref.Branch
550554
}
551555
checkoutStrat, err := gitstrat.CheckoutStrategyForImplementation(ctx, sourcev1.LibGit2Implementation, opts)
@@ -748,7 +752,6 @@ func switchToBranch(repo *libgit2.Repository, ctx context.Context, branch string
748752
callbacks = managed.RemoteCallbacks()
749753
}
750754

751-
branchRef := fmt.Sprintf("origin/%s", branch)
752755
// Force the fetching of the remote branch.
753756
err = origin.Fetch([]string{branch}, &libgit2.FetchOptions{
754757
RemoteCallbacks: callbacks,
@@ -757,7 +760,7 @@ func switchToBranch(repo *libgit2.Repository, ctx context.Context, branch string
757760
return fmt.Errorf("cannot fetch remote branch: %w", err)
758761
}
759762

760-
remoteBranch, err := repo.LookupBranch(branchRef, libgit2.BranchRemote)
763+
remoteBranch, err := repo.References.Lookup(fmt.Sprintf("refs/remotes/origin/%s", branch))
761764
if err != nil && !libgit2.IsErrorCode(err, libgit2.ErrorCodeNotFound) {
762765
return err
763766
}
@@ -784,15 +787,25 @@ func switchToBranch(repo *libgit2.Repository, ctx context.Context, branch string
784787
}
785788
defer commit.Free()
786789

787-
localBranch, err := repo.LookupBranch(branch, libgit2.BranchLocal)
790+
localBranch, err := repo.References.Lookup(fmt.Sprintf("refs/heads/%s", branch))
788791
if err != nil && !libgit2.IsErrorCode(err, libgit2.ErrorCodeNotFound) {
789792
return fmt.Errorf("cannot lookup branch '%s': %w", branch, err)
790793
}
791794
if localBranch == nil {
792-
localBranch, err = repo.CreateBranch(branch, commit, false)
793-
}
794-
if localBranch == nil {
795-
return fmt.Errorf("cannot create local branch '%s': %w", branch, err)
795+
lb, err := repo.CreateBranch(branch, commit, false)
796+
if err != nil {
797+
return fmt.Errorf("cannot create branch '%s': %w", branch, err)
798+
}
799+
defer lb.Free()
800+
// We could've done something like:
801+
// localBranch = lb.Reference
802+
// But for some reason, calling `lb.Free()` AND using it, causes a really
803+
// nasty crash. Since, we can't avoid calling `lb.Free()`, in order to prevent
804+
// memory leaks, we don't use `lb` and instead manually lookup the ref.
805+
localBranch, err = repo.References.Lookup(fmt.Sprintf("refs/heads/%s", branch))
806+
if err != nil {
807+
return fmt.Errorf("cannot lookup branch '%s': %w", branch, err)
808+
}
796809
}
797810
defer localBranch.Free()
798811

@@ -810,6 +823,12 @@ func switchToBranch(repo *libgit2.Repository, ctx context.Context, branch string
810823
return fmt.Errorf("cannot checkout tree for branch '%s': %w", branch, err)
811824
}
812825

826+
ref, err := localBranch.SetTarget(commit.Id(), "")
827+
if err != nil {
828+
return fmt.Errorf("cannot update branch '%s' to be at target commit: %w", branch, err)
829+
}
830+
ref.Free()
831+
813832
return repo.SetHead("refs/heads/" + branch)
814833
}
815834

@@ -964,3 +983,9 @@ func templateMsg(messageTemplate string, templateValues *TemplateData) (string,
964983
}
965984
return b.String(), nil
966985
}
986+
987+
func recoverPanic(err *error) {
988+
if r := recover(); r != nil {
989+
*err = fmt.Errorf("recovered from git2go panic: %v", r)
990+
}
991+
}

controllers/suite_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@ import (
2424
"testing"
2525
"time"
2626

27+
"github.com/go-logr/logr"
2728
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
2829
"k8s.io/client-go/kubernetes/scheme"
2930
ctrl "sigs.k8s.io/controller-runtime"
3031

3132
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta1"
3233
"github.com/fluxcd/pkg/runtime/testenv"
3334
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
35+
"github.com/fluxcd/source-controller/pkg/git/libgit2/managed"
3436

3537
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
3638
// +kubebuilder:scaffold:imports
@@ -61,6 +63,8 @@ func TestMain(m *testing.M) {
6163
filepath.Join("testdata", "crds"),
6264
))
6365

66+
managed.InitManagedTransport(logr.Discard())
67+
6468
controllerName := "image-automation-controller"
6569
if err := (&ImageUpdateAutomationReconciler{
6670
Client: testEnv,

0 commit comments

Comments
 (0)