Add support for .spec.proxySecretRef for generic provider of Bucket API
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
This commit is contained in:
parent
59ad5a72ee
commit
4d9c87cf3f
|
@ -100,6 +100,13 @@ type BucketSpec struct {
|
||||||
// +optional
|
// +optional
|
||||||
CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`
|
CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`
|
||||||
|
|
||||||
|
// ProxySecretRef specifies the Secret containing the proxy configuration
|
||||||
|
// to use while communicating with the Bucket server.
|
||||||
|
//
|
||||||
|
// Only supported for the generic provider.
|
||||||
|
// +optional
|
||||||
|
ProxySecretRef *meta.LocalObjectReference `json:"proxySecretRef,omitempty"`
|
||||||
|
|
||||||
// Interval at which the Bucket Endpoint is checked for updates.
|
// Interval at which the Bucket Endpoint is checked for updates.
|
||||||
// This interval is approximate and may be subject to jitter to ensure
|
// This interval is approximate and may be subject to jitter to ensure
|
||||||
// efficient use of resources.
|
// efficient use of resources.
|
||||||
|
|
|
@ -128,6 +128,11 @@ func (in *BucketSpec) DeepCopyInto(out *BucketSpec) {
|
||||||
*out = new(meta.LocalObjectReference)
|
*out = new(meta.LocalObjectReference)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.ProxySecretRef != nil {
|
||||||
|
in, out := &in.ProxySecretRef, &out.ProxySecretRef
|
||||||
|
*out = new(meta.LocalObjectReference)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
out.Interval = in.Interval
|
out.Interval = in.Interval
|
||||||
if in.Timeout != nil {
|
if in.Timeout != nil {
|
||||||
in, out := &in.Timeout, &out.Timeout
|
in, out := &in.Timeout, &out.Timeout
|
||||||
|
|
|
@ -391,6 +391,20 @@ spec:
|
||||||
- gcp
|
- gcp
|
||||||
- azure
|
- azure
|
||||||
type: string
|
type: string
|
||||||
|
proxySecretRef:
|
||||||
|
description: |-
|
||||||
|
ProxySecretRef specifies the Secret containing the proxy configuration
|
||||||
|
to use while communicating with the Bucket server.
|
||||||
|
|
||||||
|
|
||||||
|
Only supported for the generic provider.
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
description: Name of the referent.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
type: object
|
||||||
region:
|
region:
|
||||||
description: Region of the Endpoint where the BucketName is located
|
description: Region of the Endpoint where the BucketName is located
|
||||||
in.
|
in.
|
||||||
|
|
|
@ -191,6 +191,22 @@ be of type <code>Opaque</code> or <code>kubernetes.io/tls</code>.</p>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
<code>proxySecretRef</code><br>
|
||||||
|
<em>
|
||||||
|
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
||||||
|
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
|
||||||
|
</a>
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>ProxySecretRef specifies the Secret containing the proxy configuration
|
||||||
|
to use while communicating with the Bucket server.</p>
|
||||||
|
<p>Only supported for the generic provider.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
<code>interval</code><br>
|
<code>interval</code><br>
|
||||||
<em>
|
<em>
|
||||||
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||||
|
@ -1541,6 +1557,22 @@ be of type <code>Opaque</code> or <code>kubernetes.io/tls</code>.</p>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
<code>proxySecretRef</code><br>
|
||||||
|
<em>
|
||||||
|
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
||||||
|
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
|
||||||
|
</a>
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>ProxySecretRef specifies the Secret containing the proxy configuration
|
||||||
|
to use while communicating with the Bucket server.</p>
|
||||||
|
<p>Only supported for the generic provider.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
<code>interval</code><br>
|
<code>interval</code><br>
|
||||||
<em>
|
<em>
|
||||||
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||||
|
|
|
@ -824,6 +824,41 @@ stringData:
|
||||||
ca.crt: <PEM-encoded cert>
|
ca.crt: <PEM-encoded cert>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
This API is only supported for the `generic` [provider](#provider).
|
||||||
|
|
||||||
|
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
|
### Insecure
|
||||||
|
|
||||||
`.spec.insecure` is an optional field to allow connecting to an insecure (HTTP)
|
`.spec.insecure` is an optional field to allow connecting to an insecure (HTTP)
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -19,6 +19,7 @@ require (
|
||||||
github.com/distribution/distribution/v3 v3.0.0-alpha.1
|
github.com/distribution/distribution/v3 v3.0.0-alpha.1
|
||||||
github.com/docker/cli v24.0.9+incompatible
|
github.com/docker/cli v24.0.9+incompatible
|
||||||
github.com/docker/go-units v0.5.0
|
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/cli-utils v0.36.0-flux.7
|
||||||
github.com/fluxcd/pkg/apis/event v0.9.0
|
github.com/fluxcd/pkg/apis/event v0.9.0
|
||||||
github.com/fluxcd/pkg/apis/meta v1.5.0
|
github.com/fluxcd/pkg/apis/meta v1.5.0
|
||||||
|
|
3
go.sum
3
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/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 h1:m62nsMU279qRD9PQSWD1l66kmkXzuYcnVJqL4XLeV2M=
|
||||||
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
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 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk=
|
||||||
github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||||
github.com/emicklei/proto v1.12.1 h1:6n/Z2pZAnBwuhU66Gs8160B8rrrYKo7h2F2sCOnNceE=
|
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.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
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 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
stdtls "crypto/tls"
|
stdtls "crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -468,7 +469,23 @@ func (r *BucketReconciler) reconcileSource(ctx context.Context, sp *patch.Serial
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
|
||||||
return sreconcile.ResultEmpty, e
|
return sreconcile.ResultEmpty, e
|
||||||
}
|
}
|
||||||
if provider, err = minio.NewClient(obj, secret, tlsConfig); err != nil {
|
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
|
||||||
|
}
|
||||||
|
var opts []minio.Option
|
||||||
|
if secret != nil {
|
||||||
|
opts = append(opts, minio.WithSecret(secret))
|
||||||
|
}
|
||||||
|
if tlsConfig != nil {
|
||||||
|
opts = append(opts, minio.WithTLSConfig(tlsConfig))
|
||||||
|
}
|
||||||
|
if proxyURL != nil {
|
||||||
|
opts = append(opts, minio.WithProxyURL(proxyURL))
|
||||||
|
}
|
||||||
|
if provider, err = minio.NewClient(obj, opts...); err != nil {
|
||||||
e := serror.NewGeneric(err, "ClientError")
|
e := serror.NewGeneric(err, "ClientError")
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
|
||||||
return sreconcile.ResultEmpty, e
|
return sreconcile.ResultEmpty, e
|
||||||
|
@ -703,6 +720,30 @@ func (r *BucketReconciler) getTLSConfig(ctx context.Context, obj *bucketv1.Bucke
|
||||||
return tlsConfig, nil
|
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.
|
// eventLogf records events, and logs at the same time.
|
||||||
//
|
//
|
||||||
// This log is different from the debug log in the EventRecorder, in the sense
|
// This log is different from the debug log in the EventRecorder, in the sense
|
||||||
|
|
|
@ -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"),
|
*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",
|
name: "Observes non-existing bucket name",
|
||||||
bucketName: "dummy",
|
bucketName: "dummy",
|
||||||
|
@ -1536,3 +1577,188 @@ func TestBucketReconciler_notify(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBucketReconciler_getProxyURL(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
bucket *bucketv1.Bucket
|
||||||
|
objects []client.Object
|
||||||
|
expectedURL string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty proxySecretRef",
|
||||||
|
bucket: &bucketv1.Bucket{
|
||||||
|
Spec: bucketv1.BucketSpec{
|
||||||
|
ProxySecretRef: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-existing proxySecretRef",
|
||||||
|
bucket: &bucketv1.Bucket{
|
||||||
|
Spec: bucketv1.BucketSpec{
|
||||||
|
ProxySecretRef: &meta.LocalObjectReference{
|
||||||
|
Name: "non-existing",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: "failed to get secret '/non-existing': secrets \"non-existing\" not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing address in proxySecretRef",
|
||||||
|
bucket: &bucketv1.Bucket{
|
||||||
|
Spec: bucketv1.BucketSpec{
|
||||||
|
ProxySecretRef: &meta.LocalObjectReference{
|
||||||
|
Name: "dummy",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
objects: []client.Object{
|
||||||
|
&corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "dummy",
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: "invalid proxy secret 'dummy/': key 'address' is missing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid address in proxySecretRef",
|
||||||
|
bucket: &bucketv1.Bucket{
|
||||||
|
Spec: bucketv1.BucketSpec{
|
||||||
|
ProxySecretRef: &meta.LocalObjectReference{
|
||||||
|
Name: "dummy",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
objects: []client.Object{
|
||||||
|
&corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "dummy",
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"address": {0x7f},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: "failed to parse proxy address '\x7f': parse \"\\x7f\": net/url: invalid control character in URL",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no user, no password",
|
||||||
|
bucket: &bucketv1.Bucket{
|
||||||
|
Spec: bucketv1.BucketSpec{
|
||||||
|
ProxySecretRef: &meta.LocalObjectReference{
|
||||||
|
Name: "dummy",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
objects: []client.Object{
|
||||||
|
&corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "dummy",
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"address": []byte("http://proxy.example.com"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedURL: "http://proxy.example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "user, no password",
|
||||||
|
bucket: &bucketv1.Bucket{
|
||||||
|
Spec: bucketv1.BucketSpec{
|
||||||
|
ProxySecretRef: &meta.LocalObjectReference{
|
||||||
|
Name: "dummy",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
objects: []client.Object{
|
||||||
|
&corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "dummy",
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"address": []byte("http://proxy.example.com"),
|
||||||
|
"username": []byte("user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedURL: "http://user:@proxy.example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no user, password",
|
||||||
|
bucket: &bucketv1.Bucket{
|
||||||
|
Spec: bucketv1.BucketSpec{
|
||||||
|
ProxySecretRef: &meta.LocalObjectReference{
|
||||||
|
Name: "dummy",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
objects: []client.Object{
|
||||||
|
&corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "dummy",
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"address": []byte("http://proxy.example.com"),
|
||||||
|
"password": []byte("password"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedURL: "http://:password@proxy.example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "user, password",
|
||||||
|
bucket: &bucketv1.Bucket{
|
||||||
|
Spec: bucketv1.BucketSpec{
|
||||||
|
ProxySecretRef: &meta.LocalObjectReference{
|
||||||
|
Name: "dummy",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
objects: []client.Object{
|
||||||
|
&corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "dummy",
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"address": []byte("http://proxy.example.com"),
|
||||||
|
"username": []byte("user"),
|
||||||
|
"password": []byte("password"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedURL: "http://user:password@proxy.example.com",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
g := NewWithT(t)
|
||||||
|
|
||||||
|
c := fakeclient.NewClientBuilder().
|
||||||
|
WithScheme(testEnv.Scheme()).
|
||||||
|
WithObjects(tt.objects...).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
r := &BucketReconciler{
|
||||||
|
Client: c,
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := r.getProxyURL(ctx, tt.bucket)
|
||||||
|
if tt.expectedErr == "" {
|
||||||
|
g.Expect(err).To(BeNil())
|
||||||
|
} else {
|
||||||
|
g.Expect(err.Error()).To(ContainSubstring(tt.expectedErr))
|
||||||
|
}
|
||||||
|
if tt.expectedURL == "" {
|
||||||
|
g.Expect(u).To(BeNil())
|
||||||
|
} else {
|
||||||
|
g.Expect(u.String()).To(Equal(tt.expectedURL))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
|
@ -36,9 +38,49 @@ type MinioClient struct {
|
||||||
*minio.Client
|
*minio.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// options holds the configuration for the Minio client.
|
||||||
|
type options struct {
|
||||||
|
secret *corev1.Secret
|
||||||
|
tlsConfig *tls.Config
|
||||||
|
proxyURL *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option is a function that configures the Minio client.
|
||||||
|
type Option func(*options)
|
||||||
|
|
||||||
|
// WithSecret sets the secret for the Minio client.
|
||||||
|
func WithSecret(secret *corev1.Secret) Option {
|
||||||
|
return func(o *options) {
|
||||||
|
o.secret = secret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTLSConfig sets the TLS configuration for the Minio client.
|
||||||
|
func WithTLSConfig(tlsConfig *tls.Config) Option {
|
||||||
|
return func(o *options) {
|
||||||
|
o.tlsConfig = tlsConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithProxyURL sets the proxy URL for the Minio client.
|
||||||
|
func WithProxyURL(proxyURL *url.URL) Option {
|
||||||
|
return func(o *options) {
|
||||||
|
o.proxyURL = proxyURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewClient creates a new Minio storage client.
|
// 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, opts ...Option) (*MinioClient, error) {
|
||||||
opt := minio.Options{
|
|
||||||
|
var o options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&o)
|
||||||
|
}
|
||||||
|
secret := o.secret
|
||||||
|
tlsConfig := o.tlsConfig
|
||||||
|
proxyURL := o.proxyURL
|
||||||
|
|
||||||
|
minioOpts := minio.Options{
|
||||||
Region: bucket.Spec.Region,
|
Region: bucket.Spec.Region,
|
||||||
Secure: !bucket.Spec.Insecure,
|
Secure: !bucket.Spec.Insecure,
|
||||||
// About BucketLookup, it should be noted that not all S3 providers support
|
// About BucketLookup, it should be noted that not all S3 providers support
|
||||||
|
@ -55,25 +97,38 @@ func NewClient(bucket *sourcev1.Bucket, secret *corev1.Secret, tlsConfig *tls.Co
|
||||||
secretKey = string(k)
|
secretKey = string(k)
|
||||||
}
|
}
|
||||||
if accessKey != "" && secretKey != "" {
|
if accessKey != "" && secretKey != "" {
|
||||||
opt.Creds = credentials.NewStaticV4(accessKey, secretKey, "")
|
minioOpts.Creds = credentials.NewStaticV4(accessKey, secretKey, "")
|
||||||
}
|
}
|
||||||
} else if bucket.Spec.Provider == sourcev1.AmazonBucketProvider {
|
} else if bucket.Spec.Provider == sourcev1.AmazonBucketProvider {
|
||||||
opt.Creds = credentials.NewIAM("")
|
minioOpts.Creds = credentials.NewIAM("")
|
||||||
}
|
}
|
||||||
|
|
||||||
if opt.Secure && tlsConfig != nil {
|
var transportOpts []func(*http.Transport)
|
||||||
// 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.
|
if minioOpts.Secure && tlsConfig != nil {
|
||||||
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(minioOpts.Secure)
|
||||||
if err != nil {
|
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)
|
return nil, fmt.Errorf("failed to create default minio transport: %w", err)
|
||||||
}
|
}
|
||||||
transport.TLSClientConfig = tlsConfig.Clone()
|
for _, opt := range transportOpts {
|
||||||
opt.Transport = transport
|
opt(transport)
|
||||||
|
}
|
||||||
|
minioOpts.Transport = transport
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := minio.New(bucket.Spec.Endpoint, &opt)
|
client, err := minio.New(bucket.Spec.Endpoint, &minioOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,12 +23,16 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/elazarl/goproxy"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
miniov7 "github.com/minio/minio-go/v7"
|
miniov7 "github.com/minio/minio-go/v7"
|
||||||
"github.com/ory/dockertest/v3"
|
"github.com/ory/dockertest/v3"
|
||||||
|
@ -162,7 +166,9 @@ func TestMain(m *testing.M) {
|
||||||
testMinioAddress = fmt.Sprintf("127.0.0.1:%v", resource.GetPort("9000/tcp"))
|
testMinioAddress = fmt.Sprintf("127.0.0.1:%v", resource.GetPort("9000/tcp"))
|
||||||
|
|
||||||
// Construct a Minio client using the address of the Minio server.
|
// 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),
|
||||||
|
WithSecret(secret.DeepCopy()),
|
||||||
|
WithTLSConfig(testTLSConfig))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("cannot create Minio client: %s", err)
|
log.Fatalf("cannot create Minio client: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -195,19 +201,23 @@ func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewClient(t *testing.T) {
|
func TestNewClient(t *testing.T) {
|
||||||
minioClient, err := NewClient(bucketStub(bucket, testMinioAddress), secret.DeepCopy(), testTLSConfig)
|
minioClient, err := NewClient(bucketStub(bucket, testMinioAddress),
|
||||||
|
WithSecret(secret.DeepCopy()),
|
||||||
|
WithTLSConfig(testTLSConfig))
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Assert(t, minioClient != nil)
|
assert.Assert(t, minioClient != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewClientEmptySecret(t *testing.T) {
|
func TestNewClientEmptySecret(t *testing.T) {
|
||||||
minioClient, err := NewClient(bucketStub(bucket, testMinioAddress), emptySecret.DeepCopy(), testTLSConfig)
|
minioClient, err := NewClient(bucketStub(bucket, testMinioAddress),
|
||||||
|
WithSecret(emptySecret.DeepCopy()),
|
||||||
|
WithTLSConfig(testTLSConfig))
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Assert(t, minioClient != nil)
|
assert.Assert(t, minioClient != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewClientAwsProvider(t *testing.T) {
|
func TestNewClientAwsProvider(t *testing.T) {
|
||||||
minioClient, err := NewClient(bucketStub(bucketAwsProvider, testMinioAddress), nil, nil)
|
minioClient, err := NewClient(bucketStub(bucketAwsProvider, testMinioAddress))
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Assert(t, minioClient != nil)
|
assert.Assert(t, minioClient != nil)
|
||||||
}
|
}
|
||||||
|
@ -234,6 +244,36 @@ func TestFGetObject(t *testing.T) {
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewClientAndFGetObjectWithProxy(t *testing.T) {
|
||||||
|
// start proxy
|
||||||
|
proxyListener, err := net.Listen("tcp", ":0")
|
||||||
|
assert.NilError(t, err, "could not start proxy server")
|
||||||
|
defer proxyListener.Close()
|
||||||
|
proxyAddr := proxyListener.Addr().String()
|
||||||
|
proxyHandler := goproxy.NewProxyHttpServer()
|
||||||
|
proxyHandler.Verbose = true
|
||||||
|
proxyServer := &http.Server{
|
||||||
|
Addr: proxyAddr,
|
||||||
|
Handler: proxyHandler,
|
||||||
|
}
|
||||||
|
go proxyServer.Serve(proxyListener)
|
||||||
|
defer proxyServer.Shutdown(context.Background())
|
||||||
|
proxyURL := &url.URL{Scheme: "http", Host: proxyAddr}
|
||||||
|
|
||||||
|
// run test
|
||||||
|
minioClient, err := NewClient(bucketStub(bucket, testMinioAddress),
|
||||||
|
WithSecret(secret.DeepCopy()),
|
||||||
|
WithTLSConfig(testTLSConfig),
|
||||||
|
WithProxyURL(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) {
|
func TestFGetObjectNotExists(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
tempDir := t.TempDir()
|
tempDir := t.TempDir()
|
||||||
|
|
Loading…
Reference in New Issue