dataall-cli 0.4.2__tar.gz → 0.5.0__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.
- {dataall_cli-0.4.2 → dataall_cli-0.5.0}/PKG-INFO +9 -6
- {dataall_cli-0.4.2 → dataall_cli-0.5.0}/dataall_cli/bind_commands.py +8 -1
- dataall_cli-0.5.0/dataall_cli/cli.py +288 -0
- dataall_cli-0.5.0/dataall_cli/utils/__init__.py +6 -0
- dataall_cli-0.5.0/dataall_cli/utils/discovery.py +54 -0
- {dataall_cli-0.4.2 → dataall_cli-0.5.0}/pyproject.toml +19 -19
- dataall_cli-0.4.2/dataall_cli/cli.py +0 -148
- dataall_cli-0.4.2/dataall_cli/utils/__init__.py +0 -5
- {dataall_cli-0.4.2 → dataall_cli-0.5.0}/LICENSE +0 -0
- {dataall_cli-0.4.2 → dataall_cli-0.5.0}/NOTICE +0 -0
- {dataall_cli-0.4.2 → dataall_cli-0.5.0}/NOTICE.txt +0 -0
- {dataall_cli-0.4.2 → dataall_cli-0.5.0}/README.md +0 -0
- {dataall_cli-0.4.2 → dataall_cli-0.5.0}/dataall_cli/__init__.py +0 -0
- {dataall_cli-0.4.2 → dataall_cli-0.5.0}/dataall_cli/__main__.py +0 -0
- {dataall_cli-0.4.2 → dataall_cli-0.5.0}/dataall_cli/utils/config.py +0 -0
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: dataall-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: AWS data.all CLI
|
|
5
5
|
License: Apache-2.0
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
License-File: NOTICE
|
|
8
|
+
License-File: NOTICE.txt
|
|
6
9
|
Keywords: dataall,aws
|
|
7
10
|
Author: Amazon Web Services
|
|
8
11
|
Requires-Python: >=3.9,<4.0
|
|
@@ -12,11 +15,11 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
12
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
16
|
Requires-Dist: PyYAML (>=6.0.1)
|
|
14
17
|
Requires-Dist: atomicfile (>=1.0.1)
|
|
15
|
-
Requires-Dist: boto3 (>=1.
|
|
16
|
-
Requires-Dist: botocore (>=1.
|
|
18
|
+
Requires-Dist: boto3 (>=1.40.55)
|
|
19
|
+
Requires-Dist: botocore (>=1.40.55)
|
|
17
20
|
Requires-Dist: click (>=8.1.6)
|
|
18
|
-
Requires-Dist: dataall-core (>=0.
|
|
19
|
-
Requires-Dist: packaging (>=
|
|
21
|
+
Requires-Dist: dataall-core (>=0.5.0,<0.6.0)
|
|
22
|
+
Requires-Dist: packaging (>=24.2)
|
|
20
23
|
Requires-Dist: setuptools ; python_version >= "3.12"
|
|
21
24
|
Requires-Dist: typing-extensions (>=4.4.0)
|
|
22
25
|
Description-Content-Type: text/markdown
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
|
+
from pathlib import Path
|
|
5
6
|
from typing import Any, Callable, Dict, Optional
|
|
6
7
|
|
|
7
8
|
import click
|
|
8
9
|
from dataall_core.dataall_client import DataallClient
|
|
10
|
+
from dataall_core.profile import get_profile
|
|
9
11
|
|
|
10
12
|
logger = logging.getLogger(__name__)
|
|
11
13
|
|
|
@@ -45,10 +47,15 @@ def _bind_function(
|
|
|
45
47
|
|
|
46
48
|
def func(**kwargs: Any) -> None:
|
|
47
49
|
logger.debug("I am the '{}' command".format(name))
|
|
50
|
+
profile = kwargs.get("profile", "default")
|
|
51
|
+
if get_profile(profile=profile, config_path=Path(config_path)) is None:
|
|
52
|
+
raise click.ClickException(
|
|
53
|
+
f"Profile '{profile}' is not configured; run: dataall_cli configure --profile {profile}"
|
|
54
|
+
)
|
|
48
55
|
da_client = DataallClient(
|
|
49
56
|
schema_path=schema_path, schema_version=schema_version
|
|
50
57
|
).client(
|
|
51
|
-
profile=
|
|
58
|
+
profile=profile,
|
|
52
59
|
config_path=config_path,
|
|
53
60
|
custom_headers=custom_headers,
|
|
54
61
|
)
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""CLI for data.all."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
from dataall_core.dataall_client import DataallClient
|
|
11
|
+
from dataall_core.profile import CONFIG_PATH
|
|
12
|
+
|
|
13
|
+
from dataall_cli.bind_commands import bind
|
|
14
|
+
from dataall_cli.utils import discover_from_frontend, frontend_origin, save_config
|
|
15
|
+
|
|
16
|
+
DA_CONFIG_PATH = os.getenv("dataall_config_path", CONFIG_PATH)
|
|
17
|
+
CREDS_PATH = os.getenv("dataall_creds_path", None)
|
|
18
|
+
SCHEMA_PATH = os.getenv("dataall_schema_path", None)
|
|
19
|
+
SCHEMA_VERSION = os.getenv("dataall_schema_version", None)
|
|
20
|
+
DA_CUSTOM_HEADERS_JSON: str = os.getenv("dataall_custom_headers_json", "{}")
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
custom_headers = json.loads(DA_CUSTOM_HEADERS_JSON)
|
|
26
|
+
except ValueError:
|
|
27
|
+
logger.info(
|
|
28
|
+
f"Invalid custom headers json string: {DA_CUSTOM_HEADERS_JSON}. Using default headers..."
|
|
29
|
+
)
|
|
30
|
+
custom_headers = {}
|
|
31
|
+
|
|
32
|
+
da = DataallClient(schema_path=SCHEMA_PATH, schema_version=SCHEMA_VERSION)
|
|
33
|
+
default_client = da.client(config_path=DA_CONFIG_PATH, custom_headers=custom_headers)
|
|
34
|
+
commands = da.op_dict
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@click.group(name="dataall_cli", invoke_without_command=True)
|
|
38
|
+
def dataall_cli() -> None:
|
|
39
|
+
"""data.all cli groups."""
|
|
40
|
+
click.echo("Executing dataall_cli.", err=True)
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
bind(
|
|
45
|
+
dataall_cli=dataall_cli,
|
|
46
|
+
commands=commands,
|
|
47
|
+
config_path=DA_CONFIG_PATH,
|
|
48
|
+
schema_path=SCHEMA_PATH,
|
|
49
|
+
schema_version=SCHEMA_VERSION,
|
|
50
|
+
custom_headers=custom_headers,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
AUTH_TYPES = ["CognitoAuth", "CustomAuth", "OidcBrowserAuth"]
|
|
55
|
+
DEFAULT_REDIRECT_URI = "http://localhost:8765/callback"
|
|
56
|
+
DEFAULT_FALLBACK_REDIRECT_URI = "http://localhost:8766/callback"
|
|
57
|
+
DEFAULT_SCOPES = "openid offline_access"
|
|
58
|
+
DISCOVERED = "dataall_discovered"
|
|
59
|
+
DISCOVERED_FOR = "dataall_discovered_for"
|
|
60
|
+
|
|
61
|
+
DOMAIN_PROMPT = "Enter data.all's domain URL (e.g. https://<DOMAIN>.com)"
|
|
62
|
+
IDP_PROMPT = "Enter data.all Identity Provider Domain (e.g. https://<IdP-DOMAIN>.com)"
|
|
63
|
+
ISSUER_PROMPT = (
|
|
64
|
+
"Enter OIDC issuer URL (e.g. https://<ORG>.okta.com/oauth2/<AUTH-SERVER-ID>)"
|
|
65
|
+
)
|
|
66
|
+
SECRET_PROMPT = "Enter IdP client secret (if applicable)"
|
|
67
|
+
AUTH_SERVER_PROMPT = "Enter IdP custom auth server (if applicable)"
|
|
68
|
+
FRONT_PAGE_PROMPT = "Enter data.all front page URL (leave empty to configure manually)"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class AuthScopedOption(click.Option):
|
|
72
|
+
"""Option whose prompt depends on ``--auth_type``.
|
|
73
|
+
|
|
74
|
+
``scoped`` maps an auth type to ``{"prompt": text, "default": value}``. A spec
|
|
75
|
+
without ``prompt`` uses its default silently; auth types not listed get ``None``;
|
|
76
|
+
without ``scoped`` the option prompts normally. Values discovered from
|
|
77
|
+
``--dataall_url`` are used without prompting.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
*args: Any,
|
|
83
|
+
scoped: Optional[Dict[str, Dict[str, Any]]] = None,
|
|
84
|
+
**kwargs: Any,
|
|
85
|
+
) -> None:
|
|
86
|
+
self.scoped = scoped
|
|
87
|
+
kwargs.setdefault("prompt", True)
|
|
88
|
+
super().__init__(*args, **kwargs)
|
|
89
|
+
|
|
90
|
+
def prompt_for_value(self, ctx: click.Context) -> Any:
|
|
91
|
+
"""Return the value for the active auth type, prompting only when needed."""
|
|
92
|
+
discovered = ctx.meta.get(DISCOVERED, {})
|
|
93
|
+
if self.name in discovered:
|
|
94
|
+
return discovered[self.name]
|
|
95
|
+
if self.scoped is None:
|
|
96
|
+
return super().prompt_for_value(ctx)
|
|
97
|
+
spec = self.scoped.get(str(ctx.params.get("auth_type")))
|
|
98
|
+
if spec is None:
|
|
99
|
+
return None
|
|
100
|
+
if "prompt" not in spec:
|
|
101
|
+
return spec.get("default")
|
|
102
|
+
return click.prompt(
|
|
103
|
+
spec["prompt"],
|
|
104
|
+
default=spec.get("default"),
|
|
105
|
+
type=self.type,
|
|
106
|
+
value_proc=lambda x: self.process_value(ctx, x),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _discover(
|
|
111
|
+
ctx: click.Context, _param: click.Parameter, value: Optional[str]
|
|
112
|
+
) -> Optional[str]:
|
|
113
|
+
if not value or ctx.meta.get(DISCOVERED_FOR) == value:
|
|
114
|
+
return value
|
|
115
|
+
ctx.meta[DISCOVERED_FOR] = value
|
|
116
|
+
try:
|
|
117
|
+
found = discover_from_frontend(value)
|
|
118
|
+
except Exception as e:
|
|
119
|
+
click.echo(f"Could not read settings from {value}: {e}", err=True)
|
|
120
|
+
found = {"frontend_url": frontend_origin(value)}
|
|
121
|
+
chosen = ctx.params.get("auth_type")
|
|
122
|
+
detected = found.get("auth_type")
|
|
123
|
+
if chosen and detected and chosen != detected:
|
|
124
|
+
click.echo(
|
|
125
|
+
f"The front page uses {detected} but --auth_type {chosen} was given; "
|
|
126
|
+
"keeping only the page URL",
|
|
127
|
+
err=True,
|
|
128
|
+
)
|
|
129
|
+
found = {"frontend_url": found["frontend_url"]}
|
|
130
|
+
for key, item in found.items():
|
|
131
|
+
click.echo(f"Discovered {key}: {item}", err=True)
|
|
132
|
+
ctx.meta[DISCOVERED] = found
|
|
133
|
+
return value
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _for(auth_types: List[str], **spec: Any) -> Dict[str, Dict[str, Any]]:
|
|
137
|
+
return {auth_type: dict(spec) for auth_type in auth_types}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@dataall_cli.command()
|
|
141
|
+
@click.option(
|
|
142
|
+
"--dataall_url",
|
|
143
|
+
prompt=FRONT_PAGE_PROMPT,
|
|
144
|
+
default="",
|
|
145
|
+
show_default=False,
|
|
146
|
+
expose_value=False,
|
|
147
|
+
callback=_discover,
|
|
148
|
+
help="data.all front page URL; the auth type, IdP, client id and API endpoint are read from it",
|
|
149
|
+
)
|
|
150
|
+
@click.option(
|
|
151
|
+
"--auth_type",
|
|
152
|
+
cls=AuthScopedOption,
|
|
153
|
+
type=click.Choice(AUTH_TYPES),
|
|
154
|
+
default="CognitoAuth",
|
|
155
|
+
prompt="Select authentication type",
|
|
156
|
+
help="Authentication type: Cognito, Custom (username/password) or OIDC browser login",
|
|
157
|
+
)
|
|
158
|
+
@click.option(
|
|
159
|
+
"--client_id",
|
|
160
|
+
cls=AuthScopedOption,
|
|
161
|
+
required=True,
|
|
162
|
+
scoped=_for(AUTH_TYPES, prompt="Enter data.all app client id"),
|
|
163
|
+
help="data.all app client id",
|
|
164
|
+
)
|
|
165
|
+
@click.option(
|
|
166
|
+
"--api_endpoint_url",
|
|
167
|
+
cls=AuthScopedOption,
|
|
168
|
+
required=True,
|
|
169
|
+
scoped=_for(AUTH_TYPES, prompt="Enter data.all API endpoint url"),
|
|
170
|
+
help="data.all API endpoint url",
|
|
171
|
+
)
|
|
172
|
+
@click.option(
|
|
173
|
+
"--redirect_uri",
|
|
174
|
+
cls=AuthScopedOption,
|
|
175
|
+
required=True,
|
|
176
|
+
scoped={
|
|
177
|
+
**_for(["CognitoAuth", "CustomAuth"], prompt=DOMAIN_PROMPT),
|
|
178
|
+
"OidcBrowserAuth": {"default": DEFAULT_REDIRECT_URI},
|
|
179
|
+
},
|
|
180
|
+
help="OAuth redirect URI: the data.all domain URL, or the loopback URI registered for the CLI",
|
|
181
|
+
)
|
|
182
|
+
@click.option(
|
|
183
|
+
"--idp_domain_url",
|
|
184
|
+
cls=AuthScopedOption,
|
|
185
|
+
required=True,
|
|
186
|
+
scoped={
|
|
187
|
+
**_for(["CognitoAuth", "CustomAuth"], prompt=IDP_PROMPT),
|
|
188
|
+
"OidcBrowserAuth": {"prompt": ISSUER_PROMPT},
|
|
189
|
+
},
|
|
190
|
+
help="Identity provider domain, or the OIDC issuer URL for browser login",
|
|
191
|
+
)
|
|
192
|
+
@click.option(
|
|
193
|
+
"--client_secret",
|
|
194
|
+
cls=AuthScopedOption,
|
|
195
|
+
required=False,
|
|
196
|
+
scoped=_for(["CognitoAuth", "CustomAuth"], prompt=SECRET_PROMPT, default=""),
|
|
197
|
+
help="IdP client secret, if the app has one",
|
|
198
|
+
)
|
|
199
|
+
@click.option(
|
|
200
|
+
"--auth_server",
|
|
201
|
+
cls=AuthScopedOption,
|
|
202
|
+
required=False,
|
|
203
|
+
scoped=_for(
|
|
204
|
+
["CognitoAuth", "CustomAuth"], prompt=AUTH_SERVER_PROMPT, default="default"
|
|
205
|
+
),
|
|
206
|
+
help="identity provider's custom authorization server used to get well-known openid config",
|
|
207
|
+
)
|
|
208
|
+
@click.option(
|
|
209
|
+
"--scopes",
|
|
210
|
+
cls=AuthScopedOption,
|
|
211
|
+
required=False,
|
|
212
|
+
scoped={"OidcBrowserAuth": {"default": DEFAULT_SCOPES}},
|
|
213
|
+
help="OIDC scopes for browser login; use 'openid' if the IdP rejects offline_access",
|
|
214
|
+
)
|
|
215
|
+
@click.option(
|
|
216
|
+
"--frontend_url",
|
|
217
|
+
cls=AuthScopedOption,
|
|
218
|
+
required=False,
|
|
219
|
+
scoped={
|
|
220
|
+
"OidcBrowserAuth": {"prompt": "Enter data.all front page URL", "default": ""}
|
|
221
|
+
},
|
|
222
|
+
help="data.all UI URL sent as Origin header; some deployments only accept API calls carrying it",
|
|
223
|
+
)
|
|
224
|
+
@click.option(
|
|
225
|
+
"--fallback_redirect_uri",
|
|
226
|
+
default=DEFAULT_FALLBACK_REDIRECT_URI,
|
|
227
|
+
help="second loopback URI tried when the first port is busy (browser login)",
|
|
228
|
+
)
|
|
229
|
+
@click.option(
|
|
230
|
+
"--profile",
|
|
231
|
+
prompt="Enter data.all profile name",
|
|
232
|
+
default="default",
|
|
233
|
+
help="profile name for dataall_cli configured user",
|
|
234
|
+
)
|
|
235
|
+
def configure(
|
|
236
|
+
auth_type: str,
|
|
237
|
+
client_id: str,
|
|
238
|
+
api_endpoint_url: str,
|
|
239
|
+
redirect_uri: str,
|
|
240
|
+
idp_domain_url: str,
|
|
241
|
+
client_secret: Optional[str],
|
|
242
|
+
auth_server: Optional[str],
|
|
243
|
+
scopes: Optional[str],
|
|
244
|
+
frontend_url: Optional[str],
|
|
245
|
+
fallback_redirect_uri: str,
|
|
246
|
+
profile: str,
|
|
247
|
+
) -> None:
|
|
248
|
+
"""Configure data.all client for a given user, use profile to setup multiple user profiles."""
|
|
249
|
+
click.echo("Configuring data.all CLI...", err=True)
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
profile_params_dict: Dict[str, Any] = {
|
|
253
|
+
"client_id": client_id,
|
|
254
|
+
"api_endpoint_url": api_endpoint_url,
|
|
255
|
+
"auth_type": auth_type,
|
|
256
|
+
"idp_domain_url": idp_domain_url,
|
|
257
|
+
"redirect_uri": redirect_uri,
|
|
258
|
+
"client_secret": client_secret,
|
|
259
|
+
}
|
|
260
|
+
if auth_type == "CustomAuth":
|
|
261
|
+
session_token_endpoint = click.prompt("Enter session token endpoint")
|
|
262
|
+
profile_params_dict.update(
|
|
263
|
+
{
|
|
264
|
+
"auth_server": auth_server,
|
|
265
|
+
"session_token_endpoint": session_token_endpoint,
|
|
266
|
+
}
|
|
267
|
+
)
|
|
268
|
+
if auth_type == "OidcBrowserAuth":
|
|
269
|
+
profile_params_dict.update(
|
|
270
|
+
{"scopes": scopes, "fallback_redirect_uri": fallback_redirect_uri}
|
|
271
|
+
)
|
|
272
|
+
if frontend_url:
|
|
273
|
+
profile_params_dict["frontend_url"] = frontend_url.rstrip("/")
|
|
274
|
+
if CREDS_PATH:
|
|
275
|
+
profile_params_dict.update(
|
|
276
|
+
{
|
|
277
|
+
"creds_path": str(CREDS_PATH),
|
|
278
|
+
}
|
|
279
|
+
)
|
|
280
|
+
save_config(
|
|
281
|
+
profile=profile,
|
|
282
|
+
auth_type=auth_type,
|
|
283
|
+
params_dict=profile_params_dict,
|
|
284
|
+
config_path=Path(DA_CONFIG_PATH),
|
|
285
|
+
)
|
|
286
|
+
click.echo("data.all CLI configured successfully.", err=True)
|
|
287
|
+
except Exception as e:
|
|
288
|
+
click.echo(f"An error occurred: {e}", err=True)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Discover CLI settings from a deployed data.all front page."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Dict
|
|
5
|
+
from urllib.parse import urljoin, urlparse
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
BUNDLE_PATTERN = re.compile(r'src="([^"]*static/js/main\.[^"]+\.js)"')
|
|
10
|
+
VALUE_PATTERN = re.compile(r'(REACT_APP_[A-Z_]+):"([^"]*)"')
|
|
11
|
+
API_SUFFIX = "/graphql/api"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def frontend_origin(dataall_url: str) -> str:
|
|
15
|
+
"""Return ``scheme://host`` of a data.all URL."""
|
|
16
|
+
parsed = urlparse(dataall_url)
|
|
17
|
+
return f"{parsed.scheme}://{parsed.netloc}"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def discover_from_frontend(dataall_url: str) -> Dict[str, str]:
|
|
21
|
+
"""Read the auth type, identity provider, client id and API endpoint from the front page bundle.
|
|
22
|
+
|
|
23
|
+
Returns ``frontend_url`` plus whichever values were found.
|
|
24
|
+
"""
|
|
25
|
+
found = {"frontend_url": frontend_origin(dataall_url)}
|
|
26
|
+
base = dataall_url.rstrip("/") + "/"
|
|
27
|
+
index = httpx.get(base, follow_redirects=True, timeout=30)
|
|
28
|
+
index.raise_for_status()
|
|
29
|
+
match = BUNDLE_PATTERN.search(index.text)
|
|
30
|
+
if not match:
|
|
31
|
+
return found
|
|
32
|
+
bundle = httpx.get(urljoin(base, match.group(1)), follow_redirects=True, timeout=60)
|
|
33
|
+
bundle.raise_for_status()
|
|
34
|
+
values = dict(VALUE_PATTERN.findall(bundle.text))
|
|
35
|
+
|
|
36
|
+
if values.get("REACT_APP_CUSTOM_AUTH") and values.get("REACT_APP_CUSTOM_AUTH_URL"):
|
|
37
|
+
found["auth_type"] = "OidcBrowserAuth"
|
|
38
|
+
found["idp_domain_url"] = values["REACT_APP_CUSTOM_AUTH_URL"]
|
|
39
|
+
if values.get("REACT_APP_CUSTOM_AUTH_CLIENT_ID"):
|
|
40
|
+
found["client_id"] = values["REACT_APP_CUSTOM_AUTH_CLIENT_ID"]
|
|
41
|
+
elif values.get("REACT_APP_COGNITO_APP_CLIENT_ID"):
|
|
42
|
+
found["auth_type"] = "CognitoAuth"
|
|
43
|
+
found["client_id"] = values["REACT_APP_COGNITO_APP_CLIENT_ID"]
|
|
44
|
+
domain = values.get("REACT_APP_COGNITO_DOMAIN", "")
|
|
45
|
+
if domain:
|
|
46
|
+
found["idp_domain_url"] = domain if "://" in domain else f"https://{domain}"
|
|
47
|
+
if values.get("REACT_APP_COGNITO_REDIRECT_SIGNIN"):
|
|
48
|
+
found["redirect_uri"] = values["REACT_APP_COGNITO_REDIRECT_SIGNIN"]
|
|
49
|
+
api = values.get("REACT_APP_GRAPHQL_API", "")
|
|
50
|
+
if api:
|
|
51
|
+
found["api_endpoint_url"] = (
|
|
52
|
+
api[: -len(API_SUFFIX)] if api.endswith(API_SUFFIX) else api
|
|
53
|
+
)
|
|
54
|
+
return found
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dataall-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5.0"
|
|
4
4
|
description = "AWS data.all CLI"
|
|
5
5
|
authors = [{ name = "Amazon Web Services" }]
|
|
6
6
|
license = { text = "Apache License 2.0" }
|
|
@@ -14,15 +14,15 @@ classifiers = [
|
|
|
14
14
|
]
|
|
15
15
|
requires-python = ">=3.9,<4.0"
|
|
16
16
|
dependencies = [
|
|
17
|
-
"boto3>=1.
|
|
18
|
-
"botocore>=1.
|
|
17
|
+
"boto3>=1.40.55",
|
|
18
|
+
"botocore>=1.40.55",
|
|
19
19
|
"typing-extensions>=4.4.0",
|
|
20
|
-
"packaging>=
|
|
20
|
+
"packaging>=24.2",
|
|
21
21
|
"click>=8.1.6",
|
|
22
22
|
"atomicfile>=1.0.1",
|
|
23
23
|
"PyYAML>=6.0.1",
|
|
24
24
|
"setuptools; python_version >= '3.12'",
|
|
25
|
-
"dataall-core>=0.
|
|
25
|
+
"dataall-core>=0.5.0,<0.6.0",
|
|
26
26
|
]
|
|
27
27
|
|
|
28
28
|
[project.scripts]
|
|
@@ -35,23 +35,23 @@ exclude = ["*.so", "*.pyc", "*~", "#*", ".git*", ".coverage*", "DS_Store", "__py
|
|
|
35
35
|
[tool.poetry.group.dev.dependencies]
|
|
36
36
|
# Build
|
|
37
37
|
setuptools = "*"
|
|
38
|
-
wheel = "^0.
|
|
38
|
+
wheel = "^0.45.1"
|
|
39
39
|
msgpack = "*"
|
|
40
40
|
|
|
41
41
|
# Lint
|
|
42
|
-
boto3-stubs = { version = "^1.
|
|
42
|
+
boto3-stubs = { version = "^1.40.50", extras = ["athena", "cleanrooms", "chime", "cloudwatch", "dynamodb", "ec2", "emr", "emr-serverless", "glue", "kms", "lakeformation", "logs", "neptune", "opensearch", "opensearchserverless", "quicksight", "rds", "rds-data", "redshift", "redshift-data", "s3", "secretsmanager", "ssm", "sts", "timestream-query", "timestream-write"] }
|
|
43
43
|
doc8 = "^1.0"
|
|
44
|
-
mypy = "^1.
|
|
45
|
-
pylint = "^3.
|
|
46
|
-
ruff = "^0.
|
|
44
|
+
mypy = "^1.18"
|
|
45
|
+
pylint = "^3.3"
|
|
46
|
+
ruff = "^0.14.0"
|
|
47
47
|
|
|
48
48
|
# Test
|
|
49
|
-
moto = "^
|
|
49
|
+
moto = "^5.1"
|
|
50
50
|
openpyxl = "^3.0"
|
|
51
|
-
pyparsing = "^3.
|
|
52
|
-
pytest = "^8.4.
|
|
53
|
-
pytest-cov = "^
|
|
54
|
-
pytest-rerunfailures = "^
|
|
51
|
+
pyparsing = "^3.2.5"
|
|
52
|
+
pytest = "^8.4.2"
|
|
53
|
+
pytest-cov = "^7.0"
|
|
54
|
+
pytest-rerunfailures = "^16.0"
|
|
55
55
|
pytest-timeout = "^2.1.0"
|
|
56
56
|
pytest-xdist = "^3.0.2"
|
|
57
57
|
tox = "^4.5.0"
|
|
@@ -64,11 +64,11 @@ nbsphinx-link = "^1.3.0"
|
|
|
64
64
|
sphinx = "^7.1"
|
|
65
65
|
sphinx-bootstrap-theme = "^0.8"
|
|
66
66
|
sphinx-copybutton = "^0.5.1"
|
|
67
|
-
pydot = "^
|
|
68
|
-
myst-parser = "^
|
|
69
|
-
sphinx-click = "^
|
|
67
|
+
pydot = "^4.0.1"
|
|
68
|
+
myst-parser = "^3.0.1"
|
|
69
|
+
sphinx-click = "^6.0.0"
|
|
70
70
|
sphinx-autobuild = "^2024.4.0"
|
|
71
|
-
pytest-mock = "^3.
|
|
71
|
+
pytest-mock = "^3.15.1"
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
[build-system]
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
"""CLI for data.all."""
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import logging
|
|
5
|
-
import os
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
import click
|
|
9
|
-
from dataall_core.dataall_client import DataallClient
|
|
10
|
-
from dataall_core.profile import CONFIG_PATH
|
|
11
|
-
|
|
12
|
-
from dataall_cli.bind_commands import bind
|
|
13
|
-
from dataall_cli.utils import save_config
|
|
14
|
-
|
|
15
|
-
DA_CONFIG_PATH = os.getenv("dataall_config_path", CONFIG_PATH)
|
|
16
|
-
CREDS_PATH = os.getenv("dataall_creds_path", None)
|
|
17
|
-
SCHEMA_PATH = os.getenv("dataall_schema_path", None)
|
|
18
|
-
SCHEMA_VERSION = os.getenv("dataall_schema_version", None)
|
|
19
|
-
DA_CUSTOM_HEADERS_JSON: str = os.getenv("dataall_custom_headers_json", "{}")
|
|
20
|
-
|
|
21
|
-
logger = logging.getLogger(__name__)
|
|
22
|
-
|
|
23
|
-
try:
|
|
24
|
-
custom_headers = json.loads(DA_CUSTOM_HEADERS_JSON)
|
|
25
|
-
except ValueError:
|
|
26
|
-
logger.info(
|
|
27
|
-
f"Invalid custom headers json string: {DA_CUSTOM_HEADERS_JSON}. Using default headers..."
|
|
28
|
-
)
|
|
29
|
-
custom_headers = {}
|
|
30
|
-
|
|
31
|
-
da = DataallClient(schema_path=SCHEMA_PATH, schema_version=SCHEMA_VERSION)
|
|
32
|
-
default_client = da.client(config_path=DA_CONFIG_PATH, custom_headers=custom_headers)
|
|
33
|
-
commands = da.op_dict
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@click.group(name="dataall_cli", invoke_without_command=True)
|
|
37
|
-
def dataall_cli() -> None:
|
|
38
|
-
"""data.all cli groups."""
|
|
39
|
-
click.echo("Executing dataall_cli.", err=True)
|
|
40
|
-
pass
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
bind(
|
|
44
|
-
dataall_cli=dataall_cli,
|
|
45
|
-
commands=commands,
|
|
46
|
-
config_path=DA_CONFIG_PATH,
|
|
47
|
-
schema_path=SCHEMA_PATH,
|
|
48
|
-
schema_version=SCHEMA_VERSION,
|
|
49
|
-
custom_headers=custom_headers,
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@dataall_cli.command()
|
|
54
|
-
@click.option(
|
|
55
|
-
"--auth_type",
|
|
56
|
-
type=click.Choice(["CognitoAuth", "CustomAuth"]),
|
|
57
|
-
default="CognitoAuth",
|
|
58
|
-
prompt="Select authentication type",
|
|
59
|
-
help="Authentication type: Cognito or Custom",
|
|
60
|
-
)
|
|
61
|
-
@click.option(
|
|
62
|
-
"--client_id",
|
|
63
|
-
required=True,
|
|
64
|
-
prompt="Enter data.all app client id",
|
|
65
|
-
help="data.all app client id",
|
|
66
|
-
)
|
|
67
|
-
@click.option(
|
|
68
|
-
"--api_endpoint_url",
|
|
69
|
-
required=True,
|
|
70
|
-
prompt="Enter data.all API endpoint url",
|
|
71
|
-
help="data.all API endpoint url",
|
|
72
|
-
)
|
|
73
|
-
@click.option(
|
|
74
|
-
"--redirect_uri",
|
|
75
|
-
required=True,
|
|
76
|
-
prompt="Enter data.all's domain URL (e.g. https://<DOMAIN>.com)",
|
|
77
|
-
help="data.all domain URL",
|
|
78
|
-
)
|
|
79
|
-
@click.option(
|
|
80
|
-
"--idp_domain_url",
|
|
81
|
-
required=True,
|
|
82
|
-
prompt="Enter data.all Identity Provider Domain (e.g. https://<IdP-DOMAIN>.com)",
|
|
83
|
-
help="data.all IdP domain URL",
|
|
84
|
-
)
|
|
85
|
-
@click.option(
|
|
86
|
-
"--client_secret",
|
|
87
|
-
required=False,
|
|
88
|
-
prompt="Enter IdP client secret (if applicable)",
|
|
89
|
-
default="",
|
|
90
|
-
help="profile name for dataall_cli configured user",
|
|
91
|
-
)
|
|
92
|
-
@click.option(
|
|
93
|
-
"--auth_server",
|
|
94
|
-
prompt="Enter IdP custom auth server (if applicable)",
|
|
95
|
-
default="default",
|
|
96
|
-
help="identity provider's custom authorization server used to get well-known openid config",
|
|
97
|
-
)
|
|
98
|
-
@click.option(
|
|
99
|
-
"--profile",
|
|
100
|
-
prompt="Enter data.all profile name",
|
|
101
|
-
default="default",
|
|
102
|
-
help="profile name for dataall_cli configured user",
|
|
103
|
-
)
|
|
104
|
-
def configure(
|
|
105
|
-
client_id: str,
|
|
106
|
-
api_endpoint_url: str,
|
|
107
|
-
auth_type: str,
|
|
108
|
-
redirect_uri: str,
|
|
109
|
-
idp_domain_url: str,
|
|
110
|
-
client_secret: str,
|
|
111
|
-
auth_server: str,
|
|
112
|
-
profile: str,
|
|
113
|
-
) -> None:
|
|
114
|
-
"""Configure data.all client for a given user, use profile to setup multiple user profiles."""
|
|
115
|
-
click.echo("Configuring data.all CLI...", err=True)
|
|
116
|
-
|
|
117
|
-
try:
|
|
118
|
-
profile_params_dict = {
|
|
119
|
-
"client_id": client_id,
|
|
120
|
-
"api_endpoint_url": api_endpoint_url,
|
|
121
|
-
"auth_type": auth_type,
|
|
122
|
-
"idp_domain_url": idp_domain_url,
|
|
123
|
-
"redirect_uri": redirect_uri,
|
|
124
|
-
"client_secret": client_secret,
|
|
125
|
-
}
|
|
126
|
-
if auth_type == "CustomAuth":
|
|
127
|
-
session_token_endpoint = click.prompt("Enter session token endpoint")
|
|
128
|
-
profile_params_dict.update(
|
|
129
|
-
{
|
|
130
|
-
"auth_server": auth_server,
|
|
131
|
-
"session_token_endpoint": session_token_endpoint,
|
|
132
|
-
}
|
|
133
|
-
)
|
|
134
|
-
if CREDS_PATH:
|
|
135
|
-
profile_params_dict.update(
|
|
136
|
-
{
|
|
137
|
-
"creds_path": str(CREDS_PATH),
|
|
138
|
-
}
|
|
139
|
-
)
|
|
140
|
-
save_config(
|
|
141
|
-
profile=profile,
|
|
142
|
-
auth_type=auth_type,
|
|
143
|
-
params_dict=profile_params_dict,
|
|
144
|
-
config_path=Path(DA_CONFIG_PATH),
|
|
145
|
-
)
|
|
146
|
-
click.echo("data.all CLI configured successfully.", err=True)
|
|
147
|
-
except Exception as e:
|
|
148
|
-
click.echo(f"An error occurred: {e}", err=True)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|