Skip to content

Commit 86712ff

Browse files
author
Philip Laine
authored
Merge pull request #213 from phillebaba/feature/git2go
Add git2go option to enable Azure DevOps
2 parents aed11a1 + 847499b commit 86712ff

25 files changed

+1126
-168
lines changed

.github/actions/run-tests/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
FROM golang:1.15-alpine
22

33
# Add any build or testing essential system packages
4-
RUN apk add --no-cache build-base git
4+
RUN apk add --no-cache build-base git pkgconf
5+
RUN apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/community libgit2-dev=1.1.0-r1
56

67
# Use the GitHub Actions uid:gid combination for proper fs permissions
78
RUN addgroup -g 116 -S test && adduser -u 1001 -S -g test test

CONTRIBUTING.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,21 @@ to join the conversation (this will also add an invitation to your
2828
Google calendar for our [Flux
2929
meeting](https://docs.google.com/document/d/1l_M0om0qUEN_NNiGgpqJ2tvsF2iioHkaARDeh6b70B0/edit#)).
3030

31+
### Installing required dependencies
32+
33+
The dependency [libgit2](https://libgit2.org/) needs to be installed to be able to run
34+
Source Controller or its test-suite locally (not in a container).
35+
36+
**macOS**
37+
```
38+
brew install libgit2
39+
```
40+
41+
**Arch Linux**
42+
```
43+
pacman -S libgit2
44+
```
45+
3146
### How to run the test suite
3247

3348
You can run the unit tests by simply doing

Dockerfile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Docker buildkit multi-arch build requires golang alpine
22
FROM golang:1.15-alpine as builder
33

4+
RUN apk add gcc pkgconfig libc-dev
5+
RUN apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/community libgit2-dev=1.1.0-r1
6+
47
WORKDIR /workspace
58

69
# copy api submodule
@@ -20,14 +23,15 @@ COPY pkg/ pkg/
2023
COPY internal/ internal/
2124

2225
# build without specifing the arch
23-
RUN CGO_ENABLED=0 go build -a -o source-controller main.go
26+
RUN CGO_ENABLED=1 go build -o source-controller main.go
2427

2528
FROM alpine:3.12
2629

2730
# link repo to the GitHub Container Registry image
2831
LABEL org.opencontainers.image.source="https://github.com/fluxcd/source-controller"
2932

3033
RUN apk add --no-cache ca-certificates tini
34+
RUN apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/community libgit2=1.1.0-r1
3135

3236
COPY --from=builder /workspace/source-controller /usr/local/bin/
3337

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ deploy: manifests
3939
kustomize build config/default | kubectl apply -f -
4040

4141
# Deploy controller dev image in the configured Kubernetes cluster in ~/.kube/config
42-
dev-deploy: manifests
42+
dev-deploy:
4343
mkdir -p config/dev && cp config/default/* config/dev
4444
cd config/dev && kustomize edit set image fluxcd/source-controller=${IMG}
4545
kustomize build config/dev | kubectl apply -f -

api/v1beta1/gitrepository_types.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ import (
2626
const (
2727
// GitRepositoryKind is the string representation of a GitRepository.
2828
GitRepositoryKind = "GitRepository"
29+
// Represents the go-git git implementation kind
30+
GoGitImplementation = "go-git"
31+
// Represents the gi2go git implementation kind
32+
LibGit2Implementation = "libgit2"
2933
)
3034

3135
// GitRepositorySpec defines the desired state of a Git repository.
@@ -70,6 +74,13 @@ type GitRepositorySpec struct {
7074
// This flag tells the controller to suspend the reconciliation of this source.
7175
// +optional
7276
Suspend bool `json:"suspend,omitempty"`
77+
78+
// Determines which git client library to use.
79+
// Defaults to go-git, valid values are ('go-git', 'libgit2').
80+
// +kubebuilder:validation:Enum=go-git;libgit2
81+
// +kubebuilder:default:=go-git
82+
// +optional
83+
GitImplementation string `json:"gitImplementation,omitempty"`
7384
}
7485

7586
// GitRepositoryRef defines the Git ref used for pull and checkout operations.

config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ spec:
4949
spec:
5050
description: GitRepositorySpec defines the desired state of a Git repository.
5151
properties:
52+
gitImplementation:
53+
default: go-git
54+
description: Determines which git client library to use. Defaults
55+
to go-git, valid values are ('go-git', 'libgit2').
56+
enum:
57+
- go-git
58+
- libgit2
59+
type: string
5260
ignore:
5361
description: Ignore overrides the set of excluded patterns in the
5462
.sourceignore format (which is the same as .gitignore). If not provided,

controllers/gitrepository_controller.go

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ import (
2525
"time"
2626

2727
"github.com/fluxcd/pkg/apis/meta"
28-
"github.com/go-git/go-git/v5/plumbing/object"
29-
"github.com/go-git/go-git/v5/plumbing/transport"
3028
"github.com/go-logr/logr"
3129
corev1 "k8s.io/api/core/v1"
3230
apimeta "k8s.io/apimachinery/pkg/api/meta"
@@ -46,6 +44,7 @@ import (
4644

4745
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
4846
"github.com/fluxcd/source-controller/pkg/git"
47+
"github.com/fluxcd/source-controller/pkg/git/common"
4948
)
5049

5150
// GitRepositoryReconciler reconciles a GitRepository object
@@ -154,7 +153,6 @@ func (r *GitRepositoryReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
154153
))
155154

156155
return ctrl.Result{RequeueAfter: repository.GetInterval().Duration}, nil
157-
158156
}
159157

160158
type GitRepositoryReconcilerOptions struct {
@@ -183,9 +181,9 @@ func (r *GitRepositoryReconciler) reconcile(ctx context.Context, repository sour
183181
defer os.RemoveAll(tmpGit)
184182

185183
// determine auth method
186-
var auth transport.AuthMethod
184+
auth := &common.Auth{}
187185
if repository.Spec.SecretRef != nil {
188-
authStrategy, err := git.AuthSecretStrategyForURL(repository.Spec.URL)
186+
authStrategy, err := git.AuthSecretStrategyForURL(repository.Spec.URL, repository.Spec.GitImplementation)
189187
if err != nil {
190188
return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
191189
}
@@ -209,14 +207,17 @@ func (r *GitRepositoryReconciler) reconcile(ctx context.Context, repository sour
209207
}
210208
}
211209

212-
checkoutStrategy := git.CheckoutStrategyForRef(repository.Spec.Reference)
210+
checkoutStrategy, err := git.CheckoutStrategyForRef(repository.Spec.Reference, repository.Spec.GitImplementation)
211+
if err != nil {
212+
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
213+
}
213214
commit, revision, err := checkoutStrategy.Checkout(ctx, tmpGit, repository.Spec.URL, auth)
214215
if err != nil {
215216
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
216217
}
217218

218219
// return early on unchanged revision
219-
artifact := r.Storage.NewArtifactFor(repository.Kind, repository.GetObjectMeta(), revision, fmt.Sprintf("%s.tar.gz", commit.Hash.String()))
220+
artifact := r.Storage.NewArtifactFor(repository.Kind, repository.GetObjectMeta(), revision, fmt.Sprintf("%s.tar.gz", commit.Hash()))
220221
if apimeta.IsStatusConditionTrue(repository.Status.Conditions, meta.ReadyCondition) && repository.GetArtifact().HasRevision(artifact.Revision) {
221222
if artifact.URL != repository.GetArtifact().URL {
222223
r.Storage.SetArtifactURL(repository.GetArtifact())
@@ -227,10 +228,17 @@ func (r *GitRepositoryReconciler) reconcile(ctx context.Context, repository sour
227228

228229
// verify PGP signature
229230
if repository.Spec.Verification != nil {
230-
err := r.verify(ctx, types.NamespacedName{
231+
publicKeySecret := types.NamespacedName{
231232
Namespace: repository.Namespace,
232233
Name: repository.Spec.Verification.SecretRef.Name,
233-
}, commit)
234+
}
235+
var secret corev1.Secret
236+
if err := r.Client.Get(ctx, publicKeySecret, &secret); err != nil {
237+
err = fmt.Errorf("PGP public keys secret error: %w", err)
238+
return sourcev1.GitRepositoryNotReady(repository, sourcev1.VerificationFailedReason, err.Error()), err
239+
}
240+
241+
err := commit.Verify(secret)
234242
if err != nil {
235243
return sourcev1.GitRepositoryNotReady(repository, sourcev1.VerificationFailedReason, err.Error()), err
236244
}
@@ -288,30 +296,6 @@ func (r *GitRepositoryReconciler) reconcileDelete(ctx context.Context, repositor
288296
return ctrl.Result{}, nil
289297
}
290298

291-
// verify returns an error if the PGP signature can't be verified
292-
func (r *GitRepositoryReconciler) verify(ctx context.Context, publicKeySecret types.NamespacedName, commit *object.Commit) error {
293-
if commit.PGPSignature == "" {
294-
return fmt.Errorf("no PGP signature found for commit: %s", commit.Hash)
295-
}
296-
297-
var secret corev1.Secret
298-
if err := r.Client.Get(ctx, publicKeySecret, &secret); err != nil {
299-
return fmt.Errorf("PGP public keys secret error: %w", err)
300-
}
301-
302-
var verified bool
303-
for _, bytes := range secret.Data {
304-
if _, err := commit.Verify(string(bytes)); err == nil {
305-
verified = true
306-
break
307-
}
308-
}
309-
if !verified {
310-
return fmt.Errorf("PGP signature '%s' of '%s' can't be verified", commit.PGPSignature, commit.Author)
311-
}
312-
return nil
313-
}
314-
315299
// resetStatus returns a modified v1beta1.GitRepository and a boolean indicating
316300
// if the status field has been reset.
317301
func (r *GitRepositoryReconciler) resetStatus(repository sourcev1.GitRepository) (sourcev1.GitRepository, bool) {

controllers/gitrepository_controller_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ var _ = Describe("GitRepositoryReconciler", func() {
8686
expectStatus metav1.ConditionStatus
8787
expectMessage string
8888
expectRevision string
89+
90+
gitImplementation string
8991
}
9092

9193
DescribeTable("Git references tests", func(t refTestCase) {
@@ -262,5 +264,65 @@ var _ = Describe("GitRepositoryReconciler", func() {
262264
expectMessage: "git commit 'invalid' not found: object not found",
263265
}),
264266
)
267+
268+
DescribeTable("Git self signed cert tests", func(t refTestCase) {
269+
err = gitServer.StartHTTPS(examplePublicKey, examplePrivateKey, exampleCA, "example.com")
270+
defer gitServer.StopHTTP()
271+
Expect(err).NotTo(HaveOccurred())
272+
273+
u, err := url.Parse(gitServer.HTTPAddress())
274+
Expect(err).NotTo(HaveOccurred())
275+
u.Path = path.Join(u.Path, fmt.Sprintf("repository-%s.git", randStringRunes(5)))
276+
277+
key := types.NamespacedName{
278+
Name: fmt.Sprintf("git-ref-test-%s", randStringRunes(5)),
279+
Namespace: namespace.Name,
280+
}
281+
created := &sourcev1.GitRepository{
282+
ObjectMeta: metav1.ObjectMeta{
283+
Name: key.Name,
284+
Namespace: key.Namespace,
285+
},
286+
Spec: sourcev1.GitRepositorySpec{
287+
URL: u.String(),
288+
Interval: metav1.Duration{Duration: indexInterval},
289+
Reference: t.reference,
290+
GitImplementation: t.gitImplementation,
291+
},
292+
}
293+
Expect(k8sClient.Create(context.Background(), created)).Should(Succeed())
294+
defer k8sClient.Delete(context.Background(), created)
295+
296+
got := &sourcev1.GitRepository{}
297+
var cond metav1.Condition
298+
Eventually(func() bool {
299+
_ = k8sClient.Get(context.Background(), key, got)
300+
for _, c := range got.Status.Conditions {
301+
if c.Reason == t.waitForReason {
302+
cond = c
303+
return true
304+
}
305+
}
306+
return false
307+
}, timeout, interval).Should(BeTrue())
308+
309+
Expect(cond.Status).To(Equal(t.expectStatus))
310+
Expect(cond.Message).To(ContainSubstring(t.expectMessage))
311+
Expect(got.Status.Artifact == nil).To(Equal(t.expectRevision == ""))
312+
},
313+
Entry("self signed v1", refTestCase{
314+
reference: &sourcev1.GitRepositoryRef{Branch: "main"},
315+
waitForReason: sourcev1.GitOperationFailedReason,
316+
expectStatus: metav1.ConditionFalse,
317+
expectMessage: "x509: certificate signed by unknown authority",
318+
}),
319+
Entry("self signed v2", refTestCase{
320+
reference: &sourcev1.GitRepositoryRef{Branch: "main"},
321+
waitForReason: sourcev1.GitOperationFailedReason,
322+
expectStatus: metav1.ConditionFalse,
323+
expectMessage: "error: user rejected certificate",
324+
gitImplementation: sourcev1.LibGit2Implementation,
325+
}),
326+
)
265327
})
266328
})

docs/api/source.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,19 @@ bool
387387
<p>This flag tells the controller to suspend the reconciliation of this source.</p>
388388
</td>
389389
</tr>
390+
<tr>
391+
<td>
392+
<code>gitImplementation</code><br>
393+
<em>
394+
string
395+
</em>
396+
</td>
397+
<td>
398+
<em>(Optional)</em>
399+
<p>Determines which git client library to use.
400+
Defaults to go-git, valid values are (&lsquo;go-git&rsquo;, &lsquo;libgit2&rsquo;).</p>
401+
</td>
402+
</tr>
390403
</table>
391404
</td>
392405
</tr>
@@ -1220,6 +1233,19 @@ bool
12201233
<p>This flag tells the controller to suspend the reconciliation of this source.</p>
12211234
</td>
12221235
</tr>
1236+
<tr>
1237+
<td>
1238+
<code>gitImplementation</code><br>
1239+
<em>
1240+
string
1241+
</em>
1242+
</td>
1243+
<td>
1244+
<em>(Optional)</em>
1245+
<p>Determines which git client library to use.
1246+
Defaults to go-git, valid values are (&lsquo;go-git&rsquo;, &lsquo;libgit2&rsquo;).</p>
1247+
</td>
1248+
</tr>
12231249
</tbody>
12241250
</table>
12251251
</div>

0 commit comments

Comments
 (0)