client_option.go raw
1 package scw
2
3 import (
4 "net/http"
5 "strings"
6
7 "github.com/scaleway/scaleway-sdk-go/errors"
8 "github.com/scaleway/scaleway-sdk-go/internal/auth"
9 "github.com/scaleway/scaleway-sdk-go/validation"
10 )
11
12 // ClientOption is a function which applies options to a settings object.
13 type ClientOption func(*settings)
14
15 // httpClient wraps the net/http Client Do method
16 type httpClient interface {
17 Do(*http.Request) (*http.Response, error)
18 }
19
20 // WithHTTPClient client option allows passing a custom http.Client which will be used for all requests.
21 func WithHTTPClient(httpClient httpClient) ClientOption {
22 return func(s *settings) {
23 s.httpClient = httpClient
24 }
25 }
26
27 // WithoutAuth client option sets the client token to an empty token.
28 func WithoutAuth() ClientOption {
29 return func(s *settings) {
30 s.token = auth.NewNoAuth()
31 }
32 }
33
34 // WithAuth client option sets the client access key and secret key.
35 func WithAuth(accessKey, secretKey string) ClientOption {
36 return func(s *settings) {
37 s.token = auth.NewToken(accessKey, secretKey)
38 }
39 }
40
41 // WithJWT client option sets the client session token.
42 func WithJWT(token string) ClientOption {
43 return func(s *settings) {
44 s.token = auth.NewJWT(token)
45 }
46 }
47
48 // WithAPIURL client option overrides the API URL of the Scaleway API to the given URL.
49 func WithAPIURL(apiURL string) ClientOption {
50 return func(s *settings) {
51 s.apiURL = apiURL
52 }
53 }
54
55 // WithInsecure client option enables insecure transport on the client.
56 func WithInsecure() ClientOption {
57 return func(s *settings) {
58 s.insecure = true
59 }
60 }
61
62 // WithUserAgent client option append a user agent to the default user agent of the SDK.
63 func WithUserAgent(ua string) ClientOption {
64 return func(s *settings) {
65 if s.userAgent != "" && ua != "" {
66 s.userAgent += " "
67 }
68 s.userAgent += ua
69 }
70 }
71
72 // withDefaultUserAgent client option overrides the default user agent of the SDK.
73 func withDefaultUserAgent(ua string) ClientOption {
74 return func(s *settings) {
75 s.userAgent = ua
76 }
77 }
78
79 // WithProfile client option configures a client from the given profile.
80 func WithProfile(p *Profile) ClientOption {
81 return func(s *settings) {
82 accessKey := ""
83 if p.AccessKey != nil {
84 accessKey = *p.AccessKey
85 s.token = auth.NewAccessKeyOnly(accessKey)
86 }
87
88 if p.SecretKey != nil {
89 s.token = auth.NewToken(accessKey, *p.SecretKey)
90 }
91
92 if p.APIURL != nil {
93 s.apiURL = *p.APIURL
94 }
95
96 if p.Insecure != nil {
97 s.insecure = *p.Insecure
98 }
99
100 if p.DefaultOrganizationID != nil {
101 organizationID := *p.DefaultOrganizationID
102 s.defaultOrganizationID = &organizationID
103 }
104
105 if p.DefaultProjectID != nil {
106 projectID := *p.DefaultProjectID
107 s.defaultProjectID = &projectID
108 }
109
110 if p.DefaultRegion != nil {
111 defaultRegion := Region(*p.DefaultRegion)
112 s.defaultRegion = &defaultRegion
113 }
114
115 if p.DefaultZone != nil {
116 defaultZone := Zone(*p.DefaultZone)
117 s.defaultZone = &defaultZone
118 }
119 }
120 }
121
122 // WithEnv client option configures a client from the environment variables.
123 func WithEnv() ClientOption {
124 return WithProfile(LoadEnvProfile())
125 }
126
127 // WithDefaultOrganizationID client option sets the client default organization ID.
128 //
129 // It will be used as the default value of the organization_id field in all requests made with this client.
130 func WithDefaultOrganizationID(organizationID string) ClientOption {
131 return func(s *settings) {
132 s.defaultOrganizationID = &organizationID
133 }
134 }
135
136 // WithDefaultProjectID client option sets the client default project ID.
137 //
138 // It will be used as the default value of the projectID field in all requests made with this client.
139 func WithDefaultProjectID(projectID string) ClientOption {
140 return func(s *settings) {
141 s.defaultProjectID = &projectID
142 }
143 }
144
145 // WithDefaultRegion client option sets the client default region.
146 //
147 // It will be used as the default value of the region field in all requests made with this client.
148 func WithDefaultRegion(region Region) ClientOption {
149 return func(s *settings) {
150 s.defaultRegion = ®ion
151 }
152 }
153
154 // WithDefaultZone client option sets the client default zone.
155 //
156 // It will be used as the default value of the zone field in all requests made with this client.
157 func WithDefaultZone(zone Zone) ClientOption {
158 return func(s *settings) {
159 s.defaultZone = &zone
160 }
161 }
162
163 // WithDefaultPageSize client option overrides the default page size of the SDK.
164 //
165 // It will be used as the default value of the page_size field in all requests made with this client.
166 func WithDefaultPageSize(pageSize uint32) ClientOption {
167 return func(s *settings) {
168 s.defaultPageSize = &pageSize
169 }
170 }
171
172 // settings hold the values of all client options
173 type settings struct {
174 apiURL string
175 token auth.Auth
176 userAgent string
177 httpClient httpClient
178 insecure bool
179 defaultOrganizationID *string
180 defaultProjectID *string
181 defaultRegion *Region
182 defaultZone *Zone
183 defaultPageSize *uint32
184 }
185
186 func newSettings() *settings {
187 return &settings{}
188 }
189
190 func (s *settings) apply(opts []ClientOption) {
191 for _, opt := range opts {
192 opt(s)
193 }
194 }
195
196 func (s *settings) validate() error {
197 // Auth.
198 if s.token == nil {
199 // It should not happen, WithoutAuth option is used by default.
200 panic(errors.New("no credential option provided"))
201 }
202 if token, isToken := s.token.(*auth.Token); isToken {
203 if token.AccessKey == "" {
204 return NewInvalidClientOptionError("access key cannot be empty")
205 }
206 if !validation.IsAccessKey(token.AccessKey) {
207 return NewInvalidClientOptionError("invalid access key format '%s', expected SCWXXXXXXXXXXXXXXXXX format", token.AccessKey)
208 }
209 if token.SecretKey == "" {
210 return NewInvalidClientOptionError("secret key cannot be empty")
211 }
212 if !validation.IsSecretKey(token.SecretKey) {
213 return NewInvalidClientOptionError("invalid secret key format '%s', expected a UUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", token.SecretKey)
214 }
215 }
216
217 // Default Organization ID.
218 if s.defaultOrganizationID != nil {
219 if *s.defaultOrganizationID == "" {
220 return NewInvalidClientOptionError("default organization ID cannot be empty")
221 }
222 if !validation.IsOrganizationID(*s.defaultOrganizationID) {
223 return NewInvalidClientOptionError("invalid organization ID format '%s', expected a UUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", *s.defaultOrganizationID)
224 }
225 }
226
227 // Default Project ID.
228 if s.defaultProjectID != nil {
229 if *s.defaultProjectID == "" {
230 return NewInvalidClientOptionError("default project ID cannot be empty")
231 }
232 if !validation.IsProjectID(*s.defaultProjectID) {
233 return NewInvalidClientOptionError("invalid project ID format '%s', expected a UUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", *s.defaultProjectID)
234 }
235 }
236
237 // Default Region.
238 if s.defaultRegion != nil {
239 if *s.defaultRegion == "" {
240 return NewInvalidClientOptionError("default region cannot be empty")
241 }
242 if !validation.IsRegion(string(*s.defaultRegion)) {
243 regions := []string(nil)
244 for _, r := range AllRegions {
245 regions = append(regions, string(r))
246 }
247 return NewInvalidClientOptionError("invalid default region format '%s', available regions are: %s", *s.defaultRegion, strings.Join(regions, ", "))
248 }
249 }
250
251 // Default Zone.
252 if s.defaultZone != nil {
253 if *s.defaultZone == "" {
254 return NewInvalidClientOptionError("default zone cannot be empty")
255 }
256 if !validation.IsZone(string(*s.defaultZone)) {
257 zones := []string(nil)
258 for _, z := range AllZones {
259 zones = append(zones, string(z))
260 }
261 return NewInvalidClientOptionError("invalid default zone format '%s', available zones are: %s", *s.defaultZone, strings.Join(zones, ", "))
262 }
263 }
264
265 // API URL.
266 if !validation.IsURL(s.apiURL) {
267 return NewInvalidClientOptionError("invalid API url '%s'", s.apiURL)
268 }
269 if s.apiURL[len(s.apiURL)-1:] == "/" {
270 return NewInvalidClientOptionError("invalid API url '%s' it should not have a trailing slash", s.apiURL)
271 }
272
273 // TODO: check for max s.defaultPageSize
274
275 return nil
276 }
277