Skip to content

Commit 29160a2

Browse files
authored
Merge pull request #398 from linode/dev
Release v5.15.0
2 parents 18f96db + 23d41fd commit 29160a2

28 files changed

+351
-95
lines changed

.github/workflows/e2e-test.yml

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: Integration Tests
2+
3+
on:
4+
workflow_dispatch: null
5+
push:
6+
branches:
7+
- main
8+
- dev
9+
10+
jobs:
11+
integration-tests:
12+
runs-on: ubuntu-latest
13+
env:
14+
EXIT_STATUS: 0
15+
steps:
16+
- name: Clone Repository
17+
uses: actions/checkout@v4
18+
with:
19+
fetch-depth: 0
20+
submodules: 'recursive'
21+
22+
- name: Setup Python
23+
uses: actions/setup-python@v5
24+
with:
25+
python-version: '3.x'
26+
27+
- name: Install Python deps
28+
run: pip install -U setuptools wheel boto3 certifi
29+
30+
- name: Install Python SDK
31+
run: make dev-install
32+
env:
33+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34+
35+
- name: Run Integration tests
36+
run: |
37+
timestamp=$(date +'%Y%m%d%H%M')
38+
report_filename="${timestamp}_sdk_test_report.xml"
39+
status=0
40+
if ! python3 -m pytest test/integration/${INTEGRATION_TEST_PATH} --disable-warnings --junitxml="${report_filename}"; then
41+
echo "EXIT_STATUS=1" >> $GITHUB_ENV
42+
fi
43+
env:
44+
LINODE_TOKEN: ${{ secrets.LINODE_TOKEN }}
45+
46+
- name: Add additional information to XML report
47+
run: |
48+
filename=$(ls | grep -E '^[0-9]{12}_sdk_test_report\.xml$')
49+
python tod_scripts/add_to_xml_test_report.py \
50+
--branch_name "${GITHUB_REF#refs/*/}" \
51+
--gha_run_id "$GITHUB_RUN_ID" \
52+
--gha_run_number "$GITHUB_RUN_NUMBER" \
53+
--xmlfile "${filename}"
54+
55+
- name: Upload test results
56+
run: |
57+
report_filename=$(ls | grep -E '^[0-9]{12}_sdk_test_report\.xml$')
58+
python3 tod_scripts/test_report_upload_script.py "${report_filename}"
59+
env:
60+
LINODE_CLI_OBJ_ACCESS_KEY: ${{ secrets.LINODE_CLI_OBJ_ACCESS_KEY }}
61+
LINODE_CLI_OBJ_SECRET_KEY: ${{ secrets.LINODE_CLI_OBJ_SECRET_KEY }}
62+
63+
- name: Test Execution Status Handler
64+
run: |
65+
if [[ "$EXIT_STATUS" != 0 ]]; then
66+
echo "Test execution contains failure(s)"
67+
exit $EXIT_STATUS
68+
else
69+
echo "Tests passed!"
70+
fi

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,4 @@ testunit:
7575

7676
.PHONY: smoketest
7777
smoketest:
78-
$(PYTHON) -m pytest -m smoke test/integration --disable-warnings
78+
$(PYTHON) -m pytest -m smoke test/integration

linode_api4/groups/account.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -487,10 +487,10 @@ def join_beta_program(self, beta: Union[str, BetaProgram]):
487487

