Skip to content

Commit a2e44fd

Browse files
authored
[confcom] Add containers from_image command (#9505)
* Restore the behaviour of `--upload-fragment` for acifragmentgen (#13) ### Why Addresses - #9222 ### How - [x] Update the code to restore the "attach to first image in input" behaviour - [x] Add two new commands: `fragment push` and `fragment attach` to allow the user to explicitly do one or the other (or both!) - [x] Add new tests which run a local docker registry, and test that the fragments are generated, signed, pushed and attached as expected (as well as the default behaviour) --- This checklist is used to make sure that common guidelines for a pull request are followed. ### Related command <!--- Please provide the related command with az {command} if you can, so that we can quickly route to the related person to review. ---> ### General Guidelines - [x] Have you run `azdev style <YOUR_EXT>` locally? (`pip install azdev` required) - [x] Have you run `python scripts/ci/test_index.py -q` locally? (`pip install wheel==0.30.0` required) - [x] My extension version conforms to the [Extension version schema](https://github.com/Azure/azure-cli/blob/release/doc/extensions/versioning_guidelines.md) * Add containers from_image command * Satisfy azdev style * Add tests * Split command lib versions of command * Rename the containers lib code file * Build images with buildkit for deterministic hashes * Revert "Build images with buildkit for deterministic hashes" This reverts commit e8f7637. * Change the working_dir sample for extra_layers * . * Remove extra layers test * Review tidy ups * Restore the behaviour of `--upload-fragment` for acifragmentgen (#13) Addresses - #9222 - [x] Update the code to restore the "attach to first image in input" behaviour - [x] Add two new commands: `fragment push` and `fragment attach` to allow the user to explicitly do one or the other (or both!) - [x] Add new tests which run a local docker registry, and test that the fragments are generated, signed, pushed and attached as expected (as well as the default behaviour) --- This checklist is used to make sure that common guidelines for a pull request are followed. <!--- Please provide the related command with az {command} if you can, so that we can quickly route to the related person to review. ---> - [x] Have you run `azdev style <YOUR_EXT>` locally? (`pip install azdev` required) - [x] Have you run `python scripts/ci/test_index.py -q` locally? (`pip install wheel==0.30.0` required) - [x] My extension version conforms to the [Extension version schema](https://github.com/Azure/azure-cli/blob/release/doc/extensions/versioning_guidelines.md) * Bump confcom version * Address review comments
1 parent 91bcb12 commit a2e44fd

File tree

18 files changed

+358
-1
lines changed

18 files changed

+358
-1
lines changed

linter_exclusions.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3525,3 +3525,9 @@ confcom fragment attach:
35253525
signed_fragment:
35263526
rule_exclusions:
35273527
- no_positional_parameters
3528+
3529+
confcom containers from_image:
3530+
parameters:
3531+
image:
3532+
rule_exclusions:
3533+
- no_positional_parameters

src/confcom/HISTORY.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
Release History
44
===============
55

6+
1.6.0
7+
++++++
8+
* Added confcom containers from_image command to generate container definitions from an image reference
9+
610
1.5.1
711
++++++
812
* Bumped the Kata genpolicy version to gen4

src/confcom/azext_confcom/_help.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,3 +321,28 @@
321321
- name: Attach the output of acifragmentgen to a registry
322322
text: az confcom acifragmentgen --chain my.cert.pem --key my_key.pem --svn "1" --namespace contoso --feed "test-feed" --input ./fragment_spec.json | az confcom fragment attach --manifest-tag myregistry.azurecr.io/image:latest
323323
"""
324+
325+
helps[
326+
"confcom containers"
327+
] = """
328+
type: group
329+
short-summary: Commands which generate Security Policy Container Definitions.
330+
"""
331+
332+
333+
helps[
334+
"confcom containers from_image"
335+
] = """
336+
type: command
337+
short-summary: Create a Security Policy Container Definition based on an image reference.
338+
339+
parameters:
340+
- name: --platform
341+
type: str
342+
short-summary: 'The name of the platform the container definition will run on'
343+
344+
345+
examples:
346+
- name: Input an image reference and generate container definitions
347+
text: az confcom containers from_image my.azurecr.io/myimage:tag
348+
"""

src/confcom/azext_confcom/_params.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,3 +469,18 @@ def load_arguments(self, _):
469469
help="Path to containerd socket if not using the default",
470470
validator=validate_katapolicygen_input,
471471
)
472+
473+
with self.argument_context("confcom containers from_image") as c:
474+
c.positional(
475+
"image",
476+
type=str,
477+
help="Image to create container definition from",
478+
)
479+
c.argument(
480+
"platform",
481+
options_list=("--platform",),
482+
required=False,
483+
default="aci",
484+
type=str,
485+
help="Platform to create container definition for",
486+
)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
import json
7+
8+
from azext_confcom.lib.containers import from_image as lib_containers_from_image
9+
10+
11+
def containers_from_image(image: str, platform: str) -> None:
12+
print(json.dumps(lib_containers_from_image(image, platform)))

src/confcom/azext_confcom/commands.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@ def load_command_table(self, _):
1717

1818
with self.command_group("confcom"):
1919
pass
20+
21+
with self.command_group("confcom containers") as g:
22+
g.custom_command("from_image", "containers_from_image")

src/confcom/azext_confcom/custom.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
print_existing_policy_from_yaml, print_func, str_to_sha256)
2626
from azext_confcom.command.fragment_attach import fragment_attach as _fragment_attach
2727
from azext_confcom.command.fragment_push import fragment_push as _fragment_push
28+
from azext_confcom.command.containers_from_image import containers_from_image as _containers_from_image
2829
from knack.log import get_logger
2930
from pkg_resources import parse_version
3031

@@ -552,3 +553,13 @@ def fragment_push(
552553
signed_fragment=signed_fragment,
553554
manifest_tag=manifest_tag
554555
)
556+
557+
558+
def containers_from_image(
559+
image: str,
560+
platform: str,
561+
) -> None:
562+
_containers_from_image(
563+
image=image,
564+
platform=platform,
565+
)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
from dataclasses import asdict
7+
from azext_confcom.lib.images import get_image_layers, get_image_config
8+
from azext_confcom.lib.platform import ACI_MOUNTS
9+
10+
11+
def from_image(image: str, platform: str) -> dict:
12+
13+
mounts = {
14+
"aci": [asdict(mount) for mount in ACI_MOUNTS],
15+
}.get(platform, None)
16+
17+
return {
18+
"id": image,
19+
"name": image,
20+
"layers": get_image_layers(image),
21+
**({"mounts": mounts} if mounts else {}),
22+
**get_image_config(image),
23+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
import functools
7+
import subprocess
8+
import docker
9+
10+
from pathlib import Path
11+
12+
13+
@functools.lru_cache()
14+
def get_image(image_ref: str) -> docker.models.images.Image:
15+
16+
client = docker.from_env()
17+
18+
try:
19+
image = client.images.get(image_ref)
20+
except docker.errors.ImageNotFound:
21+
client.images.pull(image_ref)
22+
23+
image = client.images.get(image_ref)
24+
return image
25+
26+
27+
def get_image_layers(image: str) -> list[str]:
28+
29+
binary_path = Path(__file__).parent.parent / "bin" / "dmverity-vhd"
30+
31+
get_image(image)
32+
result = subprocess.run(
33+
[binary_path.as_posix(), "-d", "roothash", "-i", image],
34+
stdout=subprocess.PIPE,
35+
stderr=subprocess.DEVNULL,
36+
check=True,
37+
text=True,
38+
)
39+
40+
return [line.split("hash: ")[-1] for line in result.stdout.splitlines()]
41+
42+
43+
def get_image_config(image: str) -> dict:
44+
45+
image_config = get_image(image).attrs.get("Config")
46+
47+
config = {}
48+
49+
if image_config.get("Cmd") or image_config.get("Entrypoint"):
50+
config["command"] = (
51+
image_config.get("Entrypoint") or [] +
52+
image_config.get("Cmd") or []
53+
)
54+
55+
if image_config.get("Env"):
56+
config["env_rules"] = [{
57+
"pattern": p,
58+
"strategy": "string",
59+
"required": False,
60+
} for p in image_config.get("Env")]
61+
62+
if image_config.get("WorkingDir"):
63+
config["working_dir"] = image_config.get("WorkingDir")
64+
65+
return config
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
from azext_confcom.lib.policy import ContainerMount
7+
8+
ACI_MOUNTS = [
9+
ContainerMount(
10+
destination="/etc/resolv.conf",
11+
options=[
12+
"rbind",
13+
"rshared",
14+
"rw"
15+
],
16+
source="sandbox:///tmp/atlas/resolvconf/.+",
17+
type="bind"
18+
)
19+
]

0 commit comments

Comments
 (0)