Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/confcom/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
Release History
===============

1.2.7
++++++
* bugfix making it so that oras discover function doesn't error when no fragments are found in the remote repository

1.2.6
++++++
* bugfix making it so the fields in the --input format are case-insensitive

1.2.5
++++++
* consolidating functions for --input policygen
Expand Down
2 changes: 1 addition & 1 deletion src/confcom/azext_confcom/data/internal_config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "1.2.5",
"version": "1.2.6",
Copy link

Copilot AI Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The internal_config.json version (1.2.6) does not match setup.py (1.2.7); update this to reflect the same version.

Suggested change
"version": "1.2.6",
"version": "1.2.7",

Copilot uses AI. Check for mistakes.
"hcsshim_config": {
"maxVersion": "1.0.0",
"minVersion": "0.0.1"
Expand Down
5 changes: 4 additions & 1 deletion src/confcom/azext_confcom/oras_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ def discover(
f"Error pulling the policy fragment from {image}.\n\n"
+ "Please log into the registry and try again.\n\n"
)
eprint(f"Error retrieving fragments from remote repo: {item.stderr.decode('utf-8')}", exit_code=item.returncode)
elif "not found" in item.stderr.decode("utf-8"):
Copy link

Copilot AI Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Matching the 'not found' substring in stderr may catch unrelated errors; consider using a more precise check or inspecting the exit code for a 404.

Suggested change
elif "not found" in item.stderr.decode("utf-8"):
elif item.returncode == 404:

Copilot uses AI. Check for mistakes.
logger.warning("No policy fragments found for image %s", image)
else:
eprint(f"Error retrieving fragments from remote repo: {item.stderr.decode('utf-8')}", exit_code=item.returncode)
return hashes


Expand Down
84 changes: 53 additions & 31 deletions src/confcom/azext_confcom/template_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def __exit__(self, exc_type, exc_value, traceback) -> None:
self._client.close()


def case_insensitive_dict_get(dictionary, search_key) -> Any:
def case_insensitive_dict_get(dictionary, search_key, default_value=None) -> Any:
if not isinstance(dictionary, dict):
return None
# if the cases happen to match, immediately return .get() result
Expand All @@ -55,7 +55,7 @@ def case_insensitive_dict_get(dictionary, search_key) -> Any:
for key in dictionary.keys():
if key.lower() == search_key.lower():
return dictionary[key]
return None
return default_value


def deep_dict_update(source: dict, destination: dict):
Expand Down Expand Up @@ -1572,99 +1572,119 @@ def convert_config_v0_to_v1(old_data):

# Prepare the structure of the new JSON
new_data = {
config.ACI_FIELD_VERSION: old_data.get(config.ACI_FIELD_VERSION, "1.0"), # default if missing
config.ACI_FIELD_CONTAINERS_REGO_FRAGMENTS: [], # empty by default in your example
config.ACI_FIELD_VERSION: case_insensitive_dict_get(
old_data, config.ACI_FIELD_VERSION, "1.0"
), # default if missing
config.ACI_FIELD_CONTAINERS_REGO_FRAGMENTS: [],
config.ACI_FIELD_CONTAINERS: []
}

old_containers = old_data.get(config.ACI_FIELD_CONTAINERS, [])
old_containers = case_insensitive_dict_get(old_data, config.ACI_FIELD_CONTAINERS, [])

for old_container in old_containers:
# Build the 'environmentVariables' section in the new format
new_envs = []
for env_var in old_container.get(config.ACI_FIELD_CONTAINERS_ENVS) or []:
for env_var in case_insensitive_dict_get(old_container, config.ACI_FIELD_CONTAINERS_ENVS) or []:
# Decide if we need 'regex' or not, based on 'strategy' or your custom logic
# Here we'll assume "strategy"=="re2" means 'regex' = True
# If strategy is missing or 'string', omit 'regex' or set it to False
env_entry = {
config.ACI_FIELD_CONTAINERS_ENVS_NAME: env_var.get(config.ACI_FIELD_CONTAINERS_ENVS_NAME),
config.ACI_FIELD_CONTAINERS_ENVS_VALUE: env_var.get(config.ACI_FIELD_CONTAINERS_ENVS_VALUE, "")
config.ACI_FIELD_CONTAINERS_ENVS_NAME: case_insensitive_dict_get(
env_var, config.ACI_FIELD_CONTAINERS_ENVS_NAME
),
config.ACI_FIELD_CONTAINERS_ENVS_VALUE: case_insensitive_dict_get(
env_var, config.ACI_FIELD_CONTAINERS_ENVS_VALUE, ""
)
}
strategy = env_var.get(config.ACI_FIELD_CONTAINERS_ENVS_STRATEGY)
strategy = case_insensitive_dict_get(env_var, config.ACI_FIELD_CONTAINERS_ENVS_STRATEGY)
if strategy == "re2":
env_entry["regex"] = True

new_envs.append(env_entry)

# Build the 'execProcesses' from the old 'command'
exec_processes = []
old_command_list = old_container.get(config.ACI_FIELD_CONTAINERS_EXEC_PROCESSES, [])
old_command_list = case_insensitive_dict_get(old_container, config.ACI_FIELD_CONTAINERS_EXEC_PROCESSES, [])
if old_command_list:
exec_processes.append({config.ACI_FIELD_CONTAINERS_COMMAND: old_command_list})

command = old_container.get(config.ACI_FIELD_CONTAINERS_COMMAND)
command = case_insensitive_dict_get(old_container, config.ACI_FIELD_CONTAINERS_COMMAND)

# Liveness probe => exec process
liveness_probe = old_container.get(config.ACI_FIELD_CONTAINERS_LIVENESS_PROBE, {})
liveness_exec = liveness_probe.get(config.ACI_FIELD_CONTAINERS_PROBE_ACTION, {})
liveness_command = liveness_exec.get(config.ACI_FIELD_CONTAINERS_COMMAND, [])
liveness_probe = case_insensitive_dict_get(old_container, config.ACI_FIELD_CONTAINERS_LIVENESS_PROBE, {})
liveness_exec = case_insensitive_dict_get(liveness_probe, config.ACI_FIELD_CONTAINERS_PROBE_ACTION, {})
liveness_command = case_insensitive_dict_get(liveness_exec, config.ACI_FIELD_CONTAINERS_COMMAND, [])
if liveness_command:
exec_processes.append({
config.ACI_FIELD_CONTAINERS_COMMAND: liveness_command
})

# Readiness probe => exec process
readiness_probe = old_container.get(config.ACI_FIELD_CONTAINERS_READINESS_PROBE, {})
readiness_exec = readiness_probe.get(config.ACI_FIELD_CONTAINERS_PROBE_ACTION, {})
readiness_command = readiness_exec.get(config.ACI_FIELD_CONTAINERS_COMMAND, [])
readiness_probe = case_insensitive_dict_get(old_container, config.ACI_FIELD_CONTAINERS_READINESS_PROBE, {})
readiness_exec = case_insensitive_dict_get(readiness_probe, config.ACI_FIELD_CONTAINERS_PROBE_ACTION, {})
readiness_command = case_insensitive_dict_get(readiness_exec, config.ACI_FIELD_CONTAINERS_COMMAND, [])
if readiness_command:
exec_processes.append({
config.ACI_FIELD_CONTAINERS_COMMAND: readiness_command
})

# Build the 'volumeMounts' section
volume_mounts = []
for mount in old_container.get(config.ACI_FIELD_CONTAINERS_MOUNTS) or []:
for mount in case_insensitive_dict_get(old_container, config.ACI_FIELD_CONTAINERS_MOUNTS) or []:
# For 'name', we can take the mountType or generate something else:
# e.g. if mountType is "azureFile", name "azurefile"
mount_name = mount.get(config.ACI_FIELD_CONTAINERS_MOUNTS_TYPE, "defaultName").lower()
mount_name = case_insensitive_dict_get(
mount, config.ACI_FIELD_CONTAINERS_MOUNTS_TYPE, "defaultName"
).lower()
volume_mount = {
config.ACI_FIELD_CONTAINERS_ENVS_NAME: mount_name,
config.ACI_FIELD_TEMPLATE_MOUNTS_PATH: mount.get(config.ACI_FIELD_CONTAINERS_MOUNTS_PATH),
config.ACI_FIELD_TEMPLATE_MOUNTS_TYPE: mount.get(config.ACI_FIELD_CONTAINERS_MOUNTS_TYPE),
config.ACI_FIELD_TEMPLATE_MOUNTS_READONLY: mount.get(config.ACI_FIELD_CONTAINERS_MOUNTS_READONLY, True),
config.ACI_FIELD_TEMPLATE_MOUNTS_PATH: case_insensitive_dict_get(
mount, config.ACI_FIELD_CONTAINERS_MOUNTS_PATH
),
config.ACI_FIELD_TEMPLATE_MOUNTS_TYPE: case_insensitive_dict_get(
mount, config.ACI_FIELD_CONTAINERS_MOUNTS_TYPE
),
config.ACI_FIELD_TEMPLATE_MOUNTS_READONLY: case_insensitive_dict_get(
mount, config.ACI_FIELD_CONTAINERS_MOUNTS_READONLY, True
),
}
volume_mounts.append(volume_mount)

# Create the container's "properties" object
container_properties = {
config.ACI_FIELD_TEMPLATE_IMAGE: old_container.get(config.ACI_FIELD_CONTAINERS_CONTAINERIMAGE),
config.ACI_FIELD_TEMPLATE_IMAGE: case_insensitive_dict_get(
old_container, config.ACI_FIELD_CONTAINERS_CONTAINERIMAGE
),
config.ACI_FIELD_CONTAINERS_EXEC_PROCESSES: exec_processes,
config.ACI_FIELD_TEMPLATE_VOLUME_MOUNTS: volume_mounts,
config.ACI_FIELD_CONTAINERS_ENVS: new_envs,
config.ACI_FIELD_CONTAINERS_COMMAND: command,
}

if old_container.get(config.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT) is not None:
if case_insensitive_dict_get(old_container, config.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT) is not None:
container_properties[
config.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT
] = old_container[config.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT]

if old_container.get(config.ACI_FIELD_CONTAINERS_ALLOW_ELEVATED) is not None:
if case_insensitive_dict_get(old_container, config.ACI_FIELD_CONTAINERS_ALLOW_ELEVATED) is not None:
if config.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT not in container_properties:
container_properties[config.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT] = {}
container_properties[
config.ACI_FIELD_TEMPLATE_SECURITY_CONTEXT
][config.ACI_FIELD_CONTAINERS_PRIVILEGED] = old_container.get(config.ACI_FIELD_CONTAINERS_ALLOW_ELEVATED)
][config.ACI_FIELD_CONTAINERS_PRIVILEGED] = case_insensitive_dict_get(
old_container, config.ACI_FIELD_CONTAINERS_ALLOW_ELEVATED
)

