azure-deploy-cli 0.1.6__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.
- azure_deploy_cli/__init__.py +44 -0
- azure_deploy_cli/_version.py +34 -0
- azure_deploy_cli/aca/aca_cli.py +518 -0
- azure_deploy_cli/aca/bash/aca-cert/create.sh +203 -0
- azure_deploy_cli/aca/bash/aca-cert/destroy.sh +44 -0
- azure_deploy_cli/aca/deploy_aca.py +794 -0
- azure_deploy_cli/aca/model.py +35 -0
- azure_deploy_cli/cli.py +66 -0
- azure_deploy_cli/identity/__init__.py +36 -0
- azure_deploy_cli/identity/group.py +84 -0
- azure_deploy_cli/identity/identity_cli.py +453 -0
- azure_deploy_cli/identity/managed_identity.py +177 -0
- azure_deploy_cli/identity/models.py +167 -0
- azure_deploy_cli/identity/py.typed +0 -0
- azure_deploy_cli/identity/role.py +338 -0
- azure_deploy_cli/identity/service_principal.py +268 -0
- azure_deploy_cli/py.typed +0 -0
- azure_deploy_cli/utils/__init__.py +0 -0
- azure_deploy_cli/utils/azure_cli.py +96 -0
- azure_deploy_cli/utils/docker.py +137 -0
- azure_deploy_cli/utils/env.py +108 -0
- azure_deploy_cli/utils/key_vault.py +11 -0
- azure_deploy_cli/utils/logging.py +125 -0
- azure_deploy_cli/utils/py.typed +0 -0
- azure_deploy_cli-0.1.6.dist-info/METADATA +678 -0
- azure_deploy_cli-0.1.6.dist-info/RECORD +30 -0
- azure_deploy_cli-0.1.6.dist-info/WHEEL +5 -0
- azure_deploy_cli-0.1.6.dist-info/entry_points.txt +3 -0
- azure_deploy_cli-0.1.6.dist-info/licenses/LICENSE +373 -0
- azure_deploy_cli-0.1.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from azure_deploy_cli.identity import (
|
|
2
|
+
AzureGroup,
|
|
3
|
+
ManagedIdentity,
|
|
4
|
+
RoleConfig,
|
|
5
|
+
RoleDefinition,
|
|
6
|
+
SPAuthCredentials,
|
|
7
|
+
SPAuthCredentialsWithSecret,
|
|
8
|
+
SPCreateResult,
|
|
9
|
+
assign_roles,
|
|
10
|
+
create_or_get_user_identity,
|
|
11
|
+
create_sp,
|
|
12
|
+
delete_user_identity,
|
|
13
|
+
get_identity_principal_id,
|
|
14
|
+
reset_sp_credentials,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# Get version using standard importlib.metadata (preferred method)
|
|
18
|
+
try:
|
|
19
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
20
|
+
|
|
21
|
+
__version__ = version("azure-deploy-cli")
|
|
22
|
+
except PackageNotFoundError:
|
|
23
|
+
# Fallback to setuptools-scm generated file
|
|
24
|
+
try:
|
|
25
|
+
from azure_deploy_cli._version import __version__
|
|
26
|
+
except ImportError:
|
|
27
|
+
# Final fallback for development installations without git
|
|
28
|
+
__version__ = "0.0.0.dev0"
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"SPAuthCredentials",
|
|
32
|
+
"SPAuthCredentialsWithSecret",
|
|
33
|
+
"SPCreateResult",
|
|
34
|
+
"RoleConfig",
|
|
35
|
+
"RoleDefinition",
|
|
36
|
+
"ManagedIdentity",
|
|
37
|
+
"AzureGroup",
|
|
38
|
+
"create_sp",
|
|
39
|
+
"reset_sp_credentials",
|
|
40
|
+
"create_or_get_user_identity",
|
|
41
|
+
"delete_user_identity",
|
|
42
|
+
"get_identity_principal_id",
|
|
43
|
+
"assign_roles",
|
|
44
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '0.1.6'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 6)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from azure.mgmt.appcontainers import ContainerAppsAPIClient
|
|
9
|
+
|
|
10
|
+
from ..identity.managed_identity import create_or_get_user_identity
|
|
11
|
+
from ..identity.role import assign_role_by_files
|
|
12
|
+
from ..utils.azure_cli import get_credential, get_subscription_and_tenant
|
|
13
|
+
from ..utils.key_vault import get_key_vault_client
|
|
14
|
+
from ..utils.logging import get_logger
|
|
15
|
+
from .deploy_aca import (
|
|
16
|
+
SecretKeyVaultConfig,
|
|
17
|
+
bind_aca_managed_certificate,
|
|
18
|
+
build_acr_image,
|
|
19
|
+
create_container_app_env,
|
|
20
|
+
deploy_revision,
|
|
21
|
+
generate_revision_suffix,
|
|
22
|
+
get_aca_docker_image_name,
|
|
23
|
+
update_traffic_weights,
|
|
24
|
+
validate_revision_suffix_and_throw,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
logger = get_logger(__name__)
|
|
28
|
+
|
|
29
|
+
REGISTRY_PASS_SECRET_ENV_NAME = "ACA_REGISTRY_PASS"
|
|
30
|
+
REGISTRY_USER_SECRET_ENV_NAME = "ACA_REGISTRY_USER"
|
|
31
|
+
|
|
32
|
+
# Traffic weight configuration constants
|
|
33
|
+
MIN_TRAFFIC_WEIGHT = 0
|
|
34
|
+
MAX_TRAFFIC_WEIGHT = 100
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _label_weight_pair(pair_str: str) -> tuple[str, int]:
|
|
38
|
+
if "=" not in pair_str:
|
|
39
|
+
raise argparse.ArgumentTypeError(
|
|
40
|
+
f"Invalid format: '{pair_str}'. Expected format: label=weight"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
label, weight_str = pair_str.split("=", 1)
|
|
44
|
+
label = label.strip()
|
|
45
|
+
weight_str = weight_str.strip()
|
|
46
|
+
|
|
47
|
+
if not label:
|
|
48
|
+
raise argparse.ArgumentTypeError("Label name cannot be empty")
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
weight = int(weight_str)
|
|
52
|
+
except ValueError as e:
|
|
53
|
+
raise argparse.ArgumentTypeError(
|
|
54
|
+
f"Invalid weight '{weight_str}' for label '{label}'. Weight must be an integer"
|
|
55
|
+
) from e
|
|
56
|
+
|
|
57
|
+
if weight < MIN_TRAFFIC_WEIGHT or weight > MAX_TRAFFIC_WEIGHT:
|
|
58
|
+
raise argparse.ArgumentTypeError(
|
|
59
|
+
f"Invalid weight {weight} for label '{label}'. "
|
|
60
|
+
f"Weight must be between {MIN_TRAFFIC_WEIGHT} and {MAX_TRAFFIC_WEIGHT}"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return (label, weight)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _convert_label_traffic_args(label_traffic_list: list[tuple[str, int]]) -> dict[str, int]:
|
|
67
|
+
"""
|
|
68
|
+
Convert list of (label, weight) tuples to dictionary.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
label_traffic_list: List of (label, weight) tuples from argparse
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Dictionary mapping labels to traffic weights
|
|
75
|
+
"""
|
|
76
|
+
return dict(label_traffic_list)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _validate_cli_deploy(args: Any):
|
|
80
|
+
if args.revision_suffix:
|
|
81
|
+
validate_revision_suffix_and_throw(args.revision_suffix, args.stage)
|
|
82
|
+
if not os.getenv(REGISTRY_PASS_SECRET_ENV_NAME):
|
|
83
|
+
raise ValueError(f"Environment variable {REGISTRY_PASS_SECRET_ENV_NAME} is not set")
|
|
84
|
+
|
|
85
|
+
if args.role_config and args.role_env_vars_files:
|
|
86
|
+
pass
|
|
87
|
+
elif args.role_config:
|
|
88
|
+
raise ValueError("Role config provided without env vars files")
|
|
89
|
+
elif args.role_env_vars_files:
|
|
90
|
+
raise ValueError("Role env vars files provided without role config")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def cli_deploy(args: Any) -> None:
|
|
94
|
+
"""
|
|
95
|
+
Note: this is an opinionated deployment flow for ACA.
|
|
96
|
+
Deploy Azure Container App revision without updating traffic.
|
|
97
|
+
|
|
98
|
+
This command orchestrates:
|
|
99
|
+
1. Create/get user-assigned managed identity (if specified)
|
|
100
|
+
2. Assign roles to the identity (if role config provided)
|
|
101
|
+
3. Build and push container image (if not skipped)
|
|
102
|
+
4. Deploy new revision with 0% traffic
|
|
103
|
+
5. Verify revision activation and health
|
|
104
|
+
6. Output the revision name for use in traffic management
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
args: Parsed command line arguments
|
|
108
|
+
"""
|
|
109
|
+
_validate_cli_deploy(args)
|
|
110
|
+
registry_user = os.getenv(REGISTRY_USER_SECRET_ENV_NAME)
|
|
111
|
+
if not registry_user:
|
|
112
|
+
raise ValueError(f"Environment variable {REGISTRY_USER_SECRET_ENV_NAME} is not set")
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
logger.critical("Starting ACA revision deployment process...")
|
|
116
|
+
subscription_id, _ = get_subscription_and_tenant()
|
|
117
|
+
credential = get_credential(cache=True)
|
|
118
|
+
container_apps_api_client = ContainerAppsAPIClient(credential, subscription_id)
|
|
119
|
+
key_vault_client = get_key_vault_client(
|
|
120
|
+
subscription_id=subscription_id,
|
|
121
|
+
resource_group=args.resource_group,
|
|
122
|
+
key_vault_name=args.keyvault_name,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
revision_suffix = (
|
|
126
|
+
args.revision_suffix
|
|
127
|
+
if args.revision_suffix
|
|
128
|
+
else generate_revision_suffix(stage=args.stage)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
logger.critical("Building and pushing ACR image...")
|
|
132
|
+
logger.info(f"Using revision suffix '{revision_suffix}' as target image tag")
|
|
133
|
+
build_acr_image(
|
|
134
|
+
registry_server=args.registry_server,
|
|
135
|
+
dockerfile=args.dockerfile,
|
|
136
|
+
full_image_name=get_aca_docker_image_name(
|
|
137
|
+
registry_server=args.registry_server,
|
|
138
|
+
image_name=args.image_name,
|
|
139
|
+
image_tag=revision_suffix,
|
|
140
|
+
),
|
|
141
|
+
source_full_image_name=get_aca_docker_image_name(
|
|
142
|
+
registry_server=args.registry_server,
|
|
143
|
+
image_name=args.image_name,
|
|
144
|
+
image_tag=args.existing_image_tag,
|
|
145
|
+
)
|
|
146
|
+
if args.existing_image_tag
|
|
147
|
+
else None,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
logger.critical("Setting up managed identity and roles...")
|
|
151
|
+
user_identity = create_or_get_user_identity(
|
|
152
|
+
args.user_assigned_identity_name, args.resource_group, subscription_id
|
|
153
|
+
)
|
|
154
|
+
if args.role_config and args.role_env_vars_files:
|
|
155
|
+
assign_role_by_files(
|
|
156
|
+
user_identity.principalId,
|
|
157
|
+
args.role_config,
|
|
158
|
+
args.role_env_vars_files,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
logger.critical("Creating or getting Container App Environment...")
|
|
162
|
+
env = create_container_app_env(
|
|
163
|
+
container_apps_api_client,
|
|
164
|
+
resource_group=args.resource_group,
|
|
165
|
+
container_app_env_name=args.container_app_env,
|
|
166
|
+
location=args.location,
|
|
167
|
+
logs_workspace_id=args.logs_workspace_id,
|
|
168
|
+
)
|
|
169
|
+
if not env:
|
|
170
|
+
raise ValueError("Cannot create container app env")
|
|
171
|
+
|
|
172
|
+
logger.critical("Deploying new revision...")
|
|
173
|
+
result = deploy_revision(
|
|
174
|
+
client=container_apps_api_client,
|
|
175
|
+
subscription_id=subscription_id,
|
|
176
|
+
resource_group=args.resource_group,
|
|
177
|
+
container_app_env=env,
|
|
178
|
+
user_identity=user_identity,
|
|
179
|
+
container_app_name=args.container_app,
|
|
180
|
+
registry_server=args.registry_server,
|
|
181
|
+
registry_user=registry_user,
|
|
182
|
+
registry_pass_env_name=REGISTRY_PASS_SECRET_ENV_NAME,
|
|
183
|
+
image_name=args.image_name,
|
|
184
|
+
image_tag=revision_suffix,
|
|
185
|
+
revision_suffix=revision_suffix,
|
|
186
|
+
location=args.location,
|
|
187
|
+
stage=args.stage,
|
|
188
|
+
target_port=args.target_port,
|
|
189
|
+
cpu=args.cpu,
|
|
190
|
+
memory=args.memory,
|
|
191
|
+
min_replicas=args.min_replicas,
|
|
192
|
+
max_replicas=args.max_replicas,
|
|
193
|
+
secret_key_vault_config=SecretKeyVaultConfig(
|
|
194
|
+
key_vault_client=key_vault_client,
|
|
195
|
+
key_vault_name=args.keyvault_name,
|
|
196
|
+
secret_names=args.env_var_secrets or [],
|
|
197
|
+
user_identity=user_identity,
|
|
198
|
+
),
|
|
199
|
+
env_var_names=args.env_vars,
|
|
200
|
+
existing_image_tag=args.existing_image_tag,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
if args.custom_domains:
|
|
204
|
+
logger.critical("Binding SSL certificate to Container App...")
|
|
205
|
+
bind_aca_managed_certificate(
|
|
206
|
+
custom_domains=args.custom_domains,
|
|
207
|
+
container_app_name=args.container_app,
|
|
208
|
+
container_app_env_name=args.container_app_env,
|
|
209
|
+
resource_group=args.resource_group,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
logger.success("========== Deployment Complete ==========")
|
|
213
|
+
logger.success(
|
|
214
|
+
f"Deployed revision: {result.revision_name} "
|
|
215
|
+
f"(active={result.active}, healthy={result.is_healthy})"
|
|
216
|
+
)
|
|
217
|
+
logger.stdout(
|
|
218
|
+
f"""
|
|
219
|
+
{
|
|
220
|
+
json.dumps(
|
|
221
|
+
{
|
|
222
|
+
"revisionName": result.revision_name,
|
|
223
|
+
"revisionUrl": result.revision_url,
|
|
224
|
+
}
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
"""
|
|
228
|
+
)
|
|
229
|
+
if not result.is_healthy:
|
|
230
|
+
logger.error(
|
|
231
|
+
f"Revision '{result.revision_name}' is not healthy: "
|
|
232
|
+
f"active={result.active}, health={result.health_state}, "
|
|
233
|
+
f"provisioning={result.provisioning_state}, running={result.running_state}"
|
|
234
|
+
)
|
|
235
|
+
sys.exit(1)
|
|
236
|
+
except Exception:
|
|
237
|
+
logger.error("Failed to deploy revision", exc_info=True)
|
|
238
|
+
sys.exit(1)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def cli_update_traffic(args: Any) -> None:
|
|
242
|
+
"""
|
|
243
|
+
Update traffic weights for Azure Container App labels.
|
|
244
|
+
|
|
245
|
+
This command:
|
|
246
|
+
1. Updates traffic distribution across labels based on configuration
|
|
247
|
+
2. Optionally deactivates revisions not receiving traffic
|
|
248
|
+
3. Optionally deletes unused ACR images to prevent accumulation
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
args: Parsed command line arguments
|
|
252
|
+
"""
|
|
253
|
+
label_traffic_map = _convert_label_traffic_args(args.label_stage_traffic)
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
logger.critical("Starting traffic weight update process...")
|
|
257
|
+
subscription_id, _ = get_subscription_and_tenant()
|
|
258
|
+
credential = get_credential(cache=True)
|
|
259
|
+
container_apps_api_client = ContainerAppsAPIClient(credential, subscription_id)
|
|
260
|
+
|
|
261
|
+
logger.critical("Updating traffic weights...")
|
|
262
|
+
update_traffic_weights(
|
|
263
|
+
client=container_apps_api_client,
|
|
264
|
+
resource_group=args.resource_group,
|
|
265
|
+
container_app_name=args.container_app,
|
|
266
|
+
label_traffic_map=label_traffic_map,
|
|
267
|
+
deactivate_old_revisions=not args.no_deactivate,
|
|
268
|
+
should_delete_acr_images=args.delete_acr_images,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
logger.success("========== Traffic Update Complete ==========")
|
|
272
|
+
except Exception:
|
|
273
|
+
logger.error("Failed to update traffic weights", exc_info=True)
|
|
274
|
+
sys.exit(1)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def add_commands(subparsers: argparse._SubParsersAction) -> None:
|
|
278
|
+
"""
|
|
279
|
+
Register ACA namespace commands under the 'aca' subparser.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
subparsers: The subparsers action from the main parser
|
|
283
|
+
"""
|
|
284
|
+
aca_parser = subparsers.add_parser(
|
|
285
|
+
"azaca",
|
|
286
|
+
help="Azure Container Apps management",
|
|
287
|
+
description="Manage Azure Container Apps deployments and configurations",
|
|
288
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
289
|
+
add_help=True,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
aca_subparsers = aca_parser.add_subparsers(dest="aca_command", help="ACA commands")
|
|
293
|
+
|
|
294
|
+
deploy_parser = aca_subparsers.add_parser(
|
|
295
|
+
"deploy",
|
|
296
|
+
help="Deploy ACA with optional identity and role setup",
|
|
297
|
+
description=(
|
|
298
|
+
"Set up managed identity and optionally assign roles before ACA deployment. "
|
|
299
|
+
"All unrecognized arguments are passed to the bash deployment script."
|
|
300
|
+
f"Required env vars: {REGISTRY_USER_SECRET_ENV_NAME} "
|
|
301
|
+
f"and {REGISTRY_PASS_SECRET_ENV_NAME}."
|
|
302
|
+
),
|
|
303
|
+
add_help=True,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
deploy_parser.add_argument(
|
|
307
|
+
"--resource-group",
|
|
308
|
+
required=True,
|
|
309
|
+
type=str,
|
|
310
|
+
help="Azure resource group name",
|
|
311
|
+
)
|
|
312
|
+
deploy_parser.add_argument(
|
|
313
|
+
"--location",
|
|
314
|
+
required=True,
|
|
315
|
+
type=str,
|
|
316
|
+
help="Azure region location (e.g., eastus, westus2)",
|
|
317
|
+
)
|
|
318
|
+
deploy_parser.add_argument(
|
|
319
|
+
"--container-app-env",
|
|
320
|
+
required=True,
|
|
321
|
+
type=str,
|
|
322
|
+
help="Name of the container app environment.",
|
|
323
|
+
)
|
|
324
|
+
deploy_parser.add_argument(
|
|
325
|
+
"--logs-workspace-id",
|
|
326
|
+
required=True,
|
|
327
|
+
type=str,
|
|
328
|
+
help="Log Analytics workspace ID for the container app environment.",
|
|
329
|
+
)
|
|
330
|
+
deploy_parser.add_argument(
|
|
331
|
+
"--user-assigned-identity-name",
|
|
332
|
+
required=True,
|
|
333
|
+
type=str,
|
|
334
|
+
help="Name of the user-assigned managed identity.",
|
|
335
|
+
)
|
|
336
|
+
deploy_parser.add_argument(
|
|
337
|
+
"--container-app",
|
|
338
|
+
required=True,
|
|
339
|
+
type=str,
|
|
340
|
+
help="Name of the container app.",
|
|
341
|
+
)
|
|
342
|
+
deploy_parser.add_argument(
|
|
343
|
+
"--revision-suffix",
|
|
344
|
+
required=False,
|
|
345
|
+
type=str,
|
|
346
|
+
help="Suffix to append to the revision name for identification.",
|
|
347
|
+
)
|
|
348
|
+
deploy_parser.add_argument(
|
|
349
|
+
"--registry-server",
|
|
350
|
+
required=True,
|
|
351
|
+
type=str,
|
|
352
|
+
help="Container registry server.",
|
|
353
|
+
)
|
|
354
|
+
deploy_parser.add_argument(
|
|
355
|
+
"--image-name",
|
|
356
|
+
required=True,
|
|
357
|
+
type=str,
|
|
358
|
+
help="Name of the container image.",
|
|
359
|
+
)
|
|
360
|
+
deploy_parser.add_argument(
|
|
361
|
+
"--existing-image-tag",
|
|
362
|
+
required=False,
|
|
363
|
+
type=str,
|
|
364
|
+
help=(
|
|
365
|
+
"Tag of an existing image to retag to the revision suffix. "
|
|
366
|
+
"If provided, the image with this tag will be pulled from the registry, "
|
|
367
|
+
"retagged to the revision suffix, and pushed. "
|
|
368
|
+
"If the image doesn't exist, deployment will fail."
|
|
369
|
+
),
|
|
370
|
+
)
|
|
371
|
+
deploy_parser.add_argument(
|
|
372
|
+
"--target-port",
|
|
373
|
+
required=True,
|
|
374
|
+
type=int,
|
|
375
|
+
help="Target port for the container app.",
|
|
376
|
+
)
|
|
377
|
+
deploy_parser.add_argument(
|
|
378
|
+
"--cpu",
|
|
379
|
+
required=True,
|
|
380
|
+
type=float,
|
|
381
|
+
help="CPU allocation for the container app.",
|
|
382
|
+
)
|
|
383
|
+
deploy_parser.add_argument(
|
|
384
|
+
"--memory",
|
|
385
|
+
required=True,
|
|
386
|
+
type=str,
|
|
387
|
+
help="Memory allocation for the container app (e.g., '2.0Gi').",
|
|
388
|
+
)
|
|
389
|
+
deploy_parser.add_argument(
|
|
390
|
+
"--min-replicas",
|
|
391
|
+
required=True,
|
|
392
|
+
type=int,
|
|
393
|
+
help="Minimum number of replicas for the container app.",
|
|
394
|
+
)
|
|
395
|
+
deploy_parser.add_argument(
|
|
396
|
+
"--max-replicas",
|
|
397
|
+
required=True,
|
|
398
|
+
type=int,
|
|
399
|
+
help="Maximum number of replicas for the container app.",
|
|
400
|
+
)
|
|
401
|
+
deploy_parser.add_argument(
|
|
402
|
+
"--keyvault-name",
|
|
403
|
+
required=True,
|
|
404
|
+
type=str,
|
|
405
|
+
help="Name of the Key Vault for storing secrets.",
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
deploy_parser.add_argument(
|
|
409
|
+
"--stage",
|
|
410
|
+
required=True,
|
|
411
|
+
type=str,
|
|
412
|
+
help="Deployment stage label (e.g., staging, prod) used for revision naming.",
|
|
413
|
+
)
|
|
414
|
+
deploy_parser.add_argument(
|
|
415
|
+
"--env-var-secrets",
|
|
416
|
+
required=False,
|
|
417
|
+
type=str,
|
|
418
|
+
nargs="+",
|
|
419
|
+
help="Space-separated names of environment variables to be stored as secrets in Key Vault.",
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
deploy_parser.add_argument(
|
|
423
|
+
"--env-vars",
|
|
424
|
+
required=False,
|
|
425
|
+
type=str,
|
|
426
|
+
nargs="+",
|
|
427
|
+
help="Space-separated names of environment variables to pass to the container.",
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
deploy_parser.add_argument(
|
|
431
|
+
"--role-config",
|
|
432
|
+
required=False,
|
|
433
|
+
type=Path,
|
|
434
|
+
help=(
|
|
435
|
+
"Path to role configuration JSON file for role assignment. "
|
|
436
|
+
"Must be provided together with --role-env-vars-files."
|
|
437
|
+
),
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
deploy_parser.add_argument(
|
|
441
|
+
"--role-env-vars-files",
|
|
442
|
+
required=False,
|
|
443
|
+
type=Path,
|
|
444
|
+
nargs="+",
|
|
445
|
+
help=(
|
|
446
|
+
"Environment files for variable substitution in role config scopes. "
|
|
447
|
+
"Must be provided together with --role-config."
|
|
448
|
+
),
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
deploy_parser.add_argument(
|
|
452
|
+
"--custom-domains",
|
|
453
|
+
required=False,
|
|
454
|
+
type=str,
|
|
455
|
+
nargs="+",
|
|
456
|
+
help="Space-separated list of custom domains to "
|
|
457
|
+
+ "bind SSL certificates to the container app.",
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
deploy_parser.add_argument(
|
|
461
|
+
"--dockerfile",
|
|
462
|
+
required=True,
|
|
463
|
+
type=str,
|
|
464
|
+
help="Path to the Dockerfile for building the container image.",
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
deploy_parser.set_defaults(func=cli_deploy)
|
|
468
|
+
|
|
469
|
+
# Add update-traffic command
|
|
470
|
+
update_traffic_parser = aca_subparsers.add_parser(
|
|
471
|
+
"update-traffic",
|
|
472
|
+
help="Update traffic weights and deactivate old revisions",
|
|
473
|
+
description=(
|
|
474
|
+
"Update traffic distribution across stage labels and optionally "
|
|
475
|
+
"deactivate revisions not receiving traffic and clean up ACR images."
|
|
476
|
+
),
|
|
477
|
+
add_help=True,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
update_traffic_parser.add_argument(
|
|
481
|
+
"--resource-group",
|
|
482
|
+
required=True,
|
|
483
|
+
type=str,
|
|
484
|
+
help="Azure resource group name",
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
update_traffic_parser.add_argument(
|
|
488
|
+
"--container-app",
|
|
489
|
+
required=True,
|
|
490
|
+
type=str,
|
|
491
|
+
help="Name of the container app.",
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
update_traffic_parser.add_argument(
|
|
495
|
+
"--label-stage-traffic",
|
|
496
|
+
type=_label_weight_pair,
|
|
497
|
+
nargs="+",
|
|
498
|
+
required=True,
|
|
499
|
+
metavar="LABEL=WEIGHT",
|
|
500
|
+
help=(
|
|
501
|
+
"Traffic weight configuration for stage labels (e.g., prod=100 staging=0). "
|
|
502
|
+
"Specify one or more label=weight pairs."
|
|
503
|
+
),
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
update_traffic_parser.add_argument(
|
|
507
|
+
"--no-deactivate",
|
|
508
|
+
action="store_true",
|
|
509
|
+
help="Skip deactivation of revisions not receiving traffic.",
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
update_traffic_parser.add_argument(
|
|
513
|
+
"--delete-acr-images",
|
|
514
|
+
action="store_true",
|
|
515
|
+
help="Disable deletion of unused ACR images when deactivating revisions.",
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
update_traffic_parser.set_defaults(func=cli_update_traffic)
|