Skip to content

Commit b3b1371

Browse files
committed
fix: get instances private IPs
1 parent 3eaa74b commit b3b1371

7 files changed

Lines changed: 116 additions & 56 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,5 @@ dmypy.json
133133

134134
# Archive files
135135
*.tar.gz
136+
137+
tests/.../inventory

plugins/inventory/scaleway.py

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@
6262
- public_ipv4
6363
choices:
6464
- public_ipv4
65-
- private_ipv4
65+
- vpc_ipv4
66+
- vpc_ipv6
6667
- public_ipv6
6768
- hostname
6869
- id
@@ -76,8 +77,8 @@
7677
- " - C(state): The server state."
7778
- " - C(hostname): The server hostname."
7879
- " - C(public_ipv4): The server public ipv4."
79-
- " - C(private_ipv4): The server private ipv4."
80-
- " - C(public_ipv6): The server public ipv6."
80+
- " - C(vpc_ipv4): The server private ipv4."
81+
- " - C(vpc_ipv6): The server public ipv6."
8182
- " - C(public_dns): The server public dns."
8283
- " - C(private_dns): The server private dns."
8384
- "If the variable is not found, the host will be ignored."
@@ -100,24 +101,28 @@
100101

101102
import os
102103
from dataclasses import dataclass, field
103-
from types import SimpleNamespace
104-
from typing import List
104+
from typing import List, Optional
105105

106106
from ansible.errors import AnsibleError
107+
from ansible.inventory.data import Host
107108
from ansible.module_utils.basic import missing_required_lib
108109
from ansible.plugins.inventory import BaseInventoryPlugin, Cacheable, Constructable
109110

111+
from ..module_utils.instance import get_instance_private_ips
112+
110113
try:
111-
from scaleway_core.bridge import Zone
112114
from scaleway import Client, ScalewayException
113115
from scaleway.applesilicon.v1alpha1 import ApplesiliconV1Alpha1API
114116
from scaleway.applesilicon.v1alpha1 import Server as ApplesiliconServer
115-
from scaleway.baremetal.v1 import BaremetalV1API, IPVersion as BaremetalIPVersion
117+
from scaleway.baremetal.v1 import BaremetalV1API
118+
from scaleway.baremetal.v1 import IPVersion as BaremetalIPVersion
116119
from scaleway.baremetal.v1 import Server as BaremetalServer
120+
from scaleway.dedibox.v1 import DediboxV1API
121+
from scaleway.dedibox.v1 import IPVersion as DediboxIPVersion
122+
from scaleway.dedibox.v1 import ServerSummary as DediboxServer
117123
from scaleway.instance.v1 import InstanceV1API, ServerState
118124
from scaleway.instance.v1 import Server as InstanceServer
119-
from scaleway.dedibox.v1 import DediboxV1API, IPVersion as DediboxIPVersion
120-
from scaleway.dedibox.v1 import ServerSummary as DediboxServer
125+
from scaleway_core.bridge import Zone
121126

122127
HAS_SCALEWAY_SDK = True
123128
except ImportError:
@@ -139,16 +144,17 @@ class _Filters:
139144

140145

141146
@dataclass
142-
class _Host:
147+
class _Host(Host):
143148
id: str
144149
tags: List[str]
145150
zone: "Zone"
146151
state: "ServerState"
147152

148153
hostname: str
149-
public_ipv4: list[str] = field(default_factory=list)
150-
private_ipv4: list[str] = field(default_factory=list)
151-
public_ipv6: list[str] = field(default_factory=list)
154+
public_ipv4: Optional[str] = None
155+
public_ipv6: Optional[str] = None
156+
vpc_ipv4: Optional[str] = None
157+
vpc_ipv6: Optional[str] = None
152158

153159
# Instances-only
154160
public_dns: str | None = None
@@ -225,7 +231,6 @@ def populate(self, all_hosts: List[_Host]):
225231
self.display.warning(
226232
f"Skipping host {host.id}: Field {source} is not available."
227233
)
228-
self.inventory.remove_host(SimpleNamespace(name=hostname))
229234
should_skip = True
230235
break
231236

@@ -281,17 +286,27 @@ def _get_instances(self, client: "Client", filters: _Filters) -> List[_Host]:
281286
)
282287
)
283288

