Skip to content

Commit 7ca8dc7

Browse files
committed
gitrepo: Add support for specifying proxy per GitRepository
Add `.spec.proxy.secretRef.name` to `GitRepository` to allow referencing a secret containing the proxy settings to be used for all remote Git operations for a particular object. Signed-off-by: Sanskar Jaiswal <[email protected]>
1 parent 44e64f6 commit 7ca8dc7

File tree

7 files changed

+155
-17
lines changed

7 files changed

+155
-17
lines changed

api/v1/gitrepository_types.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ type GitRepositorySpec struct {
7878
// +optional
7979
Verification *GitRepositoryVerification `json:"verify,omitempty"`
8080

81+
// Proxy specifies the proxy configuration to use while contacting
82+
// the Git server.
83+
// +optional
84+
Proxy *GitRepositoryProxy `json:"proxy,omitempty"`
85+
8186
// Ignore overrides the set of excluded patterns in the .sourceignore format
8287
// (which is the same as .gitignore). If not provided, a default will be used,
8388
// consult the documentation for your version to find out what those are.
@@ -175,6 +180,13 @@ type GitRepositoryVerification struct {
175180
SecretRef meta.LocalObjectReference `json:"secretRef"`
176181
}
177182

183+
type GitRepositoryProxy struct {
184+
// SecretRef specifies the Secret containing the proxy address and optionally
185+
// the proxy username and password.
186+
// +required
187+
SecretRef meta.LocalObjectReference `json:"secretRef"`
188+
}
189+
178190
// GitRepositoryStatus records the observed state of a Git repository.
179191
type GitRepositoryStatus struct {
180192
// ObservedGeneration is the last observed generation of the GitRepository

api/v1/zz_generated.deepcopy.go

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,23 @@ spec:
9090
description: Interval at which to check the GitRepository for updates.
9191
pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$
9292
type: string
93+
proxy:
94+
description: Proxy specifies the proxy configuration to use while
95+
contacting the Git server.
96+
properties:
97+
secretRef:
98+
description: SecretRef specifies the Secret containing the proxy
99+
address and optionally the proxy username and password.
100+
properties:
101+
name:
102+
description: Name of the referent.
103+
type: string
104+
required:
105+
- name
106+
type: object
107+
required:
108+
- secretRef
109+
type: object
93110
recurseSubmodules:
94111
description: RecurseSubmodules enables the initialization of all submodules
95112
within the GitRepository as cloned from the URL, using their default

docs/spec/v1/gitrepositories.md

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -451,10 +451,53 @@ controller with the argument `--feature-gates=OptimizedGitClones=false`.
451451
NB: GitRepository objects configured for SemVer or Commit clones are
452452
not affected by this functionality.
453453

454-
#### Proxy support
454+
### Proxy
455455

456-
When a proxy is configured in the source-controller Pod through the appropriate
457-
environment variables, for example `HTTPS_PROXY`, `NO_PROXY`, etc.
456+
`.spec.proxy.secretRef.name` is an optional field to specify a name of a Secret
457+
containing the proxy settings that need to be used for all remote Git operations
458+
for the particular GitRepository object. The Secret can contain three keys:
459+
460+
- `address`: The address of the proxy server. This is a required key.
461+
- `username`: The username to use if the proxy server is protected by basic
462+
authentication. This is an optinal key.
463+
- `password`: The password to use if the proxy server is protected by basic
464+
authentication. This is an optinal key.
465+
466+
The proxy server must be either HTTP/S or SOCKS5. You can use a SOCKS5 proxy
467+
with a HTTP/S Git repository url.
468+
469+
Examples:
470+
471+
```yaml
472+
---
473+
apiVersion: v1
474+
kind: Secret
475+
metadata:
476+
name: http-proxy
477+
type: Opaque
478+
stringData:
479+
address: http://proxy.com
480+
username: mandalorian
481+
password: grogu
482+
```
483+
484+
```yaml
485+
---
486+
apiVersion: v1
487+
kind: Secret
488+
metadata:
489+
name: ssh-proxy
490+
type: Opaque
491+
stringData:
492+
address: socks5://proxy.com
493+
username: mandalorian
494+
password: grogu
495+
```
496+
497+
Proxying can also be configured in the source-controller pod directly by using
498+
the standard environment variables such as `HTTPS_PROXY`, `ALL_PROXY`, etc.
499+
500+
`.spec.proxy.secretRef.name` takes precedence over all environment variables.
458501

459502
### Recurse submodules
460503

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ replace github.com/docker/docker => github.com/docker/docker v23.0.6+incompatibl
2121
// is compatible with github.com/google/go-containerregistry v0.15.x.
2222
replace github.com/google/go-containerregistry => github.com/google/go-containerregistry v0.14.1-0.20230409045903-ed5c185df419
2323

24+
// temp replace directive to enable the use of `WithProxy()`
25+
replace github.com/fluxcd/pkg/git/gogit => github.com/fluxcd/pkg/git/gogit v0.11.2-0.20230530083208-12339fbae0ae
26+
2427
require (
2528
cloud.google.com/go/storage v1.30.1
2629
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,8 +382,8 @@ github.com/fluxcd/pkg/apis/meta v1.1.0 h1:vYU1mvUzztnQyTzZOLHQ3wm/tXd7E1QZ2V91zu
382382
github.com/fluxcd/pkg/apis/meta v1.1.0/go.mod h1:/QwCotRKL/BT6RSa4O75FlYW14fU8eRfKnoagzbkmL4=
383383
github.com/fluxcd/pkg/git v0.12.2 h1:96xH3hy3WfwiD0DioyJZcGapYT3lmPc2s7jU5UM8buw=
384384
github.com/fluxcd/pkg/git v0.12.2/go.mod h1:9TG4fEfGCF1XHLt9Xs7X2YOmkmWOiwfjH9tdGIQs8/8=
385-
github.com/fluxcd/pkg/git/gogit v0.11.1 h1:17UbHEPQovLOhlrsPaDoJa3J7jX0I7G92TWXeEDf2eU=
386-
github.com/fluxcd/pkg/git/gogit v0.11.1/go.mod h1:Hh358WYfwmvGf6Aaj1wjGZMN2AWlAcXRR6aubMQYq8M=
385+
github.com/fluxcd/pkg/git/gogit v0.11.2-0.20230530083208-12339fbae0ae h1:JCU+9lTKHPIE7bS5Q9DUoGNa2QuGNhIch/3rt5v8Pso=
386+
github.com/fluxcd/pkg/git/gogit v0.11.2-0.20230530083208-12339fbae0ae/go.mod h1:Kn+GfYfZBBIaXmQj39cQvrDxT/6y8leQxXZ5/B+YYTQ=
387387
github.com/fluxcd/pkg/gittestserver v0.8.4 h1:rA/QUZnfH77ZZG+5xfMqjgEHJdzeeE6Nn1o8cops/bU=
388388
github.com/fluxcd/pkg/gittestserver v0.8.4/go.mod h1:i3Vng3Stl5zOuGhN4+RuP2NWf5snJCeGUKA7pzAvcHU=
389389
github.com/fluxcd/pkg/helmtestserver v0.13.0 h1:bRzOO955nDKWKJZvDORfmDvRdb/558BX4ffgx1vT4LI=

internal/controller/gitrepository_controller.go

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828

2929
securejoin "github.com/cyphar/filepath-securejoin"
3030
"github.com/fluxcd/pkg/runtime/logger"
31+
"github.com/go-git/go-git/v5/plumbing/transport"
3132
corev1 "k8s.io/api/core/v1"
3233
"k8s.io/apimachinery/pkg/runtime"
3334
"k8s.io/apimachinery/pkg/types"
@@ -473,24 +474,50 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
473474
conditions.Delete(obj, sourcev1.SourceVerifiedCondition)
474475
}
475476

477+
var proxyOpts *transport.ProxyOptions
478+
if obj.Spec.Proxy != nil {
479+
proxySecretName := obj.Spec.Proxy.SecretRef.Name
480+
proxyData, err := r.getSecretData(ctx, proxySecretName, obj.GetNamespace())
481+
if err != nil {
482+
e := serror.NewGeneric(
483+
fmt.Errorf("failed to get secret '%s/%s': %w", proxySecretName, obj.GetNamespace(), err),
484+
sourcev1.AuthenticationFailedReason,
485+
)
486+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
487+
// Return error as the world as observed may change
488+
return sreconcile.ResultEmpty, e
489+
}
490+
address, ok := proxyData["address"]
491+
if !ok {
492+
e := serror.NewGeneric(
493+
fmt.Errorf("invalid proxy secret '%s/%s': key 'address' is missing", proxySecretName, obj.GetNamespace()),
494+
sourcev1.AuthenticationFailedReason,
495+
)
496+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
497+
// Return error as the world as observed may change
498+
return sreconcile.ResultEmpty, e
499+
}
500+
proxyOpts = &transport.ProxyOptions{
501+
URL: string(address),
502+
}
503+
proxyOpts.Username = string(proxyData["username"])
504+
proxyOpts.Password = string(proxyData["password"])
505+
}
506+
476507
var authData map[string][]byte
477508
if obj.Spec.SecretRef != nil {
478509
// Attempt to retrieve secret
479-
name := types.NamespacedName{
480-
Namespace: obj.GetNamespace(),
481-
Name: obj.Spec.SecretRef.Name,
482-
}
483-
var secret corev1.Secret
484-
if err := r.Client.Get(ctx, name, &secret); err != nil {
510+
var err error
511+
authData, err = r.getSecretData(ctx, obj.Spec.SecretRef.Name, obj.GetNamespace())
512+
if err != nil {
485513
e := serror.NewGeneric(
486-
fmt.Errorf("failed to get secret '%s': %w", name.String(), err),
514+
fmt.Errorf("failed to get secret '%s/%s': %w", obj.Spec.SecretRef.Name, obj.GetNamespace(), err),
487515
sourcev1.AuthenticationFailedReason,
488516
)
489517
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
490518
// Return error as the world as observed may change
491519
return sreconcile.ResultEmpty, e
492520
}
493-
authData = secret.Data
494521
}
495522

496523
u, err := url.Parse(obj.Spec.URL)
@@ -541,7 +568,7 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
541568
optimizedClone = true
542569
}
543570

544-
c, err := r.gitCheckout(ctx, obj, authOpts, dir, optimizedClone)
571+
c, err := r.gitCheckout(ctx, obj, authOpts, proxyOpts, dir, optimizedClone)
545572
if err != nil {
546573
return sreconcile.ResultEmpty, err
547574
}
@@ -583,7 +610,7 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
583610

584611
// If we can't skip the reconciliation, checkout again without any
585612
// optimization.
586-
c, err := r.gitCheckout(ctx, obj, authOpts, dir, false)
613+
c, err := r.gitCheckout(ctx, obj, authOpts, proxyOpts, dir, false)
587614
if err != nil {
588615
return sreconcile.ResultEmpty, err
589616
}
@@ -611,6 +638,18 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
611638
return sreconcile.ResultSuccess, nil
612639
}
613640

641+
func (r *GitRepositoryReconciler) getSecretData(ctx context.Context, name, namespace string) (map[string][]byte, error) {
642+
key := types.NamespacedName{
643+
Namespace: namespace,
644+
Name: name,
645+
}
646+
var secret corev1.Secret
647+
if err := r.Client.Get(ctx, key, &secret); err != nil {
648+
return nil, err
649+
}
650+
return secret.Data, nil
651+
}
652+
614653
// reconcileArtifact archives a new Artifact to the Storage, if the current
615654
// (Status) data on the object does not match the given.
616655
//
@@ -782,8 +821,8 @@ func (r *GitRepositoryReconciler) reconcileInclude(ctx context.Context, sp *patc
782821
// gitCheckout builds checkout options with the given configurations and
783822
// performs a git checkout.
784823
func (r *GitRepositoryReconciler) gitCheckout(ctx context.Context,
785-
obj *sourcev1.GitRepository, authOpts *git.AuthOptions, dir string,
786-
optimized bool) (*git.Commit, error) {
824+
obj *sourcev1.GitRepository, authOpts *git.AuthOptions, proxyOpts *transport.ProxyOptions,
825+
dir string, optimized bool) (*git.Commit, error) {
787826
// Configure checkout strategy.
788827
cloneOpts := repository.CloneConfig{
789828
RecurseSubmodules: obj.Spec.RecurseSubmodules,
@@ -813,6 +852,9 @@ func (r *GitRepositoryReconciler) gitCheckout(ctx context.Context,
813852
if authOpts.Transport == git.HTTP {
814853
clientOpts = append(clientOpts, gogit.WithInsecureCredentialsOverHTTP())
815854
}
855+
if proxyOpts != nil {
856+
clientOpts = append(clientOpts, gogit.WithProxy(*proxyOpts))
857+
}
816858

817859
gitReader, err := gogit.NewClient(dir, authOpts, clientOpts...)
818860
if err != nil {

0 commit comments

Comments
 (0)