nextmv 0.18.0__py3-none-any.whl → 1.0.0.dev2__py3-none-any.whl

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.
Files changed (175) hide show
  1. nextmv/__about__.py +1 -1
  2. nextmv/__entrypoint__.py +8 -13
  3. nextmv/__init__.py +53 -0
  4. nextmv/_serialization.py +96 -0
  5. nextmv/base_model.py +54 -9
  6. nextmv/cli/CONTRIBUTING.md +511 -0
  7. nextmv/cli/__init__.py +0 -0
  8. nextmv/cli/cloud/__init__.py +47 -0
  9. nextmv/cli/cloud/acceptance/__init__.py +27 -0
  10. nextmv/cli/cloud/acceptance/create.py +393 -0
  11. nextmv/cli/cloud/acceptance/delete.py +68 -0
  12. nextmv/cli/cloud/acceptance/get.py +104 -0
  13. nextmv/cli/cloud/acceptance/list.py +62 -0
  14. nextmv/cli/cloud/acceptance/update.py +95 -0
  15. nextmv/cli/cloud/account/__init__.py +28 -0
  16. nextmv/cli/cloud/account/create.py +83 -0
  17. nextmv/cli/cloud/account/delete.py +60 -0
  18. nextmv/cli/cloud/account/get.py +66 -0
  19. nextmv/cli/cloud/account/update.py +70 -0
  20. nextmv/cli/cloud/app/__init__.py +35 -0
  21. nextmv/cli/cloud/app/create.py +141 -0
  22. nextmv/cli/cloud/app/delete.py +58 -0
  23. nextmv/cli/cloud/app/exists.py +44 -0
  24. nextmv/cli/cloud/app/get.py +66 -0
  25. nextmv/cli/cloud/app/list.py +61 -0
  26. nextmv/cli/cloud/app/push.py +137 -0
  27. nextmv/cli/cloud/app/update.py +124 -0
  28. nextmv/cli/cloud/batch/__init__.py +29 -0
  29. nextmv/cli/cloud/batch/create.py +454 -0
  30. nextmv/cli/cloud/batch/delete.py +68 -0
  31. nextmv/cli/cloud/batch/get.py +104 -0
  32. nextmv/cli/cloud/batch/list.py +63 -0
  33. nextmv/cli/cloud/batch/metadata.py +66 -0
  34. nextmv/cli/cloud/batch/update.py +95 -0
  35. nextmv/cli/cloud/data/__init__.py +26 -0
  36. nextmv/cli/cloud/data/upload.py +162 -0
  37. nextmv/cli/cloud/ensemble/__init__.py +31 -0
  38. nextmv/cli/cloud/ensemble/create.py +414 -0
  39. nextmv/cli/cloud/ensemble/delete.py +67 -0
  40. nextmv/cli/cloud/ensemble/get.py +65 -0
  41. nextmv/cli/cloud/ensemble/update.py +103 -0
  42. nextmv/cli/cloud/input_set/__init__.py +30 -0
  43. nextmv/cli/cloud/input_set/create.py +170 -0
  44. nextmv/cli/cloud/input_set/get.py +63 -0
  45. nextmv/cli/cloud/input_set/list.py +63 -0
  46. nextmv/cli/cloud/input_set/update.py +123 -0
  47. nextmv/cli/cloud/instance/__init__.py +35 -0
  48. nextmv/cli/cloud/instance/create.py +290 -0
  49. nextmv/cli/cloud/instance/delete.py +62 -0
  50. nextmv/cli/cloud/instance/exists.py +39 -0
  51. nextmv/cli/cloud/instance/get.py +62 -0
  52. nextmv/cli/cloud/instance/list.py +60 -0
  53. nextmv/cli/cloud/instance/update.py +216 -0
  54. nextmv/cli/cloud/managed_input/__init__.py +31 -0
  55. nextmv/cli/cloud/managed_input/create.py +146 -0
  56. nextmv/cli/cloud/managed_input/delete.py +65 -0
  57. nextmv/cli/cloud/managed_input/get.py +63 -0
  58. nextmv/cli/cloud/managed_input/list.py +60 -0
  59. nextmv/cli/cloud/managed_input/update.py +97 -0
  60. nextmv/cli/cloud/run/__init__.py +37 -0
  61. nextmv/cli/cloud/run/cancel.py +37 -0
  62. nextmv/cli/cloud/run/create.py +530 -0
  63. nextmv/cli/cloud/run/get.py +199 -0
  64. nextmv/cli/cloud/run/input.py +86 -0
  65. nextmv/cli/cloud/run/list.py +80 -0
  66. nextmv/cli/cloud/run/logs.py +167 -0
  67. nextmv/cli/cloud/run/metadata.py +67 -0
  68. nextmv/cli/cloud/run/track.py +501 -0
  69. nextmv/cli/cloud/scenario/__init__.py +29 -0
  70. nextmv/cli/cloud/scenario/create.py +451 -0
  71. nextmv/cli/cloud/scenario/delete.py +65 -0
  72. nextmv/cli/cloud/scenario/get.py +102 -0
  73. nextmv/cli/cloud/scenario/list.py +63 -0
  74. nextmv/cli/cloud/scenario/metadata.py +67 -0
  75. nextmv/cli/cloud/scenario/update.py +93 -0
  76. nextmv/cli/cloud/secrets/__init__.py +33 -0
  77. nextmv/cli/cloud/secrets/create.py +206 -0
  78. nextmv/cli/cloud/secrets/delete.py +67 -0
  79. nextmv/cli/cloud/secrets/get.py +66 -0
  80. nextmv/cli/cloud/secrets/list.py +60 -0
  81. nextmv/cli/cloud/secrets/update.py +147 -0
  82. nextmv/cli/cloud/shadow/__init__.py +33 -0
  83. nextmv/cli/cloud/shadow/create.py +184 -0
  84. nextmv/cli/cloud/shadow/delete.py +68 -0
  85. nextmv/cli/cloud/shadow/get.py +61 -0
  86. nextmv/cli/cloud/shadow/list.py +63 -0
  87. nextmv/cli/cloud/shadow/metadata.py +66 -0
  88. nextmv/cli/cloud/shadow/start.py +43 -0
  89. nextmv/cli/cloud/shadow/stop.py +43 -0
  90. nextmv/cli/cloud/shadow/update.py +95 -0
  91. nextmv/cli/cloud/upload/__init__.py +22 -0
  92. nextmv/cli/cloud/upload/create.py +39 -0
  93. nextmv/cli/cloud/version/__init__.py +33 -0
  94. nextmv/cli/cloud/version/create.py +97 -0
  95. nextmv/cli/cloud/version/delete.py +62 -0
  96. nextmv/cli/cloud/version/exists.py +39 -0
  97. nextmv/cli/cloud/version/get.py +62 -0
  98. nextmv/cli/cloud/version/list.py +60 -0
  99. nextmv/cli/cloud/version/update.py +92 -0
  100. nextmv/cli/community/__init__.py +24 -0
  101. nextmv/cli/community/clone.py +270 -0
  102. nextmv/cli/community/list.py +265 -0
  103. nextmv/cli/configuration/__init__.py +23 -0
  104. nextmv/cli/configuration/config.py +195 -0
  105. nextmv/cli/configuration/create.py +94 -0
  106. nextmv/cli/configuration/delete.py +67 -0
  107. nextmv/cli/configuration/list.py +77 -0
  108. nextmv/cli/main.py +188 -0
  109. nextmv/cli/message.py +153 -0
  110. nextmv/cli/options.py +206 -0
  111. nextmv/cli/version.py +38 -0
  112. nextmv/cloud/__init__.py +71 -17
  113. nextmv/cloud/acceptance_test.py +757 -51
  114. nextmv/cloud/account.py +406 -17
  115. nextmv/cloud/application/__init__.py +957 -0
  116. nextmv/cloud/application/_acceptance.py +419 -0
  117. nextmv/cloud/application/_batch_scenario.py +860 -0
  118. nextmv/cloud/application/_ensemble.py +251 -0
  119. nextmv/cloud/application/_input_set.py +227 -0
  120. nextmv/cloud/application/_instance.py +289 -0
  121. nextmv/cloud/application/_managed_input.py +227 -0
  122. nextmv/cloud/application/_run.py +1393 -0
  123. nextmv/cloud/application/_secrets.py +294 -0
  124. nextmv/cloud/application/_shadow.py +314 -0
  125. nextmv/cloud/application/_utils.py +54 -0
  126. nextmv/cloud/application/_version.py +303 -0
  127. nextmv/cloud/assets.py +48 -0
  128. nextmv/cloud/batch_experiment.py +294 -33
  129. nextmv/cloud/client.py +307 -66
  130. nextmv/cloud/ensemble.py +247 -0
  131. nextmv/cloud/input_set.py +120 -2
  132. nextmv/cloud/instance.py +133 -8
  133. nextmv/cloud/integration.py +533 -0
  134. nextmv/cloud/package.py +168 -53
  135. nextmv/cloud/scenario.py +410 -0
  136. nextmv/cloud/secrets.py +234 -0
  137. nextmv/cloud/shadow.py +190 -0
  138. nextmv/cloud/url.py +73 -0
  139. nextmv/cloud/version.py +132 -4
  140. nextmv/default_app/.gitignore +1 -0
  141. nextmv/default_app/README.md +32 -0
  142. nextmv/default_app/app.yaml +12 -0
  143. nextmv/default_app/input.json +5 -0
  144. nextmv/default_app/main.py +37 -0
  145. nextmv/default_app/requirements.txt +2 -0
  146. nextmv/default_app/src/__init__.py +0 -0
  147. nextmv/default_app/src/visuals.py +36 -0
  148. nextmv/deprecated.py +47 -0
  149. nextmv/input.py +861 -90
  150. nextmv/local/__init__.py +5 -0
  151. nextmv/local/application.py +1251 -0
  152. nextmv/local/executor.py +1042 -0
  153. nextmv/local/geojson_handler.py +323 -0
  154. nextmv/local/local.py +97 -0
  155. nextmv/local/plotly_handler.py +61 -0
  156. nextmv/local/runner.py +274 -0
  157. nextmv/logger.py +80 -9
  158. nextmv/manifest.py +1466 -0
  159. nextmv/model.py +241 -66
  160. nextmv/options.py +708 -115
  161. nextmv/output.py +1301 -274
  162. nextmv/polling.py +325 -0
  163. nextmv/run.py +1702 -0
  164. nextmv/safe.py +145 -0
  165. nextmv/status.py +122 -0
  166. nextmv-1.0.0.dev2.dist-info/METADATA +311 -0
  167. nextmv-1.0.0.dev2.dist-info/RECORD +170 -0
  168. {nextmv-0.18.0.dist-info → nextmv-1.0.0.dev2.dist-info}/WHEEL +1 -1
  169. nextmv-1.0.0.dev2.dist-info/entry_points.txt +2 -0
  170. nextmv/cloud/application.py +0 -1405
  171. nextmv/cloud/manifest.py +0 -234
  172. nextmv/cloud/status.py +0 -29
  173. nextmv-0.18.0.dist-info/METADATA +0 -770
  174. nextmv-0.18.0.dist-info/RECORD +0 -25
  175. {nextmv-0.18.0.dist-info → nextmv-1.0.0.dev2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,67 @@
1
+ """
2
+ This module defines the cloud scenario metadata command for the Nextmv CLI.
3
+ """
4
+
5
+ import json
6
+ from typing import Annotated
7
+
8
+ import typer
9
+
10
+ from nextmv.cli.configuration.config import build_app
11
+ from nextmv.cli.message import in_progress, print_json, success
12
+ from nextmv.cli.options import AppIDOption, ProfileOption, ScenarioTestIDOption
13
+
14
+ # Set up subcommand application.
15
+ app = typer.Typer()
16
+
17
+
18
+ @app.command()
19
+ def metadata(
20
+ app_id: AppIDOption,
21
+ scenario_test_id: ScenarioTestIDOption,
22
+ output: Annotated[
23
+ str | None,
24
+ typer.Option(
25
+ "--output",
26
+ "-o",
27
+ help="Saves the scenario test metadata to this location.",
28
+ metavar="OUTPUT_PATH",
29
+ ),
30
+ ] = None,
31
+ profile: ProfileOption = None,
32
+ ) -> None:
33
+ """
34
+ Get metadata for a Nextmv Cloud scenario test.
35
+
36
+ This command retrieves metadata for a specific scenario test, including
37
+ status, creation date, and other high-level information without the full
38
+ run details.
39
+
40
+ [bold][underline]Examples[/underline][/bold]
41
+
42
+ - Get metadata for scenario test [magenta]bunny-warren-optimization[/magenta] from application
43
+ [magenta]hare-app[/magenta].
44
+ $ [green]nextmv cloud scenario metadata --app-id hare-app --scenario-test-id bunny-warren-optimization[/green]
45
+
46
+ - Get metadata and save to a file.
47
+ $ [green]nextmv cloud scenario metadata --app-id hare-app --scenario-test-id lettuce-delivery \\
48
+ --output metadata.json[/green]
49
+
50
+ - Get metadata using a specific profile.
51
+ $ [green]nextmv cloud scenario metadata --app-id hare-app --scenario-test-id hop-schedule --profile prod[/green]
52
+ """
53
+
54
+ cloud_app = build_app(app_id=app_id, profile=profile)
55
+ in_progress(msg="Getting scenario test metadata...")
56
+ scenario_metadata = cloud_app.scenario_test_metadata(scenario_test_id=scenario_test_id)
57
+ scenario_metadata_dict = scenario_metadata.to_dict()
58
+
59
+ if output is not None and output != "":
60
+ with open(output, "w") as f:
61
+ json.dump(scenario_metadata_dict, f, indent=2)
62
+
63
+ success(msg=f"Scenario test metadata saved to [magenta]{output}[/magenta].")
64
+
65
+ return
66
+
67
+ print_json(scenario_metadata_dict)
@@ -0,0 +1,93 @@
1
+ """
2
+ This module defines the cloud scenario update command for the Nextmv CLI.
3
+ """
4
+
5
+ import json
6
+ from typing import Annotated
7
+
8
+ import typer
9
+
10
+ from nextmv.cli.configuration.config import build_app
11
+ from nextmv.cli.message import in_progress, print_json, success
12
+ from nextmv.cli.options import AppIDOption, ProfileOption, ScenarioTestIDOption
13
+
14
+ # Set up subcommand application.
15
+ app = typer.Typer()
16
+
17
+
18
+ @app.command()
19
+ def update(
20
+ app_id: AppIDOption,
21
+ scenario_test_id: ScenarioTestIDOption,
22
+ description: Annotated[
23
+ str | None,
24
+ typer.Option(
25
+ "--description",
26
+ "-d",
27
+ help="Updated description of the scenario test.",
28
+ metavar="DESCRIPTION",
29
+ ),
30
+ ] = None,
31
+ name: Annotated[
32
+ str | None,
33
+ typer.Option(
34
+ "--name",
35
+ "-n",
36
+ help="Updated name of the scenario test.",
37
+ metavar="NAME",
38
+ ),
39
+ ] = None,
40
+ output: Annotated[
41
+ str | None,
42
+ typer.Option(
43
+ "--output",
44
+ "-o",
45
+ help="Saves the updated scenario test information to this location.",
46
+ metavar="OUTPUT_PATH",
47
+ ),
48
+ ] = None,
49
+ profile: ProfileOption = None,
50
+ ) -> None:
51
+ """
52
+ Update a Nextmv Cloud scenario test.
53
+
54
+ Update the name and/or description of a scenario test. Any fields not
55
+ specified will remain unchanged.
56
+
57
+ [bold][underline]Examples[/underline][/bold]
58
+
59
+ - Update the name of a scenario test.
60
+ $ [green]nextmv cloud scenario update --app-id hare-app --scenario-test-id carrot-feast \\
61
+ --name "Spring Carrot Harvest"[/green]
62
+
63
+ - Update the description of a scenario test.
64
+ $ [green]nextmv cloud scenario update --app-id hare-app --scenario-test-id bunny-hop-routes \\
65
+ --description "Optimizing hop paths through the meadow"[/green]
66
+
67
+ - Update both name and description and save the result.
68
+ $ [green]nextmv cloud scenario update --app-id hare-app --scenario-test-id lettuce-delivery \\
69
+ --name "Warren Lettuce Express" --description "Fast lettuce delivery to all burrows" \\
70
+ --output updated-scenario.json[/green]
71
+ """
72
+
73
+ cloud_app = build_app(app_id=app_id, profile=profile)
74
+ in_progress(msg="Updating scenario test...")
75
+ scenario_info = cloud_app.update_scenario_test(
76
+ scenario_test_id=scenario_test_id,
77
+ name=name,
78
+ description=description,
79
+ )
80
+ success(
81
+ f"Scenario test [magenta]{scenario_test_id}[/magenta] updated successfully "
82
+ f"in application [magenta]{app_id}[/magenta]."
83
+ )
84
+ scenario_info_dict = scenario_info.to_dict()
85
+
86
+ if output is not None and output != "":
87
+ with open(output, "w") as f:
88
+ json.dump(scenario_info_dict, f, indent=2)
89
+
90
+ success(msg=f"Scenario test information saved to [magenta]{output}[/magenta].")
91
+ return
92
+
93
+ print_json(scenario_info_dict)
@@ -0,0 +1,33 @@
1
+ """
2
+ This module defines the cloud secrets command tree for the Nextmv CLI.
3
+ """
4
+
5
+ import typer
6
+
7
+ from nextmv.cli.cloud.secrets.create import app as create_app
8
+ from nextmv.cli.cloud.secrets.delete import app as delete_app
9
+ from nextmv.cli.cloud.secrets.get import app as get_app
10
+ from nextmv.cli.cloud.secrets.list import app as list_app
11
+ from nextmv.cli.cloud.secrets.update import app as update_app
12
+
13
+ # Set up subcommand application.
14
+ app = typer.Typer()
15
+ app.add_typer(create_app)
16
+ app.add_typer(delete_app)
17
+ app.add_typer(get_app)
18
+ app.add_typer(list_app)
19
+ app.add_typer(update_app)
20
+
21
+
22
+ @app.callback()
23
+ def callback() -> None:
24
+ """
25
+ Create and manage Nextmv Cloud secrets collections.
26
+
27
+ A secret collection defines one or more secrets used by your optimization
28
+ model during execution. You can reference a secret collection either in an
29
+ application instance configuration, or directly when starting a run. The
30
+ platform then injects the secrets into the container during the
31
+ optimization run as environment variables and files.
32
+ """
33
+ pass
@@ -0,0 +1,206 @@
1
+ """
2
+ This module defines the cloud secrets create command for the Nextmv CLI.
3
+ """
4
+
5
+ from typing import Annotated
6
+
7
+ import typer
8
+
9
+ from nextmv.cli.configuration.config import build_app
10
+ from nextmv.cli.message import enum_values, error, in_progress, print_json
11
+ from nextmv.cli.options import AppIDOption, ProfileOption
12
+ from nextmv.cloud.secrets import Secret, SecretType
13
+
14
+ # Set up subcommand application.
15
+ app = typer.Typer()
16
+
17
+
18
+ @app.command()
19
+ def create(
20
+ app_id: AppIDOption,
21
+ secrets: Annotated[
22
+ list[str],
23
+ typer.Option(
24
+ "--secrets",
25
+ "-e",
26
+ help="Secrets to configure in the app. Data should be valid [magenta]json[/magenta]. "
27
+ "Pass multiple secrets by repeating the flag, or providing a list of objects. "
28
+ "Allowed values for [magenta]type[/magenta] are: "
29
+ f"{enum_values(SecretType)}. "
30
+ "Object format: [green]{'type': type, 'location': location, 'value': value}[/green].",
31
+ metavar="SECRETS",
32
+ ),
33
+ ],
34
+ description: Annotated[
35
+ str | None,
36
+ typer.Option(
37
+ "--description",
38
+ "-d",
39
+ help="An optional description for the secrets collection.",
40
+ metavar="DESCRIPTION",
41
+ ),
42
+ ] = None,
43
+ name: Annotated[
44
+ str | None,
45
+ typer.Option(
46
+ "--name",
47
+ "-n",
48
+ help="A name for the secrets collection.",
49
+ metavar="NAME",
50
+ ),
51
+ ] = None,
52
+ secrets_collection_id: Annotated[
53
+ str | None,
54
+ typer.Option(
55
+ "--secrets-collection-id",
56
+ "-s",
57
+ help="The ID to assign to the new secrets collection. If not provided, a random ID will be generated.",
58
+ envvar="NEXTMV_SECRETS_COLLECTION_ID",
59
+ metavar="SECRETS_COLLECTION_ID",
60
+ ),
61
+ ] = None,
62
+ profile: ProfileOption = None,
63
+ ) -> None:
64
+ """
65
+ Create a new Nextmv Cloud secrets collection.
66
+
67
+ A secrets collection is a group of key-value pairs that can be used by
68
+ your application instances during execution. Each collection can contain
69
+ up to 20 secrets. Secrets are provided as JSON objects using the
70
+ [code]--secrets[/code] flag.
71
+
72
+ Each secret must include three fields:
73
+ - [magenta]type[/magenta]: Either [magenta]env[/magenta] or [magenta]file[/magenta],
74
+ which determines how the secret is injected into the runtime.
75
+ - [magenta]location[/magenta]: Where to place the secret.
76
+ - [magenta]env[/magenta]: the environment variable name. E.g.: [magenta]BURROW_ENTRANCE[/magenta].
77
+ - [magenta]file[/magenta]: the relative path from the execution
78
+ directory. E.g.: [magenta]licenses/burrow.entr[/magenta].
79
+ - [magenta]value[/magenta]: The secret value as text (limited to 1 KB).
80
+
81
+ You can provide secrets in three ways:
82
+ - A single secret as a [magenta]json[/magenta] object.
83
+ - Multiple secrets by repeating the [code]--secrets[/code] flag.
84
+ - Multiple secrets as a [magenta]json[/magenta] array in a single [code]--secrets[/code] flag.
85
+
86
+ The [code]--secrets-collection-id[/code] and [code]--name[/code] are optional.
87
+ If not provided, they will be automatically generated.
88
+
89
+ [bold][underline]Examples[/underline][/bold]
90
+
91
+ - Create a secrets collection with a single environment variable secret.
92
+ $ [green]nextmv cloud secrets create --app-id hare-app \\
93
+ --secrets '{"type": "env", "location": "API_KEY", "value": "secret-value"}'[/green]
94
+
95
+ - Create a secrets collection with multiple secrets by repeating the flag.
96
+ $ [green]nextmv cloud secrets create --app-id hare-app \\
97
+ --secrets '{"type": "env", "location": "API_KEY", "value": "secret-value"}' \\
98
+ --secrets '{"type": "env", "location": "DATABASE_URL", "value": "postgres://localhost"}'[/green]
99
+
100
+ - Create a secrets collection with multiple secrets in a single JSON array.
101
+ $ [green]nextmv cloud secrets create --app-id hare-app \\
102
+ --secrets '[{"type": "env", "location": "DB_USER", "value": "admin"}, {...}]'[/green]
103
+
104
+ - Create a secrets collection with custom ID, name, and description.
105
+ $ [green]nextmv cloud secrets create --app-id hare-app \\
106
+ --secrets-collection-id db-creds --name "Database Credentials" \\
107
+ --description "Production database credentials" \\
108
+ --secrets '{"type": "env", "location": "DB_USER", "value": "admin"}' \\
109
+ --secrets '{"type": "env", "location": "DB_PASS", "value": "secure123"}'[/green]
110
+
111
+ - Create a secrets collection with file-based secrets.
112
+ $ [green]nextmv cloud secrets create --app-id hare-app \\
113
+ --secrets-collection-id certs --name "Certificates" \\
114
+ --secrets '{"type": "file", "location": "licenses/acme.lic", "value": "LICENSE_CONTENT_HERE"}'[/green]
115
+
116
+ - Mix environment and file-based secrets.
117
+ $ [green]nextmv cloud secrets create --app-id hare-app \\
118
+ --secrets '{"type": "env", "location": "ACME_LICENSE_KEY", "value": "abc123"}' \\
119
+ --secrets '{"type": "file", "location": "config/app.conf", "value": "server=prod\\nport=8080"}'[/green]
120
+ """
121
+
122
+ cloud_app = build_app(app_id=app_id, profile=profile)
123
+ in_progress(msg="Creating secrets collection...")
124
+
125
+ # Build the secrets list from the CLI options
126
+ secrets_list = build_secrets(secrets)
127
+
128
+ collection = cloud_app.new_secrets_collection(
129
+ secrets=secrets_list,
130
+ id=secrets_collection_id,
131
+ name=name,
132
+ description=description,
133
+ )
134
+ print_json(collection.to_dict())
135
+
136
+
137
+ def build_secrets(secrets: list[str]) -> list[Secret]:
138
+ """
139
+ Builds the secrets list from the CLI option(s).
140
+
141
+ Parameters
142
+ ----------
143
+ secrets : list[str]
144
+ List of secrets provided via the CLI.
145
+
146
+ Returns
147
+ -------
148
+ list[Secret]
149
+ The built secrets list.
150
+ """
151
+ import json
152
+
153
+ secrets_list = []
154
+
155
+ for secret_str in secrets:
156
+ try:
157
+ secret_data = json.loads(secret_str)
158
+
159
+ # Handle the case where the value is a list of secrets.
160
+ if isinstance(secret_data, list):
161
+ for ix, item in enumerate(secret_data):
162
+ if item.get("type") is None or item.get("location") is None or item.get("value") is None:
163
+ error(
164
+ f"Invalid secret format at index [magenta]{ix}[/magenta] in "
165
+ f"[magenta]{secret_str}[/magenta]. Each secret must have "
166
+ "[magenta]type[/magenta], [magenta]location[/magenta], "
167
+ "and [magenta]value[/magenta] fields."
168
+ )
169
+
170
+ secret = Secret(
171
+ type=SecretType(item["type"]),
172
+ location=item["location"],
173
+ value=item["value"],
174
+ )
175
+ secrets_list.append(secret)
176
+
177
+ # Handle the case where the value is a single secret.
178
+ elif isinstance(secret_data, dict):
179
+ if (
180
+ secret_data.get("type") is None
181
+ or secret_data.get("location") is None
182
+ or secret_data.get("value") is None
183
+ ):
184
+ error(
185
+ f"Invalid secret format in [magenta]{secret_str}[/magenta]. "
186
+ "Each secret must have [magenta]type[/magenta], [magenta]location[/magenta], "
187
+ "and [magenta]value[/magenta] fields."
188
+ )
189
+
190
+ secret = Secret(
191
+ type=SecretType(secret_data["type"]),
192
+ location=secret_data["location"],
193
+ value=secret_data["value"],
194
+ )
195
+ secrets_list.append(secret)
196
+
197
+ else:
198
+ error(
199
+ f"Invalid secret format: [magenta]{secret_str}[/magenta]. "
200
+ "Expected [magenta]json[/magenta] object or array."
201
+ )
202
+
203
+ except (json.JSONDecodeError, KeyError, ValueError) as e:
204
+ error(f"Invalid secret format: [magenta]{secret_str}[/magenta]. Error: {e}")
205
+
206
+ return secrets_list
@@ -0,0 +1,67 @@
1
+ """
2
+ This module defines the cloud secrets delete command for the Nextmv CLI.
3
+ """
4
+
5
+ from typing import Annotated
6
+
7
+ import typer
8
+ from rich.prompt import Confirm
9
+
10
+ from nextmv.cli.configuration.config import build_app
11
+ from nextmv.cli.message import info, success
12
+ from nextmv.cli.options import AppIDOption, ProfileOption, SecretsCollectionIDOption
13
+
14
+ # Set up subcommand application.
15
+ app = typer.Typer()
16
+
17
+
18
+ @app.command()
19
+ def delete(
20
+ app_id: AppIDOption,
21
+ secrets_collection_id: SecretsCollectionIDOption,
22
+ yes: Annotated[
23
+ bool,
24
+ typer.Option(
25
+ "--yes",
26
+ "-y",
27
+ help="Agree to deletion confirmation prompt. Useful for non-interactive sessions.",
28
+ ),
29
+ ] = False,
30
+ profile: ProfileOption = None,
31
+ ) -> None:
32
+ """
33
+ Deletes a Nextmv Cloud secrets collection.
34
+
35
+ This action is permanent and cannot be undone. Use the [code]--yes[/code]
36
+ flag to skip the confirmation prompt.
37
+
38
+ [bold][underline]Examples[/underline][/bold]
39
+
40
+ - Delete the secrets collection with the ID [magenta]api-keys[/magenta] from application
41
+ [magenta]hare-app[/magenta].
42
+ $ [green]nextmv cloud secrets delete --app-id hare-app --secrets-collection-id api-keys[/green]
43
+
44
+ - Delete the secrets collection without confirmation prompt.
45
+ $ [green]nextmv cloud secrets delete --app-id hare-app --secrets-collection-id api-keys --yes[/green]
46
+ """
47
+
48
+ if not yes:
49
+ confirm = Confirm.ask(
50
+ f"Are you sure you want to delete secrets collection [magenta]{secrets_collection_id}[/magenta] "
51
+ f"from application [magenta]{app_id}[/magenta]? This action cannot be undone.",
52
+ default=False,
53
+ )
54
+
55
+ if not confirm:
56
+ info(
57
+ msg=f"Secrets collection [magenta]{secrets_collection_id}[/magenta] will not be deleted.",
58
+ emoji=":bulb:",
59
+ )
60
+ return
61
+
62
+ cloud_app = build_app(app_id=app_id, profile=profile)
63
+ cloud_app.delete_secrets_collection(secrets_collection_id=secrets_collection_id)
64
+ success(
65
+ f"Secrets collection [magenta]{secrets_collection_id}[/magenta] deleted successfully "
66
+ f"from application [magenta]{app_id}[/magenta]."
67
+ )
@@ -0,0 +1,66 @@
1
+ """
2
+ This module defines the cloud secrets get command for the Nextmv CLI.
3
+ """
4
+
5
+ import json
6
+ from typing import Annotated
7
+
8
+ import typer
9
+
10
+ from nextmv.cli.configuration.config import build_app
11
+ from nextmv.cli.message import in_progress, print_json, success
12
+ from nextmv.cli.options import AppIDOption, ProfileOption, SecretsCollectionIDOption
13
+
14
+ # Set up subcommand application.
15
+ app = typer.Typer()
16
+
17
+
18
+ @app.command()
19
+ def get(
20
+ app_id: AppIDOption,
21
+ secrets_collection_id: SecretsCollectionIDOption,
22
+ output: Annotated[
23
+ str | None,
24
+ typer.Option(
25
+ "--output",
26
+ "-o",
27
+ help="Saves the secrets collection information to this location.",
28
+ metavar="OUTPUT_PATH",
29
+ ),
30
+ ] = None,
31
+ profile: ProfileOption = None,
32
+ ) -> None:
33
+ """
34
+ Get a Nextmv Cloud secrets collection.
35
+
36
+ This command is useful to get the attributes of an existing Nextmv Cloud
37
+ secrets collection by its ID. :construction: [yellow bold]Warning:
38
+ secret values will be included in the output.[/yellow bold]
39
+
40
+ [bold][underline]Examples[/underline][/bold]
41
+
42
+ - Get the secrets collection with the ID [magenta]api-keys[/magenta] from
43
+ application [magenta]hare-app[/magenta].
44
+ $ [green]nextmv cloud secrets get --app-id hare-app \\
45
+ --secrets-collection-id api-keys[/green]
46
+
47
+ - Get the secrets collection with the ID [magenta]api-keys[/magenta] and
48
+ save the information to a [magenta]secrets.json[/magenta] file.
49
+ $ [green]nextmv cloud secrets get --app-id hare-app \\
50
+ --secrets-collection-id api-keys --output secrets.json[/green]
51
+ """
52
+
53
+ cloud_app = build_app(app_id=app_id, profile=profile)
54
+ in_progress(msg="Getting secrets collection...")
55
+ collection = cloud_app.secrets_collection(secrets_collection_id=secrets_collection_id)
56
+ collection_dict = collection.to_dict()
57
+
58
+ if output is not None and output != "":
59
+ with open(output, "w") as f:
60
+ json.dump(collection_dict, f, indent=2)
61
+
62
+ success(msg=f"Secrets collection information saved to [magenta]{output}[/magenta].")
63
+
64
+ return
65
+
66
+ print_json(collection_dict)
@@ -0,0 +1,60 @@
1
+ """
2
+ This module defines the cloud secrets list command for the Nextmv CLI.
3
+ """
4
+
5
+ import json
6
+ from typing import Annotated
7
+
8
+ import typer
9
+
10
+ from nextmv.cli.configuration.config import build_app
11
+ from nextmv.cli.message import in_progress, print_json, success
12
+ from nextmv.cli.options import AppIDOption, ProfileOption
13
+
14
+ # Set up subcommand application.
15
+ app = typer.Typer()
16
+
17
+
18
+ @app.command()
19
+ def list(
20
+ app_id: AppIDOption,
21
+ output: Annotated[
22
+ str | None,
23
+ typer.Option(
24
+ "--output",
25
+ "-o",
26
+ help="Saves the secrets collections list information to this location.",
27
+ metavar="OUTPUT_PATH",
28
+ ),
29
+ ] = None,
30
+ profile: ProfileOption = None,
31
+ ) -> None:
32
+ """
33
+ List all secrets collections of a Nextmv Cloud application.
34
+
35
+ [bold][underline]Examples[/underline][/bold]
36
+
37
+ - List all secrets collections of application [magenta]hare-app[/magenta].
38
+ $ [green]nextmv cloud secrets list --app-id hare-app[/green]
39
+
40
+ - List all secrets collections using the profile named [magenta]hare[/magenta].
41
+ $ [green]nextmv cloud secrets list --app-id hare-app --profile hare[/green]
42
+
43
+ - List all secrets collections and save the information to a [magenta]secrets.json[/magenta] file.
44
+ $ [green]nextmv cloud secrets list --app-id hare-app --output secrets.json[/green]
45
+ """
46
+
47
+ cloud_app = build_app(app_id=app_id, profile=profile)
48
+ in_progress(msg="Listing secrets collections...")
49
+ collections = cloud_app.list_secrets_collections()
50
+ collections_dicts = [collection.to_dict() for collection in collections]
51
+
52
+ if output is not None and output != "":
53
+ with open(output, "w") as f:
54
+ json.dump(collections_dicts, f, indent=2)
55
+
56
+ success(msg=f"Secrets collections list information saved to [magenta]{output}[/magenta].")
57
+
58
+ return
59
+
60
+ print_json(collections_dicts)