nextmv 1.0.0.dev3__py3-none-any.whl → 1.0.0.dev5__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 (135) hide show
  1. nextmv/__about__.py +1 -1
  2. nextmv/__entrypoint__.py +1 -2
  3. nextmv/__init__.py +0 -4
  4. nextmv/_serialization.py +1 -1
  5. nextmv/cli/CONTRIBUTING.md +81 -29
  6. nextmv/cli/cloud/acceptance/create.py +24 -26
  7. nextmv/cli/cloud/acceptance/delete.py +7 -8
  8. nextmv/cli/cloud/acceptance/get.py +9 -10
  9. nextmv/cli/cloud/acceptance/list.py +3 -3
  10. nextmv/cli/cloud/acceptance/update.py +6 -6
  11. nextmv/cli/cloud/account/__init__.py +3 -3
  12. nextmv/cli/cloud/account/create.py +11 -11
  13. nextmv/cli/cloud/account/delete.py +6 -7
  14. nextmv/cli/cloud/account/get.py +3 -3
  15. nextmv/cli/cloud/account/update.py +5 -5
  16. nextmv/cli/cloud/app/create.py +25 -26
  17. nextmv/cli/cloud/app/delete.py +5 -6
  18. nextmv/cli/cloud/app/exists.py +2 -2
  19. nextmv/cli/cloud/app/get.py +2 -2
  20. nextmv/cli/cloud/app/list.py +3 -3
  21. nextmv/cli/cloud/app/push.py +368 -54
  22. nextmv/cli/cloud/app/update.py +12 -12
  23. nextmv/cli/cloud/batch/create.py +26 -28
  24. nextmv/cli/cloud/batch/delete.py +5 -6
  25. nextmv/cli/cloud/batch/get.py +8 -8
  26. nextmv/cli/cloud/batch/list.py +3 -3
  27. nextmv/cli/cloud/batch/metadata.py +4 -4
  28. nextmv/cli/cloud/batch/update.py +6 -6
  29. nextmv/cli/cloud/data/__init__.py +1 -1
  30. nextmv/cli/cloud/data/upload.py +15 -15
  31. nextmv/cli/cloud/ensemble/__init__.py +2 -0
  32. nextmv/cli/cloud/ensemble/create.py +21 -22
  33. nextmv/cli/cloud/ensemble/delete.py +5 -6
  34. nextmv/cli/cloud/ensemble/get.py +4 -4
  35. nextmv/cli/cloud/ensemble/list.py +63 -0
  36. nextmv/cli/cloud/ensemble/update.py +9 -9
  37. nextmv/cli/cloud/input_set/create.py +20 -22
  38. nextmv/cli/cloud/input_set/get.py +3 -3
  39. nextmv/cli/cloud/input_set/list.py +3 -3
  40. nextmv/cli/cloud/input_set/update.py +24 -24
  41. nextmv/cli/cloud/instance/create.py +14 -15
  42. nextmv/cli/cloud/instance/delete.py +5 -6
  43. nextmv/cli/cloud/instance/exists.py +2 -2
  44. nextmv/cli/cloud/instance/get.py +2 -2
  45. nextmv/cli/cloud/instance/list.py +3 -3
  46. nextmv/cli/cloud/instance/update.py +14 -14
  47. nextmv/cli/cloud/managed_input/create.py +14 -16
  48. nextmv/cli/cloud/managed_input/delete.py +6 -7
  49. nextmv/cli/cloud/managed_input/get.py +3 -3
  50. nextmv/cli/cloud/managed_input/list.py +3 -3
  51. nextmv/cli/cloud/managed_input/update.py +9 -9
  52. nextmv/cli/cloud/run/cancel.py +2 -2
  53. nextmv/cli/cloud/run/create.py +32 -33
  54. nextmv/cli/cloud/run/get.py +8 -8
  55. nextmv/cli/cloud/run/input.py +4 -4
  56. nextmv/cli/cloud/run/list.py +6 -6
  57. nextmv/cli/cloud/run/logs.py +9 -10
  58. nextmv/cli/cloud/run/metadata.py +4 -4
  59. nextmv/cli/cloud/run/track.py +32 -33
  60. nextmv/cli/cloud/scenario/create.py +21 -21
  61. nextmv/cli/cloud/scenario/delete.py +5 -6
  62. nextmv/cli/cloud/scenario/get.py +8 -8
  63. nextmv/cli/cloud/scenario/list.py +3 -3
  64. nextmv/cli/cloud/scenario/metadata.py +4 -4
  65. nextmv/cli/cloud/scenario/update.py +6 -6
  66. nextmv/cli/cloud/secrets/create.py +17 -17
  67. nextmv/cli/cloud/secrets/delete.py +5 -6
  68. nextmv/cli/cloud/secrets/get.py +4 -4
  69. nextmv/cli/cloud/secrets/list.py +3 -3
  70. nextmv/cli/cloud/secrets/update.py +17 -20
  71. nextmv/cli/cloud/shadow/create.py +31 -31
  72. nextmv/cli/cloud/shadow/delete.py +5 -6
  73. nextmv/cli/cloud/shadow/get.py +2 -2
  74. nextmv/cli/cloud/shadow/list.py +3 -3
  75. nextmv/cli/cloud/shadow/metadata.py +4 -4
  76. nextmv/cli/cloud/shadow/start.py +3 -3
  77. nextmv/cli/cloud/shadow/stop.py +4 -6
  78. nextmv/cli/cloud/shadow/update.py +6 -6
  79. nextmv/cli/cloud/switchback/create.py +19 -15
  80. nextmv/cli/cloud/switchback/delete.py +5 -6
  81. nextmv/cli/cloud/switchback/get.py +3 -3
  82. nextmv/cli/cloud/switchback/list.py +3 -3
  83. nextmv/cli/cloud/switchback/metadata.py +6 -6
  84. nextmv/cli/cloud/switchback/start.py +4 -4
  85. nextmv/cli/cloud/switchback/stop.py +4 -6
  86. nextmv/cli/cloud/switchback/update.py +6 -6
  87. nextmv/cli/cloud/upload/create.py +2 -2
  88. nextmv/cli/cloud/version/create.py +9 -10
  89. nextmv/cli/cloud/version/delete.py +5 -6
  90. nextmv/cli/cloud/version/exists.py +2 -2
  91. nextmv/cli/cloud/version/get.py +2 -2
  92. nextmv/cli/cloud/version/list.py +3 -3
  93. nextmv/cli/cloud/version/update.py +8 -8
  94. nextmv/cli/community/clone.py +12 -10
  95. nextmv/cli/community/list.py +9 -9
  96. nextmv/cli/configuration/config.py +43 -10
  97. nextmv/cli/configuration/create.py +3 -3
  98. nextmv/cli/configuration/delete.py +7 -7
  99. nextmv/cli/configuration/list.py +3 -3
  100. nextmv/cli/confirm.py +34 -0
  101. nextmv/cli/main.py +27 -36
  102. nextmv/cli/message.py +2 -2
  103. nextmv/cli/version.py +1 -1
  104. nextmv/cloud/__init__.py +0 -38
  105. nextmv/cloud/acceptance_test.py +1 -65
  106. nextmv/cloud/account.py +1 -6
  107. nextmv/cloud/application/__init__.py +192 -54
  108. nextmv/cloud/application/_batch_scenario.py +4 -19
  109. nextmv/cloud/application/_instance.py +3 -3
  110. nextmv/cloud/application/_managed_input.py +2 -2
  111. nextmv/cloud/application/_run.py +8 -1
  112. nextmv/cloud/application/_shadow.py +2 -2
  113. nextmv/cloud/application/_switchback.py +12 -4
  114. nextmv/cloud/application/_version.py +4 -3
  115. nextmv/cloud/client.py +1 -1
  116. nextmv/cloud/shadow.py +43 -4
  117. nextmv/cloud/switchback.py +46 -9
  118. nextmv/default_app/main.py +4 -6
  119. nextmv/deprecated.py +5 -3
  120. nextmv/input.py +0 -52
  121. nextmv/local/executor.py +83 -3
  122. nextmv/local/geojson_handler.py +1 -1
  123. nextmv/local/runner.py +1 -1
  124. nextmv/manifest.py +11 -7
  125. nextmv/model.py +2 -2
  126. nextmv/options.py +10 -255
  127. nextmv/output.py +57 -83
  128. nextmv/run.py +13 -13
  129. nextmv/status.py +1 -51
  130. {nextmv-1.0.0.dev3.dist-info → nextmv-1.0.0.dev5.dist-info}/METADATA +1 -1
  131. nextmv-1.0.0.dev5.dist-info/RECORD +183 -0
  132. nextmv-1.0.0.dev3.dist-info/RECORD +0 -181
  133. {nextmv-1.0.0.dev3.dist-info → nextmv-1.0.0.dev5.dist-info}/WHEEL +0 -0
  134. {nextmv-1.0.0.dev3.dist-info → nextmv-1.0.0.dev5.dist-info}/entry_points.txt +0 -0
  135. {nextmv-1.0.0.dev3.dist-info → nextmv-1.0.0.dev5.dist-info}/licenses/LICENSE +0 -0
