azure-deploy-cli 1.0.5__py3-none-any.whl → 1.1.1__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.
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '1.0.5'
32
- __version_tuple__ = version_tuple = (1, 0, 5)
31
+ __version__ = version = '1.1.1'
32
+ __version_tuple__ = version_tuple = (1, 1, 1)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -6,6 +6,7 @@ from pathlib import Path
6
6
  from typing import Any
7
7
 
8
8
  from azure.mgmt.appcontainers import ContainerAppsAPIClient
9
+ from azure.mgmt.appcontainers.models import IpSecurityRestrictionRule
9
10
 
10
11
  from ..identity.managed_identity import create_or_get_user_identity
11
12
  from ..identity.role import assign_role_by_files
@@ -62,7 +63,9 @@ def _label_weight_pair(pair_str: str) -> tuple[str, int]:
62
63
  return (label, weight)
63
64
 
64
65
 
65
- def _convert_label_traffic_args(label_traffic_list: list[tuple[str, int]]) -> dict[str, int]:
66
+ def _convert_label_traffic_args(
67
+ label_traffic_list: list[tuple[str, int]],
68
+ ) -> dict[str, int]:
66
69
  """
67
70
  Convert list of (label, weight) tuples to dictionary.
68
71
 
@@ -153,6 +156,19 @@ def cli_deploy(args: Any) -> None:
153
156
  if not env:
154
157
  raise ValueError("Cannot create container app env")
155
158
 
159
+ ip_rules: list[IpSecurityRestrictionRule] = []
160
+ if args.allowed_ips:
161
+ for name, cidr_ranges in args.allowed_ips:
162
+ for idx, cidr_range in enumerate(cidr_ranges):
163
+ rule = IpSecurityRestrictionRule(
164
+ name=f"{name}-{idx + 1}",
165
+ action="Allow",
166
+ ip_address_range=cidr_range,
167
+ description=f"Allowed {name} IP range",
168
+ )
169
+ ip_rules.append(rule)
170
+ logger.critical(f"Configured {len(ip_rules)} Allowed IP restriction rules.")
171
+
156
172
  logger.critical("Deploying new revision...")
157
173
  result = deploy_revision(
158
174
  client=container_apps_api_client,
@@ -179,6 +195,7 @@ def cli_deploy(args: Any) -> None:
179
195
  secret_names=args.env_var_secrets or [],
180
196
  user_identity=user_identity,
181
197
  ),
198
+ ip_rules=ip_rules,
182
199
  )
183
200
 
184
201
  if args.custom_domains:
@@ -430,6 +447,24 @@ def add_commands(subparsers: argparse._SubParsersAction) -> None:
430
447
  "(includes image names, cpu, memory, env_vars, probes, ingress, and scale settings)",
431
448
  )
432
449
 
450
+ def tuple_ip(value: str) -> tuple[str, list[str]]:
451
+ if "=" not in value:
452
+ raise argparse.ArgumentTypeError(
453
+ f"Invalid format: '{value}'. Expected format: Name=IP1,IP2/CIDR"
454
+ )
455
+ name, ranges = value.split("=")
456
+ cidr_ranges = [cidr.strip() for cidr in ranges.split(",") if cidr.strip()]
457
+ return name, cidr_ranges
458
+
459
+ deploy_parser.add_argument(
460
+ "--allowed-ips",
461
+ nargs="*",
462
+ required=True,
463
+ type=tuple_ip,
464
+ help="List of allowed IP addresses or CIDR ranges for IP restriction. "
465
+ + "e.g., Name=1.3.5.7/32,2.3.4.3/24 Name2=3.4.5.6/43",
466
+ )
467
+
433
468
  deploy_parser.set_defaults(func=cli_deploy)
434
469
 
435
470
  # Add update-traffic command
@@ -20,6 +20,7 @@ from azure.mgmt.appcontainers.models import (
20
20
  ContainerResources,
21
21
  EnvironmentVar,
22
22
  Ingress,
23
+ IpSecurityRestrictionRule,
23
24
  LogAnalyticsConfiguration,
24
25
  ManagedEnvironment,
25
26
  ManagedServiceIdentity,
@@ -221,7 +222,12 @@ def build_container_images(
221
222
  source_full_image_name = get_aca_docker_image_name(
222
223
  registry_server, container_config.image_name, container_config.existing_image_tag
223
224
  )
224
- docker.pull_retag_and_push_image(source_full_image_name, target_full_image_name)
225
+ _login_to_acr(registry_server)
226
+ docker.pull_retag_and_push_image(
227
+ source_full_image_name,
228
+ target_full_image_name,
229
+ container_config.existing_image_platform,
230
+ )
225
231
  logger.success(f"Image retagged successfully to '{image_tag}'")
226
232
  elif container_config.dockerfile:
227
233
  logger.info(
@@ -259,6 +265,7 @@ def deploy_revision(
259
265
  min_replicas: int,
260
266
  max_replicas: int,
261
267
  secret_key_vault_config: SecretKeyVaultConfig,
268
+ ip_rules: list[IpSecurityRestrictionRule],
262
269
  ) -> RevisionDeploymentResult:
263
270
  """
