nextmv 1.0.0.dev2__py3-none-any.whl → 1.0.0.dev4__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 (120) hide show
  1. nextmv/__about__.py +1 -1
  2. nextmv/cli/CONTRIBUTING.md +81 -29
  3. nextmv/cli/cloud/__init__.py +2 -0
  4. nextmv/cli/cloud/acceptance/create.py +20 -22
  5. nextmv/cli/cloud/acceptance/delete.py +7 -8
  6. nextmv/cli/cloud/acceptance/get.py +9 -10
  7. nextmv/cli/cloud/acceptance/list.py +3 -3
  8. nextmv/cli/cloud/acceptance/update.py +6 -6
  9. nextmv/cli/cloud/account/__init__.py +3 -3
  10. nextmv/cli/cloud/account/create.py +11 -11
  11. nextmv/cli/cloud/account/delete.py +6 -7
  12. nextmv/cli/cloud/account/get.py +3 -3
  13. nextmv/cli/cloud/account/update.py +5 -5
  14. nextmv/cli/cloud/app/create.py +25 -26
  15. nextmv/cli/cloud/app/delete.py +5 -6
  16. nextmv/cli/cloud/app/exists.py +2 -2
  17. nextmv/cli/cloud/app/get.py +2 -2
  18. nextmv/cli/cloud/app/list.py +3 -3
  19. nextmv/cli/cloud/app/push.py +269 -45
  20. nextmv/cli/cloud/app/update.py +12 -12
  21. nextmv/cli/cloud/batch/create.py +26 -28
  22. nextmv/cli/cloud/batch/delete.py +5 -6
  23. nextmv/cli/cloud/batch/get.py +8 -8
  24. nextmv/cli/cloud/batch/list.py +3 -3
  25. nextmv/cli/cloud/batch/metadata.py +4 -4
  26. nextmv/cli/cloud/batch/update.py +6 -6
  27. nextmv/cli/cloud/data/__init__.py +1 -1
  28. nextmv/cli/cloud/data/upload.py +15 -15
  29. nextmv/cli/cloud/ensemble/__init__.py +2 -0
  30. nextmv/cli/cloud/ensemble/create.py +21 -22
  31. nextmv/cli/cloud/ensemble/delete.py +5 -6
  32. nextmv/cli/cloud/ensemble/get.py +4 -4
  33. nextmv/cli/cloud/ensemble/list.py +63 -0
  34. nextmv/cli/cloud/ensemble/update.py +9 -9
  35. nextmv/cli/cloud/input_set/create.py +20 -22
  36. nextmv/cli/cloud/input_set/get.py +3 -3
  37. nextmv/cli/cloud/input_set/list.py +3 -3
  38. nextmv/cli/cloud/input_set/update.py +24 -24
  39. nextmv/cli/cloud/instance/create.py +14 -15
  40. nextmv/cli/cloud/instance/delete.py +5 -6
  41. nextmv/cli/cloud/instance/exists.py +2 -2
  42. nextmv/cli/cloud/instance/get.py +2 -2
  43. nextmv/cli/cloud/instance/list.py +3 -3
  44. nextmv/cli/cloud/instance/update.py +14 -14
  45. nextmv/cli/cloud/managed_input/create.py +14 -16
  46. nextmv/cli/cloud/managed_input/delete.py +6 -7
  47. nextmv/cli/cloud/managed_input/get.py +3 -3
  48. nextmv/cli/cloud/managed_input/list.py +3 -3
  49. nextmv/cli/cloud/managed_input/update.py +9 -9
  50. nextmv/cli/cloud/run/cancel.py +2 -2
  51. nextmv/cli/cloud/run/create.py +32 -33
  52. nextmv/cli/cloud/run/get.py +8 -8
  53. nextmv/cli/cloud/run/input.py +4 -4
  54. nextmv/cli/cloud/run/list.py +6 -6
  55. nextmv/cli/cloud/run/logs.py +9 -10
  56. nextmv/cli/cloud/run/metadata.py +4 -4
  57. nextmv/cli/cloud/run/track.py +32 -33
  58. nextmv/cli/cloud/scenario/create.py +21 -21
  59. nextmv/cli/cloud/scenario/delete.py +5 -6
  60. nextmv/cli/cloud/scenario/get.py +8 -8
  61. nextmv/cli/cloud/scenario/list.py +3 -3
  62. nextmv/cli/cloud/scenario/metadata.py +4 -4
  63. nextmv/cli/cloud/scenario/update.py +6 -6
  64. nextmv/cli/cloud/secrets/create.py +17 -17
  65. nextmv/cli/cloud/secrets/delete.py +5 -6
  66. nextmv/cli/cloud/secrets/get.py +4 -4
  67. nextmv/cli/cloud/secrets/list.py +3 -3
  68. nextmv/cli/cloud/secrets/update.py +17 -20
  69. nextmv/cli/cloud/shadow/__init__.py +1 -1
  70. nextmv/cli/cloud/shadow/create.py +32 -32
  71. nextmv/cli/cloud/shadow/delete.py +5 -6
  72. nextmv/cli/cloud/shadow/get.py +2 -2
  73. nextmv/cli/cloud/shadow/list.py +3 -3
  74. nextmv/cli/cloud/shadow/metadata.py +4 -4
  75. nextmv/cli/cloud/shadow/start.py +3 -3
  76. nextmv/cli/cloud/shadow/stop.py +8 -10
  77. nextmv/cli/cloud/shadow/update.py +7 -6
  78. nextmv/cli/cloud/switchback/__init__.py +33 -0
  79. nextmv/cli/cloud/switchback/create.py +151 -0
  80. nextmv/cli/cloud/switchback/delete.py +67 -0
  81. nextmv/cli/cloud/switchback/get.py +62 -0
  82. nextmv/cli/cloud/switchback/list.py +63 -0
  83. nextmv/cli/cloud/switchback/metadata.py +68 -0
  84. nextmv/cli/cloud/switchback/start.py +43 -0
  85. nextmv/cli/cloud/switchback/stop.py +41 -0
  86. nextmv/cli/cloud/switchback/update.py +96 -0
  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 +32 -0
  101. nextmv/cli/main.py +27 -36
  102. nextmv/cli/message.py +2 -2
  103. nextmv/cli/options.py +14 -0
  104. nextmv/cli/version.py +1 -1
  105. nextmv/cloud/__init__.py +5 -0
  106. nextmv/cloud/application/__init__.py +192 -54
  107. nextmv/cloud/application/_batch_scenario.py +2 -2
  108. nextmv/cloud/application/_instance.py +2 -2
  109. nextmv/cloud/application/_managed_input.py +1 -1
  110. nextmv/cloud/application/_shadow.py +1 -1
  111. nextmv/cloud/application/_switchback.py +323 -0
  112. nextmv/cloud/application/_version.py +3 -2
  113. nextmv/cloud/shadow.py +43 -4
  114. nextmv/cloud/switchback.py +226 -0
  115. {nextmv-1.0.0.dev2.dist-info → nextmv-1.0.0.dev4.dist-info}/METADATA +1 -1
  116. nextmv-1.0.0.dev4.dist-info/RECORD +183 -0
  117. nextmv-1.0.0.dev2.dist-info/RECORD +0 -170
  118. {nextmv-1.0.0.dev2.dist-info → nextmv-1.0.0.dev4.dist-info}/WHEEL +0 -0
  119. {nextmv-1.0.0.dev2.dist-info → nextmv-1.0.0.dev4.dist-info}/entry_points.txt +0 -0
  120. {nextmv-1.0.0.dev2.dist-info → nextmv-1.0.0.dev4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,96 @@
1
+ """
2
+ This module defines the cloud switchback 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, SwitchbackTestIDOption
13
+
14
+ # Set up subcommand application.
15
+ app = typer.Typer()
16
+
17
+
18
+ @app.command()
19
+ def update(
20
+ app_id: AppIDOption,
21
+ switchback_test_id: SwitchbackTestIDOption,
22
+ description: Annotated[
23
+ str | None,
24
+ typer.Option(
25
+ "--description",
26
+ "-d",
27
+ help="Updated description of the switchback 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 switchback 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 switchback test information to this location.",
46
+ metavar="OUTPUT_PATH",
47
+ ),
48
+ ] = None,
49
+ profile: ProfileOption = None,
50
+ ) -> None:
51
+ """
52
+ Update a Nextmv Cloud switchback test.
53
+
54
+ Update the name and/or description of a switchback test. Any fields not
55
+ specified will remain unchanged.
56
+
57
+ [bold][underline]Examples[/underline][/bold]
58
+
59
+ - Update the name of a switchback test.
60
+ $ [dim]nextmv cloud switchback update --app-id hare-app --switchback-test-id carrot-feast \\
61
+ --name "Spring Carrot Harvest"[/dim]
62
+
63
+ - Update the description of a switchback test.
64
+ $ [dim]nextmv cloud switchback update --app-id hare-app --switchback-test-id bunny-hop-routes \\
65
+ --description "Optimizing hop paths through the meadow"[/dim]
66
+
67
+ - Update both name and description and save the result.
68
+ $ [dim]nextmv cloud switchback update --app-id hare-app --switchback-test-id lettuce-delivery \\
69
+ --name "Warren Lettuce Express" --description "Fast lettuce delivery to all burrows" \\
70
+ --output updated-switchback-test.json[/dim]
71
+ """
72
+
73
+ cloud_app = build_app(app_id=app_id, profile=profile)
74
+
75
+ in_progress(msg="Updating switchback test...")
76
+ switchback_test = cloud_app.update_switchback_test(
77
+ switchback_test_id=switchback_test_id,
78
+ name=name,
79
+ description=description,
80
+ )
81
+
82
+ switchback_test_dict = switchback_test.to_dict()
83
+ success(
84
+ f"Switchback test [magenta]{switchback_test_id}[/magenta] updated successfully "
85
+ f"in application [magenta]{app_id}[/magenta]."
86
+ )
87
+
88
+ if output is not None and output != "":
89
+ with open(output, "w") as f:
90
+ json.dump(switchback_test_dict, f, indent=2)
91
+
92
+ success(msg=f"Updated switchback test information saved to [magenta]{output}[/magenta].")
93
+
94
+ return
95
+
96
+ print_json(switchback_test_dict)
@@ -23,10 +23,10 @@ def create(
23
23
  [bold][underline]Examples[/underline][/bold]
24
24
 
25
25
  - Create an upload URL for application [magenta]hare-app[/magenta].
26
- $ [green]nextmv cloud upload create --app-id hare-app[/green]
26
+ $ [dim]nextmv cloud upload create --app-id hare-app[/dim]
27
27
 
28
28
  - Create an upload URL for application [magenta]hare-app[/magenta] using profile [magenta]hare[/magenta].
29
- $ [green]nextmv cloud upload create --app-id hare-app --profile hare[/green]
29
+ $ [dim]nextmv cloud upload create --app-id hare-app --profile hare[/dim]
30
30
  """
31
31
 
32
32
  cloud_app = build_app(app_id=app_id, profile=profile)
@@ -58,28 +58,27 @@ def create(
58
58
  """
59
59
  Create a new Nextmv Cloud application version.
60
60
 
61
- Use the [code]--exist-ok[/code] flag to avoid errors when creating a
62
- version with an ID that already exists. This is useful for scripts that
63
- need to ensure a version exists without worrying about whether it was
64
- created previously.
61
+ Use the --exist-ok flag to avoid errors when creating a version with an ID
62
+ that already exists. This is useful for scripts that need to ensure a
63
+ version exists without worrying about whether it was created previously.
65
64
 
66
65
  [bold][underline]Examples[/underline][/bold]
67
66
 
68
67
  - Create a version for application [magenta]hare-app[/magenta]. A random ID will be generated.
69
- $ [green]nextmv cloud version create --app-id hare-app[/green]
68
+ $ [dim]nextmv cloud version create --app-id hare-app[/dim]
70
69
 
71
70
  - Create a version with a specific name.
72
- $ [green]nextmv cloud version create --app-id hare-app --name "v1.0.0"[/green]
71
+ $ [dim]nextmv cloud version create --app-id hare-app --name "v1.0.0"[/dim]
73
72
 
74
73
  - Create a version with a specific ID.
75
- $ [green]nextmv cloud version create --app-id hare-app --version-id v1[/green]
74
+ $ [dim]nextmv cloud version create --app-id hare-app --version-id v1[/dim]
76
75
 
77
76
  - Create a version with a name and description.
78
- $ [green]nextmv cloud version create --app-id hare-app --name "v1.0.0" \\
79
- --description "Initial release with routing optimization"[/green]
77
+ $ [dim]nextmv cloud version create --app-id hare-app --name "v1.0.0" \\
78
+ --description "Initial release with routing optimization"[/dim]
80
79
 
81
80
  - Create a version, or get it if it already exists.
82
- $ [green]nextmv cloud version create --app-id hare-app --version-id v1 --exist-ok[/green]
81
+ $ [dim]nextmv cloud version create --app-id hare-app --version-id v1 --exist-ok[/dim]
83
82
  """
84
83
 
85
84
  cloud_app = build_app(app_id=app_id, profile=profile)
@@ -5,9 +5,9 @@ This module defines the cloud version delete command for the Nextmv CLI.
5
5
  from typing import Annotated
6
6
 
7
7
  import typer
8
- from rich.prompt import Confirm
9
8
 
10
9
  from nextmv.cli.configuration.config import build_app
10
+ from nextmv.cli.confirm import get_confirmation
11
11
  from nextmv.cli.message import info, success
12
12
  from nextmv.cli.options import AppIDOption, ProfileOption, VersionIDOption
13
13
 
@@ -32,23 +32,22 @@ def delete(
32
32
  """
33
33
  Deletes a Nextmv Cloud application version.
34
34
 
35
- This action is permanent and cannot be undone. Use the [code]--yes[/code]
35
+ This action is permanent and cannot be undone. Use the --yes
36
36
  flag to skip the confirmation prompt.
37
37
 
38
38
  [bold][underline]Examples[/underline][/bold]
39
39
 
40
40
  - Delete the version with the ID [magenta]v1[/magenta] from application [magenta]hare-app[/magenta].
41
- $ [green]nextmv cloud version delete --app-id hare-app --version-id v1[/green]
41
+ $ [dim]nextmv cloud version delete --app-id hare-app --version-id v1[/dim]
42
42
 
43
43
  - Delete the version without confirmation prompt.
44
- $ [green]nextmv cloud version delete --app-id hare-app --version-id v1 --yes[/green]
44
+ $ [dim]nextmv cloud version delete --app-id hare-app --version-id v1 --yes[/dim]
45
45
  """
46
46
 
47
47
  if not yes:
48
- confirm = Confirm.ask(
48
+ confirm = get_confirmation(
49
49
  f"Are you sure you want to delete version [magenta]{version_id}[/magenta] "
50
50
  f"from application [magenta]{app_id}[/magenta]? This action cannot be undone.",
51
- default=False,
52
51
  )
53
52
 
54
53
  if not confirm:
@@ -27,10 +27,10 @@ def exists(
27
27
  [bold][underline]Examples[/underline][/bold]
28
28
 
29
29
  - Check if the version with the ID [magenta]v1[/magenta] exists in application [magenta]hare-app[/magenta].
30
- $ [green]nextmv cloud version exists --app-id hare-app --version-id v1[/green]
30
+ $ [dim]nextmv cloud version exists --app-id hare-app --version-id v1[/dim]
31
31
 
32
32
  - Check if the version exists using the profile named [magenta]hare[/magenta].
33
- $ [green]nextmv cloud version exists --app-id hare-app --version-id v1 --profile hare[/green]
33
+ $ [dim]nextmv cloud version exists --app-id hare-app --version-id v1 --profile hare[/dim]
34
34
  """
35
35
 
36
36
  cloud_app = build_app(app_id=app_id, profile=profile)
@@ -39,11 +39,11 @@ def get(
39
39
  [bold][underline]Examples[/underline][/bold]
40
40
 
41
41
  - Get the version with the ID [magenta]v1[/magenta] from application [magenta]hare-app[/magenta].
42
- $ [green]nextmv cloud version get --app-id hare-app --version-id v1[/green]
42
+ $ [dim]nextmv cloud version get --app-id hare-app --version-id v1[/dim]
43
43
 
44
44
  - Get the version with the ID [magenta]v1[/magenta] and save the information to a
45
45
  [magenta]version.json[/magenta] file.
46
- $ [green]nextmv cloud version get --app-id hare-app --version-id v1 --output version.json[/green]
46
+ $ [dim]nextmv cloud version get --app-id hare-app --version-id v1 --output version.json[/dim]
47
47
  """
48
48
 
49
49
  cloud_app = build_app(app_id=app_id, profile=profile)
@@ -35,13 +35,13 @@ def list(
35
35
  [bold][underline]Examples[/underline][/bold]
36
36
 
37
37
  - List all versions of application [magenta]hare-app[/magenta].
38
- $ [green]nextmv cloud version list --app-id hare-app[/green]
38
+ $ [dim]nextmv cloud version list --app-id hare-app[/dim]
39
39
 
40
40
  - List all versions using the profile named [magenta]hare[/magenta].
41
- $ [green]nextmv cloud version list --app-id hare-app --profile hare[/green]
41
+ $ [dim]nextmv cloud version list --app-id hare-app --profile hare[/dim]
42
42
 
43
43
  - List all versions and save the information to a [magenta]versions.json[/magenta] file.
44
- $ [green]nextmv cloud version list --app-id hare-app --output versions.json[/green]
44
+ $ [dim]nextmv cloud version list --app-id hare-app --output versions.json[/dim]
45
45
  """
46
46
 
47
47
  cloud_app = build_app(app_id=app_id, profile=profile)
@@ -54,23 +54,23 @@ def update(
54
54
  [bold][underline]Examples[/underline][/bold]
55
55
 
56
56
  - Update a version's name.
57
- $ [green]nextmv cloud version update --app-id hare-app --version-id v1 --name "Version 1.0"[/green]
57
+ $ [dim]nextmv cloud version update --app-id hare-app --version-id v1 --name "Version 1.0"[/dim]
58
58
 
59
59
  - Update a version's description.
60
- $ [green]nextmv cloud version update --app-id hare-app --version-id v1 \\
61
- --description "Initial stable release"[/green]
60
+ $ [dim]nextmv cloud version update --app-id hare-app --version-id v1 \\
61
+ --description "Initial stable release"[/dim]
62
62
 
63
63
  - Update a version's name and description at once.
64
- $ [green]nextmv cloud version update --app-id hare-app --version-id v1 \\
65
- --name "Version 1.0" --description "Initial stable release"[/green]
64
+ $ [dim]nextmv cloud version update --app-id hare-app --version-id v1 \\
65
+ --name "Version 1.0" --description "Initial stable release"[/dim]
66
66
 
67
67
  - Update a version and save the updated information to a [magenta]updated_version.json[/magenta] file.
68
- $ [green]nextmv cloud version update --app-id hare-app --version-id v1 \\
69
- --name "Version 1.0" --output updated_version.json[/green]
68
+ $ [dim]nextmv cloud version update --app-id hare-app --version-id v1 \\
69
+ --name "Version 1.0" --output updated_version.json[/dim]
70
70
  """
71
71
 
72
72
  if name is None and description is None:
73
- error("Provide at least one option to update: [code]--name[/code] or [code]--description[/code].")
73
+ error("Provide at least one option to update: --name or --description.")
74
74
 
75
75
  cloud_app = build_app(app_id=app_id, profile=profile)
76
76
  updated_version = cloud_app.update_version(
@@ -53,35 +53,35 @@ def clone(
53
53
  Clone a community app locally.
54
54
 
55
55
  By default, the [magenta]latest[/magenta] version will be used. You can
56
- specify a version with the [code]--version[/code] flag, and customize the
57
- output directory with the [code]--directory[/code] flag. If you want to
58
- list the available apps, use the [code]nextmv community list[/code] command.
56
+ specify a version with the --version flag, and customize the output
57
+ directory with the --directory flag. If you want to list the available
58
+ apps, use the [code]nextmv community list[/code] command.
59
59
 
60
60
  [bold][underline]Examples[/underline][/bold]
61
61
 
62
62
  - Clone the [magenta]go-nextroute[/magenta] community app (under the
63
63
  [magenta]"go-nextroute"[/magenta] directory), using the [magenta]latest[/magenta] version.
64
- $ [green]nextmv community clone --app go-nextroute[/green]
64
+ $ [dim]nextmv community clone --app go-nextroute[/dim]
65
65
 
66
66
  - Clone the [magenta]go-nextroute[/magenta] community app under the
67
67
  [magenta]"~/sample/my_app"[/magenta] directory, using the [magenta]latest[/magenta] version.
68
- $ [green]nextmv community clone --app go-nextroute --directory ~/sample/my_app[/green]
68
+ $ [dim]nextmv community clone --app go-nextroute --directory ~/sample/my_app[/dim]
69
69
 
70
70
  - Clone the [magenta]go-nextroute[/magenta] community app (under the
71
71
  [magenta]"go-nextroute"[/magenta] directory), using version [magenta]v1.2.0[/magenta].
72
- $ [green]nextmv community clone --app go-nextroute --version v1.2.0[/green]
72
+ $ [dim]nextmv community clone --app go-nextroute --version v1.2.0[/dim]
73
73
 
74
74
  - Clone the [magenta]go-nextroute[/magenta] community app (under the
75
75
  [magenta]"go-nextroute"[/magenta] directory), using the [magenta]latest[/magenta] version
76
76
  and a profile named [magenta]hare[/magenta].
77
- $ [green]nextmv community clone --app go-nextroute --profile hare[/green]
77
+ $ [dim]nextmv community clone --app go-nextroute --profile hare[/dim]
78
78
  """
79
79
 
80
80
  manifest = download_manifest(profile=profile)
81
81
  app_obj = find_app(manifest, app)
82
82
 
83
83
  if version is not None and version == "":
84
- error("The [code]--version[/code] flag cannot be an empty string.")
84
+ error("The --version flag cannot be an empty string.")
85
85
 
86
86
  if not app_has_version(app_obj, version):
87
87
  # We don't use error() here to allow printing something before exiting.
@@ -97,8 +97,10 @@ def clone(
97
97
  if version == LATEST_VERSION:
98
98
  version = app_obj.get("latest_app_version")
99
99
 
100
- destination = directory
101
- if directory is None or directory == "":
100
+ # Clean and normalize directory path in an OS-independent way
101
+ if directory is not None and directory != "":
102
+ destination = os.path.normpath(directory)
103
+ else:
102
104
  destination = app
103
105
 
104
106
  full_destination = get_valid_path(destination, os.stat)
@@ -37,30 +37,30 @@ def list(
37
37
  """
38
38
  List the available community apps
39
39
 
40
- Use the [code]--app[/code] flag to list that app's versions. Use the
41
- [code]--flat[/code] flag to flatten the list of names/versions. If you
42
- want to clone a community app locally, use the [code]nextmv community clone[/code] command.
40
+ Use the --app flag to list that app's versions. Use the --flat flag to
41
+ flatten the list of names/versions. If you want to clone a community app
42
+ locally, use the [code]nextmv community clone[/code] command.
43
43
 
44
44
  [bold][underline]Examples[/underline][/bold]
45
45
 
46
46
  - List the available community apps.
47
- $ [green]nextmv community list[/green]
47
+ $ [dim]nextmv community list[/dim]
48
48
 
49
49
  - List the available versions of the [magenta]go-nextroute[/magenta] community app.
50
- $ [green]nextmv community list --app go-nextroute[/green]
50
+ $ [dim]nextmv community list --app go-nextroute[/dim]
51
51
 
52
52
  - List the names of the available community apps as a flat list.
53
- $ [green]nextmv community list --flat[/green]
53
+ $ [dim]nextmv community list --flat[/dim]
54
54
 
55
55
  - List the available versions of the [magenta]go-nextroute[/magenta] community app as a flat list.
56
- $ [green]nextmv community list --app go-nextroute --flat[/green]
56
+ $ [dim]nextmv community list --app go-nextroute --flat[/dim]
57
57
 
58
58
  - List the available community apps using a profile named [magenta]hare[/magenta].
59
- $ [green]nextmv community list --profile hare[/green]
59
+ $ [dim]nextmv community list --profile hare[/dim]
60
60
  """
61
61
 
62
62
  if app is not None and app == "":
63
- error("The [code]--app[/code] flag cannot be an empty string.")
63
+ error("The --app flag cannot be an empty string.")
64
64
 
65
65
  manifest = download_manifest(profile=profile)
66
66
  if flat and app is None:
@@ -7,7 +7,8 @@ from typing import Any
7
7
 
8
8
  import yaml
9
9
 
10
- from nextmv.cli.message import error
10
+ from nextmv.cli.confirm import get_confirmation
11
+ from nextmv.cli.message import error, success, warning
11
12
  from nextmv.cloud.account import Account
12
13
  from nextmv.cloud.application import Application
13
14
  from nextmv.cloud.client import Client
@@ -59,6 +60,18 @@ def save_config(config: dict[str, Any]) -> None:
59
60
  yaml.safe_dump(config, file)
60
61
 
61
62
 
63
+ def non_profile_keys() -> set[str]:
64
+ """
65
+ Returns the set of keys that are not profile names in the configuration.
66
+
67
+ Returns
68
+ -------
69
+ set[str]
70
+ The set of non-profile keys.
71
+ """
72
+ return {API_KEY_KEY, ENDPOINT_KEY}
73
+
74
+
62
75
  def build_client(profile: str | None = None) -> Client:
63
76
  """
64
77
  Builds a `cloud.Client` using the API key and endpoint for the given
@@ -91,23 +104,35 @@ def build_client(profile: str | None = None) -> Client:
91
104
 
92
105
  if profile is not None:
93
106
  if profile not in config:
94
- error(f"Profile [magenta]{profile}[/magenta] does not exist.")
107
+ error(
108
+ f"Profile [magenta]{profile}[/magenta] does not exist. "
109
+ "Create it using [code]nextmv configuration create[/code] with the --profile option."
110
+ )
95
111
 
96
112
  api_key = config[profile].get(API_KEY_KEY)
97
113
  if api_key is None or api_key == "":
98
- error(f"API key for profile [magenta]{profile}[/magenta] is not set or is empty.")
114
+ error(
115
+ f"API key for profile [magenta]{profile}[/magenta] is not set or is empty. "
116
+ "Set it using [code]nextmv configuration create[/code] with the --profile and --api-key options."
117
+ )
99
118
 
100
119
  endpoint = config[profile].get(ENDPOINT_KEY)
101
120
  if endpoint is None or endpoint == "":
102
- error(f"Endpoint for profile [magenta]{profile}[/magenta] is not set or is empty.")
121
+ error(
122
+ f"Endpoint for profile [magenta]{profile}[/magenta] is not set or is empty. "
123
+ "Please run [code]nextmv configuration create[/code]."
124
+ )
103
125
  else:
104
126
  api_key = config.get(API_KEY_KEY)
105
127
  if api_key is None or api_key == "":
106
- error("Default API key is not set or is empty.")
128
+ error(
129
+ "Default API key is not set or is empty. "
130
+ "Please run [code]nextmv configuration create[/code] with the --api-key option."
131
+ )
107
132
 
108
133
  endpoint = config.get(ENDPOINT_KEY)
109
134
  if endpoint is None or endpoint == "":
110
- error("Default endpoint is not set or is empty.")
135
+ error("Default endpoint is not set or is empty. Please run [code]nextmv configuration create[/code].")
111
136
 
112
137
  return Client(api_key=api_key, url=f"https://{endpoint}")
113
138
 
@@ -137,13 +162,21 @@ def build_app(app_id: str, profile: str | None = None) -> Application:
137
162
  """
138
163
  client = build_client(profile)
139
164
  exists = Application.exists(client=client, id=app_id)
140
- if not exists:
165
+ if exists:
166
+ return Application(client=client, id=app_id)
167
+
168
+ warning(f"Application with ID [magenta]{app_id}[/magenta] does not exist.")
169
+ should_create = get_confirmation(f"Do you want to create a new application with ID [magenta]{app_id}[/magenta]?")
170
+ if not should_create:
141
171
  error(
142
- f"Application with ID [magenta]{app_id}[/magenta] does not exist. "
143
- "Use [code]nextmv cloud app create[/code] to create a new application."
172
+ f"Application with ID [magenta]{app_id}[/magenta] was not created and does not exist. "
173
+ "Use [code]nextmv cloud app create[/code] to create a new app."
144
174
  )
145
175
 
146
- return Application(client=client, id=app_id)
176
+ app = Application.new(client=client, id=app_id, name=app_id)
177
+ success(f"Application with ID and name [magenta]{app_id}[/magenta] created successfully.")
178
+
179
+ return app
147
180
 
148
181
 
149
182
  def build_account(account_id: str | None = None, profile: str | None = None) -> Account:
@@ -58,14 +58,14 @@ def create(
58
58
  [bold][underline]Examples[/underline][/bold]
59
59
 
60
60
  - Default configuration.
61
- $ [green]nextmv configuration create --api-key NEXTMV_API_KEY[/green]
61
+ $ [dim]nextmv configuration create --api-key NEXTMV_API_KEY[/dim]
62
62
 
63
63
  - Configure a profile named [magenta]hare[/magenta].
64
- $ [green]nextmv configuration create --api-key NEXTMV_API_KEY --profile hare[/green]
64
+ $ [dim]nextmv configuration create --api-key NEXTMV_API_KEY --profile hare[/dim]
65
65
  """
66
66
 
67
67
  if profile is not None and profile.strip().lower() == "default":
68
- error("[code]default[/code] is a reserved profile name.")
68
+ error("[magenta]default[/magenta] is a reserved profile name.")
69
69
 
70
70
  endpoint = str(endpoint)
71
71
  if endpoint.startswith("https://"):
@@ -5,9 +5,9 @@ This module defines the configuration delete command for the Nextmv CLI.
5
5
  from typing import Annotated
6
6
 
7
7
  import typer
8
- from rich.prompt import Confirm
9
8
 
10
9
  from nextmv.cli.configuration.config import load_config, save_config
10
+ from nextmv.cli.confirm import get_confirmation
11
11
  from nextmv.cli.message import error, info, success
12
12
 
13
13
  # Set up subcommand application.
@@ -36,25 +36,25 @@ def delete(
36
36
  ] = False,
37
37
  ) -> None:
38
38
  """
39
- Delete a profile from the configuration. Use the [code]--yes[/code]
40
- flag to skip the confirmation prompt.
39
+ Delete a profile from the configuration.
40
+
41
+ Use the --yes flag to skip the confirmation prompt.
41
42
 
42
43
  [bold][underline]Examples[/underline][/bold]
43
44
 
44
45
  - Delete a profile named [magenta]hare[/magenta].
45
- $ [green]nextmv configuration delete --profile hare[/green]
46
+ $ [dim]nextmv configuration delete --profile hare[/dim]
46
47
 
47
48
  - Delete a profile named [magenta]hare[/magenta] without confirmation prompt.
48
- $ [green]nextmv configuration delete --profile hare --yes[/green]
49
+ $ [dim]nextmv configuration delete --profile hare --yes[/dim]
49
50
  """
50
51
  config = load_config()
51
52
  if profile not in config:
52
53
  error(f"Profile [magenta]{profile}[/magenta] does not exist.")
53
54
 
54
55
  if not yes:
55
- confirm = Confirm.ask(
56
+ confirm = get_confirmation(
56
57
  f"Are you sure you want to delete profile [magenta]{profile}[/magenta]? This action cannot be undone.",
57
- default=False,
58
58
  )
59
59
 
60
60
  if not confirm:
@@ -6,7 +6,7 @@ import typer
6
6
  from rich.console import Console
7
7
  from rich.table import Table
8
8
 
9
- from nextmv.cli.configuration.config import API_KEY_KEY, ENDPOINT_KEY, load_config, obscure_api_key
9
+ from nextmv.cli.configuration.config import API_KEY_KEY, ENDPOINT_KEY, load_config, non_profile_keys, obscure_api_key
10
10
  from nextmv.cli.message import error
11
11
 
12
12
  # Set up subcommand application.
@@ -22,7 +22,7 @@ def list() -> None:
22
22
  [bold][underline]Examples[/underline][/bold]
23
23
 
24
24
  - Show current configuration and all profiles.
25
- $ [green]nextmv configuration list[/green]
25
+ $ [dim]nextmv configuration list[/dim]
26
26
  """
27
27
 
28
28
  config = load_config()
@@ -38,7 +38,7 @@ def list() -> None:
38
38
 
39
39
  for k, v in config.items():
40
40
  # Skip default configuration.
41
- if k in {API_KEY_KEY, ENDPOINT_KEY}:
41
+ if k in non_profile_keys():
42
42
  continue
43
43
 
44
44
  profile = {
nextmv/cli/confirm.py ADDED
@@ -0,0 +1,32 @@
1
+ import sys
2
+
3
+ from rich.prompt import Confirm
4
+
5
+
6
+ def get_confirmation(msg: str) -> bool:
7
+ """
8
+ Method to get a yes/no confirmation from the user.
9
+
10
+ Parameters
11
+ ----------
12
+ msg : str
13
+ The message to display to the user.
14
+
15
+ Returns
16
+ -------
17
+ bool
18
+ True if the user confirmed, False otherwise.
19
+ """
20
+
21
+ # If this is not an interactive terminal, do not ask for confirmation, to
22
+ # avoid hanging indefinitely waiting for a user response.
23
+ if not sys.stdin.isatty():
24
+ return False
25
+
26
+ return Confirm.ask(
27
+ msg,
28
+ default=False,
29
+ case_sensitive=False,
30
+ show_default=True,
31
+ show_choices=True,
32
+ )