Skip to content

Commit bbed69e

Browse files
authored
Merge pull request #148 from NASA-AMMOS/release--v2.7.0
Release v2.7.0
2 parents 918fee8 + 192a831 commit bbed69e

File tree

11 files changed

+159
-12
lines changed

11 files changed

+159
-12
lines changed

.env

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
DOCKER_TAG=v2.11.0
1+
DOCKER_TAG=v2.18.0
22
REPOSITORY_DOCKER_URL=ghcr.io/nasa-ammos
33

44
AERIE_USERNAME=aerie

.github/workflows/test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
strategy:
4848
matrix:
4949
python-version: ["3.6.15", "3.11"]
50-
aerie-version: ["2.11.0"]
50+
aerie-version: ["2.18.0"]
5151
steps:
5252
- uses: actions/checkout@v3
5353
- name: Set up Python ${{ matrix.python-version }}

pyproject.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "aerie-cli"
3-
version = "2.6.0"
3+
version = "2.7.0"
44
description = "A CLI application and Python API for interacting with Aerie."
55
authors = []
66
license = "MIT"
@@ -19,6 +19,7 @@ requests = "^2.27.1"
1919
rich = "^12.6.0"
2020
attrs = "^22.2.0"
2121
pandas = "^1.1.5"
22+
numpy = "^1.19.5"
2223
appdirs = "^1.4.4"
2324
importlib-metadata = "^4.8.2"
2425