264
271
  Deploy a new revision with multiple containers without updating traffic weights.
@@ -348,6 +355,7 @@ def deploy_revision(
348
355
  transport=ingress_transport,
349
356
  traffic=existing_traffic_weights, # Preserve existing traffic
350
357
  custom_domains=existing_custom_domains,
358
+ ip_security_restrictions=ip_rules,
351
359
  )
352
360
 
353
361
  revision_name = generate_revision_name(container_app_name, revision_suffix, stage)
@@ -52,6 +52,9 @@ class ContainerConfig(BaseModel):
52
52
  default=None, description="List of probe configurations"
53
53
  )
54
54
  existing_image_tag: str | None = Field(default=None, description="Optional tag to retag from")
55
+ existing_image_platform: str | None = Field(
56
+ default=None, description="Optional platform for existing image pull"
57
+ )
55
58
  dockerfile: str | None = Field(default=None, description="Optional dockerfile path")
56
59
 
57
60
  def post_init(self):
@@ -61,17 +61,22 @@ def push_image(full_image_name: str) -> None:
61
61
  raise RuntimeError("Docker push failed")
62
62
 
63
63
 
64
- def pull_image(full_image_name: str) -> None:
64
+ def pull_image(full_image_name: str, platform: str | None = None) -> None:
65
65
  """
66
66
  Pull a Docker image from the registry.
67
67
 
68
68
  Args:
69
69
  full_image_name: Full image name including registry, repository, and tag
70
+ platform: Optional platform specification (e.g., "linux/amd64")
70
71
 
71
72
  Raises:
72
73
  RuntimeError: If the docker pull command fails
73
74
  """
74
- returncode = _run_and_stream(["docker", "pull", full_image_name])
75
+ cmd = ["docker", "pull"]
76
+ if platform:
77
+ cmd.extend(["--platform", platform])
78
+ cmd.append(full_image_name)
79
+ returncode = _run_and_stream(cmd)
75
80
  if returncode != 0:
76
81
  raise RuntimeError("Docker pull failed")
77
82
 
@@ -95,6 +100,7 @@ def tag_image(source_image: str, target_image: str) -> None:
95
100
  def pull_retag_and_push_image(
96
101
  source_full_image_name: str,
97
102
  target_full_image_name: str,
103
+ platform: str | None = None,
98
104
  ) -> None:
99
105
  """
100
106
  Pull an existing image, retag it, and push to registry.
@@ -107,7 +113,7 @@ def pull_retag_and_push_image(
107
113
  RuntimeError: If the source image doesn't exist or operations fail
108
114
  """
109
115
  if not image_exists(source_full_image_name):
110
- pull_image(source_full_image_name)
116
+ pull_image(source_full_image_name, platform)
111
117
 
