nextmv 0.39.0.dev1__py3-none-any.whl → 1.0.0__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 (161) hide show
  1. nextmv/__about__.py +1 -1
  2. nextmv/__entrypoint__.py +1 -2
  3. nextmv/__init__.py +2 -4
  4. nextmv/cli/CONTRIBUTING.md +583 -0
  5. nextmv/cli/cloud/__init__.py +49 -0
  6. nextmv/cli/cloud/acceptance/__init__.py +27 -0
  7. nextmv/cli/cloud/acceptance/create.py +391 -0
  8. nextmv/cli/cloud/acceptance/delete.py +64 -0
  9. nextmv/cli/cloud/acceptance/get.py +103 -0
  10. nextmv/cli/cloud/acceptance/list.py +62 -0
  11. nextmv/cli/cloud/acceptance/update.py +95 -0
  12. nextmv/cli/cloud/account/__init__.py +28 -0
  13. nextmv/cli/cloud/account/create.py +83 -0
  14. nextmv/cli/cloud/account/delete.py +59 -0
  15. nextmv/cli/cloud/account/get.py +66 -0
  16. nextmv/cli/cloud/account/update.py +70 -0
  17. nextmv/cli/cloud/app/__init__.py +35 -0
  18. nextmv/cli/cloud/app/create.py +140 -0
  19. nextmv/cli/cloud/app/delete.py +57 -0
  20. nextmv/cli/cloud/app/exists.py +44 -0
  21. nextmv/cli/cloud/app/get.py +66 -0
  22. nextmv/cli/cloud/app/list.py +61 -0
  23. nextmv/cli/cloud/app/push.py +432 -0
  24. nextmv/cli/cloud/app/update.py +124 -0
  25. nextmv/cli/cloud/batch/__init__.py +29 -0
  26. nextmv/cli/cloud/batch/create.py +452 -0
  27. nextmv/cli/cloud/batch/delete.py +64 -0
  28. nextmv/cli/cloud/batch/get.py +104 -0
  29. nextmv/cli/cloud/batch/list.py +63 -0
  30. nextmv/cli/cloud/batch/metadata.py +66 -0
  31. nextmv/cli/cloud/batch/update.py +95 -0
  32. nextmv/cli/cloud/data/__init__.py +26 -0
  33. nextmv/cli/cloud/data/upload.py +162 -0
  34. nextmv/cli/cloud/ensemble/__init__.py +33 -0
  35. nextmv/cli/cloud/ensemble/create.py +413 -0
  36. nextmv/cli/cloud/ensemble/delete.py +63 -0
  37. nextmv/cli/cloud/ensemble/get.py +65 -0
  38. nextmv/cli/cloud/ensemble/list.py +63 -0
  39. nextmv/cli/cloud/ensemble/update.py +103 -0
  40. nextmv/cli/cloud/input_set/__init__.py +32 -0
  41. nextmv/cli/cloud/input_set/create.py +168 -0
  42. nextmv/cli/cloud/input_set/delete.py +64 -0
  43. nextmv/cli/cloud/input_set/get.py +63 -0
  44. nextmv/cli/cloud/input_set/list.py +63 -0
  45. nextmv/cli/cloud/input_set/update.py +123 -0
  46. nextmv/cli/cloud/instance/__init__.py +35 -0
  47. nextmv/cli/cloud/instance/create.py +289 -0
  48. nextmv/cli/cloud/instance/delete.py +61 -0
  49. nextmv/cli/cloud/instance/exists.py +39 -0
  50. nextmv/cli/cloud/instance/get.py +62 -0
  51. nextmv/cli/cloud/instance/list.py +60 -0
  52. nextmv/cli/cloud/instance/update.py +216 -0
  53. nextmv/cli/cloud/managed_input/__init__.py +31 -0
  54. nextmv/cli/cloud/managed_input/create.py +144 -0
  55. nextmv/cli/cloud/managed_input/delete.py +64 -0
  56. nextmv/cli/cloud/managed_input/get.py +63 -0
  57. nextmv/cli/cloud/managed_input/list.py +60 -0
  58. nextmv/cli/cloud/managed_input/update.py +97 -0
  59. nextmv/cli/cloud/run/__init__.py +37 -0
  60. nextmv/cli/cloud/run/cancel.py +37 -0
  61. nextmv/cli/cloud/run/create.py +524 -0
  62. nextmv/cli/cloud/run/get.py +199 -0
  63. nextmv/cli/cloud/run/input.py +86 -0
  64. nextmv/cli/cloud/run/list.py +80 -0
  65. nextmv/cli/cloud/run/logs.py +166 -0
  66. nextmv/cli/cloud/run/metadata.py +67 -0
  67. nextmv/cli/cloud/run/track.py +500 -0
  68. nextmv/cli/cloud/scenario/__init__.py +29 -0
  69. nextmv/cli/cloud/scenario/create.py +451 -0
  70. nextmv/cli/cloud/scenario/delete.py +61 -0
  71. nextmv/cli/cloud/scenario/get.py +102 -0
  72. nextmv/cli/cloud/scenario/list.py +63 -0
  73. nextmv/cli/cloud/scenario/metadata.py +67 -0
  74. nextmv/cli/cloud/scenario/update.py +93 -0
  75. nextmv/cli/cloud/secrets/__init__.py +33 -0
  76. nextmv/cli/cloud/secrets/create.py +206 -0
  77. nextmv/cli/cloud/secrets/delete.py +63 -0
  78. nextmv/cli/cloud/secrets/get.py +66 -0
  79. nextmv/cli/cloud/secrets/list.py +60 -0
  80. nextmv/cli/cloud/secrets/update.py +144 -0
  81. nextmv/cli/cloud/shadow/__init__.py +33 -0
  82. nextmv/cli/cloud/shadow/create.py +184 -0
  83. nextmv/cli/cloud/shadow/delete.py +64 -0
  84. nextmv/cli/cloud/shadow/get.py +61 -0
  85. nextmv/cli/cloud/shadow/list.py +63 -0
  86. nextmv/cli/cloud/shadow/metadata.py +66 -0
  87. nextmv/cli/cloud/shadow/start.py +43 -0
  88. nextmv/cli/cloud/shadow/stop.py +53 -0
  89. nextmv/cli/cloud/shadow/update.py +96 -0
  90. nextmv/cli/cloud/switchback/__init__.py +33 -0
  91. nextmv/cli/cloud/switchback/create.py +151 -0
  92. nextmv/cli/cloud/switchback/delete.py +64 -0
  93. nextmv/cli/cloud/switchback/get.py +62 -0
  94. nextmv/cli/cloud/switchback/list.py +63 -0
  95. nextmv/cli/cloud/switchback/metadata.py +68 -0
  96. nextmv/cli/cloud/switchback/start.py +43 -0
  97. nextmv/cli/cloud/switchback/stop.py +53 -0
  98. nextmv/cli/cloud/switchback/update.py +96 -0
  99. nextmv/cli/cloud/upload/__init__.py +22 -0
  100. nextmv/cli/cloud/upload/create.py +39 -0
  101. nextmv/cli/cloud/version/__init__.py +33 -0
  102. nextmv/cli/cloud/version/create.py +96 -0
  103. nextmv/cli/cloud/version/delete.py +61 -0
  104. nextmv/cli/cloud/version/exists.py +39 -0
  105. nextmv/cli/cloud/version/get.py +62 -0
  106. nextmv/cli/cloud/version/list.py +60 -0
  107. nextmv/cli/cloud/version/update.py +92 -0
  108. nextmv/cli/community/__init__.py +24 -0
  109. nextmv/cli/community/clone.py +86 -0
  110. nextmv/cli/community/list.py +200 -0
  111. nextmv/cli/configuration/__init__.py +23 -0
  112. nextmv/cli/configuration/config.py +228 -0
  113. nextmv/cli/configuration/create.py +94 -0
  114. nextmv/cli/configuration/delete.py +67 -0
  115. nextmv/cli/configuration/list.py +77 -0
  116. nextmv/cli/confirm.py +34 -0
  117. nextmv/cli/main.py +161 -3
  118. nextmv/cli/message.py +170 -0
  119. nextmv/cli/options.py +220 -0
  120. nextmv/cli/version.py +22 -2
  121. nextmv/cloud/__init__.py +17 -38
  122. nextmv/cloud/acceptance_test.py +20 -83
  123. nextmv/cloud/account.py +269 -30
  124. nextmv/cloud/application/__init__.py +898 -0
  125. nextmv/cloud/application/_acceptance.py +424 -0
  126. nextmv/cloud/application/_batch_scenario.py +845 -0
  127. nextmv/cloud/application/_ensemble.py +251 -0
  128. nextmv/cloud/application/_input_set.py +263 -0
  129. nextmv/cloud/application/_instance.py +289 -0
  130. nextmv/cloud/application/_managed_input.py +227 -0
  131. nextmv/cloud/application/_run.py +1393 -0
  132. nextmv/cloud/application/_secrets.py +294 -0
  133. nextmv/cloud/application/_shadow.py +320 -0
  134. nextmv/cloud/application/_switchback.py +332 -0
  135. nextmv/cloud/application/_utils.py +54 -0
  136. nextmv/cloud/application/_version.py +304 -0
  137. nextmv/cloud/batch_experiment.py +6 -2
  138. nextmv/cloud/community.py +446 -0
  139. nextmv/cloud/instance.py +11 -1
  140. nextmv/cloud/integration.py +8 -5
  141. nextmv/cloud/package.py +50 -9
  142. nextmv/cloud/shadow.py +254 -0
  143. nextmv/cloud/switchback.py +228 -0
  144. nextmv/deprecated.py +5 -3
  145. nextmv/input.py +20 -88
  146. nextmv/local/application.py +3 -15
  147. nextmv/local/runner.py +1 -1
  148. nextmv/model.py +50 -11
  149. nextmv/options.py +11 -256
  150. nextmv/output.py +0 -62
  151. nextmv/polling.py +54 -16
  152. nextmv/run.py +84 -37
  153. nextmv/status.py +1 -51
  154. {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/METADATA +37 -11
  155. nextmv-1.0.0.dist-info/RECORD +185 -0
  156. nextmv-1.0.0.dist-info/entry_points.txt +2 -0
  157. nextmv/cloud/application.py +0 -4204
  158. nextmv-0.39.0.dev1.dist-info/RECORD +0 -55
  159. nextmv-0.39.0.dev1.dist-info/entry_points.txt +0 -2
  160. {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/WHEEL +0 -0
  161. {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,199 @@
1
+ """
2
+ This module defines the cloud run 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, RunIDOption
13
+ from nextmv.cloud.application import Application
14
+ from nextmv.output import OutputFormat
15
+ from nextmv.polling import PollingOptions, default_polling_options
16
+
17
+ # Set up subcommand application.
18
+ app = typer.Typer()
19
+
20
+
21
+ @app.command()
22
+ def get(
23
+ app_id: AppIDOption,
24
+ run_id: RunIDOption,
25
+ output: Annotated[
26
+ str | None,
27
+ typer.Option(
28
+ "--output",
29
+ "-o",
30
+ help="Waits for the run to complete and save the output to this location. "
31
+ "A file or directory will be created depending on content format.",
32
+ metavar="OUTPUT_PATH",
33
+ ),
34
+ ] = None,
35
+ timeout: Annotated[
36
+ int,
37
+ typer.Option(
38
+ help="The maximum time in seconds to wait for results when polling. Poll indefinitely if not set.",
39
+ metavar="TIMEOUT_SECONDS",
40
+ ),
41
+ ] = -1,
42
+ wait: Annotated[
43
+ bool,
44
+ typer.Option(
45
+ "--wait",
46
+ "-w",
47
+ help="Wait for the run to complete. Run result is printed to [magenta]stdout[/magenta] for "
48
+ "[magenta]json[/magenta], to a dir for [magenta]multi-file[/magenta]. "
49
+ "Specify output location with --output.",
50
+ ),
51
+ ] = False,
52
+ profile: ProfileOption = None,
53
+ ) -> None:
54
+ """
55
+ Get the result (output) of a Nextmv Cloud application run.
56
+
57
+ Use the --wait flag to wait for the run to complete, polling
58
+ for results. Using the --output flag will also activate
59
+ waiting, and allows you to specify a destination (file or dir) for the
60
+ output, depending on the content type.
61
+
62
+ [bold][underline]Examples[/underline][/bold]
63
+
64
+ - Get the results of a run with ID [magenta]burrow-123[/magenta], belonging to an app with ID
65
+ [magenta]hare-app[/magenta].
66
+ $ [dim]nextmv cloud run get --app-id hare-app --run-id burrow-123[/dim]
67
+
68
+ - Get the results of a run with ID [magenta]burrow-123[/magenta], belonging to an app with ID
69
+ [magenta]hare-app[/magenta]. Wait for the run to complete if necessary.
70
+ $ [dim]nextmv cloud run get --app-id hare-app --run-id burrow-123 --wait[/dim]
71
+
72
+ - Get the results of a run with ID [magenta]burrow-123[/magenta], belonging to an app with ID
73
+ [magenta]hare-app[/magenta]. The app is a [magenta]json[/magenta] app.
74
+ Save the results to a [magenta]results.json[/magenta] file.
75
+ $ [dim]nextmv cloud run get --app-id hare-app --run-id burrow-123 --output results.json[/dim]
76
+
77
+ - Get the results of a run with ID [magenta]burrow-123[/magenta], belonging to an app with ID
78
+ [magenta]hare-app[/magenta]. The app is a [magenta]multi-file[/magenta] app.
79
+ Save the results to the [magenta]results[/magenta] dir.
80
+ $ [dim]nextmv cloud run get --app-id hare-app --run-id burrow-123 --output results[/dim]
81
+
82
+ - Get the results of a run with ID [magenta]burrow-123[/magenta], belonging to an app with ID
83
+ [magenta]hare-app[/magenta]. Use the profile named [magenta]hare[/magenta].
84
+ $ [dim]nextmv cloud run get --app-id hare-app --run-id burrow-123 --profile hare[/dim]
85
+ """
86
+
87
+ cloud_app = build_app(app_id=app_id, profile=profile)
88
+
89
+ # Build the polling options.
90
+ polling_options = default_polling_options()
91
+ polling_options.max_duration = timeout
92
+
93
+ handle_outputs(
94
+ cloud_app=cloud_app,
95
+ run_id=run_id,
96
+ wait=wait,
97
+ output=output,
98
+ polling_options=polling_options,
99
+ skip_wait_check=True,
100
+ )
101
+
102
+
103
+ def handle_outputs(
104
+ cloud_app: Application,
105
+ run_id: str,
106
+ wait: bool,
107
+ output: str | None,
108
+ polling_options: PollingOptions,
109
+ skip_wait_check: bool = False,
110
+ ) -> None:
111
+ """
112
+ Handle retrieving and outputting the results from a run.
113
+
114
+ If ``wait`` is False and ``output`` is not specified, this function returns
115
+ early without doing anything. Otherwise, results are retrieved using
116
+ polling (since the run may not yet be complete) and output accordingly.
117
+
118
+ The output behavior depends on the content type:
119
+
120
+ - **JSON/TEXT**: If ``output`` is specified, writes to the given file path.
121
+ Otherwise, prints to stdout.
122
+ - **MULTI_FILE/CSV_ARCHIVE**: Downloads files to a directory. If ``output``
123
+ is specified, uses that as the directory name. Otherwise, uses the
124
+ ``run_id`` as the directory name.
125
+
126
+ Parameters
127
+ ----------
128
+ cloud_app : Application
129
+ The cloud application instance used to interact with the Nextmv Cloud
130
+ API.
131
+ run_id : str
132
+ The unique identifier of the run to retrieve results for.
133
+ wait : bool
134
+ Whether to wait for the run to complete. If False and ``output`` is not
135
+ specified, the function returns early without retrieving results.
136
+ output : str | None
137
+ The location to write the output. For JSON/TEXT formats, this is a file
138
+ path. For MULTI_FILE/CSV_ARCHIVE formats, this is a directory path. If
139
+ None, JSON/TEXT output is printed to stdout and MULTI_FILE/CSV_ARCHIVE
140
+ output uses the ``run_id`` as the directory name.
141
+ polling_options : PollingOptions
142
+ Configuration options for polling behavior, including timeout and
143
+ interval settings.
144
+ skip_wait_check : bool, optional
145
+ If True, skips the early return check when both `wait` is False and
146
+ `output` is not specified. Default is False.
147
+ """
148
+
149
+ # If we don't need to wait, no output is specified, and we're not skipping
150
+ # the wait check, return early.
151
+ if not wait and (output is None or output == "") and not skip_wait_check:
152
+ return
153
+
154
+ # Get the run metadata to determine how to operate with the output.
155
+ run_info = cloud_app.run_metadata(run_id=run_id)
156
+ content_format = run_info.metadata.format.format_output.output_type
157
+
158
+ # Build kwargs for the result retrieval.
159
+ kwargs = {"run_id": run_id}
160
+
161
+ # For MULTI_FILE and CSV_ARCHIVE, we need output_dir_path.
162
+ if content_format not in {OutputFormat.JSON, OutputFormat.TEXT}:
163
+ output_dir = f"{run_id}-output" if output is None or output == "" else output
164
+ kwargs["output_dir_path"] = output_dir
165
+
166
+ # Always poll for results since we can't guarantee the run is done.
167
+ # If the run is already complete, polling returns immediately.
168
+ in_progress(msg="Getting run results...")
169
+ wait = wait or (output is not None and output != "")
170
+ if wait:
171
+ kwargs["polling_options"] = polling_options
172
+ run_result = cloud_app.run_result_with_polling(**kwargs)
173
+ else:
174
+ run_result = cloud_app.run_result(**kwargs)
175
+
176
+ # Handle the case where output is embedded directly in the result: json and text.
177
+ if content_format in {OutputFormat.JSON, OutputFormat.TEXT}:
178
+ if output is None or output == "":
179
+ print_json(run_result.to_dict())
180
+ else:
181
+ with open(output, "w") as f:
182
+ json.dump(run_result.to_dict(), f, indent=2)
183
+
184
+ success(f"Run output written to [magenta]{output}[/magenta].")
185
+
186
+ return
187
+
188
+ # At this point, we know that the output is multi-file or csv-archive.
189
+ result_dict = run_result.to_dict()
190
+ if "output" in result_dict and run_result.metadata.run_is_finalized():
191
+ del result_dict["output"]
192
+ success(f"Run outputs downloaded to [magenta]{output_dir}[/magenta]. Here is the metadata.")
193
+ else:
194
+ success(
195
+ f"Run is not finalized (status: [magenta]{run_result.metadata.status_v2.value}[/magenta]). "
196
+ "Here is the metadata."
197
+ )
198
+
199
+ print_json(result_dict)
@@ -0,0 +1,86 @@
1
+ """
2
+ This module defines the cloud run input 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, RunIDOption
13
+ from nextmv.output import OutputFormat
14
+
15
+ # Set up subcommand application.
16
+ app = typer.Typer()
17
+
18
+
19
+ @app.command()
20
+ def input(
21
+ app_id: AppIDOption,
22
+ run_id: RunIDOption,
23
+ output: Annotated[
24
+ str | None,
25
+ typer.Option(
26
+ "--output",
27
+ "-o",
28
+ help="Saves the input to this location.",
29
+ metavar="OUTPUT_PATH",
30
+ ),
31
+ ] = None,
32
+ profile: ProfileOption = None,
33
+ ) -> None:
34
+ """
35
+ Get the input of a Nextmv Cloud application run.
36
+
37
+ By default, the input is fetched and printed to [magenta]stdout[/magenta].
38
+ Use the --output flag to save the input to a file.
39
+
40
+ [bold][underline]Examples[/underline][/bold]
41
+
42
+ - Get the input of a run with ID [magenta]burrow-123[/magenta], belonging to an app with ID
43
+ [magenta]hare-app[/magenta]. Input is printed to [magenta]stdout[/magenta].
44
+ $ [dim]nextmv cloud run input --app-id hare-app --run-id burrow-123[/dim]
45
+
46
+ - Get the input of a run with ID [magenta]burrow-123[/magenta], belonging to an app with ID
47
+ [magenta]hare-app[/magenta]. Save the input to a [magenta]input.json[/magenta] file.
48
+ $ [dim]nextmv cloud run input --app-id hare-app --run-id burrow-123 --output input.json[/dim]
49
+
50
+ - Get the input of a run with ID [magenta]burrow-123[/magenta], belonging to an app with ID
51
+ [magenta]hare-app[/magenta]. Use the profile named [magenta]hare[/magenta].
52
+ $ [dim]nextmv cloud run input --app-id hare-app --run-id burrow-123 --profile hare[/dim]
53
+ """
54
+
55
+ cloud_app = build_app(app_id=app_id, profile=profile)
56
+ in_progress(msg="Getting run input...")
57
+
58
+ # First get the content type to check what we should do with the input,
59
+ # based on its format.
60
+ run_info = cloud_app.run_metadata(run_id)
61
+
62
+ # If the input is multi-file, we need to provide an `output_dir_path` to
63
+ # save the files to.
64
+ if run_info.metadata.format.format_input.input_type not in {OutputFormat.JSON, OutputFormat.TEXT}:
65
+ # If no output path is provided, use the run ID as the directory name.
66
+ output = f"{run_id}-input" if output is None or output == "" else output
67
+ cloud_app.run_input(run_id=run_id, output_dir_path=output)
68
+ success(msg=f"Run input saved to [magenta]{output}[/magenta].")
69
+
70
+ return
71
+
72
+ # At this point, we know the input is JSON or text, so we can fetch it
73
+ # normally. The method internally will take care of large inputs.
74
+ run_input = cloud_app.run_input(run_id=run_id)
75
+
76
+ # If an output path is provided, save the input to that file.
77
+ if output is not None and output != "":
78
+ with open(output, "w") as f:
79
+ json.dump(run_input, f, indent=2)
80
+
81
+ success(msg=f"Run input saved to [magenta]{output}[/magenta].")
82
+
83
+ return
84
+
85
+ # Otherwise, print the input to stdout.
86
+ print_json(data=run_input)
@@ -0,0 +1,80 @@
1
+ """
2
+ This module defines the cloud run 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 enum_values, in_progress, print_json, success
12
+ from nextmv.cli.options import AppIDOption, ProfileOption
13
+ from nextmv.status import StatusV2
14
+
15
+ # Set up subcommand application.
16
+ app = typer.Typer()
17
+
18
+
19
+ @app.command()
20
+ def list(
21
+ app_id: AppIDOption,
22
+ output: Annotated[
23
+ str | None,
24
+ typer.Option(
25
+ "--output",
26
+ "-o",
27
+ help="Saves the list of runs to this location.",
28
+ metavar="OUTPUT_PATH",
29
+ ),
30
+ ] = None,
31
+ status: Annotated[
32
+ StatusV2 | None,
33
+ typer.Option(
34
+ "--status",
35
+ "-s",
36
+ help=f"Filter runs by their status. Allowed values are: {enum_values(StatusV2)}.",
37
+ metavar="STATUS",
38
+ ),
39
+ ] = None,
40
+ profile: ProfileOption = None,
41
+ ) -> None:
42
+ """
43
+ Get the list of runs for a Nextmv Cloud application.
44
+
45
+ By default, the list of runs is fetched and printed to [magenta]stdout[/magenta].
46
+ Use the --output flag to save the list to a file.
47
+
48
+ You can use the optional --status flag to filter runs by their status.
49
+
50
+ [bold][underline]Examples[/underline][/bold]
51
+
52
+ - Get the list of runs for an app with ID [magenta]hare-app[/magenta]. List is printed to [magenta]stdout[/magenta].
53
+ $ [dim]nextmv cloud run list --app-id hare-app[/dim]
54
+
55
+ - Get the list of runs for an app with ID [magenta]hare-app[/magenta]. Save the list to a
56
+ [magenta]runs.json[/magenta] file.
57
+ $ [dim]nextmv cloud run list --app-id hare-app --output runs.json[/dim]
58
+
59
+ - Get the list of runs for an app with ID [magenta]hare-app[/magenta].
60
+ Use the profile named [magenta]hare[/magenta].
61
+ $ [dim]nextmv cloud run list --app-id hare-app --profile hare[/dim]
62
+
63
+ - Get the list of [magenta]queued[/magenta] runs for an app with ID [magenta]hare-app[/magenta].
64
+ $ [dim]nextmv cloud run list --app-id hare-app --status queued[/dim]
65
+ """
66
+
67
+ cloud_app = build_app(app_id=app_id, profile=profile)
68
+ in_progress(msg="Listing app runs...")
69
+ runs = cloud_app.list_runs(status=status)
70
+ runs_dicts = [run.to_dict() for run in runs]
71
+
72
+ if output is not None and output != "":
73
+ with open(output, "w") as f:
74
+ json.dump(runs_dicts, f, indent=2)
75
+
76
+ success(msg=f"Run list saved to [magenta]{output}[/magenta].")
77
+
78
+ return
79
+
80
+ print_json(runs_dicts)
@@ -0,0 +1,166 @@
1
+ """
2
+ This module defines the cloud run logs command for the Nextmv CLI.
3
+ """
4
+
5
+ import sys
6
+ from pathlib import Path
7
+ from typing import Annotated
8
+
9
+ import rich
10
+ import typer
11
+
12
+ from nextmv.cli.configuration.config import build_app
13
+ from nextmv.cli.message import in_progress, success
14
+ from nextmv.cli.options import AppIDOption, ProfileOption, RunIDOption
15
+ from nextmv.cloud.application import Application
16
+ from nextmv.polling import PollingOptions, default_polling_options
17
+
18
+ # Set up subcommand application.
19
+ app = typer.Typer()
20
+
21
+
22
+ @app.command()
23
+ def logs(
24
+ app_id: AppIDOption,
25
+ run_id: RunIDOption,
26
+ output: Annotated[
27
+ str | None,
28
+ typer.Option(
29
+ "--output",
30
+ "-o",
31
+ help="Waits for the run to complete and saves the logs to this location.",
32
+ metavar="OUTPUT_PATH",
33
+ ),
34
+ ] = None,
35
+ tail: Annotated[
36
+ bool,
37
+ typer.Option(
38
+ "--tail",
39
+ "-t",
40
+ help="Tail the logs until the run completes. Logs are streamed to [magenta]stderr[/magenta]. "
41
+ "Specify log output location with --output.",
42
+ ),
43
+ ] = False,
44
+ timeout: Annotated[
45
+ int,
46
+ typer.Option(
47
+ help="The maximum time in seconds to wait for results when polling. Poll indefinitely if not set.",
48
+ metavar="TIMEOUT_SECONDS",
49
+ ),
50
+ ] = -1,
51
+ profile: ProfileOption = None,
52
+ ) -> None:
53
+ """
54
+ Get the logs of a Nextmv Cloud application run.
55
+
56
+ By default, the logs are fetched and printed to [magenta]stderr[/magenta].
57
+ Use the --tail flag to stream logs to [magenta]stderr[/magenta] until the
58
+ run completes. Using the --output flag will also activate waiting, and
59
+ allows you to specify a file to write the logs to.
60
+
61
+ [bold][underline]Examples[/underline][/bold]
62
+
63
+ - Get the logs of a run with ID [magenta]burrow-123[/magenta], belonging to an app with ID
64
+ [magenta]hare-app[/magenta]. Logs are printed to [magenta]stderr[/magenta].
65
+ $ [dim]nextmv cloud run logs --app-id hare-app --run-id burrow-123[/dim]
66
+
67
+ - Get the logs of a run with ID [magenta]burrow-123[/magenta], belonging to an app with ID
68
+ [magenta]hare-app[/magenta]. Tail the logs until the run completes.
69
+ $ [dim]nextmv cloud run logs --app-id hare-app --run-id burrow-123 --tail[/dim]
70
+
71
+ - Get the logs of a run with ID [magenta]burrow-123[/magenta], belonging to an app with ID
72
+ [magenta]hare-app[/magenta]. Save the logs to a [magenta]logs.log[/magenta] file.
73
+ $ [dim]nextmv cloud run logs --app-id hare-app --run-id burrow-123 --output logs.log[/dim]
74
+
75
+ - Get the logs of a run with ID [magenta]burrow-123[/magenta], belonging to an app with ID
76
+ [magenta]hare-app[/magenta]. Tail the logs and save them to a [magenta]logs.log[/magenta] file.
77
+ $ [dim]nextmv cloud run logs --app-id hare-app --run-id burrow-123 --tail --output logs.log[/dim]
78
+
79
+ - Get the logs of a run with ID [magenta]burrow-123[/magenta], belonging to an app with ID
80
+ [magenta]hare-app[/magenta]. Use the profile named [magenta]hare[/magenta].
81
+ $ [dim]nextmv cloud run logs --app-id hare-app --run-id burrow-123 --profile hare[/dim]
82
+ """
83
+
84
+ cloud_app = build_app(app_id=app_id, profile=profile)
85
+
86
+ # Build the polling options.
87
+ polling_options = default_polling_options()
88
+ polling_options.max_duration = timeout
89
+
90
+ handle_logs(
91
+ cloud_app=cloud_app,
92
+ run_id=run_id,
93
+ tail=tail,
94
+ logs=output,
95
+ polling_options=polling_options,
96
+ file_output=output is not None,
97
+ )
98
+
99
+
100
+ def handle_logs(
101
+ cloud_app: Application,
102
+ run_id: str,
103
+ tail: bool,
104
+ logs: str | None,
105
+ polling_options: PollingOptions,
106
+ file_output: bool,
107
+ ) -> None:
108
+ """
109
+ Handle retrieving and outputting logs from a run.
110
+
111
+ If neither `tail` is True nor `logs` is specified, this function
112
+ returns early without doing anything. Otherwise, logs are retrieved and
113
+ optionally written to a file.
114
+
115
+ When `tail` is True, logs are streamed in real-time to stderr as the run
116
+ executes. When `logs` is specified (without tailing), the function waits
117
+ for the run to complete and then fetches all logs at once. In both cases,
118
+ if a `logs` file path is provided, the logs are persisted to that file.
119
+
120
+ Parameters
121
+ ----------
122
+ cloud_app : Application
123
+ The cloud application instance used to interact with the Nextmv Cloud
124
+ API.
125
+ run_id : str
126
+ The unique identifier of the run to retrieve logs for.
127
+ tail : bool
128
+ If True, streams logs in real-time to stderr as the run executes.
129
+ logs : str | None
130
+ The file path where logs should be written. If None, logs are only
131
+ displayed to stderr (when tailing) and not persisted to a file.
132
+ polling_options : PollingOptions
133
+ Configuration options for polling behavior, including timeout and
134
+ interval settings.
135
+ file_output : bool
136
+ Indicates whether logs should be written to a file. If False, logs are
137
+ only printed to stderr, no matter the status of the run.
138
+ """
139
+
140
+ if tail:
141
+ in_progress(msg="Tailing logs...")
142
+ fetched_logs = cloud_app.run_logs_with_polling(
143
+ run_id=run_id,
144
+ polling_options=polling_options,
145
+ verbose=True,
146
+ rich_print=True,
147
+ )
148
+ if logs is None:
149
+ return
150
+
151
+ log_content = "".join(log_entry.log for log_entry in fetched_logs)
152
+ elif logs is not None and logs != "" and file_output:
153
+ in_progress(msg="Getting run logs...")
154
+ cloud_app.run_result_with_polling(run_id=run_id, polling_options=polling_options)
155
+ run_logs = cloud_app.run_logs(run_id=run_id)
156
+ log_content = run_logs.log
157
+ elif not file_output:
158
+ in_progress(msg="Getting run logs...")
159
+ run_logs = cloud_app.run_logs(run_id=run_id)
160
+ rich.print(run_logs.log, file=sys.stderr)
161
+ return
162
+ else:
163
+ return
164
+
165
+ Path(logs).write_text(log_content)
166
+ success(f"Run logs written to [magenta]{logs}[/magenta].")
@@ -0,0 +1,67 @@
1
+ """
2
+ This module defines the cloud run 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, RunIDOption
13
+
14
+ # Set up subcommand application.
15
+ app = typer.Typer()
16
+
17
+
18
+ @app.command()
19
+ def metadata(
20
+ app_id: AppIDOption,
21
+ run_id: RunIDOption,
22
+ output: Annotated[
23
+ str | None,
24
+ typer.Option(
25
+ "--output",
26
+ "-o",
27
+ help="Saves the metadata to this location.",
28
+ metavar="OUTPUT_PATH",
29
+ ),
30
+ ] = None,
31
+ profile: ProfileOption = None,
32
+ ) -> None:
33
+ """
34
+ Get the metadata of a Nextmv Cloud application run.
35
+
36
+ By default, the metadata is fetched and printed to [magenta]stdout[/magenta].
37
+ Use the --output flag to save the metadata to a file.
38
+
39
+ [bold][underline]Examples[/underline][/bold]
40
+
41
+ - Get the metadata of a run with ID [magenta]burrow-123[/magenta], belonging to an app with ID
42
+ [magenta]hare-app[/magenta]. Metadata is printed to [magenta]stdout[/magenta].
43
+ $ [dim]nextmv cloud run metadata --app-id hare-app --run-id burrow-123[/dim]
44
+
45
+ - Get the metadata of a run with ID [magenta]burrow-123[/magenta], belonging to an app with ID
46
+ [magenta]hare-app[/magenta]. Save the metadata to a [magenta]metadata.json[/magenta] file.
47
+ $ [dim]nextmv cloud run metadata --app-id hare-app --run-id burrow-123 --output metadata.json[/dim]
48
+
49
+ - Get the metadata of a run with ID [magenta]burrow-123[/magenta], belonging to an app with ID
50
+ [magenta]hare-app[/magenta]. Use the profile named [magenta]hare[/magenta].
51
+ $ [dim]nextmv cloud run metadata --app-id hare-app --run-id burrow-123 --profile hare[/dim]
52
+ """
53
+
54
+ cloud_app = build_app(app_id=app_id, profile=profile)
55
+ in_progress(msg="Getting run metadata...")
56
+ run_info = cloud_app.run_metadata(run_id)
57
+ info_dict = run_info.to_dict()
58
+
59
+ if output is not None and output != "":
60
+ with open(output, "w") as f:
61
+ json.dump(info_dict, f, indent=2)
62
+
63
+ success(msg=f"Run metadata saved to [magenta]{output}[/magenta].")
64
+
65
+ return
66
+
67
+ print_json(info_dict)