@@ -2,12 +2,18 @@
2
2
  This module defines the cloud app push command for the Nextmv CLI.
3
3
  """
4
4
 
5
+ import sys
6
+ from datetime import datetime, timezone
5
7
  from typing import Annotated
6
8
 
7
9
  import typer
10
+ from rich.prompt import Prompt
8
11
 
9
12
  from nextmv.cli.configuration.config import build_app
13
+ from nextmv.cli.confirm import get_confirmation
14
+ from nextmv.cli.message import error, in_progress, info, success
10
15
  from nextmv.cli.options import AppIDOption, ProfileOption
16
+ from nextmv.cloud.application import Application
11
17
  from nextmv.manifest import Manifest
12
18
 
13
19
  # Set up subcommand application.
@@ -35,44 +41,48 @@ def push(
35
41
  metavar="MANIFEST_PATH",
36
42
  ),
37
43
  ] = None,
38
- no_version: Annotated[
39
- bool,
40
- typer.Option(
41
- "--no-version",
42
- "-n",
43
- help="The application will be pushed without creating a new version. "
44
- "Default is to create a new version on each push.",
45
- rich_help_panel="Versioning control",
46
- ),
47
- ] = False,
44
+ # Options for version control.
48
45
  version_id: Annotated[
49
46
  str | None,
50
47
  typer.Option(
51
48
  "--version-id",
52
49
  "-v",
53
- help="Custom version ID when pushing the application. Automatically generated if not provided.",
54
- rich_help_panel="Versioning control",
50
+ help="Custom ID for version creation after app push. Automatically generated if not provided. "
51
+ "Activates --version-yes.",
55
52
  metavar="VERSION_ID",
53
+ rich_help_panel="Version control",
56
54
  ),
57
55
  ] = None,
58
- version_name: Annotated[
56
+ version_yes: Annotated[
57
+ bool,
58
+ typer.Option(
59
+ "--version-yes",
60
+ "-y",
61
+ help="Create a new version after push. Skips confirmation prompt. Useful for non-interactive sessions.",
62
+ rich_help_panel="Version control",
63
+ ),
64
+ ] = False,
65
+ # Options for instance control.
66
+ create_instance_id: Annotated[
59
67
  str | None,
60
68
  typer.Option(
61
- "--version-name",
62
- "-e",
63
- help="Custom version name when pushing the application. Automatically generated if not provided.",
64
- rich_help_panel="Versioning control",
65
- metavar="VERSION_NAME",
69
+ "--create-instance-id",
70
+ "-c",
71
+ help="Link the newly created version to a [yellow]new[/yellow] instance with this ID. "
72
+ "Skips prompt to provide an instance ID. Useful for non-interactive sessions.",
73
+ metavar="CREATE_INSTANCE_ID",
74
+ rich_help_panel="Instance control",
66
75
  ),
67
76
  ] = None,
68
- version_description: Annotated[
77
+ update_instance_id: Annotated[
69
78
  str | None,
70
79
  typer.Option(
71
- "--version-description",
72
- "-r",
73
- help="Custom version description when pushing the application. Automatically generated if not provided.",
74
- rich_help_panel="Versioning control",
75
- metavar="VERSION_DESCRIPTION",
80
+ "--update-instance-id",
81
+ "-u",
82
+ help="Link the newly created version to an [yellow]existing[/yellow] instance with this ID. "
83
+ "Skips prompt to provide an instance ID. Useful for non-interactive sessions.",
84
+ metavar="UPDATE_INSTANCE_ID",
85
+ rich_help_panel="Instance control",
76
86
  ),
77
87
  ] = None,
78
88
  profile: ProfileOption = None,
@@ -80,58 +90,362 @@ def push(
80
90
  """