if old_container.get(config.ACI_FIELD_CONTAINERS_WORKINGDIR) is not None:
if case_insensitive_dict_get(old_container, config.ACI_FIELD_CONTAINERS_WORKINGDIR) is not None:
container_properties[
config.ACI_FIELD_CONTAINERS_WORKINGDIR
] = old_container.get(config.ACI_FIELD_CONTAINERS_WORKINGDIR)
] = case_insensitive_dict_get(old_container, config.ACI_FIELD_CONTAINERS_WORKINGDIR)

# Finally, assemble the new container dict
new_container = {
config.ACI_FIELD_CONTAINERS_NAME: old_container.get(config.ACI_FIELD_CONTAINERS_NAME),
config.ACI_FIELD_CONTAINERS_NAME: case_insensitive_dict_get(
old_container, config.ACI_FIELD_CONTAINERS_NAME
),
config.ACI_FIELD_TEMPLATE_PROPERTIES: container_properties
}

Expand All @@ -1677,8 +1697,10 @@ def detect_old_format(old_data):
# we want to encourage customers to transition to the new format. The best way to check for the old format is
# to see if the json is flattened. This is an appropriate check since the image name is required
# and they are located in different places in the two formats
old_containers = old_data.get(config.ACI_FIELD_CONTAINERS, [])
if len(old_containers) > 0 and old_containers[0].get(config.ACI_FIELD_CONTAINERS_CONTAINERIMAGE) is not None:
old_containers = case_insensitive_dict_get(old_data, config.ACI_FIELD_CONTAINERS, [])
if len(old_containers) > 0 and case_insensitive_dict_get(
old_containers[0], config.ACI_FIELD_CONTAINERS_CONTAINERIMAGE
) is not None:
logger.warning(
"%s %s %s",
"(Deprecation Warning) The input format used is deprecated.",
Expand Down
1 change: 1 addition & 0 deletions src/confcom/azext_confcom/tests/latest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ test_fragment_user_container_customized_mounts | mcr.microsoft.com/azurelinux/di
test_fragment_user_container_mount_injected_dns | mcr.microsoft.com/azurelinux/distroless/base:3.0 | See if the resolvconf mount works properly
test_fragment_omit_id | mcr.microsoft.com/aci/msi-atlas-adapter:master_20201203.1 | Check that the id field is omitted from the policy
test_fragment_injected_sidecar_container_msi | mcr.microsoft.com/aci/msi-atlas-adapter:master_20201203.1 | Make sure User mounts and env vars aren't added to sidecar containers, using JSON output format
test_tar_file_fragment | mcr.microsoft.com/aks/e2e/library-busybox:master.220314.1-linux-amd64 | Make sure fragment generation doesn't fail for image tarball
test_debug_processes | mcr.microsoft.com/azurelinux/distroless/base:3.0 | Enable exec_processes via debug_mode
test_fragment_sidecar | mcr.microsoft.com/aci/msi-atlas-adapter:master_20201210.1 | See if sidecar fragments can be created by a given policy.json
test_fragment_sidecar_stdio_access_default | mcr.microsoft.com/aci/msi-atlas-adapter:master_20201210.1 | Check that sidecar containers have std I/O access by default
Expand Down
Loading
Loading