289+
# get vpc private ips
290+
vpc_ips = get_instance_private_ips(client, servers)
291+
284292
results: List[_Host] = []
285293
for server in servers:
294+
vpc4 = None
295+
vpc6 = None
296+
if server.id in vpc_ips:
297+
vpc4 = vpc_ips[server.id].ipv4
298+
vpc6 = vpc_ips[server.id].ipv6
299+
286300
host = _Host(
287301
id=server.id,
288302
tags=["instance", *server.tags],
289303
zone=server.zone,
290304
state=str(server.state),
291305
hostname=server.hostname,
292-
public_ipv4=[server.public_ip.address] if server.public_ip else [],
293-
private_ipv4=[server.private_ip.address] if server.private_ip else [],
294-
public_ipv6=[server.ipv6.address] if server.ipv6 else [],
306+
public_ipv4=server.public_ip.address if server.public_ip else None,
307+
public_ipv6=server.ipv6.address if server.ipv6 else None,
308+
vpc_ipv4=vpc4,
309+
vpc_ipv6=vpc6,
295310
public_dns=f"{server.id}.pub.instances.scw.cloud",
296311
private_dns=f"{server.id}.priv.instances.scw.cloud",
297312
)
@@ -317,22 +332,21 @@ def _get_elastic_metal(self, client: "Client", filters: _Filters) -> List[_Host]
317332

318333
results: List[_Host] = []
319334
for server in servers:
335+
ipv4 = None
336+
ipv6 = None
337+
for ip in server.ips:
338+
if ip.version == BaremetalIPVersion.I_PV4:
339+
ipv4 = ip.address
340+
if ip.version == BaremetalIPVersion.I_PV6:
341+
ipv6 = ip.address
320342
host = _Host(
321343
id=server.id,
322344
tags=["elastic_metal", *server.tags],
323345
zone=server.zone,
324346
state=str(server.status),
325347
hostname=server.name,
326-
public_ipv4=[
327-
ip.address
328-
for ip in server.ips
329-
if ip.version == BaremetalIPVersion.I_PV4
330-
],
331-
public_ipv6=[
332-
ip.address
333-
for ip in server.ips
334-
if ip.version == BaremetalIPVersion.I_PV6
335-
],
348+
public_ipv4=ipv4,
349+
public_ipv6=ipv6,
336350
)
337351

338352
results.append(host)
@@ -362,9 +376,7 @@ def _get_apple_sillicon(self, client: "Client", filters: _Filters) -> List[_Host
362376
zone=server.zone,
363377
state=str(server.status),
364378
hostname=server.name,
365-
public_ipv4=[server.ip],
366-
private_ipv4=None,
367-
public_ipv6=None,
379+
public_ipv4=server.ip,
368380
)
369381

370382
results.append(host)
@@ -387,15 +399,16 @@ def _get_dedibox(self, client: "Client", filters: _Filters) -> List[_Host]:
387399

388400
results: List[_Host] = []
389401
for server in servers:
390-
public_ipv4 = []
391-
public_ipv6 = []
392-
402+
public_ipv4 = None
403+
public_ipv6 = None
393404
for interface in server.interfaces:
394405
for ip in interface.ips:
395406
if ip.version == DediboxIPVersion.IPV4:
396-
public_ipv4.append(ip.address)
397-
elif ip.version == DediboxIPVersion.IPV6:
398-
public_ipv6.append(ip.address)
407+
public_ipv4 = ip.address
408+
continue
409+
if ip.version == DediboxIPVersion.IPV6:
410+
public_ipv6 = ip.address
411+
continue
399412

400413
host = _Host(
401414
id=server.id,
@@ -404,7 +417,6 @@ def _get_dedibox(self, client: "Client", filters: _Filters) -> List[_Host]:
404417
state=str(server.status),
405418
hostname=server.name,
406419
public_ipv4=public_ipv4,
407-
private_ipv4=None,
408420
public_ipv6=public_ipv6,
409421
)
410422
results.append(host)

plugins/module_utils/instance.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from dataclasses import dataclass
2+
from typing import Dict, Optional
3+
4+
try:
5+
from scaleway import Client
6+
from scaleway.instance.v1 import Server as InstanceServer
7+
from scaleway.ipam.v1 import IpamV1API
8+
from scaleway.vpc.v2 import VpcV2API
9+
except ImportError:
10+
HAS_SCALEWAY_SDK = False
11+
12+
13+
@dataclass
14+
class VpcIps:
15+
ipv4: Optional[str] = None
16+
ipv6: Optional[str] = None
17+
18+
19+
def get_instance_private_ips(
20+
client: "Client", servers: list["InstanceServer"]
21+
) -> Dict[str, VpcIps]:
22+
"""
23+
return a mapping of first found vpc IPs with the instance
24+
"""
25+
vpc_api = VpcV2API(client)
26+
ipam_api = IpamV1API(client)
27+
28+
vpc_ips = {}
29+
30+
for server in servers:
31+
for pnic in server.private_nics:
32+
server_pn = vpc_api.get_private_network(
33+
private_network_id=pnic.private_network_id
34+
)
35+
vpc_ips[server.id] = VpcIps
36+
37+
for ip in ipam_api.list_i_ps(vpc_id=server_pn.vpc_id).ips:
38+
if ip.is_ipv6:
39+
vpc_ips[server.id].ipv6 = ip.address.split("/")[0]
40+
else:
41+
vpc_ips[server.id].ipv4 = ip.address.split("/")[0]
42+
43+
return vpc_ips