112
118
  tag_image(source_full_image_name, target_full_image_name)
113
119
  push_image(target_full_image_name)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: azure-deploy-cli
3
- Version: 1.0.5
3
+ Version: 1.1.1
4
4
  Summary: Python CLI for Azure deployment automation - identity, roles, and container apps management
5
5
  Author-email: decewei <celinew1221@gmail.com>
6
6
  License: Mozilla Public License Version 2.0
@@ -400,15 +400,15 @@ Requires-Dist: python-dotenv==1.2.1
400
400
  Requires-Dist: pydantic==2.12.5
401
401
  Requires-Dist: PyYAML==6.0.3
402
402
  Provides-Extra: dev
403
- Requires-Dist: ruff==0.14.10; extra == "dev"
403
+ Requires-Dist: ruff==0.14.14; extra == "dev"
404
404
  Requires-Dist: mypy==1.19.1; extra == "dev"
405
405
  Requires-Dist: pytest==9.0.2; extra == "dev"
406
406
  Requires-Dist: pytest-asyncio==1.3.0; extra == "dev"
407
- Requires-Dist: coverage==7.13.1; extra == "dev"
407
+ Requires-Dist: coverage==7.13.2; extra == "dev"
408
408
  Requires-Dist: pytest-cov==7.0.0; extra == "dev"
409
- Requires-Dist: commitizen==4.11.0; extra == "dev"
410
- Requires-Dist: pre-commit==4.1.0; extra == "dev"
411
- Requires-Dist: types-setuptools==80.9.0.20251223; extra == "dev"
409
+ Requires-Dist: commitizen==4.12.1; extra == "dev"
410
+ Requires-Dist: pre-commit==4.5.1; extra == "dev"
411
+ Requires-Dist: types-setuptools==80.10.0.20260124; extra == "dev"
412
412
  Requires-Dist: types-PyYAML==6.0.12.20250915; extra == "dev"
413
413
  Dynamic: license-file
414
414
 
@@ -1,10 +1,10 @@
1
1
  azure_deploy_cli/__init__.py,sha256=Xj8ZMqmqKWc2hhR8rtSETkOmkvrFgh020MYVB_yHp4s,1134
2
- azure_deploy_cli/_version.py,sha256=uYAmg6HpJuEIdeeqRyfS9YeZSA-4pXIPoW7JKBe0Uvw,704
2
+ azure_deploy_cli/_version.py,sha256=O2MwkHziz63f5tgvyTDcaX64UwyuPAN_tWOgO5wh1WI,704
3
3
  azure_deploy_cli/cli.py,sha256=I1hXskorvbryjNg0XXdqLjdBuQTPKgZeW03tLHlLxzk,1663
4
4
  azure_deploy_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- azure_deploy_cli/aca/aca_cli.py,sha256=_6c6l7iH5W2Prue3JHSHBQL12WGs9twGivmsZ9h8STU,15944
6
- azure_deploy_cli/aca/deploy_aca.py,sha256=c9aZlNqGyOCKgN6aD95HNEI7Zhfyze4wM7Tfw5ZF4z8,30535
7
- azure_deploy_cli/aca/model.py,sha256=rCIVd9WF89g7r7AOZoCPTG6siMaZwSXNR_RzqmKRQEQ,2581
5
+ azure_deploy_cli/aca/aca_cli.py,sha256=Ng7faQLMoK9SEOEOrjrKJpoKq-v7RBlu2WSe7nNXTLo,17323
6
+ azure_deploy_cli/aca/deploy_aca.py,sha256=bABLN9q1qxl84kCN8xX6meNrcFMBwouBECsN8l_TYJA,30804
7
+ azure_deploy_cli/aca/model.py,sha256=elz4p02Apl0seUIA2IBz1KydaOHZpBgTVKbmSiVx_3U,2714
8
8
  azure_deploy_cli/aca/yaml_loader.py,sha256=awCJLx88j8ErLm-tLIdV8mFtlSQqGezIqugU0v4m9BI,1413
