Skip to content

Commit b0fb5e5

Browse files
committed
Merge tag 'v5.8.0' into develop
add Authorization domain
2 parents cf7125f + 207901d commit b0fb5e5

File tree

11 files changed

+1346
-1126
lines changed

11 files changed

+1346
-1126
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,11 @@ jobs:
9999
repository_url: https://test.pypi.org/legacy/
100100

101101
- name: Temporarily Store the Python Package in GitHub # for convenient reference, troubleshooting, investigation, etc...
102-
uses: actions/upload-artifact@v2
102+
uses: actions/upload-artifact@v3
103103
with:
104104
name: netfoundry-pypi-${{ github.run_id }}
105105
path: dist/netfoundry-*.tar.gz
106+
if-no-files-found: error
106107

107108
- name: Append 'latest' tag if release
108109
env:

netfoundry/ctl.py

Lines changed: 413 additions & 422 deletions
Large diffs are not rendered by default.

netfoundry/demo.py

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,35 @@
44
Usage:
55
$ python3 -m netfoundry.demo --network BibbidiBobbidiBoo
66
"""
7-
from subprocess import run
8-
from sys import argv
7+
import re
8+
import sys
99

10-
def main():
11-
"""Run the built-in demo."""
12-
raw_args = ['nfctl', 'demo']
13-
if len(argv) > 1:
14-
raw_args.extend(argv[1:])
15-
run(raw_args)
10+
try:
11+
from importlib.metadata import distribution
12+
except ImportError:
13+
try:
14+
from importlib_metadata import distribution
15+
except ImportError:
16+
from pkg_resources import load_entry_point
17+
18+
19+
def importlib_load_entry_point(spec, group, name):
20+
dist_name, _, _ = spec.partition('==')
21+
matches = (
22+
entry_point
23+
for entry_point in distribution(dist_name).entry_points
24+
if entry_point.group == group and entry_point.name == name
25+
)
26+
return next(matches).load()
1627

1728

29+
globals().setdefault('load_entry_point', importlib_load_entry_point)
30+
31+
def main():
32+
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
33+
_args = [sys.argv[0], 'demo'] + sys.argv[1:]
34+
sys.argv = _args
35+
sys.exit(load_entry_point('netfoundry', 'console_scripts', 'nfctl')())
36+
1837
if __name__ == '__main__':
1938
main()

netfoundry/exceptions.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
"""Exceptions class"""
33

44

5-
from netfoundry.utility import RESOURCES
6-
7-
85
class NFAPIError(Exception):
96
"""Base-class for all exceptions raised by this module."""
107

@@ -44,16 +41,19 @@ class NFAPINoCredentials(NFAPIError):
4441
class UnknownResourceType(NFAPIError):
4542
"""Unknown resource type."""
4643

47-
def __init__(self, resource_type: str=None) -> None:
44+
def __init__(self, resource_type: str, valid_types: list) -> None:
4845
"""Add the type as an attribute to this instance."""
4946
self.resource_type = resource_type
47+
self.valid_types = valid_types
5048

49+
# consume valid types as param to avoid circular import with .utility
5150
def __str__(self):
5251
"""Report the invalid type if provided, finally report valid types."""
5352
if self.resource_type is not None:
54-
return f"Not a valid resource type: '{self.resource_type}'. Try one of: {','.join(RESOURCES.keys())}"
53+
return f"Not a valid resource type: '{self.resource_type}'. Try one of: {','.join(self.valid_types)}"
5554
else:
56-
return f"Not a valid resource type. Try one of: {','.join(RESOURCES.keys())}"
55+
return f"Not a valid resource type. Try one of: {','.join(self.valid_types)}"
56+
5757

5858
class NeedUserInput(NFAPIError):
59-
"""Need user input to confirm action."""
59+
"""Need user input to confirm action."""

netfoundry/network.py

Lines changed: 475 additions & 454 deletions
Large diffs are not rendered by default.

netfoundry/network_group.py

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
"""Use a network group and find its networks."""
22

3-
from ast import Or
43
import json
54
import logging
65

76
from .utility import (NETWORK_RESOURCES, RESOURCES, STATUS_CODES,
87
find_generic_resources, get_generic_resource, http, is_uuidv4,
9-
normalize_caseless)
8+
normalize_caseless, any_in)
109

1110

1211
class NetworkGroup:
1312
"""use a network group by name or ID.
14-
13+
1514
The default is to use the first network group available to the organization of the caller.
1615
"""
1716

18-
def __init__(self, Organization: object, network_group_id: str=None, network_group_name: str=None, group: str=None):
17+
def __init__(self, Organization: object, network_group_id: str = None, network_group_name: str = None, group: str = None):
1918
"""Initialize the network group class with a group name or ID."""
2019
self.network_groups = Organization.get_network_groups_by_organization()
2120
if (not network_group_id and not network_group_name) and group:
@@ -25,13 +24,13 @@ def __init__(self, Organization: object, network_group_id: str=None, network_gro
2524
network_group_name = group
2625
if network_group_id:
2726
self.network_group_id = network_group_id
28-
self.network_group_name = [ ng['organizationShortName'] for ng in self.network_groups if ng['id'] == network_group_id ][0]
27+
self.network_group_name = [ng['organizationShortName'] for ng in self.network_groups if ng['id'] == network_group_id][0]
2928
# TODO: review the use of org short name ref https://mattermost.tools.netfoundry.io/netfoundry/pl/gegyzuybypb9jxnrw1g1imjywh
3029
elif network_group_name:
3130
self.network_group_name = network_group_name
32-
network_group_matches = [ ng['id'] for ng in self.network_groups if ng['organizationShortName'] == network_group_name ]
31+
network_group_matches = [ng['id'] for ng in self.network_groups if ng['organizationShortName'] == network_group_name]
3332
if len(network_group_matches) == 1:
34-
self.network_group_id = [ ng['id'] for ng in self.network_groups if ng['organizationShortName'] == network_group_name ][0]
33+
self.network_group_id = [ng['id'] for ng in self.network_groups if ng['organizationShortName'] == network_group_name][0]
3534
else:
3635
raise RuntimeError(f"there was not exactly one network group matching the name '{network_group_name}'")
3736
elif len(self.network_groups) > 0:
@@ -75,7 +74,7 @@ def nc_data_centers_by_location(self):
7574
# resolve network UUIDs by name
7675
def network_id_by_normal_name(self, name):
7776
"""Find network ID in group by case-insensitive (caseless, normalized) name.
78-
77+
7978
Case-insensitive uniqueness is enforced by the API for each type of entity.
8079
"""
8180
caseless = normalize_caseless(name)
@@ -84,9 +83,9 @@ def network_id_by_normal_name(self, name):
8483
else:
8584
raise RuntimeError(f"no network named '{name}' in this network group")
8685

87-
def network_exists(self, name: str, deleted: bool=False):
86+
def network_exists(self, name: str, deleted: bool = False):
8887
"""Check if a network exists in the current group.
89-
88+
9089
:param name: the case-insensitive string to search
9190
:param deleted: include deleted networks in results
9291
"""
@@ -103,35 +102,35 @@ def nc_data_centers(self, **kwargs):
103102
params[param] = kwargs[param]
104103
params["productVersion"] = self.find_latest_product_version(is_active=True)
105104
params["hostType"] = "NC"
106-
#params["provider"] = "AWS"
105+
params["provider"] = "AWS"
107106

108107
url = self.audience+'core/v2/data-centers'
109-
headers = { "authorization": "Bearer " + self.token }
108+
headers = {"authorization": "Bearer " + self.token}
110109
try:
111110
data_centers = list()
112111
for i in find_generic_resources(url=url, headers=headers, embedded=NETWORK_RESOURCES['data-centers']._embedded, proxies=self.proxies, verify=self.verify, **params):
113112
data_centers.extend(i)
114113
except Exception as e:
115-
raise RuntimeError(f"failed to get data-centers from url: '{url}'")
114+
raise RuntimeError(f"failed to get data-centers from url: '{url}', caught {e}")
116115
else:
117116
return(data_centers)
118117

119118
# provide a compatible alias
120-
get_controller_data_centers = nc_data_centers
119+
get_controller_data_centers = nc_data_centers
121120

122-
def get_product_metadata(self, is_active: bool=True):
121+
def get_product_metadata(self, is_active: bool = True):
123122
"""
124123
Get product version metadata.
125-
124+
126125
:param is_active: filter for only active product versions
127126
:param product_version: semver string of a single version to get, default is all versions
128127
"""
129128
url = self.audience+'product-metadata/v2/download-urls.json'
130-
headers = dict() # no auth
129+
headers = dict() # no auth
131130
try:
132131
all_product_metadata, status_symbol = get_generic_resource(url=url, headers=headers, proxies=self.proxies, verify=self.verify)
133132
except Exception as e:
134-
raise RuntimeError(f"failed to get product-metadata from url: '{url}'")
133+
raise RuntimeError(f"failed to get product-metadata from url: '{url}', caught {e}")
135134
else:
136135
if is_active:
137136
filtered_product_metadata = dict()
@@ -142,7 +141,7 @@ def get_product_metadata(self, is_active: bool=True):
142141
else:
143142
return (all_product_metadata)
144143

145-
def list_product_versions(self, product_metadata: dict=dict(), is_active: bool=True):
144+
def list_product_versions(self, product_metadata: dict = dict(), is_active: bool = True):
146145
"""Find product versions in all products' metadata."""
147146
if product_metadata:
148147
product_versions = product_metadata.keys()
@@ -152,15 +151,15 @@ def list_product_versions(self, product_metadata: dict=dict(), is_active: bool=T
152151

153152
return (product_versions)
154153

155-
def find_latest_product_version(self, product_versions: list=list(), is_active: bool=True):
154+
def find_latest_product_version(self, product_versions: list = list(), is_active: bool = True):
156155
"""Get the highest product version number (may be experimental, not stable)."""
157156
if not product_versions:
158157
product_versions = self.list_product_versions(is_active=is_active)
159158

160159
from distutils.version import LooseVersion
161160
return sorted(product_versions, key=LooseVersion)[-1]
162161

163-
def create_network(self, name: str, network_group_id: str=None, location: str="us-east-1", version: str=None, size: str="small", **kwargs):
162+
def create_network(self, name: str, network_group_id: str = None, location: str = "us-east-1", version: str = None, size: str = "small", **kwargs):
164163
"""
165164
Create a network in this network group.
166165
@@ -175,7 +174,7 @@ def create_network(self, name: str, network_group_id: str=None, location: str="u
175174
raise RuntimeError(f"unexpected network location '{location}'. Valid locations include: {','.join(my_nc_data_centers_by_location.keys())}.")
176175

177176
# map incongruent api keys from kwargs to function params ("name", "size" are congruent)
178-
for param,value in kwargs.items():
177+
for param, value in kwargs.items():
179178
if param == 'networkGroupId':
180179
if network_group_id:
181180
logging.debug("clobbering param 'network_group_id' with kwarg 'networkGroupId'")
@@ -209,7 +208,7 @@ def create_network(self, name: str, network_group_id: str=None, location: str="u
209208
elif version in product_versions:
210209
request['productVersion'] = version
211210
elif version == "default":
212-
pass # do not specify a value for productVersion
211+
pass # do not specify a value for productVersion
213212
else:
214213
raise RuntimeError(f"invalid version '{version}'. Expected one of {product_versions}")
215214
headers = {
@@ -227,9 +226,8 @@ def create_network(self, name: str, network_group_id: str=None, location: str="u
227226
)
228227
response_code = response.status_code
229228
except Exception as e:
230-
raise
229+
raise RuntimeError(f"problem creating network, caught {e}")
231230

232-
any_in = lambda a, b: any(i in b for i in a)
233231
response_code_symbols = [s.upper() for s in STATUS_CODES._codes[response_code]]
234232
if any_in(response_code_symbols, RESOURCES['networks'].create_responses):
235233
try:
@@ -258,10 +256,10 @@ def delete_network(self, network_id=None, network_name=None):
258256
elif network_name and self.network_ids_by_normal_name.get(normalize_caseless(network_name)):
259257
network_id = self.network_ids_by_normal_name[normalize_caseless(network_name)]
260258
except Exception as e:
261-
raise RuntimeError(f"need one of network_id or network_name for a network in this network group: {self.name}, got {e}")
259+
raise RuntimeError(f"need one of network_id or network_name for a network in this network group: {self.name}, caught {e}")
262260

263261
try:
264-
headers = { "authorization": "Bearer " + self.token }
262+
headers = {"authorization": "Bearer " + self.token}
265263
entity_url = self.audience+'core/v2/networks/'+network_id
266264
response = http.delete(
267265
entity_url,
@@ -272,10 +270,9 @@ def delete_network(self, network_id=None, network_name=None):
272270
response_code = response.status_code
273271
network = response.json()
274272
except Exception as e:
275-
raise RuntimeError(f"failed deleting network {entity_url} or loading JSON from response, got {e}")
273+
raise RuntimeError(f"failed deleting network {entity_url} or loading JSON from response, caught {e}")
276274

277275
if not response_code == STATUS_CODES.codes.ACCEPTED:
278276
raise RuntimeError(f"got unexpected HTTP code {STATUS_CODES._codes[response_code][0].upper()} ({response_code}) and response {response.text}")
279277

280278
return(network)
281-

0 commit comments

Comments
 (0)