Skip to content

Commit 0354d8d

Browse files
authored
Merge pull request #52 from fluxcd/git-improvements
2 parents 4f18299 + 078d005 commit 0354d8d

File tree

8 files changed

+1085
-237
lines changed

8 files changed

+1085
-237
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o source-c
2020

2121
FROM alpine:3.11
2222

23-
RUN apk add --no-cache openssh-client ca-certificates tar tini 'git>=2.12.0' socat curl bash
23+
RUN apk add --no-cache ca-certificates tar tini 'git>=2.12.0' socat curl bash
2424

2525
COPY --from=builder /workspace/source-controller /usr/local/bin/
2626

controllers/gitrepository_controller.go

Lines changed: 39 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ import (
2222
"io/ioutil"
2323
"os"
2424

25-
"github.com/blang/semver"
26-
"github.com/go-git/go-git/v5"
27-
"github.com/go-git/go-git/v5/plumbing"
25+
"github.com/go-git/go-git/v5/plumbing/object"
2826
"github.com/go-git/go-git/v5/plumbing/transport"
2927
"github.com/go-logr/logr"
3028
corev1 "k8s.io/api/core/v1"
@@ -121,34 +119,18 @@ func (r *GitRepositoryReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, o
121119
}
122120

123121
func (r *GitRepositoryReconciler) sync(ctx context.Context, repository sourcev1.GitRepository) (sourcev1.GitRepository, error) {
124-
// set defaults: master branch, no tags fetching, max two commits
125-
branch := "master"
126-
revision := ""
127-
tagMode := git.NoTags
128-
depth := 2
129-
130-
// determine ref
131-
refName := plumbing.NewBranchReferenceName(branch)
132-
if repository.Spec.Reference != nil {
133-
if repository.Spec.Reference.Branch != "" {
134-
branch = repository.Spec.Reference.Branch
135-
refName = plumbing.NewBranchReferenceName(branch)
136-
}
137-
if repository.Spec.Reference.Commit != "" {
138-
depth = 0
139-
} else {
140-
if repository.Spec.Reference.Tag != "" {
141-
refName = plumbing.NewTagReferenceName(repository.Spec.Reference.Tag)
142-
}
143-
if repository.Spec.Reference.SemVer != "" {
144-
tagMode = git.AllTags
145-
}
146-
}
122+
// create tmp dir for the Git clone
123+
tmpGit, err := ioutil.TempDir("", repository.Name)
124+
if err != nil {
125+
err = fmt.Errorf("tmp dir error: %w", err)
126+
return sourcev1.GitRepositoryNotReady(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
147127
}
128+
defer os.RemoveAll(tmpGit)
148129

149130
// determine auth method
150131
var auth transport.AuthMethod
151-
if repository.Spec.SecretRef != nil {
132+
authStrategy := intgit.AuthSecretStrategyForURL(repository.Spec.URL)
133+
if repository.Spec.SecretRef != nil && authStrategy != nil {
152134
name := types.NamespacedName{
153135
Namespace: repository.GetNamespace(),
154136
Name: repository.Spec.SecretRef.Name,
@@ -161,173 +143,32 @@ func (r *GitRepositoryReconciler) sync(ctx context.Context, repository sourcev1.
161143
return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
162144
}
163145

164-
method, cleanup, err := intgit.AuthMethodFromSecret(repository.Spec.URL, secret)
146+
auth, err = authStrategy.Method(secret)
165147
if err != nil {
166148
err = fmt.Errorf("auth error: %w", err)
167149
return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
168150
}
169-
if cleanup != nil {
170-
defer cleanup()
171-
}
172-
auth = method
173151
}
174152

175-
// create tmp dir for the Git clone
176-
tmpGit, err := ioutil.TempDir("", repository.Name)
153+
checkoutStrategy := intgit.CheckoutStrategyForRef(repository.Spec.Reference)
154+
commit, revision, err := checkoutStrategy.Checkout(ctx, tmpGit, repository.Spec.URL, auth)
177155
if err != nil {
178-
err = fmt.Errorf("tmp dir error: %w", err)
179-
return sourcev1.GitRepositoryNotReady(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
180-
}
181-
defer os.RemoveAll(tmpGit)
182-
183-
// clone to tmp
184-
gitCtx, cancel := context.WithTimeout(ctx, repository.GetTimeout())
185-
repo, err := git.PlainCloneContext(gitCtx, tmpGit, false, &git.CloneOptions{
186-
URL: repository.Spec.URL,
187-
Auth: auth,
188-
RemoteName: "origin",
189-
ReferenceName: refName,
190-
SingleBranch: true,
191-
NoCheckout: false,
192-
Depth: depth,
193-
RecurseSubmodules: 0,
194-
Progress: nil,
195-
Tags: tagMode,
196-
})
197-
cancel()
198-
if err != nil {
199-
err = fmt.Errorf("git clone error: %w", err)
200-
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
201-
}
202-
203-
// checkout commit or tag
204-
if repository.Spec.Reference != nil {
205-
if commit := repository.Spec.Reference.Commit; commit != "" {
206-
w, err := repo.Worktree()
207-
if err != nil {
208-
err = fmt.Errorf("git worktree error: %w", err)
209-
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
210-
}
211-
212-
err = w.Checkout(&git.CheckoutOptions{
213-
Hash: plumbing.NewHash(commit),
214-
Force: true,
215-
})
216-
if err != nil {
217-
err = fmt.Errorf("git checkout '%s' for '%s' error: %w", commit, branch, err)
218-
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
219-
}
220-
} else if exp := repository.Spec.Reference.SemVer; exp != "" {
221-
rng, err := semver.ParseRange(exp)
222-
if err != nil {
223-
err = fmt.Errorf("semver parse range error: %w", err)
224-
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
225-
}
226-
227-
repoTags, err := repo.Tags()
228-
if err != nil {
229-
err = fmt.Errorf("git list tags error: %w", err)
230-
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
231-
}
232-
233-
tags := make(map[string]string)
234-
_ = repoTags.ForEach(func(t *plumbing.Reference) error {
235-
tags[t.Name().Short()] = t.Strings()[1]
236-
return nil
237-
})
238-
239-
svTags := make(map[string]string)
240-
var svers []semver.Version
241-
for tag, _ := range tags {
242-
v, _ := semver.ParseTolerant(tag)
243-
if rng(v) {
244-
svers = append(svers, v)
245-
svTags[v.String()] = tag
246-
}
247-
}
248-
249-
if len(svers) > 0 {
250-
semver.Sort(svers)
251-
v := svers[len(svers)-1]
252-
t := svTags[v.String()]
253-
commit := tags[t]
254-
revision = fmt.Sprintf("%s/%s", t, commit)
255-
256-
w, err := repo.Worktree()
257-
if err != nil {
258-
err = fmt.Errorf("git worktree error: %w", err)
259-
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
260-
}
261-
262-
err = w.Checkout(&git.CheckoutOptions{
263-
Hash: plumbing.NewHash(commit),
264-
})
265-
if err != nil {
266-
err = fmt.Errorf("git checkout error: %w", err)
267-
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
268-
}
269-
} else {
270-
err = fmt.Errorf("no match found for semver: %s", repository.Spec.Reference.SemVer)
271-
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
272-
}
273-
}
274-
}
275-
276-
// read commit hash
277-
ref, err := repo.Head()
278-
if err != nil {
279-
err = fmt.Errorf("git resolve HEAD error: %w", err)
280156
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
281157
}
282158

283159
// verify PGP signature
284160
if repository.Spec.Verification != nil {
285-
commit, err := repo.CommitObject(ref.Hash())
286-
if err != nil {
287-
err = fmt.Errorf("git resolve HEAD error: %w", err)
288-
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
289-
}
290-
291-
if commit.PGPSignature == "" {
292-
err = fmt.Errorf("PGP signature not found for commit '%s'", ref.Hash())
293-
return sourcev1.GitRepositoryNotReady(repository, sourcev1.VerificationFailedReason, err.Error()), err
294-
}
295-
296-
name := types.NamespacedName{
297-
Namespace: repository.GetNamespace(),
161+
err := r.verify(ctx, types.NamespacedName{
162+
Namespace: repository.Namespace,
298163
Name: repository.Spec.Verification.SecretRef.Name,
299-
}
300-
301-
var secret corev1.Secret
302-
err = r.Client.Get(ctx, name, &secret)
164+
}, commit)
303165
if err != nil {
304-
err = fmt.Errorf("PGP public keys secret error: %w", err)
305-
return sourcev1.GitRepositoryNotReady(repository, sourcev1.VerificationFailedReason, err.Error()), err
306-
}
307-
308-
var verified bool
309-
for _, bytes := range secret.Data {
310-
if _, err := commit.Verify(string(bytes)); err == nil {
311-
verified = true
312-
break
313-
}
314-
}
315-
316-
if !verified {
317-
err = fmt.Errorf("PGP signature of '%s' can't be verified", commit.Author)
318166
return sourcev1.GitRepositoryNotReady(repository, sourcev1.VerificationFailedReason, err.Error()), err
319167
}
320168
}
321169

322-
if revision == "" {
323-
revision = fmt.Sprintf("%s/%s", branch, ref.Hash().String())
324-
if repository.Spec.Reference != nil && repository.Spec.Reference.Tag != "" {
325-
revision = fmt.Sprintf("%s/%s", repository.Spec.Reference.Tag, ref.Hash().String())
326-
}
327-
}
328-
329170
artifact := r.Storage.ArtifactFor(repository.Kind, repository.ObjectMeta.GetObjectMeta(),
330-
fmt.Sprintf("%s.tar.gz", ref.Hash().String()), revision)
171+
fmt.Sprintf("%s.tar.gz", commit.Hash.String()), revision)
331172

332173
// create artifact dir
333174
err = r.Storage.MkdirAll(artifact)
@@ -388,6 +229,29 @@ func (r *GitRepositoryReconciler) shouldResetStatus(repository sourcev1.GitRepos
388229
}
389230
}
390231

232+
func (r *GitRepositoryReconciler) verify(ctx context.Context, publicKeySecret types.NamespacedName, commit *object.Commit) error {
233+
if commit.PGPSignature == "" {
234+
return fmt.Errorf("no PGP signature found for commit: %s", commit.Hash)
235+
}
236+
237+
var secret corev1.Secret
238+
if err := r.Client.Get(ctx, publicKeySecret, &secret); err != nil {
239+
return fmt.Errorf("PGP public keys secret error: %w", err)
240+
}
241+
242+
var verified bool
243+
for _, bytes := range secret.Data {
244+
if _, err := commit.Verify(string(bytes)); err == nil {
245+
verified = true
246+
break
247+
}
248+
}
249+
if !verified {
250+
return fmt.Errorf("PGP signature '%s' of '%s' can't be verified", commit.PGPSignature, commit.Author)
251+
}
252+
return nil
253+
}
254+
391255
// gc performs a garbage collection on all but current artifacts of
392256
// the given repository.
393257
func (r *GitRepositoryReconciler) gc(repository sourcev1.GitRepository) error {

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
github.com/go-logr/logr v0.1.0
99
github.com/onsi/ginkgo v1.11.0
1010
github.com/onsi/gomega v1.8.1
11-
github.com/pkg/errors v0.9.1
11+
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073
1212
helm.sh/helm/v3 v3.1.2
1313
k8s.io/api v0.17.2
1414
k8s.io/apimachinery v0.17.2

0 commit comments

Comments
 (0)