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