@@ -21,13 +21,17 @@ import (
21
21
"errors"
22
22
"fmt"
23
23
"io"
24
+ "net/http"
25
+ "net/url"
24
26
"os"
25
27
"path/filepath"
26
28
27
29
gcpstorage "cloud.google.com/go/storage"
28
30
"github.com/go-logr/logr"
31
+ "golang.org/x/oauth2/google"
29
32
"google.golang.org/api/iterator"
30
33
"google.golang.org/api/option"
34
+ htransport "google.golang.org/api/transport/http"
31
35
corev1 "k8s.io/api/core/v1"
32
36
ctrl "sigs.k8s.io/controller-runtime"
33
37
)
@@ -48,24 +52,96 @@ type GCSClient struct {
48
52
* gcpstorage.Client
49
53
}
50
54
51
- // NewClient creates a new GCP storage client. The Client will automatically look for the Google Application
55
+ // Option is a functional option for configuring the GCS client.
56
+ type Option func (* options )
57
+
58
+ // WithSecret sets the secret to use for authenticating with GCP.
59
+ func WithSecret (secret * corev1.Secret ) Option {
60
+ return func (o * options ) {
61
+ o .secret = secret
62
+ }
63
+ }
64
+
65
+ // WithProxyURL sets the proxy URL to use for the GCS client.
66
+ func WithProxyURL (proxyURL * url.URL ) Option {
67
+ return func (o * options ) {
68
+ o .proxyURL = proxyURL
69
+ }
70
+ }
71
+
72
+ type options struct {
73
+ secret * corev1.Secret
74
+ proxyURL * url.URL
75
+
76
+ // newCustomHTTPClient should create a new HTTP client for interacting with the GCS API.
77
+ // This is a test-only option required for mocking the real logic, which requires either
78
+ // a valid Google Service Account Key or ADC. Both are not available in tests.
79
+ // The real logic is implemented in the newHTTPClient function, which is used when
80
+ // constructing the default options object.
81
+ newCustomHTTPClient func (context.Context , * options ) (* http.Client , error )
82
+ }
83
+
84
+ func newOptions () * options {
85
+ return & options {
86
+ newCustomHTTPClient : newHTTPClient ,
87
+ }
88
+ }
89
+
90
+ // NewClient creates a new GCP storage client. The Client will automatically look for the Google Application
52
91
// Credential environment variable or look for the Google Application Credential file.
53
- func NewClient (ctx context.Context , secret * corev1.Secret ) (* GCSClient , error ) {
54
- c := & GCSClient {}
55
- if secret != nil {
56
- client , err := gcpstorage .NewClient (ctx , option .WithCredentialsJSON (secret .Data ["serviceaccount" ]))
92
+ func NewClient (ctx context.Context , opts ... Option ) (* GCSClient , error ) {
93
+ o := newOptions ()
94
+ for _ , opt := range opts {
95
+ opt (o )
96
+ }
97
+
98
+ var clientOpts []option.ClientOption
99
+
100
+ switch {
101
+ case o .secret != nil && o .proxyURL == nil :
102
+ clientOpts = append (clientOpts , option .WithCredentialsJSON (o .secret .Data ["serviceaccount" ]))
103
+ case o .proxyURL != nil :
104
+ httpClient , err := o .newCustomHTTPClient (ctx , o )
57
105
if err != nil {
58
106
return nil , err
59
107
}
60
- c .Client = client
61
- } else {
62
- client , err := gcpstorage .NewClient (ctx )
108
+ clientOpts = append (clientOpts , option .WithHTTPClient (httpClient ))
109
+ }
110
+
111
+ client , err := gcpstorage .NewClient (ctx , clientOpts ... )
112
+ if err != nil {
113
+ return nil , err
114
+ }
115
+
116
+ return & GCSClient {client }, nil
117
+ }
118
+
119
+ // newHTTPClient creates a new HTTP client for interacting with Google Cloud APIs.
120
+ func newHTTPClient (ctx context.Context , o * options ) (* http.Client , error ) {
121
+ baseTransport := http .DefaultTransport .(* http.Transport ).Clone ()
122
+ if o .proxyURL != nil {
123
+ baseTransport .Proxy = http .ProxyURL (o .proxyURL )
124
+ }
125
+
126
+ var opts []option.ClientOption
127
+
128
+ if o .secret != nil {
129
+ // Here we can't use option.WithCredentialsJSON() because htransport.NewTransport()
130
+ // won't know what scopes to use and yield a 400 Bad Request error when retrieving
131
+ // the OAuth token. Instead we use google.CredentialsFromJSON(), which allows us to
132
+ // specify the GCS read-only scope.
133
+ creds , err := google .CredentialsFromJSON (ctx , o .secret .Data ["serviceaccount" ], gcpstorage .ScopeReadOnly )
63
134
if err != nil {
64
- return nil , err
135
+ return nil , fmt . Errorf ( "failed to create Google credentials from secret: %w" , err )
65
136
}
66
- c .Client = client
137
+ opts = append (opts , option .WithCredentials (creds ))
138
+ }
139
+
140
+ transport , err := htransport .NewTransport (ctx , baseTransport , opts ... )
141
+ if err != nil {
142
+ return nil , fmt .Errorf ("failed to create Google HTTP transport: %w" , err )
67
143
}
68
- return c , nil
144
+ return & http. Client { Transport : transport } , nil
69
145
}
70
146
71
147
// ValidateSecret validates the credential secret. The provided Secret may
0 commit comments