81
91
  Push (deploy) a Nextmv application to Nextmv Cloud.
82
92
 
83
- Use the [code]--app-dir[/code] option to specify the path to your
84
- application's root directory. By default, the current working directory is
85
- used.
93
+ Use the --app-dir option to specify the path to your application's root
94
+ directory. By default, the current working directory is used.
86
95
 
87
- You can also provide a custom manifest file using the [code]--manifest[/code]
88
- option. If not provided, the CLI will look for a file named [magenta]app.yaml[/magenta]
89
- in the application's root.
96
+ You can also provide a custom manifest file using the --manifest option. If
97
+ not provided, the CLI will look for a file named
98
+ [magenta]app.yaml[/magenta] in the application's root.
90
99
 
91
- The default behavior of this command is to create a new application version
92
- [italic]after[/italic] the app has been pushed. You can use the
93
- [code]--no-version[/code] option to skip this step. The
94
- [code]--version-...[/code] options allow you to customize the attributes of
95
- the version that is created. If any of these options are not provided,
96
- automatically generated values will be used.
100
+ By default, this command only pushes the app. After the push, you will be
101
+ prompted to create a new version. If a new version is created, you will be
102
+ prompted to link it to an instance. If the instance exists, you will be
103
+ asked if you want to update it. If it doesn't, you will be asked to create
104
+ it. You can use the following flags to skip the prompts, useful in
105
+ non-interactive sessions like in a CI/CD pipeline: --version-yes,
106
+ --version-id, --create-instance-id, and --update-instance-id.
97
107
 