src/aerie_cli/aerie_client.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1862,7 +1862,7 @@ def __expand_activity_arguments(self, plan: ActivityPlanRead, full_args: str = N
18621862
for activity in plan.activities:
18631863
if expand_all or activity.type in expand_types:
18641864
query = """
1865-
query ($args: ActivityArguments!, $act_type: String!, $model_id: ID!) {
1865+
query ($args: ActivityArguments!, $act_type: String!, $model_id: Int!) {
18661866
getActivityEffectiveArguments(
18671867
activityArguments: $args,
18681868
activityTypeName: $act_type,

src/aerie_cli/aerie_host.py

+40-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77

88
from attrs import define, field
99

10+
COMPATIBLE_AERIE_VERSIONS = [
11+
"2.18.0"
12+
]
13+
14+
class AerieHostVersionError(RuntimeError):
15+
pass
16+
1017

1118
def process_gateway_response(resp: requests.Response) -> dict:
1219
"""Throw a RuntimeError if the Gateway response is malformed or contains errors
@@ -18,12 +25,12 @@ def process_gateway_response(resp: requests.Response) -> dict:
1825
dict: Contents of response JSON
1926
"""
2027
if not resp.ok:
21-
raise RuntimeError(f"Bad response from Aerie Gateway.")
28+
raise RuntimeError("Bad response from Aerie Gateway")
2229

2330
try:
2431
resp_json = resp.json()
2532
except requests.exceptions.JSONDecodeError:
26-
raise RuntimeError(f"Failed to get response JSON")
33+
raise RuntimeError("Bad response from Aerie Gateway")
2734

2835
if "success" in resp_json.keys() and not resp_json["success"]:
2936
raise RuntimeError(f"Aerie Gateway request was not successful")
@@ -260,7 +267,15 @@ def is_auth_enabled(self) -> bool:
260267

261268
return True
262269

263-
def authenticate(self, username: str, password: str = None):
270+
def authenticate(self, username: str, password: str = None, force: bool = False):
271+
272+
try:
273+
self.check_aerie_version()
274+
except AerieHostVersionError as e:
275+
if force:
276+
print("Warning: " + e.args[0])
277+
else:
278+
raise
264279

265280
resp = self.session.post(
266281
self.gateway_url + "/auth/login",
@@ -278,6 +293,28 @@ def authenticate(self, username: str, password: str = None):
278293
if not self.check_auth():
279294
raise RuntimeError(f"Failed to open session")
280295

296+
def check_aerie_version(self) -> None:
297+
"""Assert that the Aerie host is a compatible version
298+
299+
Raises a `RuntimeError` if the host appears to be incompatible.
300+
"""
301+
302+
resp = self.session.get(self.gateway_url + "/version")
303+
304+
try:
305+
resp_json = process_gateway_response(resp)
306+
host_version = resp_json["version"]
307+
except (RuntimeError, KeyError):
308+
# If the Gateway responded, the route doesn't exist
309+
if resp.text and "Aerie Gateway" in resp.text:
310+
raise AerieHostVersionError("Incompatible Aerie version: host version unknown")
311+
312+
# Otherwise, it could just be a failed connection
313+
raise
314+
315+
if host_version not in COMPATIBLE_AERIE_VERSIONS:
316+
raise AerieHostVersionError(f"Incompatible Aerie version: {host_version}")
317+
281318

282319
@define
283320
class ExternalAuthConfiguration:

src/aerie_cli/app.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ def activate_session(
9090
),
9191
role: str = typer.Option(
9292
None, "--role", "-r", help="Specify a non-default role", metavar="ROLE"
93-
)
93+
),
94+
force: bool = typer.Option(False, "--force", help="Force connection to Aerie host and ignore version compatibility")
9495
):
9596
"""
9697
Activate a session with an Aerie host using a given configuration
@@ -102,7 +103,7 @@ def activate_session(
102103

103104
conf = PersistentConfigurationManager.get_configuration_by_name(name)
104105

105-
session = start_session_from_configuration(conf, username)
106+
session = start_session_from_configuration(conf, username, force=force)
106107

107108
if role is not None:
108109
if role in session.aerie_jwt.allowed_roles:

src/aerie_cli/utils/sessions.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@ def start_session_from_configuration(
118118
configuration: AerieHostConfiguration,
119119
username: str = None,
120120
password: str = None,
121-
secret_post_vars: Dict[str, str] = None
121+
secret_post_vars: Dict[str, str] = None,
122+
force: bool = False
122123
):
123124
"""Start and authenticate an Aerie Host session, with prompts if necessary
124125
@@ -136,6 +137,7 @@ def start_session_from_configuration(
136137
username (str, optional): Aerie username.
137138
password (str, optional): Aerie password.
138139
secret_post_vars (Dict[str, str], optional): Optionally provide values for some or all secret post request variable values. Defaults to None.
140+
force (bool, optional): Force connection to Aerie host and ignore version compatibility. Defaults to False.
139141
140142
Returns:
141143
AerieHost:
@@ -162,6 +164,6 @@ def start_session_from_configuration(
162164
if password is None and hs.is_auth_enabled():
163165
password = typer.prompt("Aerie Password", hide_input=True)
164166

165-
hs.authenticate(username, password)
167+
hs.authenticate(username, password, force)
166168

167169
return hs

tests/integration_tests/conftest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
CONFIGURATIONS_PATH = os.path.join(FILES_PATH, "configuration")
4444
CONFIGURATION_PATH = os.path.join(CONFIGURATIONS_PATH, "localhost_config.json")
4545
MODELS_PATH = os.path.join(FILES_PATH, "models")
46-
MODEL_VERSION = os.environ.get("AERIE_VERSION", "2.8.0")
46+
MODEL_VERSION = os.environ.get("AERIE_VERSION", "2.18.0")
4747
MODEL_JAR = os.path.join(MODELS_PATH, f"banananation-{MODEL_VERSION}.jar")
4848
MODEL_NAME = "banananation"
4949
MODEL_VERSION = "0.0.1"
Binary file not shown.

tests/unit_tests/test_aerie_host.py

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
from typing import Dict
2+
import pytest
3+
import requests
4+
5+
from aerie_cli.aerie_host import AerieHost, COMPATIBLE_AERIE_VERSIONS, AerieJWT
6+
7+
8+
class MockJWT:
9+
def __init__(self, *args, **kwargs):
10+
self.default_role = 'viewer'
11+
12+
class MockResponse:
13+
def __init__(self, json: Dict, text: str = None, ok: bool = True) -> None:
14+
self.json_data = json
15+
self.text = text
16+
self.ok = ok
17+
18+
def json(self) -> Dict:
19+
if self.json_data is None:
20+
raise requests.exceptions.JSONDecodeError("", "", 0)
21+
return self.json_data
22+
23+
24+
class MockSession:
25+
26+
def __init__(self, mock_response: MockResponse) -> None:
27+
self.mock_response = mock_response
28+
29+
def get(self, *args, **kwargs) -> MockResponse:
30+
return self.mock_response
31+
32+
def post(self, *args, **kwargs) -> MockResponse:
33+
return self.mock_response
34+
35+
36+
def get_mock_aerie_host(json: Dict = None, text: str = None, ok: bool = True) -> AerieHost:
37+
mock_response = MockResponse(json, text, ok)
38+
mock_session = MockSession(mock_response)
39+
return AerieHost("", "", mock_session)
40+
41+
42+
def test_check_aerie_version():
43+
aerie_host = get_mock_aerie_host(
44+
json={"version": COMPATIBLE_AERIE_VERSIONS[0]})
45+
46+
aerie_host.check_aerie_version()
47+
48+
49+
def test_authenticate_invalid_version(capsys, monkeypatch):
50+
ah = AerieHost("", "")
51+
52+
def mock_get(*_, **__):
53+
return MockResponse({"version": "1.0.0"})
54+
def mock_post(*_, **__):
55+
return MockResponse({"token": ""})
56+
def mock_check_auth(*_, **__):
57+
return True
58+
59+
monkeypatch.setattr(requests.Session, "get", mock_get)
60+
monkeypatch.setattr(requests.Session, "post", mock_post)
61+
monkeypatch.setattr(AerieHost, "check_auth", mock_check_auth)
62+
monkeypatch.setattr(AerieJWT, "__init__", MockJWT.__init__)
63+
64+
with pytest.raises(RuntimeError) as e:
65+
ah.authenticate("")
66+
67+
assert "Incompatible Aerie version: 1.0.0" in str(e.value)
68+
69+
70+
def test_authenticate_invalid_version_force(capsys, monkeypatch):
71+
ah = AerieHost("", "")
72+
73+
def mock_get(*_, **__):
74+
return MockResponse({"version": "1.0.0"})
75+
def mock_post(*_, **__):
76+
return MockResponse({"token": ""})
77+
def mock_check_auth(*_, **__):
78+
return True
79+
80+
monkeypatch.setattr(requests.Session, "get", mock_get)
81+
monkeypatch.setattr(requests.Session, "post", mock_post)
82+
monkeypatch.setattr(AerieHost, "check_auth", mock_check_auth)
83+
monkeypatch.setattr(AerieJWT, "__init__", MockJWT.__init__)
84+
85+
ah.authenticate("", force=True)
86+
87+
assert capsys.readouterr().out == "Warning: Incompatible Aerie version: 1.0.0\n"
88+
89+
90+
def test_no_version_endpoint():
91+
aerie_host = get_mock_aerie_host(text="blah Aerie Gateway blah", ok=True)
92+
93+
with pytest.raises(RuntimeError) as e:
94+
aerie_host.check_aerie_version()
95+
96+
assert "Incompatible Aerie version: host version unknown" in str(e.value)
97+
98+
99+
def test_version_broken_gateway():
100+
aerie_host = get_mock_aerie_host(
101+
text="502 Bad Gateway or something", ok=True)
102+
103+
with pytest.raises(RuntimeError) as e:
104+
aerie_host.check_aerie_version()
105+
106+
assert "Bad response from Aerie Gateway" in str(e.value)

0 commit comments

Comments
 (0)