functions.go raw
1 // Copyright 2022-2025 The sacloud/iaas-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 fake
16
17 import (
18 "encoding/json"
19 "fmt"
20 "math/rand"
21 "net/http"
22 "reflect"
23 "strings"
24 "time"
25
26 "github.com/sacloud/iaas-api-go"
27 "github.com/sacloud/iaas-api-go/accessor"
28 "github.com/sacloud/iaas-api-go/search"
29 "github.com/sacloud/iaas-api-go/types"
30 )
31
32 func init() {
33 rand.Seed(time.Now().UnixNano())
34 }
35
36 func random(max int) int {
37 return rand.Intn(max) //nolint:gosec
38 }
39
40 func newErrorNotFound(resourceKey string, id interface{}) error {
41 return iaas.NewAPIError("", nil, http.StatusNotFound, &iaas.APIErrorResponse{
42 IsFatal: true,
43 Serial: "",
44 Status: "404 NotFound",
45 ErrorCode: fmt.Sprintf("%d", http.StatusNotFound),
46 ErrorMessage: fmt.Sprintf("%s[ID:%s] is not found", resourceKey, id),
47 })
48 }
49
50 func newErrorBadRequest(resourceKey string, id interface{}, msgs ...string) error {
51 return iaas.NewAPIError("", nil, http.StatusBadRequest, &iaas.APIErrorResponse{
52 IsFatal: true,
53 Serial: "",
54 Status: "400 BadRequest",
55 ErrorCode: fmt.Sprintf("%d", http.StatusBadRequest),
56 ErrorMessage: fmt.Sprintf("request to %s[ID:%s] is bad: %s", resourceKey, id, strings.Join(msgs, " ")),
57 })
58 }
59
60 func newErrorConflict(resourceKey string, id interface{}, msgs ...string) error {
61 return iaas.NewAPIError("", nil, http.StatusConflict, &iaas.APIErrorResponse{
62 IsFatal: true,
63 Serial: "",
64 Status: "409 Conflict",
65 ErrorCode: fmt.Sprintf("%d", http.StatusConflict),
66 ErrorMessage: fmt.Sprintf("request to %s[ID:%s] is conflicted: %s", resourceKey, id, strings.Join(msgs, " ")),
67 })
68 }
69
70 func newInternalServerError(resourceKey string, id interface{}, msgs ...string) error {
71 return iaas.NewAPIError("", nil, http.StatusInternalServerError, &iaas.APIErrorResponse{
72 IsFatal: true,
73 Serial: "",
74 Status: "500 Internal Server Error",
75 ErrorCode: fmt.Sprintf("%d", http.StatusInternalServerError),
76 ErrorMessage: fmt.Sprintf("request to %s[ID:%s] is failed: %s", resourceKey, id, strings.Join(msgs, " ")),
77 })
78 }
79
80 func find(resourceKey, zone string, conditions *iaas.FindCondition) ([]interface{}, error) {
81 var results []interface{}
82 if conditions == nil {
83 conditions = &iaas.FindCondition{}
84 }
85
86 targets := ds().List(resourceKey, zone)
87
88 FILTER_APPLY_LOOP:
89 for i, target := range targets {
90 // count
91 if conditions.Count != 0 && len(results) >= conditions.Count {
92 break
93 }
94
95 // from
96 if i < conditions.From {
97 continue
98 }
99
100 // filter
101 for key, expression := range conditions.Filter {
102 // TODO OpGreater/OpLess is not implemented
103 if key.Op == search.OpEqual {
104 fieldName := key.String()
105
106 // only ID/Name/Tags
107 switch fieldName {
108 case "ID", "Name", "Tags.Name", "Scope", "Class":
109 exp, ok := expression.(*search.EqualExpression)
110 if !ok {
111 exp = search.OrEqual(expression)
112 }
113
114 if fieldName == "Tags.Name" {
115 var tags types.Tags
116 if v, ok := target.(accessor.Tags); ok {
117 tags = v.GetTags()
118 }
119
120 for _, cond := range exp.Conditions {
121 condTags, ok := cond.([]string)
122 if ok {
123 for _, v := range condTags {
124 exists := false
125 for _, tag := range tags {
126 if tag == v {
127 exists = true
128 }
129 }
130 if !exists {
131 continue FILTER_APPLY_LOOP
132 }
133 }
134 }
135 }
136 } else {
137 var value interface{}
138 switch fieldName {
139 case "ID":
140 if v, ok := target.(accessor.ID); ok {
141 value = v.GetID()
142 }
143 case "Name":
144 if v, ok := target.(accessor.Name); ok {
145 value = v.GetName()
146 }
147 case "Scope":
148 if v, ok := target.(accessor.Scope); ok {
149 value = v.GetScope().String()
150 }
151 case "Class":
152 if v, ok := target.(accessor.Class); ok {
153 value = v.GetClass()
154 }
155 }
156
157 switch exp.Op {
158 case search.OpAnd:
159 for _, v := range exp.Conditions {
160 v1, ok1 := v.(string)
161 v2, ok2 := value.(string)
162 if !ok1 || !ok2 || !strings.Contains(v2, v1) {
163 continue FILTER_APPLY_LOOP
164 }
165 }
166 case search.OpOr:
167 match := false
168 for _, v := range exp.Conditions {
169 if reflect.DeepEqual(value, v) {
170 match = true
171 }
172 }
173 if !match {
174 continue FILTER_APPLY_LOOP
175 }
176 }
177 }
178 }
179 }
180 }
181
182 results = append(results, target)
183 }
184
185 // TODO sort/filter/include/exclude is not implemented
186 return results, nil
187 }
188
189 func copySameNameField(source interface{}, dest interface{}) {
190 data, _ := json.Marshal(source)
191 json.Unmarshal(data, dest) //nolint
192 }
193
194 func fill(target interface{}, fillFuncs ...func(interface{})) {
195 for _, f := range fillFuncs {
196 f(target)
197 }
198 }
199
200 func fillID(target interface{}) {
201 if v, ok := target.(accessor.ID); ok {
202 id := v.GetID()
203 if id.IsEmpty() {
204 v.SetID(pool().generateID())
205 }
206 }
207 }
208
209 func fillAvailability(target interface{}) {
210 if v, ok := target.(accessor.Availability); ok {
211 value := v.GetAvailability()
212 if value == types.Availabilities.Unknown {
213 v.SetAvailability(types.Availabilities.Available)
214 }
215 }
216 }
217
218 func fillScope(target interface{}) {
219 if v, ok := target.(accessor.Scope); ok {
220 value := v.GetScope()
221 if value == types.EScope("") {
222 v.SetScope(types.Scopes.User)
223 }
224 }
225 }
226
227 func fillDiskPlan(target interface{}) {
228 if v, ok := target.(accessor.DiskPlan); ok {
229 id := v.GetDiskPlanID()
230 switch id {
231 case types.DiskPlans.HDD:
232 v.SetDiskPlanName("標準プラン")
233 case types.DiskPlans.SSD:
234 v.SetDiskPlanName("SSDプラン")
235 }
236 v.SetDiskPlanStorageClass("iscsi9999")
237 }
238 }
239
240 func fillCreatedAt(target interface{}) {
241 if v, ok := target.(accessor.CreatedAt); ok {
242 value := v.GetCreatedAt()
243 if value.IsZero() {
244 v.SetCreatedAt(time.Now())
245 }
246 }
247 }
248
249 func fillModifiedAt(target interface{}) {
250 if v, ok := target.(accessor.ModifiedAt); ok {
251 value := v.GetModifiedAt()
252 if value.IsZero() {
253 v.SetModifiedAt(time.Now())
254 }
255 }
256 }
257