98
108
  [bold][underline]Examples[/underline][/bold]
99
109
 
100
110
  - Push an application, with ID [magenta]hare-app[/magenta], from the current directory.
101
- $ [green]nextmv cloud app push --app-id hare-app[/green]
111
+ $ [dim]nextmv cloud app push --app-id hare-app[/dim]
102
112
 
103
113
  - Push an application, with ID [magenta]hare-app[/magenta], from the [magenta]./my-app[/magenta] directory.
104
- $ [green]nextmv cloud app push --app-id hare-app --app-dir ./my-app[/green]
114
+ $ [dim]nextmv cloud app push --app-id hare-app --app-dir ./my-app[/dim]
105
115
 
106
116
  - Push an application, with ID [magenta]hare-app[/magenta], using a custom manifest file.
107
- $ [green]nextmv cloud app push --app-id hare-app --manifest ./custom-manifest.yaml[/green]
117
+ $ [dim]nextmv cloud app push --app-id hare-app --manifest ./custom-manifest.yaml[/dim]
108
118
 
109
- - Push an application, with ID [magenta]hare-app[/magenta], from a specific
110
- [magenta]./my-app[/magenta] directory with a custom manifest under [magenta]./custom-manifest.yaml[/magenta].
111
- $ [green]nextmv cloud app push --app-id hare-app --app-dir ./my-app \\
112
- --manifest ./custom-manifest.yaml[/green]
119
+ - Push and automatically create a new version (no prompt).
120
+ $ [dim]nextmv cloud app push --app-id hare-app --version-yes[/dim]
113
121
 
