diff --git a/api/v1beta2/bucket_types.go b/api/v1beta2/bucket_types.go
index a1060431e..52d0b5aff 100644
--- a/api/v1beta2/bucket_types.go
+++ b/api/v1beta2/bucket_types.go
@@ -100,6 +100,11 @@ type BucketSpec struct {
// +optional
CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`
+ // ProxySecretRef specifies the Secret containing the proxy configuration
+ // to use while communicating with the Bucket server.
+ // +optional
+ ProxySecretRef *meta.LocalObjectReference `json:"proxySecretRef,omitempty"`
+
// Interval at which the Bucket Endpoint is checked for updates.
// This interval is approximate and may be subject to jitter to ensure
// efficient use of resources.
diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go
index 1611af57c..b62bafecb 100644
--- a/api/v1beta2/zz_generated.deepcopy.go
+++ b/api/v1beta2/zz_generated.deepcopy.go
@@ -128,6 +128,11 @@ func (in *BucketSpec) DeepCopyInto(out *BucketSpec) {
*out = new(meta.LocalObjectReference)
**out = **in
}
+ if in.ProxySecretRef != nil {
+ in, out := &in.ProxySecretRef, &out.ProxySecretRef
+ *out = new(meta.LocalObjectReference)
+ **out = **in
+ }
out.Interval = in.Interval
if in.Timeout != nil {
in, out := &in.Timeout, &out.Timeout
diff --git a/config/crd/bases/source.toolkit.fluxcd.io_buckets.yaml b/config/crd/bases/source.toolkit.fluxcd.io_buckets.yaml
index 49ff85c0a..992fab966 100644
--- a/config/crd/bases/source.toolkit.fluxcd.io_buckets.yaml
+++ b/config/crd/bases/source.toolkit.fluxcd.io_buckets.yaml
@@ -391,6 +391,17 @@ spec:
- gcp
- azure
type: string
+ proxySecretRef:
+ description: |-
+ ProxySecretRef specifies the Secret containing the proxy configuration
+ to use while communicating with the Bucket server.
+ properties:
+ name:
+ description: Name of the referent.
+ type: string
+ required:
+ - name
+ type: object
region:
description: Region of the Endpoint where the BucketName is located
in.
diff --git a/docs/api/v1beta2/source.md b/docs/api/v1beta2/source.md
index 0866e76fa..9410662ab 100644
--- a/docs/api/v1beta2/source.md
+++ b/docs/api/v1beta2/source.md
@@ -191,6 +191,21 @@ be of type Opaque
or kubernetes.io/tls
.
+proxySecretRef
+
+
+github.com/fluxcd/pkg/apis/meta.LocalObjectReference
+
+
+ |
+
+(Optional)
+ ProxySecretRef specifies the Secret containing the proxy configuration
+to use while communicating with the Bucket server.
+ |
+
+
+
interval
@@ -1541,6 +1556,21 @@ be of type Opaque or kubernetes.io/tls .
|
+proxySecretRef
+
+
+github.com/fluxcd/pkg/apis/meta.LocalObjectReference
+
+
+ |
+
+(Optional)
+ ProxySecretRef specifies the Secret containing the proxy configuration
+to use while communicating with the Bucket server.
+ |
+
+
+
interval
diff --git a/docs/spec/v1beta2/buckets.md b/docs/spec/v1beta2/buckets.md
index 81ae7d224..0128d44cb 100644
--- a/docs/spec/v1beta2/buckets.md
+++ b/docs/spec/v1beta2/buckets.md
@@ -824,6 +824,41 @@ stringData:
ca.crt:
```
+### Proxy secret reference
+
+`.spec.proxySecretRef.name` is an optional field used to specify the name of a
+Secret that contains the proxy settings for the object. These settings are used
+for all the remote operations related to the Bucket.
+The Secret can contain three keys:
+
+- `address`, to specify the address of the proxy server. This is a required key.
+- `username`, to specify the username to use if the proxy server is protected by
+ basic authentication. This is an optional key.
+- `password`, to specify the password to use if the proxy server is protected by
+ basic authentication. This is an optional key.
+
+The proxy server must be HTTP/S.
+
+Example:
+
+```yaml
+---
+apiVersion: v1
+kind: Secret
+metadata:
+ name: http-proxy
+type: Opaque
+stringData:
+ address: http://proxy.com
+ username: mandalorian
+ password: grogu
+```
+
+Proxying can also be configured in the source-controller Deployment directly by
+using the standard environment variables such as `HTTPS_PROXY`, `ALL_PROXY`, etc.
+
+`.spec.proxySecretRef.name` takes precedence over all environment variables.
+
### Insecure
`.spec.insecure` is an optional field to allow connecting to an insecure (HTTP)
diff --git a/go.mod b/go.mod
index 82990c75c..b8330eb4a 100644
--- a/go.mod
+++ b/go.mod
@@ -19,6 +19,7 @@ require (
github.com/distribution/distribution/v3 v3.0.0-alpha.1
github.com/docker/cli v24.0.9+incompatible
github.com/docker/go-units v0.5.0
+ github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5
github.com/fluxcd/cli-utils v0.36.0-flux.7
github.com/fluxcd/pkg/apis/event v0.9.0
github.com/fluxcd/pkg/apis/meta v1.5.0
diff --git a/go.sum b/go.sum
index 8083e29f5..75843a2a7 100644
--- a/go.sum
+++ b/go.sum
@@ -311,6 +311,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 h1:m62nsMU279qRD9PQSWD1l66kmkXzuYcnVJqL4XLeV2M=
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
+github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
+github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk=
github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/proto v1.12.1 h1:6n/Z2pZAnBwuhU66Gs8160B8rrrYKo7h2F2sCOnNceE=
@@ -831,6 +833,7 @@ github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
diff --git a/internal/controller/bucket_controller.go b/internal/controller/bucket_controller.go
index 45705e9b3..30ff7385e 100644
--- a/internal/controller/bucket_controller.go
+++ b/internal/controller/bucket_controller.go
@@ -21,6 +21,7 @@ import (
stdtls "crypto/tls"
"errors"
"fmt"
+ "net/url"
"os"
"path/filepath"
"strings"
@@ -431,6 +432,13 @@ func (r *BucketReconciler) reconcileSource(ctx context.Context, sp *patch.Serial
return sreconcile.ResultEmpty, e
}
+ proxyURL, err := r.getProxyURL(ctx, obj)
+ if err != nil {
+ e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
+ return sreconcile.ResultEmpty, e
+ }
+
// Construct provider client
var provider BucketProvider
switch obj.Spec.Provider {
@@ -440,7 +448,7 @@ func (r *BucketReconciler) reconcileSource(ctx context.Context, sp *patch.Serial
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
return sreconcile.ResultEmpty, e
}
- if provider, err = gcp.NewClient(ctx, secret); err != nil {
+ if provider, err = gcp.NewClient(ctx, obj, secret, proxyURL); err != nil {
e := serror.NewGeneric(err, "ClientError")
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
return sreconcile.ResultEmpty, e
@@ -468,7 +476,7 @@ func (r *BucketReconciler) reconcileSource(ctx context.Context, sp *patch.Serial
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
return sreconcile.ResultEmpty, e
}
- if provider, err = minio.NewClient(obj, secret, tlsConfig); err != nil {
+ if provider, err = minio.NewClient(obj, secret, tlsConfig, proxyURL); err != nil {
e := serror.NewGeneric(err, "ClientError")
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
return sreconcile.ResultEmpty, e
@@ -703,6 +711,30 @@ func (r *BucketReconciler) getTLSConfig(ctx context.Context, obj *bucketv1.Bucke
return tlsConfig, nil
}
+func (r *BucketReconciler) getProxyURL(ctx context.Context, obj *bucketv1.Bucket) (*url.URL, error) {
+ namespace := obj.GetNamespace()
+ proxySecret, err := r.getSecret(ctx, obj.Spec.ProxySecretRef, namespace)
+ if err != nil || proxySecret == nil {
+ return nil, err
+ }
+ proxyData := proxySecret.Data
+ address, ok := proxyData["address"]
+ if !ok {
+ return nil, fmt.Errorf("invalid proxy secret '%s/%s': key 'address' is missing",
+ obj.Spec.ProxySecretRef.Name, namespace)
+ }
+ proxyURL, err := url.Parse(string(address))
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse proxy address '%s': %w", address, err)
+ }
+ user, hasUser := proxyData["username"]
+ password, hasPassword := proxyData["password"]
+ if hasUser || hasPassword {
+ proxyURL.User = url.UserPassword(string(user), string(password))
+ }
+ return proxyURL, nil
+}
+
// eventLogf records events, and logs at the same time.
//
// This log is different from the debug log in the EventRecorder, in the sense
diff --git a/internal/controller/bucket_controller_test.go b/internal/controller/bucket_controller_test.go
index b17ce534e..29ea0aae0 100644
--- a/internal/controller/bucket_controller_test.go
+++ b/internal/controller/bucket_controller_test.go
@@ -551,6 +551,47 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "certificate secret does not contain any TLS configuration"),
},
},
+ {
+ name: "Observes non-existing proxySecretRef",
+ bucketName: "dummy",
+ beforeFunc: func(obj *bucketv1.Bucket) {
+ obj.Spec.ProxySecretRef = &meta.LocalObjectReference{
+ Name: "dummy",
+ }
+ conditions.MarkReconciling(obj, meta.ProgressingReason, "foo")
+ conditions.MarkUnknown(obj, meta.ReadyCondition, "foo", "bar")
+ },
+ wantErr: true,
+ assertIndex: index.NewDigester(),
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get secret '/dummy': secrets \"dummy\" not found"),
+ *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
+ *conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
+ },
+ },
+ {
+ name: "Observes invalid proxySecretRef",
+ bucketName: "dummy",
+ secret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "dummy",
+ },
+ },
+ beforeFunc: func(obj *bucketv1.Bucket) {
+ obj.Spec.ProxySecretRef = &meta.LocalObjectReference{
+ Name: "dummy",
+ }
+ conditions.MarkReconciling(obj, meta.ProgressingReason, "foo")
+ conditions.MarkUnknown(obj, meta.ReadyCondition, "foo", "bar")
+ },
+ wantErr: true,
+ assertIndex: index.NewDigester(),
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
+ *conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
+ *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "invalid proxy secret 'dummy/': key 'address' is missing"),
+ },
+ },
{
name: "Observes non-existing bucket name",
bucketName: "dummy",
diff --git a/pkg/gcp/gcp.go b/pkg/gcp/gcp.go
index 77011fada..370013f3c 100644
--- a/pkg/gcp/gcp.go
+++ b/pkg/gcp/gcp.go
@@ -21,6 +21,8 @@ import (
"errors"
"fmt"
"io"
+ "net/http"
+ "net/url"
"os"
"path/filepath"
@@ -30,6 +32,8 @@ import (
"google.golang.org/api/option"
corev1 "k8s.io/api/core/v1"
ctrl "sigs.k8s.io/controller-runtime"
+
+ sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
)
var (
@@ -48,24 +52,28 @@ type GCSClient struct {
*gcpstorage.Client
}
-// NewClient creates a new GCP storage client. The Client will automatically look for the Google Application
+// NewClient creates a new GCP storage client. The Client will automatically look for the Google Application
// Credential environment variable or look for the Google Application Credential file.
-func NewClient(ctx context.Context, secret *corev1.Secret) (*GCSClient, error) {
- c := &GCSClient{}
+func NewClient(ctx context.Context, bucket *sourcev1.Bucket,
+ secret *corev1.Secret, proxyURL *url.URL) (*GCSClient, error) {
+ var opts []option.ClientOption
+
if secret != nil {
- client, err := gcpstorage.NewClient(ctx, option.WithCredentialsJSON(secret.Data["serviceaccount"]))
- if err != nil {
- return nil, err
- }
- c.Client = client
- } else {
- client, err := gcpstorage.NewClient(ctx)
- if err != nil {
- return nil, err
- }
- c.Client = client
+ opts = append(opts, option.WithCredentialsJSON(secret.Data["serviceaccount"]))
}
- return c, nil
+
+ if proxyURL != nil {
+ transport := http.DefaultTransport.(*http.Transport).Clone()
+ transport.Proxy = http.ProxyURL(proxyURL)
+ opts = append(opts, option.WithHTTPClient(&http.Client{Transport: transport}))
+ }
+
+ client, err := gcpstorage.NewClient(ctx, opts...)
+ if err != nil {
+ return nil, err
+ }
+
+ return &GCSClient{Client: client}, nil
}
// ValidateSecret validates the credential secret. The provided Secret may
diff --git a/pkg/gcp/gcp_test.go b/pkg/gcp/gcp_test.go
index 9ccf0c645..9b96c0253 100644
--- a/pkg/gcp/gcp_test.go
+++ b/pkg/gcp/gcp_test.go
@@ -33,12 +33,13 @@ import (
gcpstorage "cloud.google.com/go/storage"
"google.golang.org/api/googleapi"
+ "google.golang.org/api/option"
raw "google.golang.org/api/storage/v1"
"gotest.tools/assert"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "google.golang.org/api/option"
+ pkgtesting "github.com/fluxcd/source-controller/pkg/testing"
)
const (
@@ -49,11 +50,12 @@ const (
)
var (
- hc *http.Client
- client *gcpstorage.Client
- close func()
- err error
- secret = corev1.Secret{
+ hc *http.Client
+ serverAddr string
+ client *gcpstorage.Client
+ close func()
+ err error
+ secret = corev1.Secret{
ObjectMeta: v1.ObjectMeta{
Name: "gcp-secret",
Namespace: "default",
@@ -76,7 +78,7 @@ var (
)
func TestMain(m *testing.M) {
- hc, close = newTestServer(func(w http.ResponseWriter, r *http.Request) {
+ hc, serverAddr, close = newTestServer(func(w http.ResponseWriter, r *http.Request) {
io.Copy(io.Discard, r.Body)
switch r.RequestURI {
case fmt.Sprintf("/storage/v1/b/%s?alt=json&prettyPrint=false&projection=full", bucketName):
@@ -140,7 +142,7 @@ func TestMain(m *testing.M) {
}
func TestNewClientWithSecretErr(t *testing.T) {
- gcpClient, err := NewClient(context.Background(), secret.DeepCopy())
+ gcpClient, err := NewClient(context.Background(), nil, secret.DeepCopy(), nil)
t.Log(err)
assert.Error(t, err, "dialing: invalid character 'e' looking for beginning of value")
assert.Assert(t, gcpClient == nil)
@@ -216,6 +218,27 @@ func TestFGetObject(t *testing.T) {
assert.Equal(t, etag, objectEtag)
}
+func TestFGetObjectWithProxy(t *testing.T) {
+ proxyURL, closeProxy := pkgtesting.NewHTTPProxy(t, serverAddr)
+ defer closeProxy()
+ transport := http.DefaultTransport.(*http.Transport).Clone()
+ transport.Proxy = http.ProxyURL(proxyURL)
+ transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} // needed because the server is a mock
+ httpClient := &http.Client{Transport: transport}
+ client, err := gcpstorage.NewClient(context.Background(), option.WithHTTPClient(httpClient))
+ assert.NilError(t, err)
+ gcpClient := &GCSClient{
+ Client: client,
+ }
+ tempDir := t.TempDir()
+ localPath := filepath.Join(tempDir, objectName)
+ etag, err := gcpClient.FGetObject(context.Background(), bucketName, objectName, localPath)
+ if err != io.EOF {
+ assert.NilError(t, err)
+ }
+ assert.Equal(t, etag, objectEtag)
+}
+
func TestFGetObjectNotExists(t *testing.T) {
object := "notexists.txt"
tempDir := t.TempDir()
@@ -272,16 +295,17 @@ func TestValidateSecret(t *testing.T) {
}
}
-func newTestServer(handler func(w http.ResponseWriter, r *http.Request)) (*http.Client, func()) {
- ts := httptest.NewTLSServer(http.HandlerFunc(handler))
+func newTestServer(handler http.HandlerFunc) (*http.Client, string, func()) {
+ ts := httptest.NewTLSServer(handler)
+ serverAddr := ts.Listener.Addr().String()
tlsConf := &tls.Config{InsecureSkipVerify: true}
tr := &http.Transport{
TLSClientConfig: tlsConf,
DialTLS: func(netw, addr string) (net.Conn, error) {
- return tls.Dial("tcp", ts.Listener.Addr().String(), tlsConf)
+ return tls.Dial("tcp", serverAddr, tlsConf)
},
}
- return &http.Client{Transport: tr}, func() {
+ return &http.Client{Transport: tr}, serverAddr, func() {
tr.CloseIdleConnections()
ts.Close()
}
diff --git a/pkg/minio/minio.go b/pkg/minio/minio.go
index 61a30ded4..d0ed9fb9c 100644
--- a/pkg/minio/minio.go
+++ b/pkg/minio/minio.go
@@ -21,6 +21,8 @@ import (
"crypto/tls"
"errors"
"fmt"
+ "net/http"
+ "net/url"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
@@ -37,7 +39,9 @@ type MinioClient struct {
}
// NewClient creates a new Minio storage client.
-func NewClient(bucket *sourcev1.Bucket, secret *corev1.Secret, tlsConfig *tls.Config) (*MinioClient, error) {
+func NewClient(bucket *sourcev1.Bucket, secret *corev1.Secret,
+ tlsConfig *tls.Config, proxyURL *url.URL) (*MinioClient, error) {
+
opt := minio.Options{
Region: bucket.Spec.Region,
Secure: !bucket.Spec.Insecure,
@@ -61,15 +65,28 @@ func NewClient(bucket *sourcev1.Bucket, secret *corev1.Secret, tlsConfig *tls.Co
opt.Creds = credentials.NewIAM("")
}
+ var transportOpts []func(*http.Transport)
+
if opt.Secure && tlsConfig != nil {
- // Use the default minio transport, but override the TLS config.
- secure := false // true causes the TLS config to be defined internally, but here we have our own so we just pass false.
- transport, err := minio.DefaultTransport(secure)
+ transportOpts = append(transportOpts, func(t *http.Transport) {
+ t.TLSClientConfig = tlsConfig.Clone()
+ })
+ }
+
+ if proxyURL != nil {
+ transportOpts = append(transportOpts, func(t *http.Transport) {
+ t.Proxy = http.ProxyURL(proxyURL)
+ })
+ }
+
+ if len(transportOpts) > 0 {
+ transport, err := minio.DefaultTransport(true /*secure*/)
if err != nil {
- // The error returned here is always nil, but we keep the check for future compatibility.
return nil, fmt.Errorf("failed to create default minio transport: %w", err)
}
- transport.TLSClientConfig = tlsConfig.Clone()
+ for _, opt := range transportOpts {
+ opt(transport)
+ }
opt.Transport = transport
}
diff --git a/pkg/minio/minio_test.go b/pkg/minio/minio_test.go
index a0b25b938..0c5ddd037 100644
--- a/pkg/minio/minio_test.go
+++ b/pkg/minio/minio_test.go
@@ -41,6 +41,7 @@ import (
"github.com/fluxcd/pkg/sourceignore"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
+ pkgtesting "github.com/fluxcd/source-controller/pkg/testing"
)
const (
@@ -162,7 +163,7 @@ func TestMain(m *testing.M) {
testMinioAddress = fmt.Sprintf("127.0.0.1:%v", resource.GetPort("9000/tcp"))
// Construct a Minio client using the address of the Minio server.
- testMinioClient, err = NewClient(bucketStub(bucket, testMinioAddress), secret.DeepCopy(), testTLSConfig)
+ testMinioClient, err = NewClient(bucketStub(bucket, testMinioAddress), secret.DeepCopy(), testTLSConfig, nil)
if err != nil {
log.Fatalf("cannot create Minio client: %s", err)
}
@@ -195,19 +196,19 @@ func TestMain(m *testing.M) {
}
func TestNewClient(t *testing.T) {
- minioClient, err := NewClient(bucketStub(bucket, testMinioAddress), secret.DeepCopy(), testTLSConfig)
+ minioClient, err := NewClient(bucketStub(bucket, testMinioAddress), secret.DeepCopy(), testTLSConfig, nil)
assert.NilError(t, err)
assert.Assert(t, minioClient != nil)
}
func TestNewClientEmptySecret(t *testing.T) {
- minioClient, err := NewClient(bucketStub(bucket, testMinioAddress), emptySecret.DeepCopy(), testTLSConfig)
+ minioClient, err := NewClient(bucketStub(bucket, testMinioAddress), emptySecret.DeepCopy(), testTLSConfig, nil)
assert.NilError(t, err)
assert.Assert(t, minioClient != nil)
}
func TestNewClientAwsProvider(t *testing.T) {
- minioClient, err := NewClient(bucketStub(bucketAwsProvider, testMinioAddress), nil, nil)
+ minioClient, err := NewClient(bucketStub(bucketAwsProvider, testMinioAddress), nil, nil, nil)
assert.NilError(t, err)
assert.Assert(t, minioClient != nil)
}
@@ -234,6 +235,19 @@ func TestFGetObject(t *testing.T) {
assert.NilError(t, err)
}
+func TestNewClientAndFGetObjectWithProxy(t *testing.T) {
+ proxyURL, closeProxy := pkgtesting.NewHTTPProxy(t, "")
+ defer closeProxy()
+ minioClient, err := NewClient(bucketStub(bucket, testMinioAddress), secret.DeepCopy(), testTLSConfig, proxyURL)
+ assert.NilError(t, err)
+ assert.Assert(t, minioClient != nil)
+ ctx := context.Background()
+ tempDir := t.TempDir()
+ path := filepath.Join(tempDir, sourceignore.IgnoreFile)
+ _, err = minioClient.FGetObject(ctx, bucketName, objectName, path)
+ assert.NilError(t, err)
+}
+
func TestFGetObjectNotExists(t *testing.T) {
ctx := context.Background()
tempDir := t.TempDir()
diff --git a/pkg/testing/http_proxy.go b/pkg/testing/http_proxy.go
new file mode 100644
index 000000000..10470615a
--- /dev/null
+++ b/pkg/testing/http_proxy.go
@@ -0,0 +1,40 @@
+package pkgtesting
+
+import (
+ "context"
+ "net"
+ "net/http"
+ "net/url"
+ "testing"
+
+ "github.com/elazarl/goproxy"
+ "gotest.tools/assert"
+)
+
+// NewHTTPProxy starts an HTTP proxy server in a random port and returns the
+// URL of the proxy server and a function to stop the server.
+// If httpsTargetAddr is not empty, the proxy server will handle CONNECT
+// HTTPS requests to that address.
+func NewHTTPProxy(t *testing.T, httpsTargetAddr string) (*url.URL, func()) {
+ listener, err := net.Listen("tcp", ":0")
+ assert.NilError(t, err, "could not start proxy server")
+
+ addr := listener.Addr().String()
+ proxy := goproxy.NewProxyHttpServer()
+ proxy.Verbose = true
+ if httpsTargetAddr != "" {
+ proxy.OnRequest().HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
+ return goproxy.OkConnect, httpsTargetAddr
+ })
+ }
+ server := &http.Server{
+ Addr: addr,
+ Handler: proxy,
+ }
+
+ go server.Serve(listener)
+ return &url.URL{Scheme: "http", Host: addr}, func() {
+ server.Shutdown(context.Background())
+ listener.Close()
+ }
+}
|