Skip to content

Commit f3e726c

Browse files
committed
[confcom] Do not use inspect, just use image pull
1 parent 53515f9 commit f3e726c

6 files changed

Lines changed: 52 additions & 66 deletions

File tree

src/confcom/azext_confcom/lib/images.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def get_image(image_ref: str) -> docker.models.images.Image:
4848

4949

5050
def get_image_platform(image_reference: str) -> str:
51+
"""Return the platform of the pulled image (e.g. 'linux/amd64')."""
5152
return "/".join([
5253
pull_image(image_reference).attrs['Os'],
5354
pull_image(image_reference).attrs['Architecture']

src/confcom/azext_confcom/security_policy.py

Lines changed: 42 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import copy
77
import json
8-
import subprocess
98
import warnings
109
from enum import Enum, auto
1110
from typing import Any, Dict, List, Optional, Tuple, Union
@@ -44,7 +43,7 @@
4443
process_mounts_from_config,
4544
readable_diff,
4645
find_value_in_params_and_vars)
47-
from azext_confcom.lib.images import get_image_platform
46+
from azext_confcom.lib.images import get_image_platform # pylint: disable=unused-import
4847
from azext_confcom.lib.defaults import get_debug_mode_exec_procs
4948
from knack.log import get_logger
5049
from tqdm import tqdm
@@ -671,75 +670,59 @@ def set_images(self, images: List[ContainerImage]) -> None:
671670
self._images = images
672671

673672

674-
def _get_image_platforms_from_docker(image_name: str) -> List[str]:
675-
"""Detect all platforms an image supports using docker manifest inspect.
673+
def validate_image_platform(image_name: str, platform: str) -> None:
674+
"""Validate that the image's platform matches --platform.
676675
677-
If the image reference is a digest (@sha256:...), strip it back to the
678-
tag so that multi-platform manifest list lookups work correctly.
676+
Checks the local Docker image first, then attempts to pull with the
677+
specified platform if not found locally. Verifies the image's
678+
Os/Architecture attrs match the requested platform.
679679
"""
680-
# Strip digest references — manifest inspect needs a tag, not a digest
681-
if "@sha256:" in image_name:
682-
image_name = image_name.split("@sha256:")[0]
683-
if ":" not in image_name.split("/")[-1]:
684-
image_name += ":latest"
685-
680+
import docker as docker_module
686681
try:
687-
result = subprocess.run(
688-
["docker", "manifest", "inspect", image_name],
689-
capture_output=True, text=True, timeout=30, check=True,
690-
)
691-
data = json.loads(result.stdout)
692-
platforms = []
693-
for manifest in data.get("manifests", []):
694-
plat = manifest.get("platform", {})
695-
os_name = plat.get("os", "unknown")
696-
arch = plat.get("architecture", "unknown")
697-
if os_name != "unknown" and arch != "unknown":
698-
platforms.append(f"{os_name}/{arch}")
699-
if platforms:
700-
return platforms
701-
except (subprocess.CalledProcessError, subprocess.TimeoutExpired,
702-
json.JSONDecodeError, FileNotFoundError):
703-
pass
704-
return []
682+
client = docker_module.from_env()
683+
except docker_module.errors.DockerException:
684+
eprint("Docker is not running. Please start Docker.")
685+
return
705686

687+
image = None
706688

707-
def validate_image_platform(image_name: str, platform: str) -> None:
708-
"""Validate that the image supports the specified platform.
709-
710-
Uses docker manifest inspect for multi-platform detection,
711-
then falls back to get_image_platform (docker pull) for single-platform.
712-
"""
713-
supported = _get_image_platforms_from_docker(image_name)
689+
# Try local image first
690+
try:
691+
image = client.images.get(image_name)
692+
except (docker_module.errors.ImageNotFound, docker_module.errors.NullResource):
693+
pass
714694

715-
# Fall back to single-platform detection via docker pull
716-
if not supported:
695+
# If not local, try pulling with the specified platform
696+
if image is None:
717697
try:
718-
detected = get_image_platform(image_name)
719-
if detected:
720-
supported = [detected]
721-
except (ValueError, KeyError, AttributeError):
722-
pass
723-
724-
if not supported:
725-
logger.warning(
726-
"Could not detect supported platforms for image '%s'. "
727-
"Skipping platform validation.", image_name,
728-
)
729-
return
698+
image = client.images.pull(image_name, platform=platform)
699+
except (docker_module.errors.ImageNotFound, docker_module.errors.NotFound):
700+
eprint(
701+
f'Image "{image_name}" is not found. '
702+
f'Please check the image name and repository.'
703+
)
704+
except docker_module.errors.APIError as e:
705+
error_msg = str(e).lower()
706+
if "not supported" in error_msg or "no matching manifest" in error_msg:
707+
eprint(
708+
f'Image "{image_name}" could not be pulled for platform "{platform}". '
709+
f'Docker Desktop must be in the correct container mode '
710+
f'(Linux containers for linux/amd64, '
711+
f'Windows containers for windows/amd64).'
712+
)
713+
else:
714+
eprint(
715+
f'Image "{image_name}" could not be pulled for platform '
716+
f'"{platform}": {e}'
717+
)
730718

731-
if len(supported) == 1 and supported[0] != platform:
719+
detected = f"{image.attrs.get('Os')}/{image.attrs.get('Architecture')}"
720+
if detected != platform:
732721
eprint(
733-
f'Image "{image_name}" only supports platform "{supported[0]}", '
722+
f'Image "{image_name}" has platform "{detected}", '
734723
f'which does not match the specified platform "{platform}".'
735724
)
736725

737-
if len(supported) > 1 and platform not in supported:
738-
eprint(
739-
f'Image "{image_name}" supports platforms {supported}, '
740-
f'which does not include the specified platform "{platform}".'
741-
)
742-
743726

744727
# pylint: disable=R0914,
745728
def load_policy_from_arm_template_str(

src/confcom/azext_confcom/template_util.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ def get_image_info(progress, message_queue, tar_mapping, image):
158158
break
159159
except (docker.errors.ImageNotFound, docker.errors.NotFound):
160160
continue
161+
if raw_image is None:
162+
raise docker.errors.ImageNotFound(image_name)
161163
image_info = {
162164
"platform": "/".join([raw_image.attrs.get("Os"), raw_image.attrs.get("Architecture")]),
163165
**raw_image.attrs.get("Config")

src/confcom/azext_confcom/tests/latest/test_confcom_image.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@ def test_sidecar_image_policy(self):
8989
class PolicyGeneratingImageInvalid(unittest.TestCase):
9090
def test_invalid_image_policy(self):
9191

92-
policy = load_policy_from_image_name(
93-
"mcr.microsoft.com/aci/fake-image:master_20201210.2"
94-
)
9592
with self.assertRaises(SystemExit) as exc_info:
93+
policy = load_policy_from_image_name(
94+
"mcr.microsoft.com/aci/fake-image:master_20201210.2"
95+
)
9696
policy.populate_policy_content_for_all_images(individual_image=True)
9797
self.assertEqual(exc_info.exception.code, 1)
9898

src/confcom/azext_confcom/tests/latest/test_confcom_scenario.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -796,10 +796,10 @@ def test_get_layers_from_not_exists_image(self):
796796
]
797797
}
798798
"""
799-
with load_policy_from_json(custom_json) as aci_policy:
800-
with self.assertRaises(SystemExit) as exc_info:
799+
with self.assertRaises(SystemExit) as exc_info:
800+
with load_policy_from_json(custom_json) as aci_policy:
801801
aci_policy.populate_policy_content_for_all_images()
802-
self.assertEqual(exc_info.exception.code, 1)
802+
self.assertEqual(exc_info.exception.code, 1)
803803

804804
def test_incorrect_allow_elevated_data_type(self):
805805
custom_json = """

src/confcom/samples/aci/minimal/arm_template.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"osType": "Linux",
1212
"restartPolicy": "OnFailure",
1313
"confidentialComputeProperties": {
14-
"ccePolicy": ""
14+
"ccePolicy": "cGFja2FnZSBwb2xpY3kKCmltcG9ydCBmdXR1cmUua2V5d29yZHMuZXZlcnkKaW1wb3J0IGZ1dHVyZS5rZXl3b3Jkcy5pbgoKYXBpX3ZlcnNpb24gOj0gIjAuMTEuMCIKZnJhbWV3b3JrX3ZlcnNpb24gOj0gIjAuMi4zIgoKZnJhZ21lbnRzIDo9IFsKICB7CiAgICAiZmVlZCI6ICJtY3IubWljcm9zb2Z0LmNvbS9hY2kvYWNpLWNjLWluZnJhLWZyYWdtZW50IiwKICAgICJpbmNsdWRlcyI6IFsKICAgICAgImNvbnRhaW5lcnMiLAogICAgICAiZnJhZ21lbnRzIgogICAgXSwKICAgICJpc3N1ZXIiOiAiZGlkOng1MDk6MDpzaGEyNTY6SV9faXVMMjVvWEVWRmRUUF9hQkx4X2VUMVJQSGJDUV9FQ0JRZllacHQ5czo6ZWt1OjEuMy42LjEuNC4xLjMxMS43Ni41OS4xLjMiLAogICAgIm1pbmltdW1fc3ZuIjogIjQiCiAgfQpdCgpjb250YWluZXJzIDo9IFt7ImFsbG93X2VsZXZhdGVkIjpmYWxzZSwiYWxsb3dfc3RkaW9fYWNjZXNzIjp0cnVlLCJjYXBhYmlsaXRpZXMiOnsiYW1iaWVudCI6W10sImJvdW5kaW5nIjpbIkNBUF9BVURJVF9XUklURSIsIkNBUF9DSE9XTiIsIkNBUF9EQUNfT1ZFUlJJREUiLCJDQVBfRk9XTkVSIiwiQ0FQX0ZTRVRJRCIsIkNBUF9LSUxMIiwiQ0FQX01LTk9EIiwiQ0FQX05FVF9CSU5EX1NFUlZJQ0UiLCJDQVBfTkVUX1JBVyIsIkNBUF9TRVRGQ0FQIiwiQ0FQX1NFVEdJRCIsIkNBUF9TRVRQQ0FQIiwiQ0FQX1NFVFVJRCIsIkNBUF9TWVNfQ0hST09UIl0sImVmZmVjdGl2ZSI6WyJDQVBfQVVESVRfV1JJVEUiLCJDQVBfQ0hPV04iLCJDQVBfREFDX09WRVJSSURFIiwiQ0FQX0ZPV05FUiIsIkNBUF9GU0VUSUQiLCJDQVBfS0lMTCIsIkNBUF9NS05PRCIsIkNBUF9ORVRfQklORF9TRVJWSUNFIiwiQ0FQX05FVF9SQVciLCJDQVBfU0VURkNBUCIsIkNBUF9TRVRHSUQiLCJDQVBfU0VUUENBUCIsIkNBUF9TRVRVSUQiLCJDQVBfU1lTX0NIUk9PVCJdLCJpbmhlcml0YWJsZSI6W10sInBlcm1pdHRlZCI6WyJDQVBfQVVESVRfV1JJVEUiLCJDQVBfQ0hPV04iLCJDQVBfREFDX09WRVJSSURFIiwiQ0FQX0ZPV05FUiIsIkNBUF9GU0VUSUQiLCJDQVBfS0lMTCIsIkNBUF9NS05PRCIsIkNBUF9ORVRfQklORF9TRVJWSUNFIiwiQ0FQX05FVF9SQVciLCJDQVBfU0VURkNBUCIsIkNBUF9TRVRHSUQiLCJDQVBfU0VUUENBUCIsIkNBUF9TRVRVSUQiLCJDQVBfU1lTX0NIUk9PVCJdfSwiY29tbWFuZCI6bnVsbCwiZW52X3J1bGVzIjpbeyJwYXR0ZXJuIjoiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iLCJyZXF1aXJlZCI6ZmFsc2UsInN0cmF0ZWd5Ijoic3RyaW5nIn0seyJwYXR0ZXJuIjoiVEVSTT14dGVybSIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJzdHJpbmcifSx7InBhdHRlcm4iOiIoP2kpKEZBQlJJQylfLis9LisiLCJyZXF1aXJlZCI6ZmFsc2UsInN0cmF0ZWd5IjoicmUyIn0seyJwYXR0ZXJuIjoiSE9TVE5BTUU9LisiLCJyZXF1aXJlZCI6ZmFsc2UsInN0cmF0ZWd5IjoicmUyIn0seyJwYXR0ZXJuIjoiVChFKT9NUD0uKyIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJyZTIifSx7InBhdHRlcm4iOiJGYWJyaWNQYWNrYWdlRmlsZU5hbWU9LisiLCJyZXF1aXJlZCI6ZmFsc2UsInN0cmF0ZWd5IjoicmUyIn0seyJwYXR0ZXJuIjoiSG9zdGVkU2VydmljZU5hbWU9LisiLCJyZXF1aXJlZCI6ZmFsc2UsInN0cmF0ZWd5IjoicmUyIn0seyJwYXR0ZXJuIjoiSURFTlRJVFlfQVBJX1ZFUlNJT049LisiLCJyZXF1aXJlZCI6ZmFsc2UsInN0cmF0ZWd5IjoicmUyIn0seyJwYXR0ZXJuIjoiSURFTlRJVFlfSEVBREVSPS4rIiwicmVxdWlyZWQiOmZhbHNlLCJzdHJhdGVneSI6InJlMiJ9LHsicGF0dGVybiI6IklERU5USVRZX1NFUlZFUl9USFVNQlBSSU5UPS4rIiwicmVxdWlyZWQiOmZhbHNlLCJzdHJhdGVneSI6InJlMiJ9LHsicGF0dGVybiI6ImF6dXJlY29udGFpbmVyaW5zdGFuY2VfcmVzdGFydGVkX2J5PS4rIiwicmVxdWlyZWQiOmZhbHNlLCJzdHJhdGVneSI6InJlMiJ9XSwiZXhlY19wcm9jZXNzZXMiOltdLCJpZCI6Im1jci5taWNyb3NvZnQuY29tL2F6dXJlbGludXgvZGlzdHJvbGVzcy9iYXNlQHNoYTI1NjoxZTc3ZDk3ZTFlMzlmMjJlZDljNTJmNDliMzUwOGI0YzEwNDRjZWMyMzc0M2RmOTA5OGFjNDRlMDI1ZjY1NGYyIiwibGF5ZXJzIjpbIjI0M2UxYjNjZTA4MDkzZjJmMGQ5Y2Q2YTllYWZkZTg3MzdmNjRmZWMxMDVlZDU5YzM0NmQzMDlmYmU3NjBiNTgiXSwibW91bnRzIjpbeyJkZXN0aW5hdGlvbiI6Ii9ldGMvcmVzb2x2LmNvbmYiLCJvcHRpb25zIjpbInJiaW5kIiwicnNoYXJlZCIsInJ3Il0sInNvdXJjZSI6InNhbmRib3g6Ly8vdG1wL2F0bGFzL3Jlc29sdmNvbmYvLisiLCJ0eXBlIjoiYmluZCJ9XSwibmFtZSI6ImNvbnRhaW5lcjEiLCJub19uZXdfcHJpdmlsZWdlcyI6ZmFsc2UsInNlY2NvbXBfcHJvZmlsZV9zaGEyNTYiOiIiLCJzaWduYWxzIjpbXSwidXNlciI6eyJncm91cF9pZG5hbWVzIjpbeyJwYXR0ZXJuIjoiIiwic3RyYXRlZ3kiOiJhbnkifV0sInVtYXNrIjoiMDAyMiIsInVzZXJfaWRuYW1lIjp7InBhdHRlcm4iOiIiLCJzdHJhdGVneSI6ImFueSJ9fSwid29ya2luZ19kaXIiOiIvIn0seyJhbGxvd19lbGV2YXRlZCI6ZmFsc2UsImFsbG93X3N0ZGlvX2FjY2VzcyI6dHJ1ZSwiY2FwYWJpbGl0aWVzIjp7ImFtYmllbnQiOltdLCJib3VuZGluZyI6WyJDQVBfQ0hPV04iLCJDQVBfREFDX09WRVJSSURFIiwiQ0FQX0ZTRVRJRCIsIkNBUF9GT1dORVIiLCJDQVBfTUtOT0QiLCJDQVBfTkVUX1JBVyIsIkNBUF9TRVRHSUQiLCJDQVBfU0VUVUlEIiwiQ0FQX1NFVEZDQVAiLCJDQVBfU0VUUENBUCIsIkNBUF9ORVRfQklORF9TRVJWSUNFIiwiQ0FQX1NZU19DSFJPT1QiLCJDQVBfS0lMTCIsIkNBUF9BVURJVF9XUklURSJdLCJlZmZlY3RpdmUiOlsiQ0FQX0NIT1dOIiwiQ0FQX0RBQ19PVkVSUklERSIsIkNBUF9GU0VUSUQiLCJDQVBfRk9XTkVSIiwiQ0FQX01LTk9EIiwiQ0FQX05FVF9SQVciLCJDQVBfU0VUR0lEIiwiQ0FQX1NFVFVJRCIsIkNBUF9TRVRGQ0FQIiwiQ0FQX1NFVFBDQVAiLCJDQVBfTkVUX0JJTkRfU0VSVklDRSIsIkNBUF9TWVNfQ0hST09UIiwiQ0FQX0tJTEwiLCJDQVBfQVVESVRfV1JJVEUiXSwiaW5oZXJpdGFibGUiOltdLCJwZXJtaXR0ZWQiOlsiQ0FQX0NIT1dOIiwiQ0FQX0RBQ19PVkVSUklERSIsIkNBUF9GU0VUSUQiLCJDQVBfRk9XTkVSIiwiQ0FQX01LTk9EIiwiQ0FQX05FVF9SQVciLCJDQVBfU0VUR0lEIiwiQ0FQX1NFVFVJRCIsIkNBUF9TRVRGQ0FQIiwiQ0FQX1NFVFBDQVAiLCJDQVBfTkVUX0JJTkRfU0VSVklDRSIsIkNBUF9TWVNfQ0hST09UIiwiQ0FQX0tJTEwiLCJDQVBfQVVESVRfV1JJVEUiXX0sImNvbW1hbmQiOlsiL3BhdXNlIl0sImVudl9ydWxlcyI6W3sicGF0dGVybiI6IlBBVEg9L3Vzci9sb2NhbC9zYmluOi91c3IvbG9jYWwvYmluOi91c3Ivc2JpbjovdXNyL2Jpbjovc2JpbjovYmluIiwicmVxdWlyZWQiOnRydWUsInN0cmF0ZWd5Ijoic3RyaW5nIn0seyJwYXR0ZXJuIjoiVEVSTT14dGVybSIsInJlcXVpcmVkIjpmYWxzZSwic3RyYXRlZ3kiOiJzdHJpbmcifV0sImV4ZWNfcHJvY2Vzc2VzIjpbXSwibGF5ZXJzIjpbIjE2YjUxNDA1N2EwNmFkNjY1ZjkyYzAyODYzYWNhMDc0ZmQ1OTc2Yzc1NWQyNmJmZjE2MzY1Mjk5MTY5ZTg0MTUiXSwibW91bnRzIjpbXSwibmFtZSI6InBhdXNlLWNvbnRhaW5lciIsIm5vX25ld19wcml2aWxlZ2VzIjpmYWxzZSwic2VjY29tcF9wcm9maWxlX3NoYTI1NiI6IiIsInNpZ25hbHMiOltdLCJ1c2VyIjp7Imdyb3VwX2lkbmFtZXMiOlt7InBhdHRlcm4iOiIiLCJzdHJhdGVneSI6ImFueSJ9XSwidW1hc2siOiIwMDIyIiwidXNlcl9pZG5hbWUiOnsicGF0dGVybiI6IiIsInN0cmF0ZWd5IjoiYW55In19LCJ3b3JraW5nX2RpciI6Ii8ifV0KCmFsbG93X3Byb3BlcnRpZXNfYWNjZXNzIDo9IHRydWUKYWxsb3dfZHVtcF9zdGFja3MgOj0gZmFsc2UKYWxsb3dfcnVudGltZV9sb2dnaW5nIDo9IGZhbHNlCmFsbG93X2Vudmlyb25tZW50X3ZhcmlhYmxlX2Ryb3BwaW5nIDo9IHRydWUKYWxsb3dfdW5lbmNyeXB0ZWRfc2NyYXRjaCA6PSBmYWxzZQphbGxvd19jYXBhYmlsaXR5X2Ryb3BwaW5nIDo9IHRydWUKCm1vdW50X2RldmljZSA6PSBkYXRhLmZyYW1ld29yay5tb3VudF9kZXZpY2UKdW5tb3VudF9kZXZpY2UgOj0gZGF0YS5mcmFtZXdvcmsudW5tb3VudF9kZXZpY2UKbW91bnRfb3ZlcmxheSA6PSBkYXRhLmZyYW1ld29yay5tb3VudF9vdmVybGF5CnVubW91bnRfb3ZlcmxheSA6PSBkYXRhLmZyYW1ld29yay51bm1vdW50X292ZXJsYXkKY3JlYXRlX2NvbnRhaW5lciA6PSBkYXRhLmZyYW1ld29yay5jcmVhdGVfY29udGFpbmVyCmV4ZWNfaW5fY29udGFpbmVyIDo9IGRhdGEuZnJhbWV3b3JrLmV4ZWNfaW5fY29udGFpbmVyCmV4ZWNfZXh0ZXJuYWwgOj0gZGF0YS5mcmFtZXdvcmsuZXhlY19leHRlcm5hbApzaHV0ZG93bl9jb250YWluZXIgOj0gZGF0YS5mcmFtZXdvcmsuc2h1dGRvd25fY29udGFpbmVyCnNpZ25hbF9jb250YWluZXJfcHJvY2VzcyA6PSBkYXRhLmZyYW1ld29yay5zaWduYWxfY29udGFpbmVyX3Byb2Nlc3MKcGxhbjlfbW91bnQgOj0gZGF0YS5mcmFtZXdvcmsucGxhbjlfbW91bnQKcGxhbjlfdW5tb3VudCA6PSBkYXRhLmZyYW1ld29yay5wbGFuOV91bm1vdW50CmdldF9wcm9wZXJ0aWVzIDo9IGRhdGEuZnJhbWV3b3JrLmdldF9wcm9wZXJ0aWVzCmR1bXBfc3RhY2tzIDo9IGRhdGEuZnJhbWV3b3JrLmR1bXBfc3RhY2tzCnJ1bnRpbWVfbG9nZ2luZyA6PSBkYXRhLmZyYW1ld29yay5ydW50aW1lX2xvZ2dpbmcKbG9hZF9mcmFnbWVudCA6PSBkYXRhLmZyYW1ld29yay5sb2FkX2ZyYWdtZW50CnNjcmF0Y2hfbW91bnQgOj0gZGF0YS5mcmFtZXdvcmsuc2NyYXRjaF9tb3VudApzY3JhdGNoX3VubW91bnQgOj0gZGF0YS5mcmFtZXdvcmsuc2NyYXRjaF91bm1vdW50CnJ3X21vdW50X2RldmljZSA6PSBkYXRhLmZyYW1ld29yay5yd19tb3VudF9kZXZpY2UKCnJlYXNvbiA6PSB7ImVycm9ycyI6IGRhdGEuZnJhbWV3b3JrLmVycm9yc30="
1515
},
1616
"containers": [
1717
{

0 commit comments

Comments
 (0)