Skip to content

Commit 9ff98d9

Browse files
authored
Merge pull request #1109 from fluxcd/gitrepo-proxy
gitrepo: Add support for specifying proxy per `GitRepository`
2 parents 6901379 + 944f4cf commit 9ff98d9

8 files changed

+245
-25
lines changed

Makefile

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,11 @@ endif
6363

6464
all: build
6565

66-
build: check-deps ## Build manager binary
66+
build: ## Build manager binary
6767
go build $(GO_STATIC_FLAGS) -o $(BUILD_DIR)/bin/manager main.go
6868

6969
KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_KUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p path)"
70-
test: install-envtest test-api check-deps ## Run all tests
70+
test: install-envtest test-api ## Run all tests
7171
HTTPS_PROXY="" HTTP_PROXY="" \
7272
KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) \
7373
GIT_CONFIG_GLOBAL=/dev/null \
@@ -76,7 +76,7 @@ test: install-envtest test-api check-deps ## Run all tests
7676
$(GO_TEST_ARGS) \
7777
-coverprofile cover.out
7878

79-
test-ctrl: install-envtest test-api check-deps ## Run controller tests
79+
test-ctrl: install-envtest test-api ## Run controller tests
8080
HTTPS_PROXY="" HTTP_PROXY="" \
8181
KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) \
8282
GIT_CONFIG_GLOBAL=/dev/null \
@@ -85,11 +85,6 @@ test-ctrl: install-envtest test-api check-deps ## Run controller tests
8585
-v ./internal/controller \
8686
-coverprofile cover.out
8787

88-
check-deps:
89-
ifeq ($(shell uname -s),Darwin)
90-
if ! command -v pkg-config &> /dev/null; then echo "pkg-config is required"; exit 1; fi
91-
endif
92-
9388
test-api: ## Run api tests
9489
cd api; go test $(GO_TEST_ARGS) ./... -coverprofile cover.out
9590

api/v1/gitrepository_types.go

