Skip to content

Commit d969e4c

Browse files
committed
initial implementation of CloudHSMAPI
1 parent 820d963 commit d969e4c

4 files changed

Lines changed: 409 additions & 2 deletions

File tree

cloudhsm.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// Copyright 2025- The sacloud/cloudhsm-api-go authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cloudhsm
16+
17+
import (
18+
"context"
19+
"net/http"
20+
21+
"github.com/go-faster/errors"
22+
ogen "github.com/ogen-go/ogen/validate"
23+
v1 "github.com/sacloud/cloudhsm-api-go/apis/v1"
24+
)
25+
26+
type CloudHSMAPI interface {
27+
List(ctx context.Context) ([]v1.CloudHSM, error)
28+
Create(ctx context.Context, request CloudHSMCreateParams) (*v1.CreateCloudHSM, error)
29+
Read(ctx context.Context, id string) (*v1.CloudHSM, error)
30+
Update(ctx context.Context, id string, params CloudHSMUpdateParams) (*v1.CloudHSM, error)
31+
Delete(ctx context.Context, id string) error
32+
}
33+
34+
var _ CloudHSMAPI = (*CloudHSMOp)(nil)
35+
36+
type CloudHSMOp struct {
37+
client *v1.Client
38+
}
39+
40+
func NewCloudHSMOp(client *v1.Client) CloudHSMAPI {
41+
return &CloudHSMOp{client: client}
42+
}
43+
44+
func (op *CloudHSMOp) List(ctx context.Context) ([]v1.CloudHSM, error) {
45+
resp, err := op.client.CloudhsmCloudhsmsList(ctx)
46+
if err != nil {
47+
return nil, NewAPIError("CloudHSM.List", 0, err)
48+
}
49+
return resp.CloudHSMs, nil
50+
}
51+
52+
type CloudHSMCreateParams struct {
53+
Name string
54+
Description *string
55+
Tags []string
56+
IPv4NetworkAddress string
57+
IPv4PrefixLength int
58+
}
59+
60+
func (op *CloudHSMOp) Create(ctx context.Context, p CloudHSMCreateParams) (*v1.CreateCloudHSM, error) {
61+
if p.Tags == nil {
62+
p.Tags = []string{}
63+
}
64+
resp, err := op.client.CloudhsmCloudhsmsCreate(
65+
ctx,
66+
&v1.WrappedCreateCloudHSM{
67+
CloudHSM: v1.CreateCloudHSM{
68+
Name: p.Name,
69+
Description: intoOpt[v1.OptString](p.Description),
70+
Tags: p.Tags,
71+
Availability: v1.AvailabilityEnumAvailable,
72+
ServiceClass: v1.ServiceClassEnumCloudCloudhsmPartition,
73+
IPv4NetworkAddress: p.IPv4NetworkAddress,
74+
IPv4PrefixLength: p.IPv4PrefixLength,
75+
},
76+
},
77+
)
78+
79+
if err == nil {
80+
ret := resp.GetCloudHSM()
81+
return &ret, nil
82+
} else if e, ok := errors.Into[*ogen.UnexpectedStatusCodeError](err); !ok {
83+
return nil, NewAPIError("CloudHSM.Create", 0, err)
84+
} else if e.StatusCode == http.StatusUnprocessableEntity {
85+
return nil, NewAPIError("CloudHSM.Create", e.StatusCode, errors.Wrap(err, "invalid parameter"))
86+
} else {
87+
return nil, NewAPIError("CloudHSM.Create", e.StatusCode, errors.Wrap(err, "internal server error"))
88+
}
89+
}
90+
91+
func (op *CloudHSMOp) Read(ctx context.Context, id string) (*v1.CloudHSM, error) {
92+
resp, err := op.client.CloudhsmCloudhsmsRetrieve(
93+
ctx,
94+
v1.CloudhsmCloudhsmsRetrieveParams{
95+
ResourceID: id,
96+
},
97+
)
98+
99+
if err == nil {
100+
ret := resp.GetCloudHSM()
101+
return &ret, nil
102+
} else if e, ok := errors.Into[*ogen.UnexpectedStatusCodeError](err); !ok {
103+
return nil, NewAPIError("CloudHSM.Read", 0, err)
104+
} else if e.StatusCode == http.StatusNotFound {
105+
return nil, NewAPIError("CloudHSM.Read", e.StatusCode, errors.Wrap(err, "not found"))
106+
} else {
107+
return nil, NewAPIError("CloudHSM.Read", e.StatusCode, errors.Wrap(err, "internal server error"))
108+
}
109+
}
110+
111+
type CloudHSMUpdateParams struct {
112+
Name string
113+
Description *string
114+
Tags []string
115+
IPv4NetworkAddress string
116+
IPv4PrefixLength int
117+
}
118+
119+
func (op *CloudHSMOp) Update(ctx context.Context, id string, p CloudHSMUpdateParams) (*v1.CloudHSM, error) {
120+
if p.Tags == nil {
121+
p.Tags = []string{}
122+
}
123+
124+
resp, err := op.client.CloudhsmCloudhsmsUpdate(
125+
ctx,
126+
&v1.WrappedCloudHSM{
127+
CloudHSM: v1.CloudHSM{
128+
ServiceClass: v1.ServiceClassEnumCloudCloudhsmPartition,
129+
Availability: v1.AvailabilityEnumAvailable,
130+
Name: p.Name,
131+
Description: intoOpt[v1.OptString](p.Description),
132+
Tags: p.Tags,
133+
IPv4NetworkAddress: p.IPv4NetworkAddress,
134+
IPv4PrefixLength: p.IPv4PrefixLength,
135+
},
136+
},
137+
v1.CloudhsmCloudhsmsUpdateParams{
138+
ResourceID: id,
139+
},
140+
)
141+
142+
if err == nil {
143+
ret := resp.GetCloudHSM()
144+
return &ret, nil
145+
} else if e, ok := errors.Into[*ogen.UnexpectedStatusCodeError](err); !ok {
146+
return nil, NewAPIError("CloudHSM.Update", 0, err)
147+
} else if e.StatusCode == http.StatusUnprocessableEntity {
148+
return nil, NewAPIError("CloudHSM.Update", e.StatusCode, errors.Wrap(err, "invalid parameter"))
149+
} else {
150+
return nil, NewAPIError("CloudHSM.Update", 0, err)
151+
}
152+
}
153+
154+
func (op *CloudHSMOp) Delete(ctx context.Context, id string) error {
155+
err := op.client.CloudhsmCloudhsmsDestroy(
156+
ctx,
157+
v1.CloudhsmCloudhsmsDestroyParams{
158+
ResourceID: id,
159+
},
160+
)
161+
162+
if err == nil {
163+
return nil
164+
} else if e, ok := errors.Into[*ogen.UnexpectedStatusCodeError](err); !ok {
165+
return NewAPIError("CloudHSM.Delete", 0, err)
166+
} else if e.StatusCode == http.StatusNotFound {
167+
return NewAPIError("CloudHSM.Delete", e.StatusCode, errors.Wrap(err, "not found"))
168+
} else {
169+
return NewAPIError("CloudHSM.Delete", e.StatusCode, errors.Wrap(err, "internal server error"))
170+
}
171+
}