9
9
  azure_deploy_cli/aca/bash/aca-cert/create.sh,sha256=FI3Mdp9qv_DqwiAdxWmZnSY98oRxH1Y6gwDQH4OAxCc,6556
10
10
  azure_deploy_cli/aca/bash/aca-cert/destroy.sh,sha256=iao35H_c4YnZHLDpKaYeyd3I7RhHKJzSAUbNovUdcFg,1171
@@ -18,14 +18,14 @@ azure_deploy_cli/identity/role.py,sha256=WlSJmn54Urar58x4HmJUC0adbeuSTPyW-0lGodv
18
18
  azure_deploy_cli/identity/service_principal.py,sha256=IDBKVVTQP4PBso0Cv0gcApexKEdXUlKEQ3sMr4OjC1U,7958
19
19
  azure_deploy_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  azure_deploy_cli/utils/azure_cli.py,sha256=mQXHedduPShY6NqG4JePijWJnK3alCMxrTjqHCn2MaM,2576
21
- azure_deploy_cli/utils/docker.py,sha256=S-X5YkuxiC6TEphwrXlM8Hk_Ik-i_eTnmAs6ymvN4Z8,3965
21
+ azure_deploy_cli/utils/docker.py,sha256=Mqm6S-UCGp2I4XV81z_1rJ_WOGomTmXKmAG3sFKnhfQ,4200
22
22
  azure_deploy_cli/utils/env.py,sha256=Qoc0mlrEcyCGk6TLl0AJNfuvhdBuok_93EW2g7Clpu8,3451
23
23
  azure_deploy_cli/utils/key_vault.py,sha256=9JYE1pmoPhggU7TZXod_qmu8TgeVmdM0OEIVHPYtPvM,354
24
24
  azure_deploy_cli/utils/logging.py,sha256=NxrhhNGnS0SWReKbwJ56A8LYLmjRd77ZBVOVQe9bgQE,3813
25
25
  azure_deploy_cli/utils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- azure_deploy_cli-1.0.5.dist-info/licenses/LICENSE,sha256=Pz2eACSxkhsGfW9_iN60pgy-enjnbGTj8df8O3ebnQQ,16726
27
- azure_deploy_cli-1.0.5.dist-info/METADATA,sha256=1TKYGTc1mNmS_L2BUdS3Hlzp__CHbbv8qpijMeRHxFI,29137
28
- azure_deploy_cli-1.0.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
29
- azure_deploy_cli-1.0.5.dist-info/entry_points.txt,sha256=vuN79v5YJtDi5tF3rCd5qVvCGAMEQMImRHq64B-y-KQ,91
30
- azure_deploy_cli-1.0.5.dist-info/top_level.txt,sha256=luw_MJJWFd2vFwCtx3wbmihlkCdlZxk1NZou1eToBtU,17
31
- azure_deploy_cli-1.0.5.dist-info/RECORD,,
26
+ azure_deploy_cli-1.1.1.dist-info/licenses/LICENSE,sha256=Pz2eACSxkhsGfW9_iN60pgy-enjnbGTj8df8O3ebnQQ,16726
27
+ azure_deploy_cli-1.1.1.dist-info/METADATA,sha256=DDOu291gsHn_wDOw9Hu8GXyUpHq20Wo9uhiitxkDMqE,29138
28
+ azure_deploy_cli-1.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
29
+ azure_deploy_cli-1.1.1.dist-info/entry_points.txt,sha256=vuN79v5YJtDi5tF3rCd5qVvCGAMEQMImRHq64B-y-KQ,91
30
+ azure_deploy_cli-1.1.1.dist-info/top_level.txt,sha256=luw_MJJWFd2vFwCtx3wbmihlkCdlZxk1NZou1eToBtU,17
31
+ azure_deploy_cli-1.1.1.dist-info/RECORD,,