Skip to content

Commit a2d6f02

Browse files
committed
fix(providers): normalize JDCloud encoding, surface Volcengine errors
1 parent 3f7836c commit a2d6f02

7 files changed

Lines changed: 112 additions & 9 deletions

File tree

pkg/providers/jdcloud/api/client_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"net/http"
77
"net/http/httptest"
8+
"net/url"
89
"regexp"
910
"strings"
1011
"testing"
@@ -113,6 +114,36 @@ func TestClientDoJSONReturnsAPIErrorFromBody(t *testing.T) {
113114
}
114115
}
115116

117+
func TestClientDoJSONUsesPercent20InQuery(t *testing.T) {
118+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
119+
if got := r.URL.RawQuery; got != "name=foo%20bar" {
120+
t.Fatalf("unexpected raw query: %s", got)
121+
}
122+
_, _ = w.Write([]byte(`{"requestId":"req-query","result":{"subUsers":[],"total":0}}`))
123+
}))
124+
defer server.Close()
125+
126+
client := newTestClient(server.URL, "", func() string {
127+
return "ebf8b26d-c3be-402f-9f10-f8b6573fd823"
128+
})
129+
130+
query := url.Values{}
131+
query.Set("name", "foo bar")
132+
133+
var resp DescribeSubUsersResponse
134+
err := client.DoJSON(context.Background(), Request{
135+
Service: "iam",
136+
Region: "",
137+
Method: http.MethodGet,
138+
Version: "v1",
139+
Path: "/subUsers",
140+
Query: query,
141+
}, &resp)
142+
if err != nil {
143+
t.Fatalf("DoJSON() error = %v", err)
144+
}
145+
}
146+
116147
func TestClientDoJSONHonorsCanceledContext(t *testing.T) {
117148
client := newTestClient("http://127.0.0.1:1", "", func() string {
118149
return "ebf8b26d-c3be-402f-9f10-f8b6573fd823"

pkg/providers/jdcloud/api/signer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ func canonicalQuery(values url.Values) string {
181181
if len(values) == 0 {
182182
return ""
183183
}
184-
return values.Encode()
184+
return strings.ReplaceAll(values.Encode(), "+", "%20")
185185
}
186186

187187
func canonicalURI(path string) string {

pkg/providers/jdcloud/iam/users.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ func (d *Driver) ListUsers(ctx context.Context) ([]schema.User, error) {
5151
var resp api.DescribeSubUsersResponse
5252
err := d.Client.DoJSON(ctx, api.Request{
5353
Service: "iam",
54+
// IAM is global. An empty region makes the signer fall back to the
55+
// jdcloud-api scope expected by this endpoint.
5456
Region: "",
5557
Method: "GET",
5658
Version: "v1",
@@ -106,6 +108,8 @@ func (d *Driver) Validator(_ string) bool {
106108
var resp api.DescribeSubUsersResponse
107109
err := d.Client.DoJSON(context.Background(), api.Request{
108110
Service: "iam",
111+
// IAM is global. An empty region makes the signer fall back to the
112+
// jdcloud-api scope expected by this endpoint.
109113
Region: "",
110114
Method: "GET",
111115
Version: "v1",

pkg/providers/jdcloud/vm/instances.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ func (d *Driver) GetResource(ctx context.Context) ([]schema.Host, error) {
3434
return list, errors.New("jdcloud vm: nil api client")
3535
}
3636

37+
if d.Region == "all" {
38+
logger.Warning("JDCloud region=all falls back to cn-north-1")
39+
}
3740
region := d.requestRegion()
3841

3942
got, err := paginate.Fetch[schema.Host, pageCursor](ctx, func(ctx context.Context, cursor pageCursor) (paginate.Page[schema.Host, pageCursor], error) {

pkg/providers/volcengine/ecs/instances.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package ecs
33
import (
44
"context"
55
"errors"
6+
"fmt"
7+
"sort"
68
"strings"
79

810
"github.com/404tk/cloudtoolkit/pkg/providers/volcengine/api"
@@ -35,7 +37,7 @@ func (d *Driver) GetResource(ctx context.Context) ([]schema.Host, error) {
3537
}
3638
tracker := processbar.NewRegionTracker()
3739
defer tracker.Finish()
38-
got, _ := regionrun.ForEach(ctx, regions, 0, tracker, func(ctx context.Context, r string) ([]schema.Host, error) {
40+
got, regionErrs := regionrun.ForEach(ctx, regions, 0, tracker, func(ctx context.Context, r string) ([]schema.Host, error) {
3941
return paginate.Fetch[schema.Host, string](ctx, func(ctx context.Context, token string) (paginate.Page[schema.Host, string], error) {
4042
resp, err := client.DescribeInstances(ctx, r, 100, token)
4143
if err != nil {
@@ -68,7 +70,7 @@ func (d *Driver) GetResource(ctx context.Context) ([]schema.Host, error) {
6870
})
6971
})
7072
list = append(list, got...)
71-
return list, nil
73+
return list, flattenRegionErrors(regionErrs)
7274
}
7375

7476
func (d *Driver) requireClient() (*api.Client, error) {
@@ -102,3 +104,24 @@ func (d *Driver) requestRegion() string {
102104
}
103105
return region
104106
}
107+
108+
func flattenRegionErrors(errs map[string]error) error {
109+
if len(errs) == 0 {
110+
return nil
111+
}
112+
regions := make([]string, 0, len(errs))
113+
for region := range errs {
114+
regions = append(regions, region)
115+
}
116+
sort.Strings(regions)
117+
parts := make([]string, 0, len(regions))
118+
for _, region := range regions {
119+
if err := errs[region]; err != nil {
120+
parts = append(parts, fmt.Sprintf("%s: %v", region, err))
121+
}
122+
}
123+
if len(parts) == 0 {
124+
return nil
125+
}
126+
return fmt.Errorf("partial region errors: %s", strings.Join(parts, "; "))
127+
}

pkg/providers/volcengine/ecs/instances_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,54 @@ func TestDriverGetResourceUsesDefaultRegionWhenEmpty(t *testing.T) {
9999
}
100100
}
101101

102+
func TestDriverGetResourceReturnsPartialRegionErrors(t *testing.T) {
103+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
104+
values, err := url.ParseQuery(r.URL.RawQuery)
105+
if err != nil {
106+
t.Fatalf("parse query: %v", err)
107+
}
108+
switch values.Get("Action") {
109+
case "DescribeRegions":
110+
_, _ = w.Write(mustJSON(t, api.DescribeRegionsResponse{
111+
Result: struct {
112+
NextToken string `json:"NextToken"`
113+
Regions []api.ECSRegion `json:"Regions"`
114+
}{
115+
Regions: []api.ECSRegion{{RegionID: "cn-beijing"}, {RegionID: "cn-shanghai"}},
116+
},
117+
}))
118+
case "DescribeInstances":
119+
switch region := scopeRegion(t, r.Header.Get(api.HeaderAuthorization)); region {
120+
case "cn-beijing":
121+
_, _ = w.Write(mustJSON(t, describeInstancesWithIDs([]string{"bj-001"}, "")))
122+
case "cn-shanghai":
123+
w.WriteHeader(http.StatusForbidden)
124+
_, _ = w.Write([]byte(`{"ResponseMetadata":{"RequestId":"req-sh","Error":{"Code":"Forbidden","Message":"denied"}}}`))
125+
default:
126+
t.Fatalf("unexpected region: %s", region)
127+
}
128+
default:
129+
t.Fatalf("unexpected action: %s", values.Get("Action"))
130+
}
131+
}))
132+
defer server.Close()
133+
134+
driver := &Driver{
135+
Client: newTestClient(server.URL),
136+
Region: "all",
137+
}
138+
got, err := driver.GetResource(context.Background())
139+
if len(got) != 1 || got[0].ID != "bj-001" {
140+
t.Fatalf("unexpected hosts: %+v", got)
141+
}
142+
if err == nil {
143+
t.Fatal("expected partial region error")
144+
}
145+
if !strings.Contains(err.Error(), "cn-shanghai") || !strings.Contains(err.Error(), "Forbidden") {
146+
t.Fatalf("unexpected error: %v", err)
147+
}
148+
}
149+
102150
func newTestClient(baseURL string) *api.Client {
103151
return api.NewClient(
104152
auth.New("AKID", "SECRET", ""),

pkg/providers/volcengine/iam/users.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77

88
"github.com/404tk/cloudtoolkit/pkg/providers/volcengine/api"
99
"github.com/404tk/cloudtoolkit/pkg/schema"
10-
"github.com/404tk/cloudtoolkit/utils"
1110
"github.com/404tk/cloudtoolkit/utils/logger"
1211
)
1312

@@ -30,7 +29,6 @@ func (d *Driver) ListUsers(ctx context.Context) ([]schema.User, error) {
3029
}
3130
region := d.requestRegion()
3231
var offset int32 = 0
33-
//policy_infos = make(map[string]string)
3432
for {
3533
resp, err := client.ListUsers(ctx, region, 100, offset)
3634
if err != nil {
@@ -57,10 +55,6 @@ func (d *Driver) ListUsers(ctx context.Context) ([]schema.User, error) {
5755

5856
}
5957

60-
if utils.ListPolicies {
61-
// _user.Policies = listPoliciesForUser(client, _user.UserName)
62-
}
63-
6458
list = append(list, _user)
6559
select {
6660
case <-ctx.Done():

0 commit comments

Comments
 (0)