Add support for gitv2 servers

Signed-off-by: Philip Laine <philip.laine@xenit.se>
This commit is contained in:
Philip Laine 2020-11-26 20:34:27 +01:00 committed by Philip Laine
parent aed11a171f
commit 2cb3754953
20 changed files with 940 additions and 164 deletions

View File

@ -1,6 +1,9 @@
# Docker buildkit multi-arch build requires golang alpine
FROM golang:1.15-alpine as builder
RUN apk add gcc pkgconfig libc-dev
RUN apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/community libgit2-dev=1.1.0-r1
WORKDIR /workspace
# copy api submodule
@ -20,7 +23,7 @@ COPY pkg/ pkg/
COPY internal/ internal/
# build without specifing the arch
RUN CGO_ENABLED=0 go build -a -o source-controller main.go
RUN CGO_ENABLED=1 go build -o source-controller main.go
FROM alpine:3.12
@ -28,6 +31,7 @@ FROM alpine:3.12
LABEL org.opencontainers.image.source="https://github.com/fluxcd/source-controller"
RUN apk add --no-cache ca-certificates tini
RUN apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/community libgit2=1.1.0-r1
COPY --from=builder /workspace/source-controller /usr/local/bin/

View File

@ -70,6 +70,10 @@ type GitRepositorySpec struct {
// This flag tells the controller to suspend the reconciliation of this source.
// +optional
Suspend bool `json:"suspend,omitempty"`
// Enables support for git servers that require v2.
// +optional
GitProtocolV2Compatibility bool `json:"gitProtocolV2Compatibility"`
}
// GitRepositoryRef defines the Git ref used for pull and checkout operations.

View File

@ -49,6 +49,9 @@ spec:
spec:
description: GitRepositorySpec defines the desired state of a Git repository.
properties:
gitProtocolV2Compatibility:
description: Enables support for git servers that require v2.
type: boolean
ignore:
description: Ignore overrides the set of excluded patterns in the
.sourceignore format (which is the same as .gitignore). If not provided,

View File