Lines changed: 5 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+
// ProxySecretRef specifies the Secret containing the proxy configuration
82+
// to use while communicating with the Git server.
83+
// +optional
84+
ProxySecretRef *meta.LocalObjectReference `json:"proxySecretRef,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.

api/v1/zz_generated.deepcopy.go

Lines changed: 5 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: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@ 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+
proxySecretRef:
94+
description: ProxySecretRef specifies the Secret containing the proxy
95+
configuration to use while communicating with the Git server.
96+
properties:
97+
name:
98+
description: Name of the referent.
99+
type: string
100+
required:
101+
- name
102+
type: object
93103
recurseSubmodules:
94104
description: RecurseSubmodules enables the initialization of all submodules
95105
within the GitRepository as cloned from the URL, using their default

docs/api/v1/source.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,21 @@ signature(s).</p>
157157
</tr>
158158
<tr>
159159
<td>
160+
<code>proxySecretRef</code><br>
161+
<em>
162+
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
163+
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
164+
</a>
165+
</em>
166+
</td>
167+
<td>
168+
<em>(Optional)</em>
169+
<p>ProxySecretRef specifies the Secret containing the proxy configuration
170+
to use while communicating with the Git server.</p>
171+
</td>
172+
</tr>
173+
<tr>
174+
<td>
160175
<code>ignore</code><br>
161176
<em>
162177
string
@@ -593,6 +608,21 @@ signature(s).</p>
593608
</tr>
594609
<tr>
595610
<td>
611+
<code>proxySecretRef</code><br>
612+
<em>
613+
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
614+
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
615+
</a>
616+
</em>
617+
</td>
618+
<td>
619+
<em>(Optional)</em>
620+
<p>ProxySecretRef specifies the Secret containing the proxy configuration
621+
to use while communicating with the Git server.</p>
622+
</td>
623+
</tr>
624+
<tr>
625+
<td>
596626
<code>ignore</code><br>
597627
<em>
598628
string

docs/spec/v1/gitrepositories.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,55 @@ GitRepository, and changes to the resource or in the Git repository will not
439439
result in a new Artifact. When the field is set to `false` or removed, it will
440440
resume.
441441

442+
### Proxy secret reference
443+
444+
`.spec.proxySecretRef.name` is an optional field used to specify the name of a
445+
Secret that contains the proxy settings for the object. These settings are used
446+
for all remote Git operations related to the GitRepository.
447+
The Secret can contain three keys:
448+
449+
- `address`, to specify the address of the proxy server. This is a required key.
450+
- `username`, to specify the username to use if the proxy server is protected by
451+
basic authentication. This is an optional key.
452+
- `password`, to specify the password to use if the proxy server is protected by
453+
basic authentication. This is an optional key.
454+
455+
The proxy server must be either HTTP/S or SOCKS5. You can use a SOCKS5 proxy
456+
with a HTTP/S Git repository url.
457+
458+
Examples:
459+
460+
```yaml
461+
---
462+
apiVersion: v1
463+
kind: Secret
464+
metadata:
465+
name: http-proxy
466+
type: Opaque
467+
stringData:
468+
address: http://proxy.com
469+
username: mandalorian
470+
password: grogu
471+
```
472+
473+
```yaml
474+
---
475+
apiVersion: v1
476+
kind: Secret
477+
metadata:
478+
name: ssh-proxy
479+
type: Opaque
480+
stringData:
481+
address: socks5://proxy.com
482+
username: mandalorian
483+
password: grogu
484+
```
485+
486+
Proxying can also be configured in the source-controller Deployment directly by
487+
using the standard environment variables such as `HTTPS_PROXY`, `ALL_PROXY`, etc.
488+
489+
`.spec.proxySecretRef.name` takes precedence over all environment variables.
490+
442491
### Recurse submodules
443492

444493
`.spec.recurseSubmodules` is an optional field to enable the initialization of

internal/controller/gitrepository_controller.go

Lines changed: 70 additions & 17 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,19 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
473474
conditions.Delete(obj, sourcev1.SourceVerifiedCondition)
474475
}
475476

476-
var authData map[string][]byte
477-
if obj.Spec.SecretRef != nil {
478-
// 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 {
477+
var proxyOpts *transport.ProxyOptions
478+
if obj.Spec.ProxySecretRef != nil {
479+
var err error
480+
proxyOpts, err = r.getProxyOpts(ctx, obj.Spec.ProxySecretRef.Name, obj.GetNamespace())
481+
if err != nil {
485482
e := serror.NewGeneric(
486-
fmt.Errorf("failed to get secret '%s': %w", name.String(), err),
483+
fmt.Errorf("failed to configure proxy options: %w", err),
487484
sourcev1.AuthenticationFailedReason,
488485
)
489486
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
490487
// Return error as the world as observed may change
491488
return sreconcile.ResultEmpty, e
492489
}
493-
authData = secret.Data
494490
}
495491

496492
u, err := url.Parse(obj.Spec.URL)
@@ -503,14 +499,14 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
503499
return sreconcile.ResultEmpty, e
504500
}
505501

506-
// Configure authentication strategy to access the source
507-
authOpts, err := git.NewAuthOptions(*u, authData)
502+
authOpts, err := r.getAuthOpts(ctx, obj, *u)
508503
if err != nil {
509504
e := serror.NewGeneric(
510505
fmt.Errorf("failed to configure authentication options: %w", err),
511506
sourcev1.AuthenticationFailedReason,
512507
)
513508
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
509+
// Return error as the world as observed may change
514510
return sreconcile.ResultEmpty, e
515511
}
516512

@@ -536,7 +532,7 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
536532
// Persist the ArtifactSet.
537533
*includes = *artifacts
538534

539-
c, err := r.gitCheckout(ctx, obj, authOpts, dir, true)
535+
c, err := r.gitCheckout(ctx, obj, authOpts, proxyOpts, dir, true)
540536
if err != nil {
541537
return sreconcile.ResultEmpty, err
542538
}
@@ -578,7 +574,7 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
578574

579575
// If we can't skip the reconciliation, checkout again without any
580576
// optimization.
581-
c, err := r.gitCheckout(ctx, obj, authOpts, dir, false)
577+
c, err := r.gitCheckout(ctx, obj, authOpts, proxyOpts, dir, false)
582578
if err != nil {
583579
return sreconcile.ResultEmpty, err
584580
}
@@ -606,6 +602,60 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
606602
return sreconcile.ResultSuccess, nil
607603
}
608604

605+
// getProxyOpts fetches the secret containing the proxy settings, constructs a
606+
// transport.ProxyOptions object using those settings and then returns it.
607+
func (r *GitRepositoryReconciler) getProxyOpts(ctx context.Context, proxySecretName,
608+
proxySecretNamespace string) (*transport.ProxyOptions, error) {
609+
proxyData, err := r.getSecretData(ctx, proxySecretName, proxySecretNamespace)
610+
if err != nil {
611+
return nil, fmt.Errorf("failed to get proxy secret '%s/%s': %w", proxySecretNamespace, proxySecretName, err)
612+
}
613+
address, ok := proxyData["address"]
614+
if !ok {
615+
return nil, fmt.Errorf("invalid proxy secret '%s/%s': key 'address' is missing", proxySecretNamespace, proxySecretName)
616+
}
617+
618+
proxyOpts := &transport.ProxyOptions{
619+
URL: string(address),
620+
Username: string(proxyData["username"]),
621+
Password: string(proxyData["password"]),
622+
}
623+
return proxyOpts, nil
624+
}
625+
626+
// getAuthOpts fetches the secret containing the auth options (if specified),
627+
// constructs a git.AuthOptions object using those options along with the provided
628+
// URL and returns it.
629+
func (r *GitRepositoryReconciler) getAuthOpts(ctx context.Context, obj *sourcev1.GitRepository, u url.URL) (*git.AuthOptions, error) {
630+
var authData map[string][]byte
631+
if obj.Spec.SecretRef != nil {
632+
var err error
633+
authData, err = r.getSecretData(ctx, obj.Spec.SecretRef.Name, obj.GetNamespace())
634+
if err != nil {
635+
return nil, fmt.Errorf("failed to get secret '%s/%s': %w", obj.GetNamespace(), obj.Spec.SecretRef.Name, err)
636+
}
637+
}
638+
639+
// Configure authentication strategy to access the source
640+
authOpts, err := git.NewAuthOptions(u, authData)
641+
if err != nil {
642+
return nil, err
643+
}
644+
return authOpts, nil
645+
}
646+
647+
func (r *GitRepositoryReconciler) getSecretData(ctx context.Context, name, namespace string) (map[string][]byte, error) {
648+
key := types.NamespacedName{
649+
Namespace: namespace,
650+
Name: name,
651+
}
652+
var secret corev1.Secret
653+
if err := r.Client.Get(ctx, key, &secret); err != nil {
654+
return nil, err
655+
}
656+
return secret.Data, nil
657+
}
658+
609659
// reconcileArtifact archives a new Artifact to the Storage, if the current
610660
// (Status) data on the object does not match the given.
611661
//
@@ -776,8 +826,8 @@ func (r *GitRepositoryReconciler) reconcileInclude(ctx context.Context, sp *patc
776826

777827
// gitCheckout builds checkout options with the given configurations and
778828
// performs a git checkout.
779-
func (r *GitRepositoryReconciler) gitCheckout(ctx context.Context,
780-
obj *sourcev1.GitRepository, authOpts *git.AuthOptions, dir string, optimized bool) (*git.Commit, error) {
829+
func (r *GitRepositoryReconciler) gitCheckout(ctx context.Context, obj *sourcev1.GitRepository,
830+
authOpts *git.AuthOptions, proxyOpts *transport.ProxyOptions, dir string, optimized bool) (*git.Commit, error) {
781831
// Configure checkout strategy.
782832
cloneOpts := repository.CloneConfig{
783833
RecurseSubmodules: obj.Spec.RecurseSubmodules,
@@ -807,6 +857,9 @@ func (r *GitRepositoryReconciler) gitCheckout(ctx context.Context,
807857
if authOpts.Transport == git.HTTP {
808858
clientOpts = append(clientOpts, gogit.WithInsecureCredentialsOverHTTP())
809859
}
860+
if proxyOpts != nil {
861+
clientOpts = append(clientOpts, gogit.WithProxy(*proxyOpts))
862+
}
810863

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

0 commit comments

Comments
 (0)