proxygen-cli 2.0.15__tar.gz → 2.0.17__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/PKG-INFO +10 -9
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/README.md +8 -8
- proxygen_cli-2.0.17/proxygen_cli/cli/command_credentials.py +101 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/cli/command_main.py +2 -5
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/cli/command_spec.py +40 -16
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/credentials.py +34 -23
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/command_credentials_test.py +64 -9
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/command_spec_test.py +29 -3
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/pyproject.toml +1 -1
- proxygen_cli-2.0.15/proxygen_cli/cli/command_credentials.py +0 -65
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/__init__.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/cli/command_docker.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/cli/command_instance.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/cli/command_secret.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/cli/command_settings.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/auth.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/constants.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/dot_proxygen.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/output.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/proxygen_api.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/settings.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/spec.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/spec_server.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/lib/version.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/__init__.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/command_instance_test.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/command_main_test.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/command_secret_test.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/command_settings_test.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/conftest.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/fixtures/client.key +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/fixtures/client.pem +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/fixtures/secret.txt +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/mock_private_key.py +0 -0
- {proxygen_cli-2.0.15 → proxygen_cli-2.0.17}/proxygen_cli/test/validate_cli_version_test.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: proxygen-cli
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.17
|
|
4
4
|
Summary: CLI for interacting with NHSD APIM's proxygen service
|
|
5
5
|
Home-page: https://github.com/NHSDigital/proxygen-cli
|
|
6
6
|
Author: Ben Strutt
|
|
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.9
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
14
|
Requires-Dist: click (>=8.1.3,<9.0.0)
|
|
14
15
|
Requires-Dist: cryptography (>=36.0.1,<37.0.0)
|
|
15
16
|
Requires-Dist: lxml (>=4.7.1,<5.0.0)
|
|
@@ -38,19 +39,19 @@ Type `proxygen` to see a list of available commands.
|
|
|
38
39
|
|
|
39
40
|
### Credentials
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
`
|
|
42
|
+
The CLI has its own client credentials, which need to be input.
|
|
43
|
+
Contact `deathstar` squad or the `platforms-api-producer-support` slack channel to find out what they are.
|
|
44
|
+
|
|
45
|
+
All users should also have individual credentials. `proxygen-cli` needs to know about them.
|
|
43
46
|
|
|
47
|
+
Simply execute the following command which will prompt you to enter your `client_id`, `client_secret`, `username`, and `password`:
|
|
44
48
|
```
|
|
45
|
-
proxygen credentials set
|
|
46
|
-
proxygen credentials set password <PASSWORD>
|
|
49
|
+
proxygen credentials set
|
|
47
50
|
```
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
Contact `deathstar` squad or the `platforms-api-producer-support` slack channel to find out what they are.
|
|
52
|
+
If you need to update any credentials in the future, use the following command:
|
|
51
53
|
```
|
|
52
|
-
|
|
53
|
-
proxgen credentials set client_secret <CLIENT_SECRET>
|
|
54
|
+
proxygen credentials set <KEY> <VALUE>
|
|
54
55
|
```
|
|
55
56
|
|
|
56
57
|
|
|
@@ -14,19 +14,19 @@ Type `proxygen` to see a list of available commands.
|
|
|
14
14
|
|
|
15
15
|
### Credentials
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
`
|
|
17
|
+
The CLI has its own client credentials, which need to be input.
|
|
18
|
+
Contact `deathstar` squad or the `platforms-api-producer-support` slack channel to find out what they are.
|
|
19
|
+
|
|
20
|
+
All users should also have individual credentials. `proxygen-cli` needs to know about them.
|
|
19
21
|
|
|
22
|
+
Simply execute the following command which will prompt you to enter your `client_id`, `client_secret`, `username`, and `password`:
|
|
20
23
|
```
|
|
21
|
-
proxygen credentials set
|
|
22
|
-
proxygen credentials set password <PASSWORD>
|
|
24
|
+
proxygen credentials set
|
|
23
25
|
```
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
Contact `deathstar` squad or the `platforms-api-producer-support` slack channel to find out what they are.
|
|
27
|
+
If you need to update any credentials in the future, use the following command:
|
|
27
28
|
```
|
|
28
|
-
|
|
29
|
-
proxgen credentials set client_secret <CLIENT_SECRET>
|
|
29
|
+
proxygen credentials set <KEY> <VALUE>
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import click
|
|
3
|
+
import yaml
|
|
4
|
+
import pydantic
|
|
5
|
+
|
|
6
|
+
from proxygen_cli.lib import output
|
|
7
|
+
from proxygen_cli.lib.credentials import (
|
|
8
|
+
Credentials, get_credentials, _yaml_credentials_file_source, create_yaml_credentials_file, initialise_credentials)
|
|
9
|
+
from proxygen_cli.lib.dot_proxygen import credentials_file
|
|
10
|
+
|
|
11
|
+
CHOICE_OF_CREDENTIAL_KEYS = click.Choice(Credentials.__fields__.keys())
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.group()
|
|
15
|
+
def credentials():
|
|
16
|
+
"""Get/set credentials."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@credentials.command()
|
|
20
|
+
def list():
|
|
21
|
+
"""
|
|
22
|
+
List all credentials values.
|
|
23
|
+
"""
|
|
24
|
+
if not _yaml_credentials_file_source(None):
|
|
25
|
+
click.echo("Credentials file does not exist. Please run 'proxygen credentials set'")
|
|
26
|
+
exit()
|
|
27
|
+
|
|
28
|
+
creds = get_credentials()
|
|
29
|
+
output.print_spec(json.loads(creds.json(exclude_none=True)))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@credentials.command()
|
|
33
|
+
@click.argument("key", type=CHOICE_OF_CREDENTIAL_KEYS)
|
|
34
|
+
def get(key):
|
|
35
|
+
"""
|
|
36
|
+
Read a value from your credentials.
|
|
37
|
+
"""
|
|
38
|
+
if not _yaml_credentials_file_source(None):
|
|
39
|
+
click.echo("Credentials file does not exist. Please run 'proxygen credentials set'")
|
|
40
|
+
exit()
|
|
41
|
+
|
|
42
|
+
creds = get_credentials()
|
|
43
|
+
click.echo(getattr(creds, key))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@credentials.command()
|
|
47
|
+
@click.argument("custom_pairs", nargs=-1, metavar="KEY VALUE", required=False)
|
|
48
|
+
@click.option("--force", is_flag=True, help="Force re-entry of standard credentials")
|
|
49
|
+
def set(custom_pairs, force):
|
|
50
|
+
"""
|
|
51
|
+
Write a value to your credentials.
|
|
52
|
+
"""
|
|
53
|
+
if not _yaml_credentials_file_source(None):
|
|
54
|
+
create_yaml_credentials_file()
|
|
55
|
+
|
|
56
|
+
current_credentials = _yaml_credentials_file_source(None)
|
|
57
|
+
|
|
58
|
+
# Check if base credentials are set
|
|
59
|
+
base_credentials_set = all(
|
|
60
|
+
current_credentials.get(field) is not None
|
|
61
|
+
for field in ["client_id", "client_secret", "username", "password"]
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if not base_credentials_set or force:
|
|
65
|
+
client_id = click.prompt("Enter client_id")
|
|
66
|
+
client_secret = click.prompt("Enter client_secret", default="", show_default=False)
|
|
67
|
+
username = click.prompt("Enter username", default="", show_default=False)
|
|
68
|
+
password = click.prompt("Enter password", default="", show_default=False)
|
|
69
|
+
|
|
70
|
+
current_credentials["client_id"] = client_id
|
|
71
|
+
current_credentials["client_secret"] = client_secret
|
|
72
|
+
current_credentials["username"] = username
|
|
73
|
+
current_credentials["password"] = password
|
|
74
|
+
|
|
75
|
+
# Prompt for individual custom key-value pairs
|
|
76
|
+
for i in range(0, len(custom_pairs), 2):
|
|
77
|
+
key, value = custom_pairs[i:i + 2]
|
|
78
|
+
current_credentials[key] = value
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
new_credentials = json.loads(Credentials(**current_credentials).json(exclude_none=True))
|
|
82
|
+
except pydantic.ValidationError as e:
|
|
83
|
+
errors = json.loads(e.json())
|
|
84
|
+
raise click.BadParameter("\n".join(error["msg"] for error in errors))
|
|
85
|
+
|
|
86
|
+
with credentials_file().open("w") as f:
|
|
87
|
+
yaml.safe_dump(new_credentials, f)
|
|
88
|
+
|
|
89
|
+
initialise_credentials()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@credentials.command()
|
|
93
|
+
@click.argument("key", type=CHOICE_OF_CREDENTIAL_KEYS)
|
|
94
|
+
def rm(key):
|
|
95
|
+
"""
|
|
96
|
+
Delete a value from your credentials.
|
|
97
|
+
"""
|
|
98
|
+
current_credentials = _yaml_credentials_file_source(None)
|
|
99
|
+
current_credentials.pop(key, None)
|
|
100
|
+
with credentials_file().open("w") as f:
|
|
101
|
+
yaml.safe_dump(current_credentials, f)
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
from typing import get_args
|
|
3
|
-
from urllib import parse
|
|
4
1
|
import click
|
|
5
2
|
from proxygen_cli.lib.settings import SETTINGS
|
|
6
3
|
|
|
7
|
-
from proxygen_cli.lib import
|
|
4
|
+
from proxygen_cli.lib import proxygen_api, output
|
|
8
5
|
from proxygen_cli.cli import (
|
|
9
6
|
command_credentials,
|
|
10
7
|
command_settings,
|
|
@@ -23,7 +20,7 @@ def main():
|
|
|
23
20
|
main.add_command(command_settings.settings)
|
|
24
21
|
main.add_command(command_credentials.credentials)
|
|
25
22
|
main.add_command(command_instance.instance)
|
|
26
|
-
main.add_command(command_spec.
|
|
23
|
+
main.add_command(command_spec.spec)
|
|
27
24
|
main.add_command(command_secret.secret)
|
|
28
25
|
main.add_command(command_docker.docker)
|
|
29
26
|
|
|
@@ -3,34 +3,58 @@ from typing import get_args
|
|
|
3
3
|
import click
|
|
4
4
|
from yaspin import yaspin
|
|
5
5
|
|
|
6
|
-
from proxygen_cli.lib import output, proxygen_api, spec,
|
|
6
|
+
from proxygen_cli.lib import output, proxygen_api, spec as lib_spec, \
|
|
7
|
+
spec_server
|
|
7
8
|
from proxygen_cli.lib.constants import LITERAL_ENVS
|
|
8
9
|
from proxygen_cli.lib.settings import SETTINGS
|
|
9
10
|
|
|
10
11
|
CHOICE_OF_ENVS = click.Choice(get_args(LITERAL_ENVS))
|
|
11
12
|
PUBLISH_SPEC_POP_KEYS = ["x-nhsd-apim"] # Don't publish deployment information
|
|
12
13
|
|
|
14
|
+
HELP_NO_CONFIRM = "Do not prompt for confirmation."
|
|
15
|
+
HELP_UAT = "Target the UAT instance of API catalogue."
|
|
16
|
+
|
|
17
|
+
UAT_KWARGS = {
|
|
18
|
+
'default': False,
|
|
19
|
+
'help': HELP_UAT,
|
|
20
|
+
'is_flag': True
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
NO_CONFIRM_KWARGS = {
|
|
24
|
+
'is_flag': True,
|
|
25
|
+
'show_default': True,
|
|
26
|
+
'help': HELP_NO_CONFIRM,
|
|
27
|
+
}
|
|
28
|
+
|
|
13
29
|
|
|
14
30
|
@click.group()
|
|
15
31
|
@click.option(
|
|
16
|
-
"--api",
|
|
32
|
+
"--api",
|
|
33
|
+
default=SETTINGS.api,
|
|
34
|
+
help="Override the default API",
|
|
35
|
+
show_default=True
|
|
36
|
+
)
|
|
37
|
+
@click.option(
|
|
38
|
+
"--uat",
|
|
39
|
+
**UAT_KWARGS
|
|
17
40
|
)
|
|
18
41
|
@click.pass_context
|
|
19
|
-
def
|
|
42
|
+
def spec(ctx, api, uat):
|
|
43
|
+
"""Publish/update/delete specifications on the API catalogue."""
|
|
20
44
|
ctx.ensure_object(dict)
|
|
21
45
|
ctx.obj["api"] = api
|
|
46
|
+
ctx.obj['uat'] = uat
|
|
22
47
|
|
|
23
48
|
|
|
24
|
-
@
|
|
49
|
+
@spec.command()
|
|
25
50
|
@click.argument("spec_file")
|
|
26
51
|
@click.option(
|
|
27
52
|
"--no-confirm",
|
|
28
|
-
|
|
29
|
-
show_default=True,
|
|
30
|
-
help="Do not prompt for confirmation.",
|
|
53
|
+
**NO_CONFIRM_KWARGS
|
|
31
54
|
)
|
|
32
55
|
@click.option(
|
|
33
|
-
"--uat",
|
|
56
|
+
"--uat",
|
|
57
|
+
**UAT_KWARGS
|
|
34
58
|
)
|
|
35
59
|
@click.pass_context
|
|
36
60
|
def publish(ctx, spec_file, no_confirm, uat):
|
|
@@ -39,7 +63,7 @@ def publish(ctx, spec_file, no_confirm, uat):
|
|
|
39
63
|
"""
|
|
40
64
|
|
|
41
65
|
api = ctx.obj["api"]
|
|
42
|
-
paas_open_api =
|
|
66
|
+
paas_open_api = lib_spec.resolve(spec_file, pop_keys=PUBLISH_SPEC_POP_KEYS)
|
|
43
67
|
|
|
44
68
|
if not no_confirm:
|
|
45
69
|
output.print_spec(paas_open_api)
|
|
@@ -69,9 +93,10 @@ def serve(spec_file):
|
|
|
69
93
|
spec_server.serve(spec_file, pop_keys=PUBLISH_SPEC_POP_KEYS)
|
|
70
94
|
|
|
71
95
|
|
|
72
|
-
@
|
|
96
|
+
@spec.command()
|
|
73
97
|
@click.option(
|
|
74
|
-
"--uat",
|
|
98
|
+
"--uat",
|
|
99
|
+
**UAT_KWARGS
|
|
75
100
|
)
|
|
76
101
|
@click.pass_context
|
|
77
102
|
def get(ctx, uat):
|
|
@@ -83,15 +108,14 @@ def get(ctx, uat):
|
|
|
83
108
|
output.print_spec(result)
|
|
84
109
|
|
|
85
110
|
|
|
86
|
-
@
|
|
111
|
+
@spec.command()
|
|
87
112
|
@click.option(
|
|
88
113
|
"--no-confirm",
|
|
89
|
-
|
|
90
|
-
show_default=True,
|
|
91
|
-
help="Do not prompt for confirmation.",
|
|
114
|
+
**NO_CONFIRM_KWARGS
|
|
92
115
|
)
|
|
93
116
|
@click.option(
|
|
94
|
-
"--uat",
|
|
117
|
+
"--uat",
|
|
118
|
+
**UAT_KWARGS
|
|
95
119
|
)
|
|
96
120
|
@click.pass_context
|
|
97
121
|
def delete(ctx, no_confirm, uat):
|
|
@@ -3,6 +3,7 @@ import json
|
|
|
3
3
|
from typing import Optional
|
|
4
4
|
import yaml
|
|
5
5
|
import sys
|
|
6
|
+
import os
|
|
6
7
|
|
|
7
8
|
import click
|
|
8
9
|
from pydantic import (
|
|
@@ -21,6 +22,14 @@ def _yaml_credentials_file_source(_):
|
|
|
21
22
|
return credentials or {}
|
|
22
23
|
|
|
23
24
|
|
|
25
|
+
def create_yaml_credentials_file():
|
|
26
|
+
file_path = os.path.expanduser("~/.proxygen/credentials.yaml")
|
|
27
|
+
|
|
28
|
+
if not os.path.exists(file_path):
|
|
29
|
+
with open(file_path, 'w'):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
24
33
|
class Credentials(BaseSettings):
|
|
25
34
|
base_url: AnyHttpUrl = (
|
|
26
35
|
"https://identity.prod.api.platform.nhs.uk/realms/api-producers"
|
|
@@ -28,9 +37,9 @@ class Credentials(BaseSettings):
|
|
|
28
37
|
private_key_path: Optional[str] = None
|
|
29
38
|
key_id: Optional[str] = None
|
|
30
39
|
client_id: str
|
|
31
|
-
client_secret: str = None
|
|
32
|
-
username: str = None
|
|
33
|
-
password: str = None
|
|
40
|
+
client_secret: Optional[str] = None
|
|
41
|
+
username: Optional[str] = None
|
|
42
|
+
password: Optional[str] = None
|
|
34
43
|
|
|
35
44
|
@validator("username", "password", "client_secret", "client_id")
|
|
36
45
|
def validate_humans_users(cls, value, values):
|
|
@@ -47,12 +56,12 @@ class Credentials(BaseSettings):
|
|
|
47
56
|
)
|
|
48
57
|
with private_key_file.open() as f:
|
|
49
58
|
return f.read()
|
|
50
|
-
|
|
59
|
+
|
|
51
60
|
@validator("key_id")
|
|
52
61
|
def validate_kid(cls, kid, values):
|
|
53
62
|
"""
|
|
54
|
-
If authenticating using a machine (with an associated jwks configured in Keycloak) key,
|
|
55
|
-
for newer versions of Keycloak.
|
|
63
|
+
If authenticating using a machine (with an associated jwks configured in Keycloak) key,
|
|
64
|
+
then the key id is required for newer versions of Keycloak.
|
|
56
65
|
"""
|
|
57
66
|
if values.get("private_key_path") and not kid:
|
|
58
67
|
raise ValueError("Private key specified with no associated Key ID (KID)")
|
|
@@ -65,7 +74,7 @@ class Credentials(BaseSettings):
|
|
|
65
74
|
So take string.
|
|
66
75
|
"""
|
|
67
76
|
return cls._validate_private_key_path(private_key_path)
|
|
68
|
-
|
|
77
|
+
|
|
69
78
|
@staticmethod
|
|
70
79
|
def _validate_private_key_path(private_key_path):
|
|
71
80
|
"""
|
|
@@ -104,24 +113,26 @@ class Credentials(BaseSettings):
|
|
|
104
113
|
|
|
105
114
|
|
|
106
115
|
_CREDENTIALS = None
|
|
107
|
-
|
|
108
|
-
_CREDENTIALS
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
116
|
+
def initialise_credentials():
|
|
117
|
+
global _CREDENTIALS
|
|
118
|
+
try:
|
|
119
|
+
_CREDENTIALS = Credentials()
|
|
120
|
+
except ValidationError as e:
|
|
121
|
+
errors = json.loads(e.json())
|
|
122
|
+
print("*" * 100, file=sys.stderr)
|
|
123
|
+
print(
|
|
124
|
+
"Warning: Credentials invalid or not configured. See `proxygen credentials`.",
|
|
125
|
+
file=sys.stderr,
|
|
126
|
+
)
|
|
127
|
+
details = " " + "\n ".join(
|
|
128
|
+
f"{error['loc'][0]}: {error['msg']}" for error in errors
|
|
129
|
+
)
|
|
130
|
+
print(details, file=sys.stderr)
|
|
131
|
+
print("*" * 100, file=sys.stderr)
|
|
132
|
+
_CREDENTIALS = None
|
|
123
133
|
|
|
124
134
|
|
|
125
135
|
def get_credentials():
|
|
136
|
+
initialise_credentials()
|
|
126
137
|
global _CREDENTIALS
|
|
127
138
|
return _CREDENTIALS
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from unittest.mock import patch
|
|
2
2
|
|
|
3
|
+
import yaml
|
|
3
4
|
import pytest
|
|
4
5
|
from click.testing import CliRunner
|
|
5
6
|
from pydantic.error_wrappers import ValidationError
|
|
@@ -72,7 +73,7 @@ def test_private_key_id_missing(patch_config):
|
|
|
72
73
|
"""
|
|
73
74
|
mock_credentials = get_test_credentials()
|
|
74
75
|
|
|
75
|
-
with patch_config(credentials=mock_credentials)
|
|
76
|
+
with patch_config(credentials=mock_credentials), \
|
|
76
77
|
patch(("proxygen_cli.lib.credentials.Credentials."
|
|
77
78
|
"_validate_private_key_path")) as mock_creds:
|
|
78
79
|
mock_creds.return_value = True
|
|
@@ -107,19 +108,73 @@ def test_set_credential(patch_config, credentials_file):
|
|
|
107
108
|
assert credentials_file() == expected_credentials
|
|
108
109
|
|
|
109
110
|
|
|
110
|
-
def
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
def test_update_credentials(patch_config, credentials_file):
|
|
112
|
+
mock_credentials = get_test_credentials()
|
|
113
|
+
|
|
114
|
+
# Convert mock_credentials to a dictionary
|
|
115
|
+
mock_credentials_dict = yaml.safe_load(mock_credentials)
|
|
113
116
|
|
|
114
|
-
|
|
117
|
+
# Set an initial value for the username field
|
|
118
|
+
initial_username = "old-username"
|
|
119
|
+
mock_credentials_dict["username"] = initial_username
|
|
115
120
|
|
|
121
|
+
with patch_config(credentials=mock_credentials):
|
|
122
|
+
runner = CliRunner()
|
|
123
|
+
runner.invoke(cmd_credentials.set, ["username", "new-username123"])
|
|
116
124
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
125
|
+
expected_credentials = "\n".join(
|
|
126
|
+
[
|
|
127
|
+
"base_url: https://mock-keycloak-url.nhs.uk",
|
|
128
|
+
"client_id: mock-api-client",
|
|
129
|
+
"client_secret: 1a2f4g5",
|
|
130
|
+
"password: mock-password",
|
|
131
|
+
"username: new-username123", # Updated value
|
|
132
|
+
]
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Check the file has been written to
|
|
136
|
+
assert credentials_file() == expected_credentials
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_add_key_id(patch_config, credentials_file):
|
|
140
|
+
mock_credentials = get_test_credentials()
|
|
141
|
+
|
|
142
|
+
with patch_config(credentials=mock_credentials):
|
|
143
|
+
runner = CliRunner()
|
|
144
|
+
runner.invoke(cmd_credentials.set, ["key_id", "test_kid"])
|
|
145
|
+
|
|
146
|
+
expected_credentials = "\n".join(
|
|
147
|
+
[
|
|
148
|
+
"base_url: https://mock-keycloak-url.nhs.uk",
|
|
149
|
+
"client_id: mock-api-client",
|
|
150
|
+
"client_secret: 1a2f4g5",
|
|
151
|
+
"key_id: test_kid",
|
|
152
|
+
"password: mock-password",
|
|
153
|
+
"username: mock-user",
|
|
154
|
+
]
|
|
121
155
|
)
|
|
122
156
|
|
|
157
|
+
# Check the file has been written to
|
|
158
|
+
assert credentials_file() == expected_credentials
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_set_invalid_setting(patch_config):
|
|
162
|
+
mock_credentials = get_test_credentials()
|
|
163
|
+
with patch_config(credentials=mock_credentials):
|
|
164
|
+
runner = CliRunner()
|
|
165
|
+
result = runner.invoke(cmd_credentials.set, ["invalid", "new-username"])
|
|
166
|
+
|
|
167
|
+
assert "Error: Invalid value: extra fields not permitted" in result.output.strip()
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_set_invalid_private_key_path(patch_config):
|
|
171
|
+
mock_credentials = get_test_credentials()
|
|
172
|
+
with patch_config(credentials=mock_credentials):
|
|
173
|
+
runner = CliRunner()
|
|
174
|
+
result = runner.invoke(
|
|
175
|
+
cmd_credentials.set, ["private_key_path", "invalid_private_key_path"]
|
|
176
|
+
)
|
|
177
|
+
|
|
123
178
|
assert ".proxygen/invalid_private_key_path does not exist" in result.output.strip()
|
|
124
179
|
|
|
125
180
|
|
|
@@ -3,9 +3,11 @@ Tests around the spec CLI command.
|
|
|
3
3
|
"""
|
|
4
4
|
from urllib.parse import urlparse
|
|
5
5
|
from click.testing import CliRunner
|
|
6
|
+
import pytest
|
|
6
7
|
|
|
7
8
|
from proxygen_cli.cli.command_spec import get as spec_get, \
|
|
8
|
-
delete as spec_delete, publish as spec_publish
|
|
9
|
+
delete as spec_delete, publish as spec_publish, spec as spec_base, \
|
|
10
|
+
serve as spec_serve
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
def test_publish_spec_std(patch_request, patch_access_token,
|
|
@@ -15,7 +17,7 @@ def test_publish_spec_std(patch_request, patch_access_token,
|
|
|
15
17
|
"""
|
|
16
18
|
|
|
17
19
|
runner = CliRunner()
|
|
18
|
-
with patch_access_token(), patch_request(200, "TEST") as patched_request
|
|
20
|
+
with patch_access_token(), patch_request(200, "TEST") as patched_request, \
|
|
19
21
|
patch_spec_resolver(), patch_click_confirm():
|
|
20
22
|
|
|
21
23
|
runner.invoke(spec_publish, ["NoFile"], obj={"api": "mock-api"})
|
|
@@ -32,7 +34,7 @@ def test_publish_spec_uat(patch_request, patch_access_token,
|
|
|
32
34
|
"""
|
|
33
35
|
|
|
34
36
|
runner = CliRunner()
|
|
35
|
-
with patch_access_token(), patch_request(200, "TEST") as patched_request
|
|
37
|
+
with patch_access_token(), patch_request(200, "TEST") as patched_request, \
|
|
36
38
|
patch_spec_resolver(), patch_click_confirm():
|
|
37
39
|
|
|
38
40
|
runner.invoke(
|
|
@@ -110,6 +112,30 @@ def test_delete_spec_uat(patch_request, patch_access_token,
|
|
|
110
112
|
assert path == "/apis/mock-api/spec/uat"
|
|
111
113
|
|
|
112
114
|
|
|
115
|
+
@pytest.mark.parametrize("cmd, uat_expected", [
|
|
116
|
+
(spec_get, True),
|
|
117
|
+
(spec_delete, True),
|
|
118
|
+
(spec_publish, True),
|
|
119
|
+
(spec_serve, False),
|
|
120
|
+
(spec_base, True)
|
|
121
|
+
])
|
|
122
|
+
def test_uat_option_in_expected_command_help(cmd, uat_expected):
|
|
123
|
+
"""
|
|
124
|
+
Ensure the --uat option is only available for the appropriate spec
|
|
125
|
+
commands.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
runner = CliRunner()
|
|
129
|
+
result = runner.invoke(cmd, ["--help"])
|
|
130
|
+
|
|
131
|
+
if uat_expected:
|
|
132
|
+
assert "--uat" in result.output, \
|
|
133
|
+
f"--uat option was expected but not found for command {cmd}"
|
|
134
|
+
else:
|
|
135
|
+
assert "--uat" not in result.output, \
|
|
136
|
+
f"--uat option was not expected but found for command {cmd}"
|
|
137
|
+
|
|
138
|
+
|
|
113
139
|
def _extract_request_path(mocked_request, method='GET'):
|
|
114
140
|
"""
|
|
115
141
|
Extract the path from the first mocked call to .get() in the provided
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import pydantic
|
|
3
|
-
import yaml
|
|
4
|
-
import click
|
|
5
|
-
|
|
6
|
-
from proxygen_cli.lib import output
|
|
7
|
-
from proxygen_cli.lib.credentials import Credentials, get_credentials, _yaml_credentials_file_source
|
|
8
|
-
from proxygen_cli.lib.dot_proxygen import credentials_file
|
|
9
|
-
|
|
10
|
-
CHOICE_OF_CREDENTIAL_KEYS = click.Choice(Credentials.__fields__.keys())
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@click.group()
|
|
14
|
-
def credentials():
|
|
15
|
-
"""Get/set credentials."""
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@credentials.command()
|
|
19
|
-
def list():
|
|
20
|
-
"""
|
|
21
|
-
List all credentials values.
|
|
22
|
-
"""
|
|
23
|
-
creds = get_credentials()
|
|
24
|
-
output.print_spec(json.loads(creds.json(exclude_none=True)))
|
|
25
|
-
|
|
26
|
-
@credentials.command()
|
|
27
|
-
@click.argument("key", type=CHOICE_OF_CREDENTIAL_KEYS)
|
|
28
|
-
def get(key):
|
|
29
|
-
"""
|
|
30
|
-
Read a value from your credentials.
|
|
31
|
-
"""
|
|
32
|
-
creds = get_credentials()
|
|
33
|
-
click.echo(getattr(creds, key))
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@credentials.command()
|
|
37
|
-
@click.argument("key", type=CHOICE_OF_CREDENTIAL_KEYS)
|
|
38
|
-
@click.argument("value")
|
|
39
|
-
def set(key, value):
|
|
40
|
-
"""
|
|
41
|
-
Write a value to your credentials.
|
|
42
|
-
"""
|
|
43
|
-
current_credentials = _yaml_credentials_file_source(None)
|
|
44
|
-
current_credentials[key] = value
|
|
45
|
-
try:
|
|
46
|
-
new_credentials = json.loads(Credentials(**current_credentials).json(exclude_none=True))
|
|
47
|
-
except pydantic.ValidationError as e:
|
|
48
|
-
errors = json.loads(e.json())
|
|
49
|
-
raise click.BadParameter("\n".join(error["msg"] for error in errors))
|
|
50
|
-
with credentials_file().open("w") as f:
|
|
51
|
-
yaml.safe_dump(new_credentials, f)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
@credentials.command()
|
|
57
|
-
@click.argument("key", type=CHOICE_OF_CREDENTIAL_KEYS)
|
|
58
|
-
def rm(key):
|
|
59
|
-
"""
|
|
60
|
-
Delete a value from your credentials.
|
|
61
|
-
"""
|
|
62
|
-
current_credentials = _yaml_credentials_file_source(None)
|
|
63
|
-
current_credentials.pop(key, None)
|
|
64
|
-
with credentials_file().open("w") as f:
|
|
65
|
-
yaml.safe_dump(current_credentials, f)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|