488488
def availabilities(self, *filters):
489489
"""
490-
Returns a list of all available regions and the resources which are NOT available
490+
Returns a list of all available regions and the resource types which are available
491491
to the account.
492492
493-
API doc: TBD
493+
API doc: https://www.linode.com/docs/api/account/#region-service-availability
494494
495495
:returns: a list of region availability information.
496496
:rtype: PaginatedList of AccountAvailability

linode_api4/objects/account.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class Account(Base):
4343
"zip": Property(mutable=True),
4444
"address_2": Property(mutable=True),
4545
"tax_id": Property(mutable=True),
46-
"capabilities": Property(),
46+
"capabilities": Property(unordered=True),
4747
"credit_card": Property(),
4848
"active_promotions": Property(),
4949
"active_since": Property(),
@@ -660,15 +660,17 @@ class AccountBetaProgram(Base):
660660

661661
class AccountAvailability(Base):
662662
"""
663-
The resources information in a region which are NOT available to an account.
663+
Contains information about the resources available for a region under the
664+
current account.
664665
665-
API doc: TBD
666+
API doc: https://www.linode.com/docs/api/account/#region-service-availability
666667
"""
667668

668669
api_endpoint = "/account/availability/{region}"
669670
id_attribute = "region"
670671

671672
properties = {
672673
"region": Property(identifier=True),
673-
"unavailable": Property(),
674+
"unavailable": Property(unordered=True),
675+
"available": Property(unordered=True),
674676
}

linode_api4/objects/base.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def __init__(
3232
id_relationship=False,
3333
slug_relationship=False,
3434
nullable=False,
35+
unordered=False,
3536
json_object=None,
3637
):
3738
"""
@@ -50,6 +51,10 @@ def __init__(
5051
(This should be used on fields ending with '_id' only)
5152
slug_relationship - This property is a slug related for a given type.
5253
nullable - This property can be explicitly null on PUT requests.
54+
unordered - The order of this property is not significant.
55+
NOTE: This field is currently only for annotations purposes
56+
and does not influence any update or decoding/encoding logic.
57+
json_object - The JSONObject class this property should be decoded into.
5358
"""
5459
self.mutable = mutable
5560
self.identifier = identifier
@@ -60,6 +65,7 @@ def __init__(
6065
self.id_relationship = id_relationship
6166
self.slug_relationship = slug_relationship
6267
self.nullable = nullable
68+
self.unordered = unordered
6369
self.json_class = json_object
6470

6571

@@ -103,9 +109,15 @@ def dict(self):
103109
result[k] = v.dict
104110
elif isinstance(v, list):
105111
result[k] = [
106-
item.dict if isinstance(item, cls) else item for item in v
112+
(
113+
item.dict
114+
if isinstance(item, cls)
115+
else item._raw_json if isinstance(item, Base) else item
116+
)
117+
for item in v
107118
]
108-
119+
elif isinstance(v, Base):
120+
result[k] = v._raw_json
109121
return result
110122

111123

linode_api4/objects/database.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ class MySQLDatabase(Base):
129129
properties = {
130130
"id": Property(identifier=True),
131131
"label": Property(mutable=True),
132-
"allow_list": Property(mutable=True),
132+
"allow_list": Property(mutable=True, unordered=True),
133133
"backups": Property(derived_class=MySQLDatabaseBackup),
134134
"cluster_size": Property(),
135135
"created": Property(is_datetime=True),
@@ -262,7 +262,7 @@ class PostgreSQLDatabase(Base):
262262
properties = {
263263
"id": Property(identifier=True),
264264
"label": Property(mutable=True),
265-
"allow_list": Property(mutable=True),
265+
"allow_list": Property(mutable=True, unordered=True),
266266
"backups": Property(derived_class=PostgreSQLDatabaseBackup),
267267
"cluster_size": Property(),
268268
"created": Property(is_datetime=True),
@@ -404,7 +404,7 @@ class Database(Base):
404404
properties = {
405405
"id": Property(),
406406
"label": Property(),
407-
"allow_list": Property(),
407+
"allow_list": Property(unordered=True),
408408
"cluster_size": Property(),
409409
"created": Property(),
410410
"encrypted": Property(),

linode_api4/objects/domain.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,14 @@ class Domain(Base):
4949
"status": Property(mutable=True),
5050
"soa_email": Property(mutable=True),
5151
"retry_sec": Property(mutable=True),
52-
"master_ips": Property(mutable=True),
53-
"axfr_ips": Property(mutable=True),
52+
"master_ips": Property(mutable=True, unordered=True),
53+
"axfr_ips": Property(mutable=True, unordered=True),
5454
"expire_sec": Property(mutable=True),
5555
"refresh_sec": Property(mutable=True),
5656
"ttl_sec": Property(mutable=True),
5757
"records": Property(derived_class=DomainRecord),
5858
"type": Property(mutable=True),
59-
"tags": Property(mutable=True),
59+
"tags": Property(mutable=True, unordered=True),
6060
}
6161

6262
def record_create(self, record_type, **kwargs):

linode_api4/objects/image.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,7 @@ class Image(Base):
2525
"vendor": Property(),
2626
"size": Property(),
2727
"deprecated": Property(),
28-
"capabilities": Property(),
28+
"capabilities": Property(
29+
unordered=True,
30+
),
2931
}

linode_api4/objects/linode.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -642,11 +642,11 @@ class Instance(Base):
642642
"configs": Property(derived_class=Config),
643643
"type": Property(slug_relationship=Type),
644644
"backups": Property(mutable=True),
645-
"ipv4": Property(),
645+
"ipv4": Property(unordered=True),
646646
"ipv6": Property(),
647647
"hypervisor": Property(),
648648
"specs": Property(),
649-
"tags": Property(mutable=True),
649+
"tags": Property(mutable=True, unordered=True),
650650
"host_uuid": Property(),
651651
"watchdog_enabled": Property(mutable=True),
652652
"has_user_data": Property(),
@@ -1745,7 +1745,9 @@ class StackScript(Base):
17451745
"created": Property(is_datetime=True),
17461746
"deployments_active": Property(),
17471747
"script": Property(mutable=True),
1748-
"images": Property(mutable=True), # TODO make slug_relationship
1748+
"images": Property(
1749+
mutable=True, unordered=True
1750+
), # TODO make slug_relationship
17491751
"deployments_total": Property(),
17501752
"description": Property(mutable=True),
17511753
"updated": Property(is_datetime=True),

linode_api4/objects/lke.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class LKENodePool(DerivedBase):
7474
volatile=True
7575
), # this is formatted in _populate below
7676
"autoscaler": Property(mutable=True),
77-
"tags": Property(mutable=True),
77+
"tags": Property(mutable=True, unordered=True),
7878
}
7979

8080
def _populate(self, json):
@@ -121,7 +121,7 @@ class LKECluster(Base):
121121
"id": Property(identifier=True),
122122
"created": Property(is_datetime=True),
123123
"label": Property(mutable=True),
124-
"tags": Property(mutable=True),
124+
"tags": Property(mutable=True, unordered=True),
125125
"updated": Property(is_datetime=True),
126126
"region": Property(slug_relationship=Region),
127127
"k8s_version": Property(slug_relationship=KubeVersion, mutable=True),

linode_api4/objects/networking.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ class IPv6Range(Base):
3434
"region": Property(slug_relationship=Region),
3535
"prefix": Property(),
3636
"route_target": Property(),
37-
"linodes": Property(),
37+
"linodes": Property(
38+
unordered=True,
39+
),
3840
"is_bgp": Property(),
3941
}
4042

@@ -151,7 +153,7 @@ class VLAN(Base):
151153
properties = {
152154
"label": Property(identifier=True),
153155
"created": Property(is_datetime=True),
154-
"linodes": Property(),
156+
"linodes": Property(unordered=True),
155157
"region": Property(slug_relationship=Region),
156158
}
157159

@@ -189,7 +191,7 @@ class Firewall(Base):
189191
properties = {
190192
"id": Property(identifier=True),
191193
"label": Property(mutable=True),
192-
"tags": Property(mutable=True),
194+
"tags": Property(mutable=True, unordered=True),
193195
"status": Property(mutable=True),
194196
"created": Property(is_datetime=True),
195197
"updated": Property(is_datetime=True),

linode_api4/objects/nodebalancer.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class NodeBalancerNode(DerivedBase):
3434
"weight": Property(mutable=True),
3535
"mode": Property(mutable=True),
3636
"status": Property(),
37-
"tags": Property(mutable=True),
37+
"tags": Property(mutable=True, unordered=True),
3838
}
3939

4040
def __init__(self, client, id, parent_id, nodebalancer_id=None, json=None):
@@ -217,7 +217,7 @@ class NodeBalancer(Base):
217217
"region": Property(slug_relationship=Region),
218218
"configs": Property(derived_class=NodeBalancerConfig),
219219
"transfer": Property(),
220-
"tags": Property(mutable=True),
220+
"tags": Property(mutable=True, unordered=True),
221221
}
222222

223223
# create derived objects

linode_api4/objects/region.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class Region(Base):
1818
properties = {
1919
"id": Property(identifier=True),
2020
"country": Property(),
21-
"capabilities": Property(),
21+
"capabilities": Property(unordered=True),
2222
"status": Property(),
2323
"resolvers": Property(),
2424
"label": Property(),

linode_api4/objects/volume.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class Volume(Base):
2121
"size": Property(),
2222
"status": Property(),
2323
"region": Property(slug_relationship=Region),
24-
"tags": Property(mutable=True),
24+
"tags": Property(mutable=True, unordered=True),
2525
"filesystem_path": Property(),
2626
"hardware_type": Property(),
2727
"linode_label": Property(),

linode_api4/objects/vpc.py

+22-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from linode_api4.errors import UnexpectedResponseError
55
from linode_api4.objects import Base, DerivedBase, Property, Region
66
from linode_api4.objects.serializable import JSONObject
7+
from linode_api4.paginated_list import PaginatedList
78

89

910
@dataclass
@@ -33,7 +34,7 @@ class VPCSubnet(DerivedBase):
3334
"id": Property(identifier=True),
3435
"label": Property(mutable=True),
3536
"ipv4": Property(),
36-
"linodes": Property(json_object=VPCSubnetLinode),
37+
"linodes": Property(json_object=VPCSubnetLinode, unordered=True),
3738
"created": Property(is_datetime=True),
3839
"updated": Property(is_datetime=True),
3940
}
@@ -97,3 +98,23 @@ def subnet_create(
9798

9899
d = VPCSubnet(self._client, result["id"], self.id, result)
99100
return d
101+
102+
@property
103+
def ips(self, *filters) -> PaginatedList:
104+
"""
105+
Get all the IP addresses under this VPC.
106+
107+
API Documentation: TODO
108+
109+
:returns: A list of VPCIPAddresses the acting user can access.
110+
:rtype: PaginatedList of VPCIPAddress
111+
"""
112+
113+
# need to avoid circular import
114+
from linode_api4.objects import ( # pylint: disable=import-outside-toplevel
115+
VPCIPAddress,
116+
)
117+
118+
return self._client._get_and_filter(
119+
VPCIPAddress, *filters, endpoint="/vpcs/{}/ips".format(self.id)
120+
)

0 commit comments

Comments
 (0)