Skip to content

Commit 6ba42de

Browse files
committed
refactor(sdk): huaweicloud SDK De-Coupling
1 parent 3cc7495 commit 6ba42de

48 files changed

Lines changed: 4455 additions & 789 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

go.mod

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ require (
1010
github.com/Azure/go-autorest/autorest/azure/auth v0.5.13
1111
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
1212
github.com/aws/aws-sdk-go-v2 v1.41.6
13-
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.25.9+incompatible
14-
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.192
1513
github.com/jdcloud-api/jdcloud-sdk-go v1.64.0
1614
github.com/tencentyun/cos-go-sdk-v5 v0.7.73
1715
github.com/volcengine/volcengine-go-sdk v1.0.168
@@ -33,39 +31,28 @@ require (
3331
github.com/aws/smithy-go v1.25.0 // indirect
3432
github.com/clbanning/mxj v1.8.4 // indirect
3533
github.com/dimchansky/utfbom v1.1.1 // indirect
36-
github.com/fatih/color v1.10.0 // indirect
37-
github.com/goccy/go-yaml v1.9.8 // indirect
3834
github.com/gofrs/uuid v4.4.0+incompatible // indirect
3935
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
4036
github.com/golang/protobuf v1.5.3 // indirect
4137
github.com/google/go-querystring v1.0.0 // indirect
4238
github.com/google/uuid v1.3.0 // indirect
4339
github.com/jmespath/go-jmespath v0.4.0 // indirect
44-
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
4540
github.com/mattn/go-colorable v0.1.12 // indirect
4641
github.com/mattn/go-isatty v0.0.14 // indirect
4742
github.com/mattn/go-runewidth v0.0.9 // indirect
4843
github.com/mattn/go-tty v0.0.5 // indirect
4944
github.com/mitchellh/go-homedir v1.1.0 // indirect
5045
github.com/mitchellh/mapstructure v1.5.0 // indirect
51-
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
52-
github.com/modern-go/reflect2 v1.0.2 // indirect
5346
github.com/mozillazg/go-httpheader v0.2.1 // indirect
5447
github.com/olekukonko/tablewriter v0.0.5 // indirect
5548
github.com/pkg/term v1.2.0-beta.2 // indirect
56-
github.com/shopspring/decimal v1.4.0 // indirect
5749
github.com/stretchr/testify v1.8.3 // indirect
58-
github.com/tjfoc/gmsm v1.4.1 // indirect
5950
github.com/volcengine/volc-sdk-golang v1.0.23 // indirect
60-
go.mongodb.org/mongo-driver v1.13.1 // indirect
6151
golang.org/x/crypto v0.17.0 // indirect
6252
golang.org/x/net v0.15.0 // indirect
6353
golang.org/x/sys v0.15.0 // indirect
64-
golang.org/x/text v0.14.0 // indirect
6554
golang.org/x/time v0.1.0 // indirect
66-
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
6755
google.golang.org/appengine v1.6.7 // indirect
6856
google.golang.org/protobuf v1.31.0 // indirect
69-
gopkg.in/ini.v1 v1.67.0 // indirect
7057
gopkg.in/yaml.v2 v2.4.0 // indirect
7158
)

go.sum

Lines changed: 0 additions & 68 deletions
Large diffs are not rendered by default.

pkg/providers/huawei/api/client.go

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
package api
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"fmt"
8+
"io"
9+
"net/http"
10+
"net/url"
11+
"strings"
12+
"time"
13+
14+
"github.com/404tk/cloudtoolkit/pkg/providers/huawei/auth"
15+
"github.com/404tk/cloudtoolkit/pkg/providers/huawei/endpoint"
16+
)
17+
18+
type Request struct {
19+
Service string
20+
Region string
21+
Intl bool
22+
Method string
23+
Path string
24+
Query url.Values
25+
Body []byte
26+
Headers http.Header
27+
Idempotent bool
28+
}
29+
30+
type Option func(*Client)
31+
32+
type Client struct {
33+
credential auth.Credential
34+
httpClient *http.Client
35+
retryPolicy RetryPolicy
36+
now func() time.Time
37+
baseURL *url.URL
38+
}
39+
40+
func NewClient(cred auth.Credential, opts ...Option) *Client {
41+
client := &Client{
42+
credential: cred,
43+
httpClient: NewHTTPClient(),
44+
retryPolicy: DefaultRetryPolicy(),
45+
now: time.Now,
46+
}
47+
for _, opt := range opts {
48+
opt(client)
49+
}
50+
return client
51+
}
52+
53+
func WithHTTPClient(hc *http.Client) Option {
54+
return func(c *Client) {
55+
if hc != nil {
56+
c.httpClient = hc
57+
}
58+
}
59+
}
60+
61+
func WithRetryPolicy(p RetryPolicy) Option {
62+
return func(c *Client) {
63+
if p != nil {
64+
c.retryPolicy = p
65+
}
66+
}
67+
}
68+
69+
func WithClock(now func() time.Time) Option {
70+
return func(c *Client) {
71+
if now != nil {
72+
c.now = now
73+
}
74+
}
75+
}
76+
77+
func WithBaseURL(raw string) Option {
78+
return func(c *Client) {
79+
if raw == "" {
80+
return
81+
}
82+
if u, err := url.Parse(raw); err == nil {
83+
c.baseURL = u
84+
}
85+
}
86+
}
87+
88+
func (c *Client) DoJSON(ctx context.Context, req Request, out any) error {
89+
if ctx == nil {
90+
ctx = context.Background()
91+
}
92+
if err := c.credential.Validate(); err != nil {
93+
return err
94+
}
95+
96+
service := strings.TrimSpace(strings.ToLower(req.Service))
97+
if service == "" {
98+
return fmt.Errorf("huawei client: empty service")
99+
}
100+
method := strings.ToUpper(strings.TrimSpace(req.Method))
101+
if method == "" {
102+
method = http.MethodGet
103+
}
104+
scheme, host, path, err := c.resolveEndpoint(req, service)
105+
if err != nil {
106+
return err
107+
}
108+
109+
body := append([]byte(nil), req.Body...)
110+
headers := cloneHeader(req.Headers)
111+
if len(body) > 0 && strings.TrimSpace(headers.Get("Content-Type")) == "" {
112+
headers.Set("Content-Type", "application/json;charset=UTF-8")
113+
}
114+
timestamp := c.now().UTC()
115+
signed, err := Sign(&SignRequest{
116+
Method: method,
117+
Host: host,
118+
Path: path,
119+
Query: cloneValues(req.Query),
120+
Headers: flattenHeaders(headers),
121+
Body: body,
122+
AccessKey: c.credential.AK,
123+
SecretKey: c.credential.SK,
124+
Timestamp: timestamp,
125+
})
126+
if err != nil {
127+
return err
128+
}
129+
130+
finalHeaders := headers.Clone()
131+
for key, value := range signed {
132+
if value == "" {
133+
continue
134+
}
135+
finalHeaders.Set(key, value)
136+
}
137+
removeReservedHeader(finalHeaders, "Host")
138+
139+
requestURL := url.URL{
140+
Scheme: scheme,
141+
Host: host,
142+
Path: path,
143+
RawQuery: cloneValues(req.Query).Encode(),
144+
}
145+
146+
idempotent := req.Idempotent || method == http.MethodGet
147+
httpResp, err := c.retryPolicy.Do(ctx, idempotent, func() (*http.Response, error) {
148+
httpReq, err := http.NewRequestWithContext(ctx, method, requestURL.String(), bytes.NewReader(body))
149+
if err != nil {
150+
return nil, err
151+
}
152+
httpReq.Host = host
153+
httpReq.Header = finalHeaders.Clone()
154+
return c.httpClient.Do(httpReq)
155+
})
156+
if err != nil {
157+
return err
158+
}
159+
defer closeResponse(httpResp)
160+
161+
respBody, err := io.ReadAll(httpResp.Body)
162+
if err != nil {
163+
return fmt.Errorf("read huawei response: %w", err)
164+
}
165+
if err := withRequestID(DecodeError(httpResp.StatusCode, respBody), httpResp.Header.Get("X-Request-Id")); err != nil {
166+
return err
167+
}
168+
if out == nil || len(respBody) == 0 {
169+
return nil
170+
}
171+
if err := json.Unmarshal(respBody, out); err != nil {
172+
return fmt.Errorf("decode huawei response: %w", err)
173+
}
174+
return nil
175+
}
176+
177+
func (c *Client) resolveEndpoint(req Request, service string) (string, string, string, error) {
178+
base := endpoint.For(service, strings.TrimSpace(req.Region), req.Intl)
179+
if c.baseURL != nil {
180+
base = c.baseURL.String()
181+
}
182+
if strings.TrimSpace(base) == "" {
183+
return "", "", "", fmt.Errorf("huawei client: empty endpoint")
184+
}
185+
u, err := url.Parse(base)
186+
if err != nil {
187+
return "", "", "", fmt.Errorf("huawei client: invalid endpoint %q: %w", base, err)
188+
}
189+
if u.Scheme == "" || u.Host == "" {
190+
return "", "", "", fmt.Errorf("huawei client: invalid endpoint %q", base)
191+
}
192+
return u.Scheme, u.Host, joinPath(u.Path, ensureLeadingSlash(req.Path)), nil
193+
}
194+
195+
func flattenHeaders(headers http.Header) map[string]string {
196+
flattened := make(map[string]string, len(headers))
197+
for key, values := range headers {
198+
name := strings.TrimSpace(key)
199+
if name == "" || isReservedHeader(name) {
200+
continue
201+
}
202+
flattened[name] = strings.Join(values, ",")
203+
}
204+
return flattened
205+
}
206+
207+
func cloneHeader(headers http.Header) http.Header {
208+
if headers == nil {
209+
return http.Header{}
210+
}
211+
return headers.Clone()
212+
}
213+
214+
func cloneValues(values url.Values) url.Values {
215+
if values == nil {
216+
return url.Values{}
217+
}
218+
cloned := make(url.Values, len(values))
219+
for key, items := range values {
220+
cloned[key] = append([]string(nil), items...)
221+
}
222+
return cloned
223+
}
224+
225+
func joinPath(basePath, requestPath string) string {
226+
switch {
227+
case basePath == "", basePath == "/":
228+
return ensureLeadingSlash(requestPath)
229+
case requestPath == "", requestPath == "/":
230+
return ensureLeadingSlash(basePath)
231+
default:
232+
return strings.TrimRight(basePath, "/") + ensureLeadingSlash(requestPath)
233+
}
234+
}
235+
236+
func isReservedHeader(name string) bool {
237+
switch strings.ToLower(strings.TrimSpace(name)) {
238+
case strings.ToLower(HeaderAuthorization), strings.ToLower(HeaderXDate), strings.ToLower(HeaderContentSha256), "host":
239+
return true
240+
default:
241+
return false
242+
}
243+
}
244+
245+
func removeReservedHeader(headers http.Header, name string) {
246+
for key := range headers {
247+
if strings.EqualFold(key, name) {
248+
headers.Del(key)
249+
}
250+
}
251+
}

0 commit comments

Comments
 (0)