114
- - Push an application without creating a new version.
115
- $ [green]nextmv cloud app push --app-id hare-app --no-version[/green]
122
+ - Push and create a new version with a custom version ID (no prompt).
123
+ $ [dim]nextmv cloud app push --app-id hare-app --version-id v1.0.0[/dim]
116
124
 
117
- - Push an application with a custom version ID.
118
- $ [green]nextmv cloud app push --app-id hare-app --version-id v1.0.0[/green]
125
+ - Push and create a new version, then link it to a new instance with a specific ID (no prompt).
126
+ $ [dim]nextmv cloud app push --app-id hare-app --version-yes --create-instance-id inst-1[/dim]
119
127
 
120
- - Push an application with custom version ID, name, and description.
121
- $ [green]nextmv cloud app push --app-id hare-app --version-id v1.0.0 \\
122
- --version-name "Release 1.0.0" \\
123
- --version-description "First stable release"[/green]
128
+ - Push and create a new version, then link it to an existing instance (no prompt).
129
+ $ [dim]nextmv cloud app push --app-id hare-app --version-yes --update-instance-id inst-1[/dim]
124
130
  """
125
131
 
132
+ # We cannot create and update an instance at the same time.
133
+ update_defined = update_instance_id is not None and update_instance_id != ""
134
+ create_defined = create_instance_id is not None and create_instance_id != ""
135
+ if update_defined and create_defined:
136
+ error("Cannot use --update-instance-id and --create-instance-id at the same time.")
137
+
126
138
  cloud_app = build_app(app_id=app_id, profile=profile)
139
+
140
+ # We cannot update an instance that does not exist.
141
+ if update_defined and not cloud_app.instance_exists(instance_id=update_instance_id):
142
+ error(
143
+ f"Used option --update-instance-id but the instance {update_instance_id} does not exist. "
144
+ "Use --create-instance-id instead."
145
+ )
146
+
147
+ # We cannot create an instance that already exists.
148
+ if create_defined and cloud_app.instance_exists(instance_id=create_instance_id):
149
+ error(
150
+ f"Used option --create-instance-id but the instance {create_instance_id} already exists. "
151
+ "Use --update-instance-id instead."
152
+ )
153
+
154
+ # Do the normal push first.
127
155
  loaded_manifest = Manifest.from_yaml(dirpath=manifest) if manifest is not None and manifest != "" else None
128
156
  cloud_app.push(
129
157
  manifest=loaded_manifest,
130
158
  app_dir=app_dir,
131
159
  verbose=True,
132
160
  rich_print=True,
133
- no_version=no_version,
161
+ )
162
+
163
+ now = datetime.now(timezone.utc)
164
+ version_id, should_continue = _handle_version_creation(
165
+ cloud_app=cloud_app,
166
+ app_id=app_id,
134
167
  version_id=version_id,
135
- version_name=version_name,
136
- version_description=version_description,
168
+ version_yes=version_yes,
169
+ now=now,
170
+ )
171
+ if not should_continue:
172
+ return
173
+
174
+ # If the override for updating an instance was used, we update the instance
175
+ # and we are done.
176
+ if update_defined:
177
+ info("Used option --update-instance-id to link version to existing instance.", emoji=":bulb:")
178
+ _update_instance(
179
+ cloud_app=cloud_app,
180
+ app_id=app_id,
181
+ version_id=version_id,
182
+ instance_id=update_instance_id,
183
+ )
184
+
185
+ return
186
+
187
+ # If the override for creating a new instance was used, we create the
188
+ # instance and we are done.
189
+ if create_defined:
190
+ info("Used option --create-instance-id to link version to new instance.", emoji=":bulb:")
191
+ _create_instance(
192
+ cloud_app=cloud_app,
193
+ app_id=app_id,
194
+ version_id=version_id,
195
+ instance_id=create_instance_id,
196
+ now=now,
197
+ )
198
+
199
+ return
200
+
201
+ # If no overrides are used, we handle instance prompting.
202
+ _handle_instance_prompting(
203
+ cloud_app=cloud_app,
204
+ app_id=app_id,
205
+ version_id=version_id,
206
+ now=now,
207
+ )
208
+
209
+
210
+ def _handle_version_creation(
211
+ cloud_app: Application,
212
+ app_id: str,
213
+ version_id: str | None,
214
+ version_yes: bool,
215
+ now: datetime,
216
+ ) -> tuple[str, bool]:
217
+ """
218
+ Handle the logic for version creation after pushing an application.
219
+
220
+ If a version ID is provided and exists, it is used directly. If not, the user is prompted (unless auto-confirmed)
221
+ to create a new version. If confirmed, a new version is created with an automatic description.
222
+
223
+ Parameters
224
+ ----------
225
+ cloud_app : Application
226
+ The cloud application object to interact with Nextmv Cloud.
227
+ app_id : str
228
+ The application ID.
229
+ version_id : str or None
230
+ The version ID to use or check for existence. If None or empty, a new version may be created.
231
+ version_yes : bool
232
+ Whether to skip the prompt and auto-create a new version.
233
+ now : datetime
234
+ The current datetime, used for version description.
235
+
236
+ Returns
237
+ -------
238
+ tuple[str, bool]
239
+ A tuple containing the version ID (empty string if not created) and a boolean indicating
240
+ whether to continue with subsequent steps (True if a version is selected or created, False otherwise).
241
+ """
242
+
243
+ # If the user provides a version, and it exists, we use it directly and we
244
+ # are done.
245
+ if version_id is not None and version_id != "":
246
+ exists = cloud_app.version_exists(version_id=version_id)
247
+ if exists:
248
+ error(
249
+ f"Version [magenta]{version_id}[/magenta] already exists for application [magenta]{app_id}[/magenta]."
250
+ )
251
+
252
+ return "", False
253
+
254
+ info(
255
+ msg=f"Version [magenta]{version_id}[/magenta] does not exist. A new version will be created.",
256
+ emoji=":bulb:",
257
+ )
258
+
259
+ version_yes = True # Activate auto-confirm since user provided a version ID.
260
+
261
+ # If we are not auto-confirming version creation, ask the user.
262
+ if not version_yes:
263
+ should_create = get_confirmation(
264
+ msg=f"Do you want to create a new version [magenta]{app_id}[/magenta] now?",
265
+ default=True,
266
+ )
267
+
268
+ # If the user does not want to create a new version, we are done.
269
+ if not should_create:
270
+ info(
271
+ msg="Will not create a new version.",
272
+ emoji=":bulb:",
273
+ )
274
+ return "", False
275
+
276
+ # Create a new version if either the user confirms by prompt or by using
277
+ # the flag.
278
+ in_progress("Creating a new version...")
279
+ version_description = f"Version created automatically from push at {now.strftime('%Y-%m-%dT%H:%M:%SZ')}"
280
+ version = cloud_app.new_version(
281
+ id=version_id,
282
+ description=version_description,
283
+ )
284
+ version_id = version.id
285
+ success(f"New version [magenta]{version_id}[/magenta] created for application [magenta]{app_id}[/magenta].")
286
+
287
+ return version_id, True
288
+
289
+
290
+ def _handle_instance_prompting(
291
+ cloud_app: Application,
292
+ app_id: str,
293
+ version_id: str,
294
+ now: datetime,
295
+ ) -> None:
296
+ """
297
+ Handle interactive prompting for linking a version to an instance after a push.
298
+
299
+ In interactive terminals, prompts the user to link the new version to an existing or new instance.
300
+ If the terminal is non-interactive, skips prompting. Handles both updating existing instances and creating new ones.
301
+
302
+ Parameters
303
+ ----------
304
+ cloud_app : Application
305
+ The cloud application object to interact with Nextmv Cloud.
306
+ app_id : str
307
+ The application ID.
308
+ version_id : str
309
+ The version ID to link to an instance.
310
+ now : datetime
311
+ The current datetime, used for instance description if a new instance is created.
312
+ """
313
+
314
+ # If this is not an interactive terminal, do not ask for instance linking,
315
+ # to avoid hanging indefinitely waiting for a user response.
316
+ if not sys.stdin.isatty():
317
+ info(
318
+ msg="Non-interactive terminal detected. Skipping instance linking.",
319
+ emoji=":bulb:",
320
+ )
321
+
322
+ return
323
+
324
+ # Prompt the user for an instance ID to link the new version to.
325
+ instance_id = Prompt.ask(
326
+ f"Do you want to link version [magenta]{version_id}[/magenta] to an instance? If so, enter the instance ID. "
327
+ "Leave blank to abort",
328
+ case_sensitive=False,
329
+ )
330
+ if instance_id == "":
331
+ info(
332
+ msg="No instance ID provided. Skipping instance linking.",
333
+ emoji=":bulb:",
334
+ )
335
+ return
336
+
337
+ # Based on whether the instance exists or not, ask the user if they want to
338
+ # update or create it.
339
+ exists = cloud_app.instance_exists(instance_id=instance_id)
340
+
341
+ # If the instance exists, ask if we want to update it.
342
+ if exists:
343
+ should_update = get_confirmation(
344
+ msg=f"Instance [magenta]{instance_id}[/magenta] exists. "
345
+ f"Do you want to link it to version [magenta]{version_id}[/magenta]?",
346
+ default=True,
347
+ )
348
+
349
+ if not should_update:
350
+ info(
351
+ msg=f"Will not update instance [magenta]{instance_id}[/magenta].",
352
+ emoji=":bulb:",
353
+ )
354
+ return
355
+
356
+ _update_instance(
357
+ cloud_app=cloud_app,
358
+ app_id=app_id,
359
+ version_id=version_id,
360
+ instance_id=instance_id,
361
+ )
362
+
363
+ return
364
+
365
+ # If the instance does not exist, ask if we want to create it.
366
+ should_create = get_confirmation(
367
+ msg=f"Instance [magenta]{instance_id}[/magenta] does not exist. "
368
+ f"Do you want to create it using version [magenta]{version_id}[/magenta]?",
369
+ default=True,
370
+ )
371
+
372
+ if not should_create:
373
+ info(
374
+ msg=f"Will not create instance [magenta]{instance_id}[/magenta].",
375
+ emoji=":bulb:",
376
+ )
377
+ return
378
+
379
+ _create_instance(
380
+ cloud_app=cloud_app,
381
+ app_id=app_id,
382
+ version_id=version_id,
383
+ instance_id=instance_id,
384
+ now=now,
385
+ )
386
+
387
+
388
+ def _update_instance(
389
+ cloud_app: Application,
390
+ app_id: str,
391
+ version_id: str,
392
+ instance_id: str,
393
+ ) -> None:
394
+ """
395
+ Update an existing instance to use a new version.
396
+
397
+ Parameters
398
+ ----------
399
+ cloud_app : Application
400
+ The cloud application object to interact with Nextmv Cloud.
401
+ app_id : str
402
+ The application ID.
403
+ version_id : str
404
+ The version ID to link to the instance.
405
+ instance_id : str
406
+ The instance ID to update.
407
+ """
408
+
409
+ in_progress(f"Updating instance [magenta]{instance_id}[/magenta] to use version [magenta]{version_id}[/magenta]...")
410
+ cloud_app.update_instance(id=instance_id, version_id=version_id)
411
+ success(
412
+ f"Instance [magenta]{instance_id}[/magenta] updated to use version [magenta]{version_id}[/magenta] "
413
+ f"for application [magenta]{app_id}[/magenta]."
414
+ )
415
+
416
+
417
+ def _create_instance(
418
+ cloud_app: Application,
419
+ app_id: str,
420
+ version_id: str,
421
+ instance_id: str,
422
+ now: datetime,
423
+ ) -> None:
424
+ """
425
+ Create a new instance linked to a specific version.
426
+
427
+ Parameters
428
+ ----------
429
+ cloud_app : Application
430
+ The cloud application object to interact with Nextmv Cloud.
431
+ app_id : str
432
+ The application ID.
433
+ version_id : str
434
+ The version ID to link to the new instance.
435
+ instance_id : str
436
+ The instance ID to create.
437
+ now : datetime
438
+ The current datetime, used for the instance description.
439
+ """
440
+
441
+ in_progress(f"Creating a new instance with ID [magenta]{instance_id}[/magenta]...")
442
+ instance_description = f"Instance created automatically from push at {now.strftime('%Y-%m-%dT%H:%M:%SZ')}"
443
+ instance = cloud_app.new_instance(
444
+ version_id=version_id,
445
+ id=instance_id,
446
+ description=instance_description,
447
+ )
448
+ success(
449
+ f"New instance [magenta]{instance.id}[/magenta] created using version [magenta]{version_id}[/magenta] "
450
+ f"for application [magenta]{app_id}[/magenta]."
137
451
  )
@@ -74,33 +74,33 @@ def update(
74
74
  [bold][underline]Examples[/underline][/bold]
75
75
 
76
76
  - Update an application's name.
77
- $ [green]nextmv cloud app update --app-id hare-app --name "New Hare App"[/green]
77
+ $ [dim]nextmv cloud app update --app-id hare-app --name "New Hare App"[/dim]
78
78
 
79
79
  - Update an application's description.
80
- $ [green]nextmv cloud app update --app-id hare-app --name "Hare App" \\
81
- --description "An updated description for routing hares"[/green]
80
+ $ [dim]nextmv cloud app update --app-id hare-app --name "Hare App" \\
81
+ --description "An updated description for routing hares"[/dim]
82
82
 
83
83
  - Update an application's default instance ID.
84
- $ [green]nextmv cloud app update --app-id hare-app --name "Hare App" \\
85
- --default-instance-id burrow[/green]
84
+ $ [dim]nextmv cloud app update --app-id hare-app --name "Hare App" \\
85
+ --default-instance-id burrow[/dim]
86
86
 
87
87
  - Update an application's default experiment instance.
88
- $ [green]nextmv cloud app update --app-id hare-app --name "Hare App" \\
89
- --default-experiment-instance experiment-v1[/green]
88
+ $ [dim]nextmv cloud app update --app-id hare-app --name "Hare App" \\
89
+ --default-experiment-instance experiment-v1[/dim]
90
90
 
91
91
  - Update multiple application properties at once.
92
- $ [green]nextmv cloud app update --app-id hare-app --name "Hare App" \\
92
+ $ [dim]nextmv cloud app update --app-id hare-app --name "Hare App" \\
93
93
  --description "Updated description" --default-instance-id burrow \\
94
- --default-experiment-instance experiment-v1[/green]
94
+ --default-experiment-instance experiment-v1[/dim]
95
95
 
96
96
  - Update an application and save the updated information to an [magenta]updated_app.json[/magenta] file.
97
- $ [green]nextmv cloud app update --app-id hare-app --name "New Hare App" --output updated_app.json[/green]
97
+ $ [dim]nextmv cloud app update --app-id hare-app --name "New Hare App" --output updated_app.json[/dim]
98
98
  """
99
99
 
100
100
  if name is None and description is None and default_instance_id is None and default_experiment_instance is None:
101
101
  error(
102
- "Provide at least one option to update: [code]--name[/code], [code]--description[/code], "
103
- "[code]--default-instance-id[/code], or [code]--default-experiment-instance[/code]."
102
+ "Provide at least one option to update: --name, --description, "
103
+ "--default-instance-id, or --default-experiment-instance."
104
104
  )
105
105
 
106
106
  cloud_app = build_app(app_id=app_id, profile=profile)