@ -25,8 +25,6 @@ import (
"time"
"github.com/fluxcd/pkg/apis/meta"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
apimeta "k8s.io/apimachinery/pkg/api/meta"
@ -46,6 +44,7 @@ import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/source-controller/pkg/git"
"github.com/fluxcd/source-controller/pkg/git/common"
)
// GitRepositoryReconciler reconciles a GitRepository object
@ -154,7 +153,6 @@ func (r *GitRepositoryReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
))
return ctrl.Result{RequeueAfter: repository.GetInterval().Duration}, nil
}
type GitRepositoryReconcilerOptions struct {
@ -183,13 +181,9 @@ func (r *GitRepositoryReconciler) reconcile(ctx context.Context, repository sour
defer os.RemoveAll(tmpGit)
// determine auth method
var auth transport.AuthMethod
if repository.Spec.SecretRef != nil {
authStrategy, err := git.AuthSecretStrategyForURL(repository.Spec.URL)
if err != nil {
return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
}
auth := &common.Auth{}
authStrategy := git.AuthSecretStrategyForURL(repository.Spec.URL, repository.Spec.GitProtocolV2Compatibility)
if repository.Spec.SecretRef != nil && authStrategy != nil {
name := types.NamespacedName{
Namespace: repository.GetNamespace(),
Name: repository.Spec.SecretRef.Name,
@ -209,14 +203,14 @@ func (r *GitRepositoryReconciler) reconcile(ctx context.Context, repository sour
}
}
checkoutStrategy := git.CheckoutStrategyForRef(repository.Spec.Reference)
checkoutStrategy := git.CheckoutStrategyForRef(repository.Spec.Reference, repository.Spec.GitProtocolV2Compatibility)
commit, revision, err := checkoutStrategy.Checkout(ctx, tmpGit, repository.Spec.URL, auth)
if err != nil {
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
}
// return early on unchanged revision
artifact := r.Storage.NewArtifactFor(repository.Kind, repository.GetObjectMeta(), revision, fmt.Sprintf("%s.tar.gz", commit.Hash.String()))
artifact := r.Storage.NewArtifactFor(repository.Kind, repository.GetObjectMeta(), revision, fmt.Sprintf("%s.tar.gz", commit.Hash()))
if apimeta.IsStatusConditionTrue(repository.Status.Conditions, meta.ReadyCondition) && repository.GetArtifact().HasRevision(artifact.Revision) {
if artifact.URL != repository.GetArtifact().URL {
r.Storage.SetArtifactURL(repository.GetArtifact())
@ -227,10 +221,17 @@ func (r *GitRepositoryReconciler) reconcile(ctx context.Context, repository sour
// verify PGP signature
if repository.Spec.Verification != nil {
err := r.verify(ctx, types.NamespacedName{
publicKeySecret := types.NamespacedName{
Namespace: repository.Namespace,
Name: repository.Spec.Verification.SecretRef.Name,
}, commit)
}
var secret corev1.Secret
if err := r.Client.Get(ctx, publicKeySecret, &secret); err != nil {
err = fmt.Errorf("PGP public keys secret error: %w", err)
return sourcev1.GitRepositoryNotReady(repository, sourcev1.VerificationFailedReason, err.Error()), err
}
err := commit.Verify(secret)
if err != nil {
return sourcev1.GitRepositoryNotReady(repository, sourcev1.VerificationFailedReason, err.Error()), err
}
@ -288,30 +289,6 @@ func (r *GitRepositoryReconciler) reconcileDelete(ctx context.Context, repositor
return ctrl.Result{}, nil
}
// verify returns an error if the PGP signature can't be verified
func (r *GitRepositoryReconciler) verify(ctx context.Context, publicKeySecret types.NamespacedName, commit *object.Commit) error {
if commit.PGPSignature == "" {
return fmt.Errorf("no PGP signature found for commit: %s", commit.Hash)
}
var secret corev1.Secret
if err := r.Client.Get(ctx, publicKeySecret, &secret); err != nil {
return fmt.Errorf("PGP public keys secret error: %w", err)
}
var verified bool
for _, bytes := range secret.Data {
if _, err := commit.Verify(string(bytes)); err == nil {
verified = true
break
}
}
if !verified {
return fmt.Errorf("PGP signature '%s' of '%s' can't be verified", commit.PGPSignature, commit.Author)
}
return nil
}
// resetStatus returns a modified v1beta1.GitRepository and a boolean indicating
// if the status field has been reset.
func (r *GitRepositoryReconciler) resetStatus(repository sourcev1.GitRepository) (sourcev1.GitRepository, bool) {

View File

@ -377,14 +377,22 @@ consult the documentation for your version to find out what those are.</p>
</tr>
<tr>
<td>
<<<<<<< HEAD
<code>suspend</code><br>
=======
<code>gitProtocolV2Compatibility</code><br>
>>>>>>> 780d289 (Refactor to support v1 and v2 git clients)
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<<<<<<< HEAD
<p>This flag tells the controller to suspend the reconciliation of this source.</p>
=======
<p>Enables support for git servers that require v2.</p>
>>>>>>> 780d289 (Refactor to support v1 and v2 git clients)
</td>
</tr>
</table>
@ -706,9 +714,9 @@ HelmRepositoryStatus
</h3>
<p>
(<em>Appears on:</em>
<a href="#source.toolkit.fluxcd.io/v1beta1.BucketStatus">BucketStatus</a>,
<a href="#source.toolkit.fluxcd.io/v1beta1.GitRepositoryStatus">GitRepositoryStatus</a>,
<a href="#source.toolkit.fluxcd.io/v1beta1.HelmChartStatus">HelmChartStatus</a>,
<a href="#source.toolkit.fluxcd.io/v1beta1.BucketStatus">BucketStatus</a>,
<a href="#source.toolkit.fluxcd.io/v1beta1.GitRepositoryStatus">GitRepositoryStatus</a>,
<a href="#source.toolkit.fluxcd.io/v1beta1.HelmChartStatus">HelmChartStatus</a>,
<a href="#source.toolkit.fluxcd.io/v1beta1.HelmRepositoryStatus">HelmRepositoryStatus</a>)
</p>
<p>Artifact represents the output of a source synchronisation.</p>
@ -1210,7 +1218,10 @@ consult the documentation for your version to find out what those are.</p>
</tr>
<tr>
<td>
<<<<<<< HEAD
<code>suspend</code><br>
=======
<code>gitProtocolV2Compatibility</code><br>
<em>
bool
</em>
@ -1218,6 +1229,7 @@ bool
<td>
<em>(Optional)</em>
<p>This flag tells the controller to suspend the reconciliation of this source.</p>
<p>Enables support for git servers that require v2.</p>
</td>
</tr>
</tbody>

5
go.mod
View File

@ -6,6 +6,7 @@ replace github.com/fluxcd/source-controller/api => ./api
require (
github.com/Masterminds/semver/v3 v3.1.0
github.com/blang/semver/v4 v4.0.0
github.com/fluxcd/pkg/apis/meta v0.4.0
github.com/fluxcd/pkg/gittestserver v0.0.2
github.com/fluxcd/pkg/helmtestserver v0.0.1
@ -16,11 +17,13 @@ require (
github.com/fluxcd/pkg/version v0.0.1
github.com/fluxcd/source-controller/api v0.4.1
github.com/go-git/go-billy/v5 v5.0.0
github.com/go-git/go-git/v5 v5.1.0
github.com/go-git/go-git/v5 v5.2.0
github.com/go-logr/logr v0.2.1
github.com/libgit2/git2go/v31 v31.3.0
github.com/minio/minio-go/v7 v7.0.5
github.com/onsi/ginkgo v1.12.1
github.com/onsi/gomega v1.10.1
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
helm.sh/helm/v3 v3.4.0
k8s.io/api v0.19.3

41
go.sum
View File

@ -1,7 +1,6 @@
bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
@ -32,7 +31,6 @@ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbt
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.4.1 h1:ThlnYciV1iM/V0OSF/dtkqWb6xo5qITT1TJBG1MRDJM=
github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
@ -105,6 +103,8 @@ github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngE
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blang/semver v3.5.0+incompatible h1:CGxCgetQ64DKk7rdZ++Vfnb1+ogGNnB17OJKJXD2Cfs=
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bshuster-repo/logrus-logstash-hook v0.4.1 h1:pgAtgj+A31JBVtEHu2uHuEx0n+2ukqUJnS2vVe5pQNA=
@ -213,7 +213,6 @@ github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4s
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@ -222,7 +221,6 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZM
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fluxcd/pkg/apis/meta v0.3.0 h1:o2YkfGgf0j8sKeZs8cBmmmMKLA7kEoS1qYViOial1Ds=
github.com/fluxcd/pkg/apis/meta v0.3.0/go.mod h1:wOzQQx8CdtUQCGaLzqGu4QgnNxYkI6/wvdvlovxWhF0=
github.com/fluxcd/pkg/apis/meta v0.4.0 h1:JChqB9GGgorW9HWKxirTVV0rzrcLyzBaVjinmqZ0iHA=
github.com/fluxcd/pkg/apis/meta v0.4.0/go.mod h1:wOzQQx8CdtUQCGaLzqGu4QgnNxYkI6/wvdvlovxWhF0=
@ -262,10 +260,10 @@ github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc=
github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
github.com/go-git/go-git/v5 v5.1.0 h1:HxJn9g/E7eYvKW3Fm7Jt4ee8LXfPOm/H1cdDu8vEssk=
github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM=
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M=
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI=
github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@ -274,13 +272,10 @@ github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgO
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY=
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v0.2.1 h1:fV3MLmabKIZ383XifUjFSwcoGee0v9qgPp8wy5svibE=
github.com/go-logr/logr v0.2.1/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54=
github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
github.com/go-logr/zapr v0.2.0 h1:v6Ji8yBW77pva6NkJKQdHLAJKrIJKRHz0RXwPqCHSR4=
github.com/go-logr/zapr v0.2.0/go.mod h1:qhKdvif7YF5GI9NWEpyxTSSBdGmzkNguibrdCNVPunU=
@ -361,7 +356,6 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekf
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -408,7 +402,6 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I=
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
@ -506,10 +499,8 @@ github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@ -524,10 +515,11 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhR
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/libgit2/git2go/v31 v31.3.0 h1:d8ciyYVKir+gKwra3KuNxTyVvbgGKn4admdt1PNNAOg=
github.com/libgit2/git2go/v31 v31.3.0/go.mod h1:mnc0hPGPs0nDi9INrurTpioeRzje9DvSXqON/+JEhwY=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
@ -556,7 +548,6 @@ github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lL
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.12.0 h1:u/x3mp++qUxvYfulZ4HKOvVO0JWhk7HtE8lWhbGz/Do=
github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
@ -677,7 +668,6 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.3.0 h1:miYCvYqFXtl/J9FIy8eNpBfYthAEFg+Ys0XyUVEcDsc=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
@ -694,7 +684,6 @@ github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7q
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
@ -705,7 +694,6 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI=
github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
@ -737,7 +725,6 @@ github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvH
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
@ -878,7 +865,6 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
@ -924,7 +910,6 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgN
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1021,7 +1006,6 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114 h1:DnSr2mCsxyCE6ZgIkmcWUQY2R5cH/6wL7eIxEmQOMSE=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200616133436-c1934b75d054 h1:HHeAlu5H9b71C+Fx0K+1dGgVFN1DM1/wz4aoGOA5qS8=
golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@ -1053,7 +1037,6 @@ google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRn
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
@ -1078,7 +1061,6 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
@ -1134,7 +1116,6 @@ honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXe
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4=
k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI=
k8s.io/api v0.19.2 h1:q+/krnHWKsL7OBZg/rxnycsl9569Pud76UJ77MvKXms=
k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI=
k8s.io/api v0.19.3 h1:GN6ntFnv44Vptj/b+OnMW7FmzkpDoIDLZRvKX3XH9aU=
k8s.io/api v0.19.3/go.mod h1:VF+5FT1B74Pw3KxMdKyinLo+zynBaMBiAfGMuldcNDs=
@ -1144,7 +1125,6 @@ k8s.io/apiextensions-apiserver v0.19.2 h1:oG84UwiDsVDu7dlsGQs5GySmQHCzMhknfhFExJ
k8s.io/apiextensions-apiserver v0.19.2/go.mod h1:EYNjpqIAvNZe+svXVx9j4uBaVhTB4C94HkY3w058qcg=
k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc=
k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
k8s.io/apimachinery v0.19.3 h1:bpIQXlKjB4cB/oNpnNnV+BybGPR7iP5oYpsOTEJ4hgc=
k8s.io/apimachinery v0.19.3/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
@ -1156,7 +1136,6 @@ k8s.io/cli-runtime v0.19.2 h1:d4uOtKhy3ImdaKqZJ8yQgLrdtUwsJLfP4Dw7L/kVPOo=
k8s.io/cli-runtime v0.19.2/go.mod h1:CMynmJM4Yf02TlkbhKxoSzi4Zf518PukJ5xep/NaNeY=
k8s.io/client-go v0.18.4/go.mod h1:f5sXwL4yAZRkAtzOxRWUhA/N8XzGCb+nPZI8PfobZ9g=
k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q=
k8s.io/client-go v0.19.2 h1:gMJuU3xJZs86L1oQ99R4EViAADUPMHHtS9jFshasHSc=
k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA=
k8s.io/client-go v0.19.3 h1:ctqR1nQ52NUs6LpI0w+a5U+xjYwflFwA13OJKcicMxg=
k8s.io/client-go v0.19.3/go.mod h1:+eEMktZM+MG0KO+PTkci8xnbCZHvj9TqR6Q1XDUIJOM=
@ -1175,11 +1154,9 @@ k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUc
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A=
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY=
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL1egv36TkkJm+bA8AxicmQ=
k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=
@ -1190,7 +1167,6 @@ k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
k8s.io/metrics v0.18.4/go.mod h1:luze4fyI9JG4eLDZy0kFdYEebqNfi0QrG4xNEbPkHOs=
k8s.io/metrics v0.19.2/go.mod h1:IlLaAGXN0q7yrtB+SV0q3JIraf6VtlDr+iuTcX21fCU=
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 h1:v8ud2Up6QK1lNOKFgiIVrZdMg7MpmSnvtrOieolJKoE=
k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20200729134348-d5654de09c73 h1:uJmqzgNWG7XyClnU/mLPBWwfKKF1K8Hf8whTseBgJcg=
k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
@ -1204,7 +1180,6 @@ sigs.k8s.io/controller-runtime v0.6.3/go.mod h1:WlZNXcM0++oyaQt4B7C2lEE5JYRs8vJU
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA=
sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=

View File

@ -1,36 +0,0 @@
package git
import (
"context"
"io/ioutil"
"os"
"testing"
)
func TestCheckoutTagSemVer_Checkout(t *testing.T) {
tag := CheckoutTag{
tag: "v1.7.0",
}
tmpDir, _ := ioutil.TempDir("", "test")
defer os.RemoveAll(tmpDir)
cTag, _, err := tag.Checkout(context.TODO(), tmpDir, "https://github.com/projectcontour/contour", nil)
if err != nil {
t.Error(err)
}
semVer := CheckoutSemVer{
semVer: ">=1.0.0 <=1.7.0",
}
tmpDir2, _ := ioutil.TempDir("", "test")
defer os.RemoveAll(tmpDir2)
cSemVer, _, err := semVer.Checkout(context.TODO(), tmpDir2, "https://github.com/projectcontour/contour", nil)
if err != nil {
t.Error(err)
}
if cTag.Hash.String() != cSemVer.Hash.String() {
t.Errorf("expected semver hash %s, got %s", cTag.Hash.String(), cSemVer.Hash.String())
}
}

49
pkg/git/common/common.go Normal file
View File

@ -0,0 +1,49 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package common
import (
"context"
"github.com/go-git/go-git/v5/plumbing/transport"
git2go "github.com/libgit2/git2go/v31"
corev1 "k8s.io/api/core/v1"
)
const (
DefaultOrigin = "origin"
DefaultBranch = "master"
)
type Commit interface {
Verify(secret corev1.Secret) error
Hash() string
}
type CheckoutStrategy interface {
Checkout(ctx context.Context, path, url string, auth *Auth) (Commit, string, error)
}
type Auth struct {
AuthMethod transport.AuthMethod
CredCallback git2go.CredentialsCallback
CertCallback git2go.CertificateCheckCallback
}
type AuthSecretStrategy interface {
Method(secret corev1.Secret) (*Auth, error)
}

44
pkg/git/git.go Normal file
View File

@ -0,0 +1,44 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package git
import (
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/source-controller/pkg/git/common"
gitv1 "github.com/fluxcd/source-controller/pkg/git/v1"
gitv2 "github.com/fluxcd/source-controller/pkg/git/v2"
)
const (
defaultBranch = "master"
)
func CheckoutStrategyForRef(ref *sourcev1.GitRepositoryRef, useGitV2 bool) common.CheckoutStrategy {
if useGitV2 {
return gitv2.CheckoutStrategyForRef(ref)
}
return gitv1.CheckoutStrategyForRef(ref)
}
func AuthSecretStrategyForURL(url string, useGitV2 bool) common.AuthSecretStrategy {
if useGitV2 {
return gitv2.AuthSecretStrategyForURL(url)
}
return gitv1.AuthSecretStrategyForURL(url)
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 The Flux authors
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package git
package v1
import (
"context"
@ -25,23 +25,16 @@ import (
"github.com/Masterminds/semver/v3"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/fluxcd/pkg/version"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/source-controller/pkg/git/common"
)
const (
defaultOrigin = "origin"
defaultBranch = "master"
)
func CheckoutStrategyForRef(ref *sourcev1.GitRepositoryRef) CheckoutStrategy {
func CheckoutStrategyForRef(ref *sourcev1.GitRepositoryRef) common.CheckoutStrategy {
switch {
case ref == nil:
return &CheckoutBranch{branch: defaultBranch}
return &CheckoutBranch{branch: common.DefaultBranch}
case ref.SemVer != "":
return &CheckoutSemVer{semVer: ref.SemVer}
case ref.Tag != "":
@ -49,29 +42,25 @@ func CheckoutStrategyForRef(ref *sourcev1.GitRepositoryRef) CheckoutStrategy {
case ref.Commit != "":
strategy := &CheckoutCommit{branch: ref.Branch, commit: ref.Commit}
if strategy.branch == "" {
strategy.branch = defaultBranch
strategy.branch = common.DefaultBranch
}
return strategy
case ref.Branch != "":
return &CheckoutBranch{branch: ref.Branch}
default:
return &CheckoutBranch{branch: defaultBranch}
return &CheckoutBranch{branch: common.DefaultBranch}
}
}
type CheckoutStrategy interface {
Checkout(ctx context.Context, path, url string, auth transport.AuthMethod) (*object.Commit, string, error)
}
type CheckoutBranch struct {
branch string
}
func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, auth transport.AuthMethod) (*object.Commit, string, error) {
func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, auth *common.Auth) (common.Commit, string, error) {
repo, err := git.PlainCloneContext(ctx, path, false, &git.CloneOptions{
URL: url,
Auth: auth,
RemoteName: defaultOrigin,
Auth: auth.AuthMethod,
RemoteName: common.DefaultOrigin,
ReferenceName: plumbing.NewBranchReferenceName(c.branch),
SingleBranch: true,
NoCheckout: false,
@ -91,18 +80,18 @@ func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, auth tr
if err != nil {
return nil, "", fmt.Errorf("git commit '%s' not found: %w", head.Hash(), err)
}
return commit, fmt.Sprintf("%s/%s", c.branch, head.Hash().String()), nil
return &Commit{commit}, fmt.Sprintf("%s/%s", c.branch, head.Hash().String()), nil
}
type CheckoutTag struct {
tag string
}
func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, auth transport.AuthMethod) (*object.Commit, string, error) {
func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, auth *common.Auth) (common.Commit, string, error) {
repo, err := git.PlainCloneContext(ctx, path, false, &git.CloneOptions{
URL: url,
Auth: auth,
RemoteName: defaultOrigin,
Auth: auth.AuthMethod,
RemoteName: common.DefaultOrigin,
ReferenceName: plumbing.NewTagReferenceName(c.tag),
SingleBranch: true,
NoCheckout: false,
@ -122,7 +111,7 @@ func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, auth trans
if err != nil {
return nil, "", fmt.Errorf("git commit '%s' not found: %w", head.Hash(), err)
}
return commit, fmt.Sprintf("%s/%s", c.tag, head.Hash().String()), nil
return &Commit{commit}, fmt.Sprintf("%s/%s", c.tag, head.Hash().String()), nil
}
type CheckoutCommit struct {
@ -130,11 +119,11 @@ type CheckoutCommit struct {
commit string
}
func (c *CheckoutCommit) Checkout(ctx context.Context, path, url string, auth transport.AuthMethod) (*object.Commit, string, error) {
func (c *CheckoutCommit) Checkout(ctx context.Context, path, url string, auth *common.Auth) (common.Commit, string, error) {
repo, err := git.PlainCloneContext(ctx, path, false, &git.CloneOptions{
URL: url,
Auth: auth,
RemoteName: defaultOrigin,
Auth: auth.AuthMethod,
RemoteName: common.DefaultOrigin,
ReferenceName: plumbing.NewBranchReferenceName(c.branch),
SingleBranch: true,
NoCheckout: false,
@ -160,14 +149,14 @@ func (c *CheckoutCommit) Checkout(ctx context.Context, path, url string, auth tr
if err != nil {
return nil, "", fmt.Errorf("git checkout error: %w", err)
}
return commit, fmt.Sprintf("%s/%s", c.branch, commit.Hash.String()), nil
return &Commit{commit}, fmt.Sprintf("%s/%s", c.branch, commit.Hash.String()), nil
}
type CheckoutSemVer struct {
semVer string
}
func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, auth transport.AuthMethod) (*object.Commit, string, error) {
func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, auth *common.Auth) (common.Commit, string, error) {
verConstraint, err := semver.NewConstraint(c.semVer)
if err != nil {
return nil, "", fmt.Errorf("semver parse range error: %w", err)
@ -175,8 +164,8 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, auth tr
repo, err := git.PlainCloneContext(ctx, path, false, &git.CloneOptions{
URL: url,
Auth: auth,
RemoteName: defaultOrigin,
Auth: auth.AuthMethod,
RemoteName: common.DefaultOrigin,
NoCheckout: false,
Depth: 1,
RecurseSubmodules: 0,
@ -265,5 +254,5 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, auth tr
return nil, "", fmt.Errorf("git commit '%s' not found: %w", head.Hash(), err)
}
return commit, fmt.Sprintf("%s/%s", t, head.Hash().String()), nil
return &Commit{commit}, fmt.Sprintf("%s/%s", t, head.Hash().String()), nil
}

View File

@ -0,0 +1,55 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
import (
"context"
"io/ioutil"
"os"
"testing"
"github.com/fluxcd/source-controller/pkg/git/common"
)
func TestCheckoutTagSemVer_Checkout(t *testing.T) {
auth := &common.Auth{}
tag := CheckoutTag{
tag: "v1.7.0",
}
tmpDir, _ := ioutil.TempDir("", "test")
defer os.RemoveAll(tmpDir)
cTag, _, err := tag.Checkout(context.TODO(), tmpDir, "https://github.com/projectcontour/contour", auth)
if err != nil {
t.Error(err)
}
semVer := CheckoutSemVer{
semVer: ">=1.0.0 <=1.7.0",
}
tmpDir2, _ := ioutil.TempDir("", "test")
defer os.RemoveAll(tmpDir2)
cSemVer, _, err := semVer.Checkout(context.TODO(), tmpDir2, "https://github.com/projectcontour/contour", auth)
if err != nil {
t.Error(err)
}
if cTag.Hash() != cSemVer.Hash() {
t.Errorf("expected semver hash %s, got %s", cTag.Hash(), cSemVer.Hash())
}
}

51
pkg/git/v1/commit.go Normal file
View File

@ -0,0 +1,51 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
import (
"fmt"
"github.com/go-git/go-git/v5/plumbing/object"
corev1 "k8s.io/api/core/v1"
)
type Commit struct {
commit *object.Commit
}
func (c *Commit) Hash() string {
return ""
}
// Verify returns an error if the PGP signature can't be verified
func (c *Commit) Verify(secret corev1.Secret) error {
if c.commit.PGPSignature == "" {
return fmt.Errorf("no PGP signature found for commit: %s", c.commit.Hash)
}
var verified bool
for _, bytes := range secret.Data {
if _, err := c.commit.Verify(string(bytes)); err == nil {
verified = true
break
}
}
if !verified {
return fmt.Errorf("PGP signature '%s' of '%s' can't be verified", c.commit.PGPSignature, c.commit.Author)
}
return nil
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 The Flux authors
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,27 +14,28 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package git
package v1
import (
"fmt"
"net/url"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
corev1 "k8s.io/api/core/v1"
"github.com/fluxcd/pkg/ssh/knownhosts"
"github.com/fluxcd/source-controller/pkg/git/common"
)
const defaultPublicKeyAuthUser = "git"
func AuthSecretStrategyForURL(URL string) (AuthSecretStrategy, error) {
func AuthSecretStrategyForURL(URL string) (common.AuthSecretStrategy, error) {
u, err := url.Parse(URL)
if err != nil {
return nil, fmt.Errorf("failed to parse URL to determine auth strategy: %w", err)
}
switch {
case u.Scheme == "http", u.Scheme == "https":
return &BasicAuth{}, nil
@ -45,13 +46,9 @@ func AuthSecretStrategyForURL(URL string) (AuthSecretStrategy, error) {
}
}
type AuthSecretStrategy interface {
Method(secret corev1.Secret) (transport.AuthMethod, error)
}
type BasicAuth struct{}
func (s *BasicAuth) Method(secret corev1.Secret) (transport.AuthMethod, error) {
func (s *BasicAuth) Method(secret corev1.Secret) (*common.Auth, error) {
auth := &http.BasicAuth{}
if username, ok := secret.Data["username"]; ok {
auth.Username = string(username)
@ -62,14 +59,14 @@ func (s *BasicAuth) Method(secret corev1.Secret) (transport.AuthMethod, error) {
if auth.Username == "" || auth.Password == "" {
return nil, fmt.Errorf("invalid '%s' secret data: required fields 'username' and 'password'", secret.Name)
}
return auth, nil
return &common.Auth{AuthMethod: auth}, nil
}
type PublicKeyAuth struct {
user string
}
func (s *PublicKeyAuth) Method(secret corev1.Secret) (transport.AuthMethod, error) {
func (s *PublicKeyAuth) Method(secret corev1.Secret) (*common.Auth, error) {
identity := secret.Data["identity"]
knownHosts := secret.Data["known_hosts"]
if len(identity) == 0 || len(knownHosts) == 0 {
@ -91,5 +88,5 @@ func (s *PublicKeyAuth) Method(secret corev1.Secret) (transport.AuthMethod, erro
return nil, err
}
pk.HostKeyCallback = callback
return pk, nil
return &common.Auth{AuthMethod: pk}, nil
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 The Flux authors
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,15 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package git
package v1
import (
"reflect"
"testing"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
corev1 "k8s.io/api/core/v1"
"github.com/fluxcd/source-controller/pkg/git/common"
)
const (
@ -68,7 +69,7 @@ func TestAuthSecretStrategyForURL(t *testing.T) {
tests := []struct {
name string
url string
want AuthSecretStrategy
want common.AuthSecretStrategy
wantErr bool
}{
{"HTTP", "http://git.example.com/org/repo.git", &BasicAuth{}, false},
@ -96,10 +97,10 @@ func TestBasicAuthStrategy_Method(t *testing.T) {
name string
secret corev1.Secret
modify func(secret *corev1.Secret)
want transport.AuthMethod
want *common.Auth
wantErr bool
}{
{"username and password", basicAuthSecretFixture, nil, &http.BasicAuth{Username: "git", Password: "password"}, false},
{"username and password", basicAuthSecretFixture, nil, &common.Auth{AuthMethod: &http.BasicAuth{Username: "git", Password: "password"}}, false},
{"without username", basicAuthSecretFixture, func(s *corev1.Secret) { delete(s.Data, "username") }, nil, true},
{"without password", basicAuthSecretFixture, func(s *corev1.Secret) { delete(s.Data, "password") }, nil, true},
{"empty", corev1.Secret{}, nil, nil, true},

221
pkg/git/v2/checkout.go Normal file
View File

@ -0,0 +1,221 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v2
import (
"context"
"fmt"
"github.com/blang/semver/v4"
git2go "github.com/libgit2/git2go/v31"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/fluxcd/source-controller/pkg/git/common"
)
func CheckoutStrategyForRef(ref *sourcev1.GitRepositoryRef) common.CheckoutStrategy {
switch {
case ref == nil:
return &CheckoutBranch{branch: common.DefaultBranch}
case ref.SemVer != "":
return &CheckoutSemVer{semVer: ref.SemVer}
case ref.Tag != "":
return &CheckoutTag{tag: ref.Tag}
case ref.Commit != "":
strategy := &CheckoutCommit{branch: ref.Branch, commit: ref.Commit}
if strategy.branch == "" {
strategy.branch = common.DefaultBranch
}
return strategy
case ref.Branch != "":
return &CheckoutBranch{branch: ref.Branch}
default:
return &CheckoutBranch{branch: common.DefaultBranch}
}
}
type CheckoutBranch struct {
branch string
}
func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, auth *common.Auth) (common.Commit, string, error) {
repo, err := git2go.Clone(url, path, &git2go.CloneOptions{
FetchOptions: &git2go.FetchOptions{
DownloadTags: git2go.DownloadTagsNone,
RemoteCallbacks: git2go.RemoteCallbacks{
CredentialsCallback: auth.CredCallback,
CertificateCheckCallback: auth.CertCallback,
},
},
CheckoutBranch: c.branch,
})
if err != nil {
return nil, "", fmt.Errorf("unable to clone '%s', error: %w", url, err)
}
head, err := repo.Head()
if err != nil {
return nil, "", fmt.Errorf("git resolve HEAD error: %w", err)
}
commit, err := repo.LookupCommit(head.Target())
if err != nil {
return nil, "", fmt.Errorf("git commit '%s' not found: %w", head.Target(), err)
}
return &Commit{commit}, fmt.Sprintf("%s/%s", c.branch, head.Target().String()), nil
}
type CheckoutTag struct {
tag string
}
func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, auth *common.Auth) (common.Commit, string, error) {
repo, err := git2go.Clone(url, path, &git2go.CloneOptions{
FetchOptions: &git2go.FetchOptions{
DownloadTags: git2go.DownloadTagsAll,
RemoteCallbacks: git2go.RemoteCallbacks{
CredentialsCallback: auth.CredCallback,
CertificateCheckCallback: auth.CertCallback,
},
},
})
if err != nil {
return nil, "", fmt.Errorf("unable to clone '%s', error: %w", url, err)
}
ref, err := repo.References.Dwim(c.tag)
if err != nil {
return nil, "", fmt.Errorf("unable to find tag '%s': %w", c.tag, err)
}
err = repo.SetHeadDetached(ref.Target())
if err != nil {
return nil, "", fmt.Errorf("git checkout error: %w", err)
}
head, err := repo.Head()
if err != nil {
return nil, "", fmt.Errorf("git resolve HEAD error: %w", err)
}
commit, err := repo.LookupCommit(head.Target())
if err != nil {
return nil, "", fmt.Errorf("git commit '%s' not found: %w", head.Target(), err)
}
return &Commit{commit}, fmt.Sprintf("%s/%s", c.tag, head.Target().String()), nil
}
type CheckoutCommit struct {
branch string
commit string
}
func (c *CheckoutCommit) Checkout(ctx context.Context, path, url string, auth *common.Auth) (common.Commit, string, error) {
repo, err := git2go.Clone(url, path, &git2go.CloneOptions{
FetchOptions: &git2go.FetchOptions{
DownloadTags: git2go.DownloadTagsNone,
RemoteCallbacks: git2go.RemoteCallbacks{
CredentialsCallback: auth.CredCallback,
CertificateCheckCallback: auth.CertCallback,
},
},
CheckoutBranch: c.branch,
})
if err != nil {
return nil, "", fmt.Errorf("unable to clone '%s', error: %w", url, err)
}
oid, err := git2go.NewOid(c.commit)
if err != nil {
return nil, "", fmt.Errorf("git commit '%s' could not be parsed", c.commit)
}
commit, err := repo.LookupCommit(oid)
if err != nil {
return nil, "", fmt.Errorf("git commit '%s' not found: %w", c.commit, err)
}
tree, err := repo.LookupTree(commit.TreeId())
if err != nil {
return nil, "", fmt.Errorf("git worktree error: %w", err)
}
err = repo.CheckoutTree(tree, &git2go.CheckoutOpts{
Strategy: git2go.CheckoutForce,
})
if err != nil {
return nil, "", fmt.Errorf("git checkout error: %w", err)
}
return &Commit{commit}, fmt.Sprintf("%s/%s", c.branch, commit.Id().String()), nil
}
type CheckoutSemVer struct {
semVer string
}
func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, auth *common.Auth) (common.Commit, string, error) {
rng, err := semver.ParseRange(c.semVer)
if err != nil {
return nil, "", fmt.Errorf("semver parse range error: %w", err)
}
repo, err := git2go.Clone(url, path, &git2go.CloneOptions{
FetchOptions: &git2go.FetchOptions{
DownloadTags: git2go.DownloadTagsAll,
RemoteCallbacks: git2go.RemoteCallbacks{
CredentialsCallback: auth.CredCallback,
CertificateCheckCallback: auth.CertCallback,
},
},
})
if err != nil {
return nil, "", fmt.Errorf("unable to clone '%s', error: %w", url, err)
}
repoTags, err := repo.Tags.List()
if err != nil {
return nil, "", fmt.Errorf("git list tags error: %w", err)
}
svTags := make(map[string]string)
var svers []semver.Version
for _, tag := range repoTags {
v, _ := semver.ParseTolerant(tag)
if rng(v) {
svers = append(svers, v)
svTags[v.String()] = tag
}
}
if len(svers) == 0 {
return nil, "", fmt.Errorf("no match found for semver: %s", c.semVer)
}
semver.Sort(svers)
v := svers[len(svers)-1]
t := svTags[v.String()]
ref, err := repo.References.Dwim(t)
if err != nil {
return nil, "", fmt.Errorf("unable to find tag '%s': %w", t, err)
}
err = repo.SetHeadDetached(ref.Target())
if err != nil {
return nil, "", fmt.Errorf("git checkout error: %w", err)
}
head, err := repo.Head()
if err != nil {
return nil, "", fmt.Errorf("git resolve HEAD error: %w", err)
}
commit, err := repo.LookupCommit(head.Target())
if err != nil {
return nil, "", fmt.Errorf("git commit '%s' not found: %w", head.Target().String(), err)
}
return &Commit{commit}, fmt.Sprintf("%s/%s", t, head.Target().String()), nil
}

View File

@ -0,0 +1,60 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v2
import (
"context"
"io/ioutil"
"os"
"testing"
"github.com/fluxcd/source-controller/pkg/git/common"
git2go "github.com/libgit2/git2go/v31"
)
func TestCheckoutTagSemVer_Checkout(t *testing.T) {
certCallback := func(cert *git2go.Certificate, valid bool, hostname string) git2go.ErrorCode {
return 0
}
auth := &common.Auth{CertCallback: certCallback}
tag := CheckoutTag{
tag: "v1.7.0",
}
tmpDir, _ := ioutil.TempDir("", "test")
defer os.RemoveAll(tmpDir)
cTag, _, err := tag.Checkout(context.TODO(), tmpDir, "https://github.com/projectcontour/contour", auth)
if err != nil {
t.Error(err)
}
semVer := CheckoutSemVer{
semVer: ">=1.0.0 <=1.7.0",
}
tmpDir2, _ := ioutil.TempDir("", "test")
defer os.RemoveAll(tmpDir2)
cSemVer, _, err := semVer.Checkout(context.TODO(), tmpDir2, "https://github.com/projectcontour/contour", auth)
if err != nil {
t.Error(err)
}
if cTag.Hash() != cSemVer.Hash() {
t.Errorf("expected semver hash %s, got %s", cTag.Hash(), cSemVer.Hash())
}
}

64
pkg/git/v2/commit.go Normal file
View File

@ -0,0 +1,64 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v2
import (
"bytes"
"fmt"
"golang.org/x/crypto/openpgp"
"strings"
git2go "github.com/libgit2/git2go/v31"
corev1 "k8s.io/api/core/v1"
)
type Commit struct {
commit *git2go.Commit
}
func (c *Commit) Hash() string {
return c.commit.Id().String()
}
// Verify returns an error if the PGP signature can't be verified
func (c *Commit) Verify(secret corev1.Secret) error {
signature, signedData, err := c.commit.ExtractSignature()
if err != nil {
return err
}
var verified bool
for _, b := range secret.Data {
keyRingReader := strings.NewReader(string(b))
keyring, err := openpgp.ReadArmoredKeyRing(keyRingReader)
if err != nil {
return err
}
_, err = openpgp.CheckArmoredDetachedSignature(keyring, strings.NewReader(signedData), bytes.NewBufferString(signature))
if err == nil {
verified = true
break
}
}
if !verified {
return fmt.Errorf("PGP signature '%s' of '%s' can't be verified", signature, c.commit.Committer().Email)
}
return nil
}

156
pkg/git/v2/transport.go Normal file
View File

@ -0,0 +1,156 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v2
import (
"bufio"
"bytes"
"crypto/sha1"
"fmt"
"golang.org/x/crypto/ssh"
"strings"
"github.com/fluxcd/source-controller/pkg/git/common"
git2go "github.com/libgit2/git2go/v31"
corev1 "k8s.io/api/core/v1"
)
func AuthSecretStrategyForURL(url string) common.AuthSecretStrategy {
switch {
case strings.HasPrefix(url, "http"):
return &BasicAuth{}
case strings.HasPrefix(url, "ssh"):
return &PublicKeyAuth{}
}
return nil
}
type BasicAuth struct{}
func (s *BasicAuth) Method(secret corev1.Secret) (*common.Auth, error) {
var username string
if d, ok := secret.Data["username"]; ok {
username = string(d)
}
var password string
if d, ok := secret.Data["password"]; ok {
password = string(d)
}
if username == "" || password == "" {
return nil, fmt.Errorf("invalid '%s' secret data: required fields 'username' and 'password'", secret.Name)
}
credCallback := func(url string, username_from_url string, allowed_types git2go.CredType) (*git2go.Cred, error) {
cred, err := git2go.NewCredUserpassPlaintext(username, password)
if err != nil {
return nil, err
}
return cred, nil
}
return &common.Auth{CredCallback: credCallback, CertCallback: nil}, nil
}
type PublicKeyAuth struct{}
func (s *PublicKeyAuth) Method(secret corev1.Secret) (*common.Auth, error) {
identity := secret.Data["identity"]
knownHosts := secret.Data["known_hosts"]
if len(identity) == 0 || len(knownHosts) == 0 {
return nil, fmt.Errorf("invalid '%s' secret data: required fields 'identity' and 'known_hosts'", secret.Name)
}
kk, err := parseKnownHosts(string(knownHosts))
if err != nil {
return nil, err
}
// Need to validate private key
_, err = ssh.ParsePrivateKey(identity)
if err != nil {
return nil, err
}
credCallback := func(url string, username_from_url string, allowed_types git2go.CredType) (*git2go.Cred, error) {
cred, err := git2go.NewCredSshKeyFromMemory("git", "", string(identity), "")
if err != nil {
return nil, err
}
return cred, nil
}
certCallback := func(cert *git2go.Certificate, valid bool, hostname string) git2go.ErrorCode {
for _, k := range kk {
if k.matches(hostname, cert.Hostkey.HashSHA1[:]) {
return git2go.ErrOk
}
}
return git2go.ErrGeneric
}
return &common.Auth{CredCallback: credCallback, CertCallback: certCallback}, nil
}
type knownKey struct {
hosts []string
key ssh.PublicKey
}
func parseKnownHosts(s string) ([]knownKey, error) {
knownHosts := []knownKey{}
scanner := bufio.NewScanner(strings.NewReader(s))
for scanner.Scan() {
_, hosts, pubKey, _, _, err := ssh.ParseKnownHosts(scanner.Bytes())
if err != nil {
return []knownKey{}, err
}
knownHost := knownKey{
hosts: hosts,
key: pubKey,
}
knownHosts = append(knownHosts, knownHost)
}
if err := scanner.Err(); err != nil {
return []knownKey{}, err
}
return knownHosts, nil
}
func (k knownKey) matches(host string, key []byte) bool {
if !containsHost(k.hosts, host) {
return false
}
hash := sha1.Sum([]byte(k.key.Marshal()))
if bytes.Compare(hash[:], key) != 0 {
return false
}
return true
}
func containsHost(hosts []string, host string) bool {
for _, h := range hosts {
if h == host {
return true
}
}
return false
}

View File

@ -0,0 +1,147 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v2
import (
"reflect"
"testing"
corev1 "k8s.io/api/core/v1"
"github.com/fluxcd/source-controller/pkg/git/common"
)
const (
// secretKeyFixture is a randomly generated password less
// 512bit RSA private key.
secretKeyFixture string = `-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCrakELAKxozvwJijQEggYlTvS1QTZx1DaBwOhW/4kRSuR21plu
xuQeyuUiztoWeb9jgW7wjzG4j1PIJjdbsgjPIcIZ4PBY7JeEW+QRopfwuN8MHXNp
uTLgIHbkmhoOg5qBEcjzO/lEOOPpV0EmbObgqv3+wRmLJrgfzWl/cTtRewIDAQAB
AoGAawKFImpEN5Xn78iwWpQVZBsbV0AjzgHuGSiloxIZrorzf2DPHkHZzYNaclVx
/o/4tBTsfg7WumH3qr541qyZJDgU7iRMABwmx0v1vm2wQiX7NJzLzH2E9vlMC3mw
d8S99g9EqRuNH98XX8su34B9WGRPqiKvEm0RW8Hideo2/KkCQQDbs6rHcriKQyPB
paidHZAfguu0eVbyHT2EgLgRboWE+tEAqFEW2ycqNL3VPz9fRvwexbB6rpOcPpQJ
DEL4XB2XAkEAx7xJz8YlCQ2H38xggK8R8EUXF9Zhb0fqMJHMNmao1HCHVMtbsa8I
jR2EGyQ4CaIqNG5tdWukXQSJrPYDRWNvvQJAZX3rP7XUYDLB2twvN12HzbbKMhX3
v2MYnxRjc9INpi/Dyzz2MMvOnOW+aDuOh/If2AtVCmeJUx1pf4CFk3viQwJBAKyC
t824+evjv+NQBlme3AOF6PgxtV4D4wWoJ5Uk/dTejER0j/Hbl6sqPxuiILRRV9qJ
Ngkgu4mLjc3RfenEhJECQAx8zjWUE6kHHPGAd9DfiAIQ4bChqnyS0Nwb9+Gd4hSE
P0Ah10mHiK/M0o3T8Eanwum0gbQHPnOwqZgsPkwXRqQ=
-----END RSA PRIVATE KEY-----`
// knownHostsFixture is known_hosts fixture in the expected
// format.
knownHostsFixture string = `github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==`
)
var (
basicAuthSecretFixture = corev1.Secret{
Data: map[string][]byte{
"username": []byte("git"),
"password": []byte("password"),
},
}
privateKeySecretFixture = corev1.Secret{
Data: map[string][]byte{
"identity": []byte(secretKeyFixture),
"known_hosts": []byte(knownHostsFixture),
},
}
)
func TestAuthSecretStrategyForURL(t *testing.T) {
tests := []struct {
name string
url string
want common.AuthSecretStrategy
}{
{"HTTP", "http://git.example.com/org/repo.git", &BasicAuth{}},
{"HTTPS", "https://git.example.com/org/repo.git", &BasicAuth{}},
{"SSH", "ssh://git.example.com:2222/org/repo.git", &PublicKeyAuth{}},
{"unsupported", "protocol://example.com", nil},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := AuthSecretStrategyForURL(tt.url)
if reflect.TypeOf(got) != reflect.TypeOf(tt.want) {
t.Errorf("AuthSecretStrategyForURL() got = %v, want %v", got, tt.want)
}
})
}
}
func TestBasicAuthStrategy_Method(t *testing.T) {
tests := []struct {
name string
secret corev1.Secret
modify func(secret *corev1.Secret)
want *common.Auth
wantErr bool
}{
{"without username", basicAuthSecretFixture, func(s *corev1.Secret) { delete(s.Data, "username") }, nil, true},
{"without password", basicAuthSecretFixture, func(s *corev1.Secret) { delete(s.Data, "password") }, nil, true},
{"empty", corev1.Secret{}, nil, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
secret := tt.secret.DeepCopy()
if tt.modify != nil {
tt.modify(secret)
}
s := &BasicAuth{}
got, err := s.Method(*secret)
if (err != nil) != tt.wantErr {
t.Errorf("Method() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Method() got = %v, want %v", got, tt.want)
}
})
}
}
func TestPublicKeyStrategy_Method(t *testing.T) {
tests := []struct {
name string
secret corev1.Secret
modify func(secret *corev1.Secret)
wantErr bool
}{
{"private key and known_hosts", privateKeySecretFixture, nil, false},
{"missing private key", privateKeySecretFixture, func(s *corev1.Secret) { delete(s.Data, "identity") }, true},
{"invalid private key", privateKeySecretFixture, func(s *corev1.Secret) { s.Data["identity"] = []byte(`-----BEGIN RSA PRIVATE KEY-----`) }, true},
{"missing known_hosts", privateKeySecretFixture, func(s *corev1.Secret) { delete(s.Data, "known_hosts") }, true},
{"invalid known_hosts", privateKeySecretFixture, func(s *corev1.Secret) { s.Data["known_hosts"] = []byte(`invalid`) }, true},
{"empty", corev1.Secret{}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
secret := tt.secret.DeepCopy()
if tt.modify != nil {
tt.modify(secret)
}
s := &PublicKeyAuth{}
_, err := s.Method(*secret)
if (err != nil) != tt.wantErr {
t.Errorf("Method() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}