1package v2
2
3import (
4	"context"
5	"net"
6
7	apiv2 "github.com/exoscale/egoscale/v2/api"
8	papi "github.com/exoscale/egoscale/v2/internal/public-api"
9)
10
11// PrivateNetworkLease represents a managed Private Network lease.
12type PrivateNetworkLease struct {
13	InstanceID *string
14	IPAddress  *net.IP
15}
16
17// PrivateNetwork represents a Private Network.
18type PrivateNetwork struct {
19	Description *string
20	EndIP       *net.IP
21	ID          *string `req-for:"update"`
22	Name        *string `req-for:"create"`
23	Netmask     *net.IP
24	StartIP     *net.IP
25	Leases      []*PrivateNetworkLease
26
27	c    *Client
28	zone string
29}
30
31func privateNetworkFromAPI(client *Client, zone string, p *papi.PrivateNetwork) *PrivateNetwork {
32	return &PrivateNetwork{
33		Description: p.Description,
34		EndIP: func() (v *net.IP) {
35			if p.EndIp != nil {
36				ip := net.ParseIP(*p.EndIp)
37				v = &ip
38			}
39			return
40		}(),
41		ID:   p.Id,
42		Name: p.Name,
43		Netmask: func() (v *net.IP) {
44			if p.Netmask != nil {
45				ip := net.ParseIP(*p.Netmask)
46				v = &ip
47			}
48			return
49		}(),
50		StartIP: func() (v *net.IP) {
51			if p.StartIp != nil {
52				ip := net.ParseIP(*p.StartIp)
53				v = &ip
54			}
55			return
56		}(),
57		Leases: func() (v []*PrivateNetworkLease) {
58			if p.Leases != nil {
59				v = make([]*PrivateNetworkLease, len(*p.Leases))
60				for i, lease := range *p.Leases {
61					v[i] = &PrivateNetworkLease{
62						InstanceID: lease.InstanceId,
63						IPAddress:  func() *net.IP { ip := net.ParseIP(*lease.Ip); return &ip }(),
64					}
65				}
66			}
67			return
68		}(),
69
70		c:    client,
71		zone: zone,
72	}
73}
74
75func (p PrivateNetwork) get(ctx context.Context, client *Client, zone, id string) (interface{}, error) {
76	return client.GetPrivateNetwork(ctx, zone, id)
77}
78
79// UpdateInstanceIPAddress updates the IP address of a Compute instance attached to the managed Private Network.
80func (p *PrivateNetwork) UpdateInstanceIPAddress(ctx context.Context, instance *Instance, ip net.IP) error {
81	resp, err := p.c.UpdatePrivateNetworkInstanceIpWithResponse(
82		apiv2.WithZone(ctx, p.zone),
83		*p.ID,
84		papi.UpdatePrivateNetworkInstanceIpJSONRequestBody{
85			Instance: papi.Instance{Id: instance.ID},
86			Ip: func() *string {
87				s := ip.String()
88				return &s
89			}(),
90		})
91	if err != nil {
92		return err
93	}
94
95	_, err = papi.NewPoller().
96		WithTimeout(p.c.timeout).
97		WithInterval(p.c.pollInterval).
98		Poll(ctx, p.c.OperationPoller(p.zone, *resp.JSON200.Id))
99
100	return err
101}
102
103// CreatePrivateNetwork creates a Private Network in the specified zone.
104func (c *Client) CreatePrivateNetwork(
105	ctx context.Context,
106	zone string,
107	privateNetwork *PrivateNetwork,
108) (*PrivateNetwork, error) {
109	if err := validateOperationParams(privateNetwork, "create"); err != nil {
110		return nil, err
111	}
112
113	resp, err := c.CreatePrivateNetworkWithResponse(
114		apiv2.WithZone(ctx, zone),
115		papi.CreatePrivateNetworkJSONRequestBody{
116			Description: privateNetwork.Description,
117			EndIp: func() (ip *string) {
118				if privateNetwork.EndIP != nil {
119					v := privateNetwork.EndIP.String()
120					return &v
121				}
122				return
123			}(),
124			Name: *privateNetwork.Name,
125			Netmask: func() (ip *string) {
126				if privateNetwork.Netmask != nil {
127					v := privateNetwork.Netmask.String()
128					return &v
129				}
130				return
131			}(),
132			StartIp: func() (ip *string) {
133				if privateNetwork.StartIP != nil {
134					v := privateNetwork.StartIP.String()
135					return &v
136				}
137				return
138			}(),
139		})
140	if err != nil {
141		return nil, err
142	}
143
144	res, err := papi.NewPoller().
145		WithTimeout(c.timeout).
146		WithInterval(c.pollInterval).
147		Poll(ctx, c.OperationPoller(zone, *resp.JSON200.Id))
148	if err != nil {
149		return nil, err
150	}
151
152	return c.GetPrivateNetwork(ctx, zone, *res.(*papi.Reference).Id)
153}
154
155// ListPrivateNetworks returns the list of existing Private Networks in the specified zone.
156func (c *Client) ListPrivateNetworks(ctx context.Context, zone string) ([]*PrivateNetwork, error) {
157	list := make([]*PrivateNetwork, 0)
158
159	resp, err := c.ListPrivateNetworksWithResponse(apiv2.WithZone(ctx, zone))
160	if err != nil {
161		return nil, err
162	}
163
164	if resp.JSON200.PrivateNetworks != nil {
165		for i := range *resp.JSON200.PrivateNetworks {
166			list = append(list, privateNetworkFromAPI(c, zone, &(*resp.JSON200.PrivateNetworks)[i]))
167		}
168	}
169
170	return list, nil
171}
172
173// GetPrivateNetwork returns the Private Network corresponding to the specified ID in the specified zone.
174func (c *Client) GetPrivateNetwork(ctx context.Context, zone, id string) (*PrivateNetwork, error) {
175	resp, err := c.GetPrivateNetworkWithResponse(apiv2.WithZone(ctx, zone), id)
176	if err != nil {
177		return nil, err
178	}
179
180	return privateNetworkFromAPI(c, zone, resp.JSON200), nil
181}
182
183// FindPrivateNetwork attempts to find a Private Network by name or ID in the specified zone.
184// In case the identifier is a name and multiple resources match, an ErrTooManyFound error is returned.
185func (c *Client) FindPrivateNetwork(ctx context.Context, zone, v string) (*PrivateNetwork, error) {
186	res, err := c.ListPrivateNetworks(ctx, zone)
187	if err != nil {
188		return nil, err
189	}
190
191	var found *PrivateNetwork
192	for _, r := range res {
193		if *r.ID == v {
194			return c.GetPrivateNetwork(ctx, zone, *r.ID)
195		}
196
197		// Historically, the Exoscale API allowed users to create multiple Private Networks sharing a common name.
198		// This function being expected to return one resource at most, in case the specified identifier is a name
199		// we have to check that there aren't more that one matching result before returning it.
200		if *r.Name == v {
201			if found != nil {
202				return nil, apiv2.ErrTooManyFound
203			}
204			found = r
205		}
206	}
207
208	if found != nil {
209		return c.GetPrivateNetwork(ctx, zone, *found.ID)
210	}
211
212	return nil, apiv2.ErrNotFound
213}
214
215// UpdatePrivateNetwork updates the specified Private Network in the specified zone.
216func (c *Client) UpdatePrivateNetwork(ctx context.Context, zone string, privateNetwork *PrivateNetwork) error {
217	if err := validateOperationParams(privateNetwork, "update"); err != nil {
218		return err
219	}
220
221	resp, err := c.UpdatePrivateNetworkWithResponse(
222		apiv2.WithZone(ctx, zone),
223		*privateNetwork.ID,
224		papi.UpdatePrivateNetworkJSONRequestBody{
225			Description: privateNetwork.Description,
226			EndIp: func() (ip *string) {
227				if privateNetwork.EndIP != nil {
228					v := privateNetwork.EndIP.String()
229					return &v
230				}
231				return
232			}(),
233			Name: privateNetwork.Name,
234			Netmask: func() (ip *string) {
235				if privateNetwork.Netmask != nil {
236					v := privateNetwork.Netmask.String()
237					return &v
238				}
239				return
240			}(),
241			StartIp: func() (ip *string) {
242				if privateNetwork.StartIP != nil {
243					v := privateNetwork.StartIP.String()
244					return &v
245				}
246				return
247			}(),
248		})
249	if err != nil {
250		return err
251	}
252
253	_, err = papi.NewPoller().
254		WithTimeout(c.timeout).
255		WithInterval(c.pollInterval).
256		Poll(ctx, c.OperationPoller(zone, *resp.JSON200.Id))
257	if err != nil {
258		return err
259	}
260
261	return nil
262}
263
264// DeletePrivateNetwork deletes the specified Private Network in the specified zone.
265func (c *Client) DeletePrivateNetwork(ctx context.Context, zone, id string) error {
266	resp, err := c.DeletePrivateNetworkWithResponse(apiv2.WithZone(ctx, zone), id)
267	if err != nil {
268		return err
269	}
270
271	_, err = papi.NewPoller().
272		WithTimeout(c.timeout).
273		WithInterval(c.pollInterval).
274		Poll(ctx, c.OperationPoller(zone, *resp.JSON200.Id))
275	if err != nil {
276		return err
277	}
278
279	return nil
280}
281