1package instance 2 3import ( 4 "encoding/json" 5 "fmt" 6 "sync" 7 8 "github.com/scaleway/scaleway-sdk-go/internal/errors" 9 "github.com/scaleway/scaleway-sdk-go/scw" 10) 11 12var ( 13 resourceLock sync.Map 14) 15 16// lockResource locks a resource from a specific resourceID 17func lockResource(resourceID string) *sync.Mutex { 18 v, _ := resourceLock.LoadOrStore(resourceID, &sync.Mutex{}) 19 mutex := v.(*sync.Mutex) 20 mutex.Lock() 21 return mutex 22} 23 24// lockServer locks a server from its zone and its ID 25func lockServer(zone scw.Zone, serverID string) *sync.Mutex { 26 return lockResource(fmt.Sprint("server", zone, serverID)) 27} 28 29// AttachIPRequest contains the parameters to attach an IP to a server 30// 31// Deprecated: UpdateIPRequest should be used instead 32type AttachIPRequest struct { 33 Zone scw.Zone `json:"-"` 34 IP string `json:"-"` 35 ServerID string `json:"server_id"` 36} 37 38// AttachIPResponse contains the updated IP after attaching 39// 40// Deprecated: UpdateIPResponse should be used instead 41type AttachIPResponse struct { 42 IP *IP 43} 44 45// AttachIP attaches an IP to a server. 46// 47// Deprecated: UpdateIP() should be used instead 48func (s *API) AttachIP(req *AttachIPRequest, opts ...scw.RequestOption) (*AttachIPResponse, error) { 49 ipResponse, err := s.UpdateIP(&UpdateIPRequest{ 50 Zone: req.Zone, 51 IP: req.IP, 52 Server: &NullableStringValue{Value: req.ServerID}, 53 }) 54 if err != nil { 55 return nil, err 56 } 57 58 return &AttachIPResponse{IP: ipResponse.IP}, nil 59} 60 61// DetachIPRequest contains the parameters to detach an IP from a server 62// 63// Deprecated: UpdateIPRequest should be used instead 64type DetachIPRequest struct { 65 Zone scw.Zone `json:"-"` 66 IP string `json:"-"` 67} 68 69// DetachIPResponse contains the updated IP after detaching 70// 71// Deprecated: UpdateIPResponse should be used instead 72type DetachIPResponse struct { 73 IP *IP 74} 75 76// DetachIP detaches an IP from a server. 77// 78// Deprecated: UpdateIP() should be used instead 79func (s *API) DetachIP(req *DetachIPRequest, opts ...scw.RequestOption) (*DetachIPResponse, error) { 80 ipResponse, err := s.UpdateIP(&UpdateIPRequest{ 81 Zone: req.Zone, 82 IP: req.IP, 83 Server: &NullableStringValue{Null: true}, 84 }) 85 if err != nil { 86 return nil, err 87 } 88 89 return &DetachIPResponse{IP: ipResponse.IP}, nil 90} 91 92// AttachVolumeRequest contains the parameters to attach a volume to a server 93type AttachVolumeRequest struct { 94 Zone scw.Zone `json:"-"` 95 ServerID string `json:"-"` 96 VolumeID string `json:"-"` 97} 98 99// AttachVolumeResponse contains the updated server after attaching a volume 100type AttachVolumeResponse struct { 101 Server *Server `json:"-"` 102} 103 104// volumesToVolumeTemplates converts a map of *Volume to a map of *VolumeTemplate 105// so it can be used in a UpdateServer request 106func volumesToVolumeTemplates(volumes map[string]*Volume) map[string]*VolumeTemplate { 107 volumeTemplates := map[string]*VolumeTemplate{} 108 for key, volume := range volumes { 109 volumeTemplates[key] = &VolumeTemplate{ID: volume.ID, Name: volume.Name} 110 } 111 return volumeTemplates 112} 113 114// AttachVolume attaches a volume to a server 115// 116// Note: Implementation is thread-safe. 117func (s *API) AttachVolume(req *AttachVolumeRequest, opts ...scw.RequestOption) (*AttachVolumeResponse, error) { 118 defer lockServer(req.Zone, req.ServerID).Unlock() 119 // get server with volumes 120 getServerResponse, err := s.GetServer(&GetServerRequest{ 121 Zone: req.Zone, 122 ServerID: req.ServerID, 123 }) 124 if err != nil { 125 return nil, err 126 } 127 volumes := getServerResponse.Server.Volumes 128 129 newVolumes := volumesToVolumeTemplates(volumes) 130 131 // add volume to volumes list 132 // We loop through all the possible volume keys (0 to len(volumes)) 133 // to find a non existing key and assign it to the requested volume. 134 // A key should always be found. However we return an error if no keys were found. 135 found := false 136 for i := 0; i <= len(volumes); i++ { 137 key := fmt.Sprintf("%d", i) 138 if _, ok := newVolumes[key]; !ok { 139 newVolumes[key] = &VolumeTemplate{ 140 ID: req.VolumeID, 141 // name is ignored on this PATCH 142 Name: req.VolumeID, 143 } 144 found = true 145 break 146 } 147 } 148 149 if !found { 150 return nil, fmt.Errorf("could not find key to attach volume %s", req.VolumeID) 151 } 152 153 // update server 154 updateServerResponse, err := s.updateServer(&UpdateServerRequest{ 155 Zone: req.Zone, 156 ServerID: req.ServerID, 157 Volumes: &newVolumes, 158 }) 159 if err != nil { 160 return nil, err 161 } 162 163 return &AttachVolumeResponse{Server: updateServerResponse.Server}, nil 164} 165 166// DetachVolumeRequest contains the parameters to detach a volume from a server 167type DetachVolumeRequest struct { 168 Zone scw.Zone `json:"-"` 169 VolumeID string `json:"-"` 170} 171 172// DetachVolumeResponse contains the updated server after detaching a volume 173type DetachVolumeResponse struct { 174 Server *Server `json:"-"` 175} 176 177// DetachVolume detaches a volume from a server 178// 179// Note: Implementation is thread-safe. 180func (s *API) DetachVolume(req *DetachVolumeRequest, opts ...scw.RequestOption) (*DetachVolumeResponse, error) { 181 // get volume 182 getVolumeResponse, err := s.GetVolume(&GetVolumeRequest{ 183 Zone: req.Zone, 184 VolumeID: req.VolumeID, 185 }) 186 if err != nil { 187 return nil, err 188 } 189 if getVolumeResponse.Volume == nil { 190 return nil, errors.New("expected volume to have value in response") 191 } 192 if getVolumeResponse.Volume.Server == nil { 193 return nil, errors.New("volume should be attached to a server") 194 } 195 serverID := getVolumeResponse.Volume.Server.ID 196 197 defer lockServer(req.Zone, serverID).Unlock() 198 // get server with volumes 199 getServerResponse, err := s.GetServer(&GetServerRequest{ 200 Zone: req.Zone, 201 ServerID: serverID, 202 }) 203 if err != nil { 204 return nil, err 205 } 206 volumes := getServerResponse.Server.Volumes 207 // remove volume from volumes list 208 for key, volume := range volumes { 209 if volume.ID == req.VolumeID { 210 delete(volumes, key) 211 } 212 } 213 214 newVolumes := volumesToVolumeTemplates(volumes) 215 216 // update server 217 updateServerResponse, err := s.updateServer(&UpdateServerRequest{ 218 Zone: req.Zone, 219 ServerID: serverID, 220 Volumes: &newVolumes, 221 }) 222 if err != nil { 223 return nil, err 224 } 225 226 return &DetachVolumeResponse{Server: updateServerResponse.Server}, nil 227} 228 229// UnsafeSetTotalCount should not be used 230// Internal usage only 231func (r *ListServersResponse) UnsafeSetTotalCount(totalCount int) { 232 r.TotalCount = uint32(totalCount) 233} 234 235// UnsafeSetTotalCount should not be used 236// Internal usage only 237func (r *ListBootscriptsResponse) UnsafeSetTotalCount(totalCount int) { 238 r.TotalCount = uint32(totalCount) 239} 240 241// UnsafeSetTotalCount should not be used 242// Internal usage only 243func (r *ListIPsResponse) UnsafeSetTotalCount(totalCount int) { 244 r.TotalCount = uint32(totalCount) 245} 246 247// UnsafeSetTotalCount should not be used 248// Internal usage only 249func (r *ListSecurityGroupRulesResponse) UnsafeSetTotalCount(totalCount int) { 250 r.TotalCount = uint32(totalCount) 251} 252 253// UnsafeSetTotalCount should not be used 254// Internal usage only 255func (r *ListSecurityGroupsResponse) UnsafeSetTotalCount(totalCount int) { 256 r.TotalCount = uint32(totalCount) 257} 258 259// UnsafeSetTotalCount should not be used 260// Internal usage only 261func (r *ListServersTypesResponse) UnsafeSetTotalCount(totalCount int) { 262 r.TotalCount = uint32(totalCount) 263} 264 265// UnsafeSetTotalCount should not be used 266// Internal usage only 267func (r *ListSnapshotsResponse) UnsafeSetTotalCount(totalCount int) { 268 r.TotalCount = uint32(totalCount) 269} 270 271// UnsafeSetTotalCount should not be used 272// Internal usage only 273func (r *ListVolumesResponse) UnsafeSetTotalCount(totalCount int) { 274 r.TotalCount = uint32(totalCount) 275} 276 277// UnsafeSetTotalCount should not be used 278// Internal usage only 279func (r *ListImagesResponse) UnsafeSetTotalCount(totalCount int) { 280 r.TotalCount = uint32(totalCount) 281} 282 283// UnsafeGetTotalCount should not be used 284// Internal usage only 285func (r *ListServersTypesResponse) UnsafeGetTotalCount() uint32 { 286 return r.TotalCount 287} 288 289// UnsafeAppend should not be used 290// Internal usage only 291func (r *ListServersTypesResponse) UnsafeAppend(res interface{}) (uint32, error) { 292 results, ok := res.(*ListServersTypesResponse) 293 if !ok { 294 return 0, errors.New("%T type cannot be appended to type %T", res, r) 295 } 296 297 if r.Servers == nil { 298 r.Servers = make(map[string]*ServerType, len(results.Servers)) 299 } 300 301 for name, serverType := range results.Servers { 302 r.Servers[name] = serverType 303 } 304 305 r.TotalCount += uint32(len(results.Servers)) 306 return uint32(len(results.Servers)), nil 307} 308 309func (v *NullableStringValue) UnmarshalJSON(b []byte) error { 310 if string(b) == "null" { 311 v.Null = true 312 return nil 313 } 314 315 var tmp string 316 if err := json.Unmarshal(b, &tmp); err != nil { 317 return err 318 } 319 v.Null = false 320 v.Value = tmp 321 return nil 322} 323 324func (v *NullableStringValue) MarshalJSON() ([]byte, error) { 325 if v.Null { 326 return []byte("null"), nil 327 } 328 return json.Marshal(v.Value) 329} 330