validio-cli 0.23.0__tar.gz → 0.24.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.
- {validio_cli-0.23.0 → validio_cli-0.24.0}/PKG-INFO +1 -1
- {validio_cli-0.23.0 → validio_cli-0.24.0}/pyproject.toml +3 -3
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/__init__.py +44 -13
- validio_cli-0.24.0/validio_cli/bin/entities/channels.py +57 -0
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/bin/entities/code.py +104 -79
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/bin/entities/config.py +3 -13
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/bin/entities/credentials.py +26 -27
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/bin/entities/dbt.py +4 -4
- validio_cli-0.24.0/validio_cli/bin/entities/filters.py +66 -0
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/bin/entities/incidents.py +28 -33
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/bin/entities/metrics.py +13 -22
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/bin/entities/namespaces.py +37 -45
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/bin/entities/notification_rules.py +25 -26
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/bin/entities/recommendations.py +10 -25
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/bin/entities/resources.py +39 -29
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/bin/entities/segmentations.py +29 -32
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/bin/entities/segments.py +10 -13
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/bin/entities/sources.py +293 -224
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/bin/entities/users.py +7 -14
- validio_cli-0.24.0/validio_cli/bin/entities/validators.py +113 -0
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/bin/entities/windows.py +22 -27
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/bin/main.py +3 -15
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/components.py +2 -0
- validio_cli-0.23.0/validio_cli/bin/entities/channels.py +0 -96
- validio_cli-0.23.0/validio_cli/bin/entities/validators.py +0 -125
- {validio_cli-0.23.0 → validio_cli-0.24.0}/LICENSE +0 -0
- {validio_cli-0.23.0 → validio_cli-0.24.0}/README_PUBLIC.md +0 -0
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/metadata.py +0 -0
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/namespace.py +0 -0
- {validio_cli-0.23.0 → validio_cli-0.24.0}/validio_cli/schema.py +0 -0
|
@@ -3,7 +3,7 @@ name = "validio-cli"
|
|
|
3
3
|
# This version does not represent the released version or any tag. For each
|
|
4
4
|
# release we automatically bump this before building and publishing so this
|
|
5
5
|
# should be kept at 0.0.1dev1
|
|
6
|
-
version = "0.
|
|
6
|
+
version = "0.24.0"
|
|
7
7
|
description = "CLI tool to interact with the Validio platform"
|
|
8
8
|
authors = ["Validio <support@validio.io>"]
|
|
9
9
|
license = "Apache-2.0"
|
|
@@ -16,20 +16,20 @@ readme = "README_PUBLIC.md"
|
|
|
16
16
|
python = "^3.10"
|
|
17
17
|
camel-converter = "^3.0.0"
|
|
18
18
|
classdiff = "^0.5.0"
|
|
19
|
-
validio-sdk = "*"
|
|
20
19
|
typer = { extras = ["all"], version = "^0.7.0" }
|
|
21
20
|
prompt-toolkit = "^3.0.38"
|
|
22
21
|
tabulate = "^0.9.0"
|
|
23
22
|
types-tabulate = "^0.9.0.2"
|
|
24
23
|
pydantic = "2.4.2"
|
|
24
|
+
validio-sdk = "*"
|
|
25
25
|
|
|
26
26
|
[tool.poetry.group.dev.dependencies]
|
|
27
27
|
ruff = "^0.1.11"
|
|
28
28
|
mypy = "^1.2.0"
|
|
29
|
-
validio-sdk = { path = "../validio-sdk", develop = true }
|
|
30
29
|
licensecheck = "^2023.1.1"
|
|
31
30
|
pytest = "^7.4.3"
|
|
32
31
|
flameprof = "^0.4"
|
|
32
|
+
validio-sdk = { path = "../validio-sdk" }
|
|
33
33
|
|
|
34
34
|
[build-system]
|
|
35
35
|
requires = ["poetry-core"]
|
|
@@ -15,6 +15,7 @@ import typer
|
|
|
15
15
|
from camel_converter import to_snake
|
|
16
16
|
from tabulate import tabulate
|
|
17
17
|
from validio_sdk import config
|
|
18
|
+
from validio_sdk._api.api import APIClient
|
|
18
19
|
from validio_sdk.config import Config, ValidioConfig
|
|
19
20
|
from validio_sdk.util import ClassJSONEncoder
|
|
20
21
|
from validio_sdk.validio_client import ValidioAPIClient
|
|
@@ -25,6 +26,14 @@ T = TypeVar("T", bound="OutputSettings")
|
|
|
25
26
|
DEFAULT_NAMESPACE: str = "default"
|
|
26
27
|
|
|
27
28
|
|
|
29
|
+
class Role(str, Enum):
|
|
30
|
+
"""User roles."""
|
|
31
|
+
|
|
32
|
+
ADMIN = "ADMIN"
|
|
33
|
+
EDITOR = "EDITOR"
|
|
34
|
+
VIEWER = "VIEWER"
|
|
35
|
+
|
|
36
|
+
|
|
28
37
|
class OutputFormat(str, Enum):
|
|
29
38
|
"""Available output formats for the CLI"""
|
|
30
39
|
|
|
@@ -45,6 +54,9 @@ OutputFormatOption = typer.Option(
|
|
|
45
54
|
Identifier = typer.Argument(
|
|
46
55
|
default=None, help="Name or ID of the resource to get of this type"
|
|
47
56
|
)
|
|
57
|
+
Identifiers = typer.Argument(
|
|
58
|
+
default=None, help="Names or IDs of the sources to apply the command on"
|
|
59
|
+
)
|
|
48
60
|
|
|
49
61
|
|
|
50
62
|
# ruff: noqa: N802
|
|
@@ -82,13 +94,20 @@ class AsyncTyper(typer.Typer):
|
|
|
82
94
|
return decorator
|
|
83
95
|
|
|
84
96
|
|
|
97
|
+
def get_client(
|
|
98
|
+
config_dir: str = ConfigDir,
|
|
99
|
+
) -> tuple[APIClient, ValidioConfig]:
|
|
100
|
+
cfg = Config(Path(config_dir)).read()
|
|
101
|
+
return APIClient(config=cfg), cfg
|
|
102
|
+
|
|
103
|
+
|
|
85
104
|
async def get_client_and_config(
|
|
86
105
|
config_dir: str = ConfigDir,
|
|
87
106
|
) -> tuple[ValidioAPIClient, ValidioConfig]:
|
|
88
107
|
cfg = Config(Path(config_dir)).read()
|
|
89
108
|
vc = ValidioAPIClient(validio_config=cfg)
|
|
90
109
|
|
|
91
|
-
return
|
|
110
|
+
return vc, cfg
|
|
92
111
|
|
|
93
112
|
|
|
94
113
|
@dataclass
|
|
@@ -104,6 +123,16 @@ class OutputSettings:
|
|
|
104
123
|
reformat=lambda x: to_snake(x.removesuffix(trim)).upper(),
|
|
105
124
|
)
|
|
106
125
|
|
|
126
|
+
@classmethod
|
|
127
|
+
def string_as_datetime(cls: type[T], attribute_name: str | None) -> T:
|
|
128
|
+
format = "%Y-%m-%dT%H:%M:%S.%fZ"
|
|
129
|
+
return cls(
|
|
130
|
+
attribute_name=attribute_name,
|
|
131
|
+
reformat=lambda x: datetime.strptime(x, format).replace(
|
|
132
|
+
tzinfo=timezone.utc
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
|
|
107
136
|
|
|
108
137
|
def output_json(obj: Any, identifier: str | None = None) -> None:
|
|
109
138
|
# If we have an identifier we only want to display a single object so grab
|
|
@@ -135,17 +164,20 @@ def output_text(items: Any, fields: dict[str, OutputSettings | None]) -> None:
|
|
|
135
164
|
row.append(settings.reformat(item))
|
|
136
165
|
continue
|
|
137
166
|
|
|
138
|
-
if
|
|
167
|
+
if isinstance(item, dict):
|
|
168
|
+
value = item.get(attribute_name)
|
|
169
|
+
elif hasattr(item, attribute_name):
|
|
170
|
+
value = getattr(item, attribute_name)
|
|
171
|
+
else:
|
|
139
172
|
row.append("UNKNOWN")
|
|
140
173
|
continue
|
|
141
174
|
|
|
142
|
-
value = getattr(item, attribute_name)
|
|
143
|
-
if isinstance(value, datetime):
|
|
144
|
-
value = _format_relative_time(value)
|
|
145
|
-
|
|
146
175
|
if settings is not None and settings.reformat is not None:
|
|
147
176
|
value = settings.reformat(value)
|
|
148
177
|
|
|
178
|
+
if isinstance(value, datetime):
|
|
179
|
+
value = _format_relative_time(value)
|
|
180
|
+
|
|
149
181
|
row.append(value)
|
|
150
182
|
|
|
151
183
|
table.append(row)
|
|
@@ -199,7 +231,10 @@ def _single_resource_if_specified(items: list[Any], identifier: str | None) -> A
|
|
|
199
231
|
for item in items
|
|
200
232
|
if (hasattr(item, "resource_name") and item.resource_name == identifier)
|
|
201
233
|
or (hasattr(item, "name") and item.name == identifier)
|
|
202
|
-
or item.id == identifier
|
|
234
|
+
or (hasattr(item, "id") and item.id == identifier)
|
|
235
|
+
or (isinstance(item, dict) and item.get("resourceName") == identifier)
|
|
236
|
+
or (isinstance(item, dict) and item.get("name") == identifier)
|
|
237
|
+
or (isinstance(item, dict) and item.get("id") == identifier)
|
|
203
238
|
),
|
|
204
239
|
None,
|
|
205
240
|
)
|
|
@@ -220,18 +255,14 @@ def _resource_filter(
|
|
|
220
255
|
|
|
221
256
|
# Traverse the object til the resource we need for filtering.
|
|
222
257
|
for field in field_path:
|
|
223
|
-
|
|
224
|
-
# it out.
|
|
225
|
-
if not hasattr(resource, field):
|
|
226
|
-
return False
|
|
258
|
+
resource = resource.get(field)
|
|
227
259
|
|
|
228
|
-
resource = getattr(resource, field)
|
|
229
260
|
# If the object field value we need for filtering out is none we filter
|
|
230
261
|
# it out.
|
|
231
262
|
if resource is None:
|
|
232
263
|
return False
|
|
233
264
|
|
|
234
|
-
return value in
|
|
265
|
+
return value in [resource["id"], resource["resourceName"]]
|
|
235
266
|
|
|
236
267
|
|
|
237
268
|
def _fixed_width(message: str, width: int = 25) -> str:
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from camel_converter import to_snake
|
|
3
|
+
|
|
4
|
+
from validio_cli import (
|
|
5
|
+
AsyncTyper,
|
|
6
|
+
ConfigDir,
|
|
7
|
+
Identifier,
|
|
8
|
+
Namespace,
|
|
9
|
+
OutputFormat,
|
|
10
|
+
OutputFormatOption,
|
|
11
|
+
OutputSettings,
|
|
12
|
+
_single_resource_if_specified,
|
|
13
|
+
get_client,
|
|
14
|
+
output_json,
|
|
15
|
+
output_text,
|
|
16
|
+
)
|
|
17
|
+
from validio_cli.namespace import get_namespace
|
|
18
|
+
|
|
19
|
+
app = AsyncTyper(help="Channels used for notifications")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@app.async_command(help="Get channels")
|
|
23
|
+
async def get(
|
|
24
|
+
config_dir: str = ConfigDir,
|
|
25
|
+
output_format: OutputFormat = OutputFormatOption,
|
|
26
|
+
namespace: str = Namespace(),
|
|
27
|
+
identifier: str = Identifier,
|
|
28
|
+
) -> None:
|
|
29
|
+
client, cfg = get_client(config_dir)
|
|
30
|
+
|
|
31
|
+
channels = await client.get_channels(
|
|
32
|
+
channel_id=identifier,
|
|
33
|
+
namespace_id=get_namespace(namespace, cfg),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# TODO(UI-2311): Fully support list/get/get_by_resource_name
|
|
37
|
+
if isinstance(channels, list):
|
|
38
|
+
channels = _single_resource_if_specified(channels, identifier)
|
|
39
|
+
|
|
40
|
+
if output_format == OutputFormat.JSON:
|
|
41
|
+
return output_json(channels, identifier)
|
|
42
|
+
|
|
43
|
+
return output_text(
|
|
44
|
+
channels,
|
|
45
|
+
fields={
|
|
46
|
+
"name": OutputSettings(attribute_name="resourceName"),
|
|
47
|
+
"type": OutputSettings(
|
|
48
|
+
attribute_name="__typename",
|
|
49
|
+
reformat=lambda x: to_snake(x.removesuffix("Channel")).upper(),
|
|
50
|
+
),
|
|
51
|
+
"age": OutputSettings.string_as_datetime("createdAt"),
|
|
52
|
+
},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
typer.run(app())
|
|
@@ -8,22 +8,22 @@ from typing import Any
|
|
|
8
8
|
import classdiff # type: ignore
|
|
9
9
|
import typer
|
|
10
10
|
from validio_sdk import ValidioError
|
|
11
|
+
from validio_sdk._api.api import APIClient
|
|
11
12
|
from validio_sdk.code import _import as code_import
|
|
12
13
|
from validio_sdk.code import apply as code_apply
|
|
13
14
|
from validio_sdk.code import plan as code_plan
|
|
14
15
|
from validio_sdk.code import scaffold
|
|
15
|
-
from validio_sdk.resource._diff import
|
|
16
|
-
from validio_sdk.resource._resource import Resource
|
|
16
|
+
from validio_sdk.resource._diff import GraphDiff, ResourceUpdate
|
|
17
|
+
from validio_sdk.resource._resource import DiffContext, Resource, ResourceDeprecation
|
|
17
18
|
from validio_sdk.resource._util import SourceSchemaReinference
|
|
18
19
|
from validio_sdk.resource.replacement import (
|
|
19
20
|
ImmutableFieldReplacementReason,
|
|
20
21
|
ReplacementReason,
|
|
21
22
|
)
|
|
22
|
-
from validio_sdk.validio_client import ValidioAPIClient
|
|
23
23
|
|
|
24
|
-
from validio_cli import AsyncTyper, ConfigDir, Namespace,
|
|
25
|
-
from validio_cli.bin.entities.resources import do_move
|
|
26
|
-
from validio_cli.components import proceed_with_operation
|
|
24
|
+
from validio_cli import AsyncTyper, ConfigDir, Namespace, get_client
|
|
25
|
+
from validio_cli.bin.entities.resources import ResourceNamesToMove, do_move
|
|
26
|
+
from validio_cli.components import BETA_BANNER, proceed_with_operation
|
|
27
27
|
|
|
28
28
|
"""Maximum number of deprecation warnings to show."""
|
|
29
29
|
MAX_DEPRECATIONS_TO_SHOW = 20
|
|
@@ -99,15 +99,16 @@ update_all_schemas_option = typer.Option(
|
|
|
99
99
|
),
|
|
100
100
|
)
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
)
|
|
102
|
+
|
|
103
|
+
def target_option(resource_type: str) -> Any:
|
|
104
|
+
return typer.Option(
|
|
105
|
+
[],
|
|
106
|
+
help=(
|
|
107
|
+
f"{BETA_BANNER} "
|
|
108
|
+
f'Only target selected {resource_type.replace("-", " ")}(s) and ignore '
|
|
109
|
+
f"changes for others. E.g. `--{resource_type} resource-a"
|
|
110
|
+
),
|
|
111
|
+
)
|
|
111
112
|
|
|
112
113
|
|
|
113
114
|
@app.command(help="Initialize a new IaC project")
|
|
@@ -137,10 +138,16 @@ async def plan(
|
|
|
137
138
|
destroy: bool = destroy_option,
|
|
138
139
|
show_secrets: bool = show_secrets_option,
|
|
139
140
|
color: bool = color_option,
|
|
140
|
-
|
|
141
|
+
channel: list[str] = target_option("channel"),
|
|
142
|
+
credential: list[str] = target_option("credential"),
|
|
143
|
+
notification_rule: list[str] = target_option("notification-rule"),
|
|
144
|
+
segmentation: list[str] = target_option("segmentation"),
|
|
145
|
+
source: list[str] = target_option("source"),
|
|
146
|
+
validator: list[str] = target_option("validator"),
|
|
147
|
+
window: list[str] = target_option("window"),
|
|
141
148
|
) -> None:
|
|
142
149
|
dir_path = directory_or_default(directory)
|
|
143
|
-
client, _ =
|
|
150
|
+
client, _ = get_client(config_dir)
|
|
144
151
|
|
|
145
152
|
schema_reinference = create_source_schema_reinference(
|
|
146
153
|
update_schema, update_all_schemas
|
|
@@ -158,7 +165,15 @@ async def plan(
|
|
|
158
165
|
show_schema=show_schema,
|
|
159
166
|
diff_output=diff_output,
|
|
160
167
|
show_secrets=show_secrets,
|
|
161
|
-
targets=
|
|
168
|
+
targets=code_plan.ResourceNames(
|
|
169
|
+
channels=set(channel),
|
|
170
|
+
credentials=set(credential),
|
|
171
|
+
notification_rules=set(notification_rule),
|
|
172
|
+
segmentations=set(segmentation),
|
|
173
|
+
sources=set(source),
|
|
174
|
+
validators=set(validator),
|
|
175
|
+
windows=set(window),
|
|
176
|
+
),
|
|
162
177
|
)
|
|
163
178
|
|
|
164
179
|
|
|
@@ -177,10 +192,16 @@ async def apply(
|
|
|
177
192
|
destroy: bool = destroy_option,
|
|
178
193
|
show_secrets: bool = show_secrets_option,
|
|
179
194
|
color: bool = color_option,
|
|
180
|
-
|
|
195
|
+
channel: list[str] = target_option("channel"),
|
|
196
|
+
credential: list[str] = target_option("credential"),
|
|
197
|
+
notification_rule: list[str] = target_option("notification-rule"),
|
|
198
|
+
segmentation: list[str] = target_option("segmentation"),
|
|
199
|
+
source: list[str] = target_option("source"),
|
|
200
|
+
validator: list[str] = target_option("validator"),
|
|
201
|
+
window: list[str] = target_option("window"),
|
|
181
202
|
) -> None:
|
|
182
203
|
dir_path = directory_or_default(directory)
|
|
183
|
-
client, _ =
|
|
204
|
+
client, _ = get_client(config_dir)
|
|
184
205
|
|
|
185
206
|
schema_reinference = create_source_schema_reinference(
|
|
186
207
|
update_schema, update_all_schemas
|
|
@@ -198,7 +219,15 @@ async def apply(
|
|
|
198
219
|
show_schema=show_schema,
|
|
199
220
|
diff_output=diff_output,
|
|
200
221
|
show_secrets=show_secrets,
|
|
201
|
-
targets=
|
|
222
|
+
targets=code_plan.ResourceNames(
|
|
223
|
+
channels=set(channel),
|
|
224
|
+
credentials=set(credential),
|
|
225
|
+
notification_rules=set(notification_rule),
|
|
226
|
+
segmentations=set(segmentation),
|
|
227
|
+
sources=set(source),
|
|
228
|
+
validators=set(validator),
|
|
229
|
+
windows=set(window),
|
|
230
|
+
),
|
|
202
231
|
)
|
|
203
232
|
|
|
204
233
|
if diff.num_operations() == 0:
|
|
@@ -241,33 +270,45 @@ async def _import(
|
|
|
241
270
|
None,
|
|
242
271
|
"--import-namespace",
|
|
243
272
|
help=(
|
|
244
|
-
"
|
|
245
|
-
"
|
|
246
|
-
" If providing file to move resources, this is a mandatory parameter"
|
|
273
|
+
"Shorthand to first 'move' resources into the current "
|
|
274
|
+
"project's namespace before performing the import. "
|
|
247
275
|
),
|
|
248
276
|
),
|
|
249
|
-
|
|
277
|
+
credential: list[str] = typer.Option(
|
|
250
278
|
None,
|
|
251
|
-
"--file",
|
|
252
|
-
"-f",
|
|
253
279
|
help=(
|
|
254
|
-
"
|
|
255
|
-
"
|
|
256
|
-
"
|
|
280
|
+
f"{BETA_BANNER} Target credential resources to move into "
|
|
281
|
+
f"the current project's namespace. "
|
|
282
|
+
f"This moves any specified credentials together with their "
|
|
283
|
+
f"attached sources, windows, filters, segmentations and validators. "
|
|
284
|
+
f"Only applicable with '--import-namespace'"
|
|
285
|
+
),
|
|
286
|
+
),
|
|
287
|
+
channel: list[str] = typer.Option(
|
|
288
|
+
None,
|
|
289
|
+
help=(
|
|
290
|
+
f"{BETA_BANNER} Target channel resources to move into "
|
|
291
|
+
f"the current project's namespace. "
|
|
292
|
+
f"This moves any specified channels together with their "
|
|
293
|
+
f"attached notification rules. "
|
|
294
|
+
f"Only applicable with '--import-namespace'"
|
|
257
295
|
),
|
|
258
296
|
),
|
|
259
297
|
auto_approve: bool = typer.Option(
|
|
260
298
|
False, help="Automatically approve and perform the import operation"
|
|
261
299
|
),
|
|
262
300
|
) -> None:
|
|
263
|
-
|
|
301
|
+
resource_names = ResourceNamesToMove(
|
|
302
|
+
credentials=set(credential), channels=set(channel)
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
if resource_names.is_empty() and import_namespace is None:
|
|
264
306
|
raise ValidioError(
|
|
265
|
-
"--import-namespace
|
|
307
|
+
"--import-namespace is required when targeting resources to move"
|
|
266
308
|
)
|
|
267
309
|
|
|
268
310
|
dir_path = directory_or_default(directory)
|
|
269
|
-
client, _ =
|
|
270
|
-
|
|
311
|
+
client, _ = get_client(config_dir)
|
|
271
312
|
namespace = get_namespace(dir_path)
|
|
272
313
|
|
|
273
314
|
if import_namespace:
|
|
@@ -275,7 +316,7 @@ async def _import(
|
|
|
275
316
|
client=client,
|
|
276
317
|
namespace=import_namespace,
|
|
277
318
|
target_namespace=namespace,
|
|
278
|
-
|
|
319
|
+
resource_names=resource_names,
|
|
279
320
|
auto_approve=auto_approve,
|
|
280
321
|
)
|
|
281
322
|
if num_moved_resources is None:
|
|
@@ -284,7 +325,7 @@ async def _import(
|
|
|
284
325
|
if num_moved_resources > 0:
|
|
285
326
|
print(f"Moved {num_moved_resources} resources")
|
|
286
327
|
|
|
287
|
-
diff,
|
|
328
|
+
diff, _ = await _plan(
|
|
288
329
|
namespace=namespace,
|
|
289
330
|
client=client,
|
|
290
331
|
directory=dir_path,
|
|
@@ -295,6 +336,7 @@ async def _import(
|
|
|
295
336
|
show_schema=False,
|
|
296
337
|
show_secrets=False,
|
|
297
338
|
show_diff=False,
|
|
339
|
+
import_mode=True,
|
|
298
340
|
)
|
|
299
341
|
|
|
300
342
|
if diff.num_deletes() == 0:
|
|
@@ -317,7 +359,7 @@ def directory_or_default(directory: str) -> pathlib.Path:
|
|
|
317
359
|
|
|
318
360
|
async def _plan(
|
|
319
361
|
namespace: str,
|
|
320
|
-
client:
|
|
362
|
+
client: APIClient,
|
|
321
363
|
directory: pathlib.Path,
|
|
322
364
|
schema_reinference: SourceSchemaReinference,
|
|
323
365
|
destroy: bool,
|
|
@@ -327,7 +369,8 @@ async def _plan(
|
|
|
327
369
|
diff_output: DiffOutput = DiffOutput.FULL,
|
|
328
370
|
show_secrets: bool = False,
|
|
329
371
|
show_diff: bool = True,
|
|
330
|
-
targets:
|
|
372
|
+
targets: code_plan.ResourceNames = code_plan.ResourceNames(),
|
|
373
|
+
import_mode: bool = False,
|
|
331
374
|
) -> tuple[GraphDiff, DiffContext]:
|
|
332
375
|
plan_result = await code_plan.plan(
|
|
333
376
|
namespace=namespace,
|
|
@@ -337,7 +380,8 @@ async def _plan(
|
|
|
337
380
|
destroy=destroy,
|
|
338
381
|
no_capture=no_capture,
|
|
339
382
|
show_secrets=show_secrets,
|
|
340
|
-
targets=
|
|
383
|
+
targets=targets,
|
|
384
|
+
import_mode=import_mode,
|
|
341
385
|
)
|
|
342
386
|
|
|
343
387
|
diff, manifest_ctx = plan_result.graph_diff, plan_result.diff_context
|
|
@@ -345,16 +389,16 @@ async def _plan(
|
|
|
345
389
|
if show_diff:
|
|
346
390
|
if diff.num_operations() == 0:
|
|
347
391
|
_print_no_changes()
|
|
348
|
-
|
|
392
|
+
else:
|
|
393
|
+
_show_resources_diff(
|
|
394
|
+
diff=diff,
|
|
395
|
+
show_schema=show_schema,
|
|
396
|
+
show_secrets=show_secrets,
|
|
397
|
+
escape=sys.stdout.isatty() and color,
|
|
398
|
+
diff_output=diff_output,
|
|
399
|
+
)
|
|
349
400
|
|
|
350
|
-
|
|
351
|
-
diff=diff,
|
|
352
|
-
show_schema=show_schema,
|
|
353
|
-
show_secrets=show_secrets,
|
|
354
|
-
escape=sys.stdout.isatty() and color,
|
|
355
|
-
diff_output=diff_output,
|
|
356
|
-
deprecations=plan_result.deprecations,
|
|
357
|
-
)
|
|
401
|
+
_show_deprecations(plan_result.deprecations)
|
|
358
402
|
|
|
359
403
|
return diff, manifest_ctx
|
|
360
404
|
|
|
@@ -365,7 +409,6 @@ def _show_resources_diff(
|
|
|
365
409
|
show_secrets: bool,
|
|
366
410
|
escape: bool,
|
|
367
411
|
diff_output: DiffOutput,
|
|
368
|
-
deprecations: list[code_plan.ResourceDeprecation],
|
|
369
412
|
) -> None:
|
|
370
413
|
resource_types = DiffContext.fields()
|
|
371
414
|
if diff.num_creates() > 0:
|
|
@@ -410,8 +453,6 @@ def _show_resources_diff(
|
|
|
410
453
|
diff_output,
|
|
411
454
|
)
|
|
412
455
|
|
|
413
|
-
_show_deprecations(deprecations)
|
|
414
|
-
|
|
415
456
|
print(
|
|
416
457
|
"\n"
|
|
417
458
|
f"Plan: {diff.num_creates()} to create, {diff.num_updates()} to update, "
|
|
@@ -477,7 +518,14 @@ def _show_update_resource_diff(
|
|
|
477
518
|
for r in resources.values():
|
|
478
519
|
class_key = r.manifest.__class__.__name__
|
|
479
520
|
|
|
480
|
-
|
|
521
|
+
cascade_update = ""
|
|
522
|
+
if r.replacement_cascaded_update_parent:
|
|
523
|
+
(cls, parent_name) = r.replacement_cascaded_update_parent
|
|
524
|
+
cascade_update = (
|
|
525
|
+
f" (due to replacement of " f"{cls.__name__}({repr(parent_name)}))"
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
print(f"{class_key} '{r.manifest.name}' will be updated{cascade_update}")
|
|
481
529
|
|
|
482
530
|
rewrites = _diff_field_rewrites(show_schema)
|
|
483
531
|
|
|
@@ -573,7 +621,7 @@ def _diff_field_rewrites(show_schema: bool) -> dict[str, Any]:
|
|
|
573
621
|
return {} if show_schema else {"jtd_schema": "[NOT SHOWN]"}
|
|
574
622
|
|
|
575
623
|
|
|
576
|
-
def _show_deprecations(deprecations: list[
|
|
624
|
+
def _show_deprecations(deprecations: list[ResourceDeprecation]) -> None:
|
|
577
625
|
if not deprecations:
|
|
578
626
|
return
|
|
579
627
|
|
|
@@ -601,32 +649,9 @@ def create_source_schema_reinference(
|
|
|
601
649
|
elif len(schemas_to_update) > 0:
|
|
602
650
|
source_names = schemas_to_update
|
|
603
651
|
|
|
604
|
-
return SourceSchemaReinference(
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
def _create_resource_names(strings: set[str]) -> code_plan.ResourceNames:
|
|
608
|
-
"""Construct object from a set of strings.
|
|
609
|
-
|
|
610
|
-
Strings in format <resource_type>:<resource_name> will be converted by
|
|
611
|
-
splitting on `:` and parsing the values.
|
|
612
|
-
"""
|
|
613
|
-
resource_names = code_plan.ResourceNames()
|
|
614
|
-
|
|
615
|
-
for target in strings:
|
|
616
|
-
# A resource name can't contain `:` so it's safe to split like this.
|
|
617
|
-
resource_type, name = target.split(":")
|
|
618
|
-
|
|
619
|
-
# User specifies resources in singular (credential) but the class fields
|
|
620
|
-
# throughout the code uses plural so we add an 's'
|
|
621
|
-
resource_type = f"{resource_type}s"
|
|
622
|
-
|
|
623
|
-
if not hasattr(resource_names, resource_type):
|
|
624
|
-
raise ValidioError(f"Invalid resource type '{resource_type}'")
|
|
625
|
-
|
|
626
|
-
v = getattr(resource_names, resource_type)
|
|
627
|
-
v.add(name)
|
|
628
|
-
|
|
629
|
-
return resource_names
|
|
652
|
+
return SourceSchemaReinference(
|
|
653
|
+
set(source_names) if source_names is not None else None
|
|
654
|
+
)
|
|
630
655
|
|
|
631
656
|
|
|
632
657
|
def _print_no_changes() -> None:
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
from typing import Any
|
|
3
3
|
|
|
4
|
-
import httpx
|
|
5
4
|
import typer
|
|
6
5
|
from prompt_toolkit import PromptSession
|
|
7
6
|
from prompt_toolkit.completion import WordCompleter
|
|
8
7
|
from prompt_toolkit.shortcuts import confirm
|
|
9
8
|
from tabulate import tabulate
|
|
9
|
+
from validio_sdk._api.api import APIClient
|
|
10
10
|
from validio_sdk.config import (
|
|
11
11
|
Config,
|
|
12
12
|
ConfigInvalidError,
|
|
13
13
|
ConfigNotFoundError,
|
|
14
14
|
ValidioConfig,
|
|
15
15
|
)
|
|
16
|
-
from validio_sdk.validio_client import UnauthorizedError, ValidioAPIClient
|
|
17
16
|
|
|
18
17
|
import validio_cli
|
|
19
18
|
from validio_cli import (
|
|
@@ -178,14 +177,5 @@ async def _prompt_for_config(current_config: ValidioConfig | None) -> ValidioCon
|
|
|
178
177
|
|
|
179
178
|
|
|
180
179
|
async def _test_configuration(config: ValidioConfig) -> str | None:
|
|
181
|
-
vc =
|
|
182
|
-
|
|
183
|
-
try:
|
|
184
|
-
# NOTE: Do we need an explicit ping command here?
|
|
185
|
-
await vc.list_users()
|
|
186
|
-
except UnauthorizedError:
|
|
187
|
-
return "Unauthorized"
|
|
188
|
-
except httpx.ConnectError:
|
|
189
|
-
return "Failed to connect to server"
|
|
190
|
-
|
|
191
|
-
return None
|
|
180
|
+
vc = APIClient(config=config)
|
|
181
|
+
return await vc.test_connection()
|