plugins/module_utils/scaleway.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# SPDX-License-Identifier: GPL-3.0-or-later
55

66
from __future__ import absolute_import, division, print_function
7+
78
import os
89
import sys
910
from typing import Any, Dict
@@ -45,7 +46,6 @@
4546

4647
try:
4748
from scaleway import Client
48-
4949
from scaleway_core.profile.env import (
5050
ENV_KEY_SCW_ACCESS_KEY,
5151
ENV_KEY_SCW_API_URL,

tests/integration/requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
scaleway>=2.9.0
2-
pyyaml
1+
scaleway>=2.10.0
2+
pyyaml

tests/unit/plugins/conftest.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import tempfile
1+
import json
22
import os
3-
import yaml
4-
import pytest
3+
import tempfile
54
import uuid
6-
import json
5+
6+
import pytest
7+
import yaml
78
from ansible.module_utils import basic
89
from ansible.module_utils.common.text.converters import to_bytes
10+
from scaleway.baremetal.v1.types import IP, IPReverseStatus, IPVersion, Server
911
from scaleway_core.profile import Profile
1012

11-
from scaleway.baremetal.v1.types import Server, IP, IPVersion, IPReverseStatus
12-
1313

1414
@pytest.fixture()
1515
def set_module_args(request):
@@ -51,6 +51,7 @@ def create_temporary_scaleway_config() -> Profile:
5151
def list_bare_metals_fixture():
5252
return [
5353
Server(
54+
protected=False,
5455
id="bmc-12345678-1234-1234-1234-123456789012",
5556
organization_id="org-12345678-1234-1234-1234-123456789012",
5657
project_id="project-12345678-1234-1234-1234-123456789012",
@@ -89,6 +90,7 @@ def list_bare_metals_fixture():
8990
],
9091
),
9192
Server(
93+
protected=False,
9294
id="bmc-12345678-1234-1234-1234-123456789012",
9395
organization_id="org-12345678-1234-1234-1234-123456789012",
9496
project_id="project-12345678-1234-1234-1234-123456789012",

tests/unit/plugins/test_inventory.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
from ....plugins.inventory import scaleway as scaleway_inventory
2-
from ....plugins.inventory.scaleway import _Filters
3-
41
from unittest.mock import patch
5-
from scaleway.baremetal.v1.api import BaremetalV1API
2+
63
from scaleway import Client
4+
from scaleway.baremetal.v1 import BaremetalV1API
5+
6+
from ....plugins.inventory import scaleway as scaleway_inventory
7+
from ....plugins.inventory.scaleway import _Filters
78

89

910
class TestInventory:
@@ -21,14 +22,14 @@ def test_get_elastic_metal(
2122
mocked_client = Client.from_profile(scaleway_config_profile)
2223

2324
instances = inventory._get_elastic_metal(
24-
mocked_client, _Filters(zones=["fr-par"])
25+
mocked_client, _Filters(zones=["fr-par-2"])
2526
)
26-
mocked_list_server_all.assert_called_once_with(zone="fr-par", tags=None)
27+
mocked_list_server_all.assert_called_once_with(zone="fr-par-2", tags=None)
2728
assert len(instances) == 2
28-
assert instances[0].public_ipv4 == ["1.1.1.1", "192.168.0.1"]
29-
assert instances[0].public_ipv6 == []
30-
assert instances[1].public_ipv4 == ["1.1.1.1"]
31-
assert instances[1].public_ipv6 == ["2001:db8::1"]
29+
assert instances[0].public_ipv4 == "192.168.0.1"
30+
assert instances[0].public_ipv6 is None
31+
assert instances[1].public_ipv4 == "1.1.1.1"
32+
assert instances[1].public_ipv6 == "2001:db8::1"
3233

3334
def test_get_host_groups(self):
3435
inventory = scaleway_inventory.InventoryModule()

0 commit comments

Comments
 (0)