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