cloudhsm_test.go

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
// Copyright 2025- The sacloud/cloudhsm-api-go Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cloudhsm_test
16+
17+
import (
18+
"context"
19+
"net/http"
20+
"testing"
21+
22+
client "github.com/sacloud/api-client-go"
23+
. "github.com/sacloud/cloudhsm-api-go"
24+
v1 "github.com/sacloud/cloudhsm-api-go/apis/v1"
25+
"github.com/sacloud/packages-go/testutil"
26+
"github.com/stretchr/testify/require"
27+
)
28+
29+
func newTestCloudHSMClient(resp interface{}, status ...int) *v1.Client {
30+
return newTestClient(resp, status...)
31+
}
32+
33+
func TestCloudHSMOp_List(t *testing.T) {
34+
assert := require.New(t)
35+
expected := v1.PaginatedCloudHSMList{
36+
Count: 1,
37+
From: v1.NewOptInt(0),
38+
Total: v1.NewOptInt(1),
39+
CloudHSMs: []v1.CloudHSM{TemplateCloudHSM},
40+
}
41+
client := newTestCloudHSMClient(expected)
42+
api := NewCloudHSMOp(client)
43+
ctx := context.Background()
44+
cloudhsms, err := api.List(ctx)
45+
46+
assert.NoError(err)
47+
assert.NotNil(cloudhsms)
48+
assert.Equal(1, len(cloudhsms))
49+
}
50+
51+
func TestCloudHSMOp_Read(t *testing.T) {
52+
assert := require.New(t)
53+
client := newTestCloudHSMClient(TemplateWrappedCloudHSM)
54+
api := NewCloudHSMOp(client)
55+
ctx := context.Background()
56+
57+
res, err := api.Read(ctx, "12345")
58+
assert.NoError(err)
59+
assert.NotNil(res)
60+
assert.Equal(TemplateWrappedCloudHSM.GetCloudHSM(), *res)
61+
}
62+
63+
func TestCloudHSMOp_Read_404(t *testing.T) {
64+
assert := require.New(t)
65+
expected := newErrorResponse("No CloudHSM matches the given query.")
66+
client := newTestCloudHSMClient(expected, http.StatusNotFound)
67+
api := NewCloudHSMOp(client)
68+
ctx := context.Background()
69+
70+
cloudhsm, err := api.Read(ctx, "99999")
71+
assert.Nil(cloudhsm)
72+
assert.Error(err)
73+
assert.ErrorContains(err, "Not Found")
74+
}
75+
76+
func TestCloudHSMOp_Create(t *testing.T) {
77+
assert := require.New(t)
78+
client := newTestCloudHSMClient(TemplateWrappedCreateCloudHSM, http.StatusCreated)
79+
api := NewCloudHSMOp(client)
80+
ctx := context.Background()
81+
82+
res, err := api.Create(ctx, CloudHSMCreateParams{
83+
Name: "Test HSM",
84+
Description: ref("This is a test HSM"),
85+
Tags: []string{
86+
"tag1",
87+
"tag2",
88+
},
89+
})
90+
assert.NoError(err)
91+
assert.NotNil(res)
92+
assert.Equal(TemplateWrappedCreateCloudHSM.GetCloudHSM(), *res)
93+
}
94+
95+
func TestCloudHSMOp_Create_422(t *testing.T) {
96+
assert := require.New(t)
97+
expected := newErrorResponse("Invalid request body.")
98+
client := newTestCloudHSMClient(expected, http.StatusUnprocessableEntity)
99+
api := NewCloudHSMOp(client)
100+
ctx := context.Background()
101+
102+
cloudhsm, err := api.Create(ctx, CloudHSMCreateParams{})
103+
assert.Nil(cloudhsm)
104+
assert.Error(err)
105+
assert.ErrorContains(err, "invalid")
106+
}
107+
108+
func TestCloudHSMOp_Update(t *testing.T) {
109+
assert := require.New(t)
110+
client := newTestCloudHSMClient(TemplateWrappedCloudHSM)
111+
api := NewCloudHSMOp(client)
112+
ctx := context.Background()
113+
114+
res, err := api.Update(ctx, "12345", CloudHSMUpdateParams{
115+
Description: ref("Updated Description"),
116+
Name: "Updated Name",
117+
Tags: []string{
118+
"tag1",
119+
"tag2",
120+
},
121+
})
122+
assert.NoError(err)
123+
assert.NotNil(res)
124+
assert.Equal(TemplateWrappedCloudHSM.GetCloudHSM(), *res)
125+
}
126+
127+
func TestCloudHSMOp_Update_400(t *testing.T) {
128+
assert := require.New(t)
129+
expected := newErrorResponse("Invalid update parameters.")
130+
client := newTestCloudHSMClient(expected, http.StatusUnprocessableEntity)
131+
api := NewCloudHSMOp(client)
132+
ctx := context.Background()
133+
134+
cloudhsm, err := api.Update(ctx, "0", CloudHSMUpdateParams{})
135+
assert.Nil(cloudhsm)
136+
assert.Error(err)
137+
assert.ErrorContains(err, "invalid")
138+
}
139+
140+
func TestCloudHSMOp_Delete(t *testing.T) {
141+
assert := require.New(t)
142+
client := newTestCloudHSMClient(nil, http.StatusNoContent)
143+
api := NewCloudHSMOp(client)
144+
ctx := context.Background()
145+
146+
err := api.Delete(ctx, "12345")
147+
assert.NoError(err)
148+
}
149+
150+
func TestCloudHSMOp_Delete_400(t *testing.T) {
151+
assert := require.New(t)
152+
expected := newErrorResponse("Not found")
153+
client := newTestCloudHSMClient(expected, http.StatusNotFound)
154+
api := NewCloudHSMOp(client)
155+
ctx := context.Background()
156+
157+
err := api.Delete(ctx, "0")
158+
assert.Error(err)
159+
assert.ErrorContains(err, "Not Found")
160+
}
161+
162+
func TestCloudHSMIntegrated(t *testing.T) {
163+
assert := require.New(t)
164+
client := newIntegratedClient(t, client.WithOptions(&client.Options{Trace: true}))
165+
api := NewCloudHSMOp(client)
166+
ctx := context.Background()
167+
168+
// Create
169+
created, err := api.Create(ctx, CloudHSMCreateParams{
170+
Name: testutil.RandomName("test-cloudhsm-", 16, testutil.CharSetAlphaNum),
171+
Description: ref(testutil.Random(128, testutil.CharSetAlphaNum)),
172+
IPv4NetworkAddress: "127.0.0.0",
173+
IPv4PrefixLength: 28,
174+
})
175+
assert.NoError(err)
176+
assert.NotNil(created)
177+
178+
// Delete
179+
t.Cleanup(func() {
180+
err := api.Delete(ctx, created.GetID())
181+
assert.NoError(err)
182+
})
183+
184+
// Read
185+
read, err := api.Read(ctx, created.GetID())
186+
assert.NoError(err)
187+
assert.NotNil(read)
188+
assert.Equal(created.GetID(), read.GetID())
189+
assert.Equal(created.GetName(), read.GetName())
190+
191+
// List
192+
cloudhsms, err := api.List(ctx)
193+
assert.NoError(err)
194+
assert.NotNil(cloudhsms)
195+
assert.NotEmpty(cloudhsms)
196+
197+
// Update
198+
newDesc := "updated integration test CloudHSM"
199+
updateReq := CloudHSMUpdateParams{
200+
Description: ref(newDesc),
201+
Name: read.GetName(),
202+
}
203+
updated, err := api.Update(ctx, created.GetID(), updateReq)
204+
assert.NoError(err)
205+
assert.NotNil(updated)
206+
assert.Equal(newDesc, updated.GetDescription())
207+
}

0 commit comments

Comments
 (0)