diff --git a/src/acrcssc/HISTORY.rst b/src/acrcssc/HISTORY.rst new file mode 100644 index 00000000000..8c34bccfff8 --- /dev/null +++ b/src/acrcssc/HISTORY.rst @@ -0,0 +1,8 @@ +.. :changelog: + +Release History +=============== + +0.1.0 +++++++ +* Initial release. \ No newline at end of file diff --git a/src/acrcssc/README.rst b/src/acrcssc/README.rst new file mode 100644 index 00000000000..7b0519e6e17 --- /dev/null +++ b/src/acrcssc/README.rst @@ -0,0 +1,5 @@ +Microsoft Azure CLI 'acrcssc' Extension +========================================== + +This package is for the 'acrcssc' extension. +i.e. 'az acrcssc' \ No newline at end of file diff --git a/src/acrcssc/azext_acrcssc/__init__.py b/src/acrcssc/azext_acrcssc/__init__.py new file mode 100644 index 00000000000..86d733516bd --- /dev/null +++ b/src/acrcssc/azext_acrcssc/__init__.py @@ -0,0 +1,32 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azure.cli.core import AzCommandsLoader + +from azext_acrcssc._help import helps # pylint: disable=unused-import + + +class AcrcsscCommandsLoader(AzCommandsLoader): + + def __init__(self, cli_ctx=None): + from azure.cli.core.commands import CliCommandType + from azext_acrcssc._client_factory import cf_acr + acrcssc_custom = CliCommandType( + operations_tmpl='azext_acrcssc.custom#{}', + client_factory=cf_acr) + super(AcrcsscCommandsLoader, self).__init__(cli_ctx=cli_ctx, + custom_command_type=acrcssc_custom) + + def load_command_table(self, args): + from azext_acrcssc.commands import load_command_table + load_command_table(self, args) + return self.command_table + + def load_arguments(self, command): + from azext_acrcssc._params import load_arguments + load_arguments(self, command) + + +COMMAND_LOADER_CLS = AcrcsscCommandsLoader diff --git a/src/acrcssc/azext_acrcssc/_client_factory.py b/src/acrcssc/azext_acrcssc/_client_factory.py new file mode 100644 index 00000000000..508e4f1a006 --- /dev/null +++ b/src/acrcssc/azext_acrcssc/_client_factory.py @@ -0,0 +1,61 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from azure.cli.core.commands.client_factory import get_mgmt_service_client +from azure.cli.core.profiles import ResourceType +from azure.mgmt.containerregistry import ContainerRegistryManagementClient +from .helper._constants import ( + ACR_API_VERSION_2023_01_01_PREVIEW, + ACR_API_VERSION_2019_06_01_PREVIEW +) + +from azure.mgmt.authorization import AuthorizationManagementClient + + +def cf_acr(cli_ctx, *_) -> ContainerRegistryManagementClient: + return get_mgmt_service_client(cli_ctx, + ResourceType.MGMT_CONTAINERREGISTRY, + api_version=ACR_API_VERSION_2023_01_01_PREVIEW) + + +def cf_acr_registries(cli_ctx, *_) -> ContainerRegistryManagementClient: + return get_mgmt_service_client(cli_ctx, + ResourceType.MGMT_CONTAINERREGISTRY, + api_version=ACR_API_VERSION_2023_01_01_PREVIEW).registries + + +def cf_acr_tasks(cli_ctx, *_): + return get_mgmt_service_client(cli_ctx, + ResourceType.MGMT_CONTAINERREGISTRY, + api_version=ACR_API_VERSION_2019_06_01_PREVIEW).tasks + + +def cf_acr_registries_tasks(cli_ctx, *_): + return get_mgmt_service_client(cli_ctx, + ResourceType.MGMT_CONTAINERREGISTRY, + api_version=ACR_API_VERSION_2019_06_01_PREVIEW).registries + + +def cf_acr_taskruns(cli_ctx, *_): + return get_mgmt_service_client(cli_ctx, + ResourceType.MGMT_CONTAINERREGISTRY, + api_version=ACR_API_VERSION_2019_06_01_PREVIEW).task_runs + + +def cf_acr_runs(cli_ctx, *_): + return get_mgmt_service_client(cli_ctx, + ResourceType.MGMT_CONTAINERREGISTRY, + api_version=ACR_API_VERSION_2019_06_01_PREVIEW).runs + + +def cf_resources(cli_ctx, subscription_id=None): + return get_mgmt_service_client(cli_ctx, + ResourceType.MGMT_RESOURCE_RESOURCES, + subscription_id=subscription_id) + + +def cf_authorization(cli_ctx, subscription_id=None) -> AuthorizationManagementClient: + return get_mgmt_service_client(cli_ctx, + ResourceType.MGMT_AUTHORIZATION, + subscription_id=subscription_id, api_version="2022-04-01") diff --git a/src/acrcssc/azext_acrcssc/_help.py b/src/acrcssc/azext_acrcssc/_help.py new file mode 100644 index 00000000000..051fb1d90ac --- /dev/null +++ b/src/acrcssc/azext_acrcssc/_help.py @@ -0,0 +1,66 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from knack.help_files import helps # pylint: disable=unused-import + +helps['acr supply-chain'] = """ + type: group + short-summary: Commands to manage acr supply chain resources. +""" + +helps['acr supply-chain workflow'] = """ + type: group + short-summary: Commands to manage acr supply chain workflows. +""" + +helps['acr supply-chain workflow create'] = """ + type: command + short-summary: Create acr supply chain workflow. + examples: + - name: Create acr supply chain workflow + text: az acr supply-chain workflow create -r $MyRegistry -g $MyResourceGroup \ + --type continuouspatchv1 --schedule 1d --config path-to-config-file +""" +helps['acr supply-chain workflow update'] = """ + type: command + short-summary: Update acr supply chain workflow. + examples: + - name: Updates acr supply chain workflow + text: az acr supply-chain workflow update -r $MyRegistry -g $MyResourceGroup --type \ + continuouspatchv1 --schedule 1d --config path-to-config-file +""" + +helps['acr supply-chain workflow show'] = """ + type: command + short-summary: Show acr supply chain workflow tasks. + examples: + - name: Show all acr supply chain workflow + text: az acr supply-chain workflow show -r $MyRegistry -g $MyResourceGroup --type continuouspatchv1 +""" + +helps['acr supply-chain workflow delete'] = """ + type: command + short-summary: Delete acr supply chain workflow. + examples: + - name: Delete acr supply chain workflow and associated configuration files + text: az acr supply-chain workflow delete -r $MyRegistry -g $MyResourceGroup --type continuouspatchv1 +""" + +helps['acr supply-chain workflow cancel-run'] = """ + type: command + short-summary: Cancel currently running supply chain workflow. + examples: + - name: Cancel currently running acr supply chain workflow scans/patch + text: az acr supply-chain workflow cancel-run -r $MyRegistry -g $MyResourceGroup --type continuouspatchv1 +""" + +helps['acr supply-chain workflow list'] = """ + type: command + short-summary: List status of acr supply chain workflow images. + examples: + - name: List all acr supply chain workflow images based on the status provided + text: az acr supply-chain workflow list -r $MyRegistry -g $MyResourceGroup --type continuouspatchv1 --run-status Failed +""" diff --git a/src/acrcssc/azext_acrcssc/_params.py b/src/acrcssc/azext_acrcssc/_params.py new file mode 100644 index 00000000000..5305a15f1d6 --- /dev/null +++ b/src/acrcssc/azext_acrcssc/_params.py @@ -0,0 +1,34 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# pylint: disable=line-too-long +from azure.cli.command_modules.acr._constants import REGISTRY_RESOURCE_TYPE +from azure.cli.command_modules.acr._validators import validate_registry_name +from azure.cli.core import AzCommandsLoader +from azure.cli.core.commands.parameters import (get_resource_name_completion_list, get_three_state_flag, get_enum_type) + + +def load_arguments(self: AzCommandsLoader, _): + from .helper._constants import CSSCTaskTypes + from .helper._workflow_status import WorkflowTaskState + + with self.argument_context("acr supply-chain workflow") as c: + c.argument('resource_group', options_list=['--resource-group', '-g'], help='Name of resource group.You can configure the default group using `az configure --defaults group=`', completer=get_resource_name_completion_list(REGISTRY_RESOURCE_TYPE), configured_default='acr', validator=validate_registry_name) + c.argument('registry_name', options_list=['--registry', '-r'], help='The name of the container registry. It should be specified in lower case. You can configure the default registry name using `az configure --defaults acr=`', completer=get_resource_name_completion_list(REGISTRY_RESOURCE_TYPE), configured_default='acr', validator=validate_registry_name) + c.argument("workflow_type", arg_type=get_enum_type(CSSCTaskTypes), options_list=['--type', '-t'], help="Type of workflow task.", required=True) + + with self.argument_context("acr supply-chain workflow create") as c: + c.argument("config", options_list=["--config"], help="Configuration file path containing the json schema for the list of repositories and tags to filter within the registry. Schema example:{\"repositories\":[{\"repository\":\"alpine\",\"tags\":[\"tag1\",\"tag2\"],\"enabled\":true},{\"repository\":\"python\",\"tags\":[\"*\"],\"enabled\":false}], \"version\": \"v1\", \"tag-convention\": \"floating\"}. \"tag-convention\" is an optional property, values can be \"incremental\" (the default behavior, will increase the patch version of the tag, for example \"{repository}:{original-tag}-1\", \"{repository}:{original-tag}-2\", etc), or \"floating\" (will reuse the tag \"{repository}:{original-tag}-patched\" for patching)", required=True) + c.argument("schedule", options_list=["--schedule"], help="schedule to run the scan and patching task. E.g. `d` where is the number of days between each run. Max value is 30d.", required=True) + c.argument("run_immediately", options_list=["--run-immediately"], help="Set this flag to trigger the immediate run of the selected workflow task. Default value: false.", arg_type=get_three_state_flag(), required=False) + c.argument("dryrun", options_list=["--dry-run"], help="Use this flag to see the qualifying repositories and tags that would be affected by the workflow. Default value: false. 'config' parameter is mandatory to provide with dry-run", arg_type=get_three_state_flag(), required=False) + + with self.argument_context("acr supply-chain workflow update") as c: + c.argument("config", options_list=["--config"], help="Configuration file path containing the json schema for the list of repositories and tags to filter within the registry. Schema example:{\"repositories\":[{\"repository\":\"alpine\",\"tags\":[\"tag1\",\"tag2\"],\"enabled\":true},{\"repository\":\"python\",\"tags\":[\"*\"],\"enabled\":false}], \"version\": \"v1\", \"tag-convention\": \"floating\"}}. \"tag-convention\" is an optional property, values can be \"incremental\" (the default behavior, will increase the patch version of the tag, for example \"{repository}:{original-tag}-1\", \"{repository}:{original-tag}-2\", etc), or \"floating\" (will reuse the tag \"{repository}:{original-tag}-patched\" for patching)", required=False) + c.argument("schedule", options_list=["--schedule"], help="schedule to run the scan and patching task. E.g. `d` where n is the number of days between each run. Max value is 30d.", required=False) + c.argument("run_immediately", options_list=["--run-immediately"], help="Set this flag to trigger the immediate run of the selected workflow task. Default value: false.", arg_type=get_three_state_flag(), required=False) + c.argument("dryrun", options_list=["--dry-run"], help="Use this flag to see the qualifying repositories and tags that would be affected by the workflow. Default value: false. 'config' parameter is mandatory to provide with dry-run", arg_type=get_three_state_flag(), required=False) + + with self.argument_context("acr supply-chain workflow list") as c: + c.argument("status", arg_type=get_enum_type(WorkflowTaskState), options_list=["--run-status"], help="Status to filter the supply-chain workflow image status.", required=False) diff --git a/src/acrcssc/azext_acrcssc/_validators.py b/src/acrcssc/azext_acrcssc/_validators.py new file mode 100644 index 00000000000..244d23cd49d --- /dev/null +++ b/src/acrcssc/azext_acrcssc/_validators.py @@ -0,0 +1,170 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# pylint: disable=line-too-long +# pylint: disable=broad-exception-caught +# pylint: disable=logging-fstring-interpolation +import json +import os +import re +from knack.log import get_logger +from azure.cli.command_modules.acr.repository import acr_repository_show +from .helper._constants import ( + BEARER_TOKEN_USERNAME, + CSSC_WORKFLOW_POLICY_REPOSITORY, + CONTINUOUSPATCH_IMAGE_LIMIT, + CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG, + CONTINUOUSPATCH_CONFIG_SCHEMA_V1, + CONTINUOUSPATCH_CONFIG_SCHEMA_SIZE_LIMIT, + CONTINUOUSPATCH_ALL_TASK_NAMES, + ERROR_MESSAGE_INVALID_TIMESPAN_FORMAT, + ERROR_MESSAGE_INVALID_TIMESPAN_VALUE, + SUBSCRIPTION) +from .helper._constants import CSSCTaskTypes, ERROR_MESSAGE_INVALID_TASK, RECOMMENDATION_SCHEDULE +from .helper._ociartifactoperations import _get_acr_token +from azure.mgmt.core.tools import (parse_resource_id) +from azure.cli.core.azclierror import InvalidArgumentValueError +from ._client_factory import cf_acr_tasks +from .helper._utility import get_task + +logger = get_logger(__name__) + + +def validate_continuouspatch_config_v1(config_path): + _validate_continuouspatch_file(config_path) + config = _validate_continuouspatch_json(config_path) + _validate_continuouspatch_config(config) + + +def _validate_continuouspatch_file(config_path): + if not os.path.exists(config_path): + raise InvalidArgumentValueError(f"Config path file: {config_path} does not exist in the path specified") + if not os.path.isfile(config_path): + raise InvalidArgumentValueError(f"Config path file: {config_path} is not a valid file") + if os.path.getsize(config_path) > CONTINUOUSPATCH_CONFIG_SCHEMA_SIZE_LIMIT: + raise InvalidArgumentValueError(f"Config path file: {config_path} is too large. Max size limit is 10 MB") + if os.path.getsize(config_path) == 0: + raise InvalidArgumentValueError(f"Config path file: {config_path} is empty") + if not os.access(config_path, os.R_OK): + raise InvalidArgumentValueError(f"Config path file: '{config_path}' is not readable") + + +def _validate_continuouspatch_json(config_path): + from jsonschema import validate + try: + with open(config_path, 'r') as f: + config = json.load(f) + validate(config, CONTINUOUSPATCH_CONFIG_SCHEMA_V1) + return config + except Exception as e: + logger.error(f"Error validating the continuous patch config file: {e}") + raise InvalidArgumentValueError("File used for --config is not a valid config JSON file. Use --help to see the schema of the config file.") + finally: + f.close() + + +def _validate_continuouspatch_config(config): + if not isinstance(config, dict): + raise InvalidArgumentValueError("Config file is not a valid JSON file. Use --help to see the schema of the config file.") + for repository in config.get("repositories", []): + for tag in repository.get("tags", []): + if re.match(r'.*-patched$', tag) or re.match(r'.*-[0-9]{1,3}$', tag): + raise InvalidArgumentValueError(f"Configuration error: Repository '{repository['repository']}' with tag '{tag}' is not allowed. Tags ending with '*-patched' (floating tag) or '*-0' to '*-999' (incremental tag) are reserved for internal use.") + if tag == "*" and len(repository.get("tags", [])) > 1: + raise InvalidArgumentValueError("Configuration error: Tag '*' is not allowed with other tags in the same repository. Use '*' as the only tag in the repository to avoid overlaps.") + + +# to save on API calls, we the list of tasks found in the registry +def check_continuous_task_exists(cmd, registry): + task_list = [] + missing_tasks = [] + try: + acrtask_client = cf_acr_tasks(cmd.cli_ctx) + for task_name in CONTINUOUSPATCH_ALL_TASK_NAMES: + task = get_task(cmd, registry, task_name, acrtask_client) + if task is None: + missing_tasks.append(task_name) + else: + task_list.append(task) + + if len(missing_tasks) > 0: + logger.debug(f"Failed to find tasks {', '.join(missing_tasks)} from registry {registry.name}") + return False, task_list + + return True, task_list + except Exception as exception: + logger.debug(f"Failed to find tasks from registry {registry.name} : {exception}") + return False, task_list + + +def check_continuous_task_config_exists(cmd, registry): + # A client cannot be used in this situation because the 'show registry/image' + # is a data plane operation and the az cli does not include the data plane API. + subscription = parse_resource_id(registry.id)[SUBSCRIPTION] + try: + token = _get_acr_token(registry.name, subscription) + acr_repository_show( + cmd=cmd, + registry_name=registry.name, + repository=f"{CSSC_WORKFLOW_POLICY_REPOSITORY}/{CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG}", + username=BEARER_TOKEN_USERNAME, + password=token) + except Exception as exception: + if hasattr(exception, 'status_code') and exception.status_code == 404: + return False + # report on the error only if we get something other than 404 + logger.debug(f"Failed to find config {CSSC_WORKFLOW_POLICY_REPOSITORY}/{CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG} from registry {registry.name} : {exception}") + raise + return True + + +def _validate_schedule(schedule): + # during update, schedule can be null if we are only updating the config + if schedule is None: + return + # Extract the numeric value and unit from the timespan expression + match = re.match(r'(\d+)(d)$', schedule) + if not match: + raise InvalidArgumentValueError(error_msg=ERROR_MESSAGE_INVALID_TIMESPAN_FORMAT, recommendation=RECOMMENDATION_SCHEDULE) + + value = int(match.group(1)) + unit = match.group(2) + if unit == 'd' and (value < 1 or value > 30): # day of the month + raise InvalidArgumentValueError(error_msg=ERROR_MESSAGE_INVALID_TIMESPAN_VALUE, recommendation=RECOMMENDATION_SCHEDULE) + + +def validate_inputs(schedule, config_file_path=None): + _validate_schedule(schedule) + if config_file_path is not None: + validate_continuouspatch_config_v1(config_file_path) + + +def validate_task_type(task_type): + if (task_type not in [item.value for item in CSSCTaskTypes]): + raise InvalidArgumentValueError(error_msg=ERROR_MESSAGE_INVALID_TASK) + + +def validate_cssc_optional_inputs(cssc_config_path, schedule): + if cssc_config_path is None and schedule is None: + raise InvalidArgumentValueError(error_msg="Provide at least one parameter to update: --schedule or --config") + + +def validate_continuous_patch_v1_image_limit(dryrun_log): + match = re.search(r"Matches found: (\d+)", dryrun_log) + if match is None: + # the quick task did not return the expected output, we cannot validate the image limit but cannot block the operation + logger.error("Error parsing for image limit.") + return + + image_limit = int(match.group(1)) + + if image_limit > CONTINUOUSPATCH_IMAGE_LIMIT: + # these expressions remove all the Task related output from the log, and only leaves the listing of repositories and tags + pattern_prefix = "Listing repositories and tags matching the filter" + result = re.sub(r'^(.*\n)*?' + re.escape(pattern_prefix), pattern_prefix, dryrun_log, flags=re.MULTILINE) + + pattern_postfix = "Adjust the JSON filter to limit the number of images." + result = re.sub(r'(?s)' + re.escape(pattern_postfix) + r'.*', pattern_postfix, result) + + raise InvalidArgumentValueError(error_msg=result) diff --git a/src/acrcssc/azext_acrcssc/azext_metadata.json b/src/acrcssc/azext_acrcssc/azext_metadata.json new file mode 100644 index 00000000000..30fdaf614ee --- /dev/null +++ b/src/acrcssc/azext_acrcssc/azext_metadata.json @@ -0,0 +1,4 @@ +{ + "azext.isPreview": true, + "azext.minCliCoreVersion": "2.15.0" +} \ No newline at end of file diff --git a/src/acrcssc/azext_acrcssc/commands.py b/src/acrcssc/azext_acrcssc/commands.py new file mode 100644 index 00000000000..74311bac307 --- /dev/null +++ b/src/acrcssc/azext_acrcssc/commands.py @@ -0,0 +1,20 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +# pylint: disable=line-too-long +from azext_acrcssc._client_factory import cf_acr + + +def load_command_table(self, _): + # required to mark the command as 'preview', can be expanded if additional commands are added + self.command_group("acr supply-chain", client_factory=cf_acr, is_preview=True) + + with self.command_group("acr supply-chain workflow", client_factory=cf_acr, is_preview=True) as g: + g.custom_command("create", "create_acrcssc") + g.custom_command("update", "update_acrcssc") + g.custom_command("delete", "delete_acrcssc") + g.custom_show_command("show", "show_acrcssc") + g.custom_command("cancel-run", "cancel_runs") + g.custom_command("list", "list_scan_status") diff --git a/src/acrcssc/azext_acrcssc/cssc.py b/src/acrcssc/azext_acrcssc/cssc.py new file mode 100644 index 00000000000..2fb9be7d392 --- /dev/null +++ b/src/acrcssc/azext_acrcssc/cssc.py @@ -0,0 +1,150 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# pylint: disable=line-too-long +# pylint: disable=logging-fstring-interpolation +from azure.cli.core.util import user_confirmation +from knack.log import get_logger +from .helper._constants import CONTINUOUS_PATCHING_WORKFLOW_NAME +from .helper._taskoperations import ( + create_update_continuous_patch_v1, + delete_continuous_patch_v1, + list_continuous_patch_v1, + acr_cssc_dry_run, + cancel_continuous_patch_runs, + track_scan_progress +) +from ._validators import ( + validate_inputs, + validate_task_type, + validate_cssc_optional_inputs, + validate_continuous_patch_v1_image_limit +) +from azext_acrcssc._client_factory import cf_acr_registries + +logger = get_logger(__name__) + + +def _perform_continuous_patch_operation(cmd, + resource_group_name, + registry_name, + config, + schedule, + dryrun=False, + run_immediately=False, + is_create=True): + acr_client_registries = cf_acr_registries(cmd.cli_ctx, None) + registry = acr_client_registries.get(resource_group_name, registry_name) + + validate_inputs(schedule, config) + + if not is_create: + validate_cssc_optional_inputs(config, schedule) + + logger.debug('validations completed successfully.') + + # every time we perform a create or update operation, we need to validate for the number of images selected on the + # configuration file. The way to do this is by silently running the dryrun operation. If the limit is exceeded, we + # will not proceed with the operation. + dryrun_output = acr_cssc_dry_run(cmd, registry=registry, config_file_path=config, is_create=is_create, remove_internal_statements=not dryrun) + validate_continuous_patch_v1_image_limit(dryrun_output) + if dryrun: + print(dryrun_output) + else: + create_update_continuous_patch_v1(cmd, registry, config, schedule, dryrun, run_immediately, is_create) + + +def create_acrcssc(cmd, + resource_group_name, + registry_name, + workflow_type, + config, + schedule, + dryrun=False, + run_immediately=False): + '''Create a continuous patch task in the registry.''' + logger.debug(f"Entering create_acrcssc with parameters: {registry_name} {workflow_type} {config} {schedule} {dryrun}") + _perform_continuous_patch_operation(cmd, + resource_group_name, + registry_name, + config, + schedule, + dryrun, + run_immediately, + is_create=True) + + +def update_acrcssc(cmd, + resource_group_name, + registry_name, + workflow_type, + config, + schedule, + dryrun=False, + run_immediately=False): + '''Update a continuous patch task in the registry.''' + logger.debug(f'Entering update_acrcssc with parameters: {registry_name} {workflow_type} {config} {schedule} {dryrun} {run_immediately}') + _perform_continuous_patch_operation(cmd, + resource_group_name, + registry_name, + config, + schedule, + dryrun, + run_immediately, + is_create=False) + + +def delete_acrcssc(cmd, + resource_group_name, + registry_name, + workflow_type): + '''Delete a continuous patch task in the registry.''' + logger.debug(f"Entering delete_acrcssc with parameters: {resource_group_name} {registry_name} {workflow_type}") + + validate_task_type(workflow_type) + acr_client_registries = cf_acr_registries(cmd.cli_ctx, None) + registry = acr_client_registries.get(resource_group_name, registry_name) + + user_confirmation(f"Are you sure you want to delete the workflow {CONTINUOUS_PATCHING_WORKFLOW_NAME} from registry {registry_name}?") + + delete_continuous_patch_v1(cmd, registry, False) + print(f"Deleted {CONTINUOUS_PATCHING_WORKFLOW_NAME} workflow successfully from registry {registry_name}") + + +def show_acrcssc(cmd, + resource_group_name, + registry_name, + workflow_type): + '''Show a continuous patch task in the registry.''' + logger.debug(f'Entering show_acrcssc with parameters: {registry_name} {workflow_type}') + + acr_client_registries = cf_acr_registries(cmd.cli_ctx, None) + registry = acr_client_registries.get(resource_group_name, registry_name) + + validate_task_type(workflow_type) + return list_continuous_patch_v1(cmd, registry) + + +def cancel_runs(cmd, + resource_group_name, + registry_name, + workflow_type): + '''cancel all running scans in continuous patch in the registry.''' + logger.debug('Entering cancel_runs with parameters:%s %s %s', resource_group_name, registry_name, workflow_type) + validate_task_type(workflow_type) + cancel_continuous_patch_runs(cmd, resource_group_name, registry_name) + + +def list_scan_status(cmd, registry_name, resource_group_name, status, workflow_type): + '''track in continuous patch in the registry.''' + logger.debug('Entering track_scan_status with parameters:%s %s %s', resource_group_name, registry_name, workflow_type) + + validate_task_type(workflow_type) + acr_client_registries = cf_acr_registries(cmd.cli_ctx, None) + registry = acr_client_registries.get(resource_group_name, registry_name) + + image_status = track_scan_progress(cmd, resource_group_name, registry, status) + print(f"Total images: {len(image_status)}") + + return image_status diff --git a/src/acrcssc/azext_acrcssc/custom.py b/src/acrcssc/azext_acrcssc/custom.py new file mode 100644 index 00000000000..3d9f68794d0 --- /dev/null +++ b/src/acrcssc/azext_acrcssc/custom.py @@ -0,0 +1,7 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# pylint: disable=unused-import + +from .cssc import create_acrcssc, update_acrcssc, delete_acrcssc, show_acrcssc, cancel_runs, list_scan_status diff --git a/src/acrcssc/azext_acrcssc/helper/__init__.py b/src/acrcssc/azext_acrcssc/helper/__init__.py new file mode 100644 index 00000000000..34913fb394d --- /dev/null +++ b/src/acrcssc/azext_acrcssc/helper/__init__.py @@ -0,0 +1,4 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- diff --git a/src/acrcssc/azext_acrcssc/helper/_constants.py b/src/acrcssc/azext_acrcssc/helper/_constants.py new file mode 100644 index 00000000000..59a0b541bc9 --- /dev/null +++ b/src/acrcssc/azext_acrcssc/helper/_constants.py @@ -0,0 +1,133 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +"""Constants used across the extension.""" +# pylint: disable=line-too-long +from enum import Enum + + +# This enum is used to define the task types for the CLI, in case new types are added this is the place to start +class CSSCTaskTypes(Enum): + """Enum for the task type.""" + ContinuousPatchV1 = 'continuouspatchv1' + # CopaV1 = "CopaV1" + # TrivyV1 = "TrivyV1" + + +class TaskRunStatus(Enum): + """Enum for the task status. ACR.Build.Contracts""" + Unknown = 'Unknown' + Queued = 'Queued' + Started = 'Started' + Running = 'Running' + Succeeded = 'Succeeded' + Failed = 'Failed' + Canceled = 'Canceled' + Error = 'Error' + Timeout = 'Timeout' + + +# General Constants +CSSC_TAGS = "acr-cssc" +ACR_API_VERSION_2023_01_01_PREVIEW = "2023-01-01-preview" +ACR_API_VERSION_2019_06_01_PREVIEW = "2019-06-01-preview" +BEARER_TOKEN_USERNAME = "00000000-0000-0000-0000-000000000000" +RESOURCE_GROUP = "resource_group" +SUBSCRIPTION = "subscription" +TMP_DRY_RUN_FILE_NAME = "tmp_dry_run_template.yaml" + + +# Continuous Patch Constants +CONTINUOUSPATCH_IMAGE_LIMIT = 100 +CONTINUOUSPATCH_OCI_ARTIFACT_TYPE = "oci-artifact" +CSSC_WORKFLOW_POLICY_REPOSITORY = "csscpolicies" +CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG = "patchpolicy" +CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG_TAG_V1 = "v1" +CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG_TAG_DRYRUN = "dryrun" +CONTINUOUSPATCH_DEPLOYMENT_NAME = "continuouspatchingdeployment" +CONTINUOUSPATCH_DEPLOYMENT_TEMPLATE = "CSSC-AutoImagePatching-encodedtasks.json" +# listing all individual tasks that are required for Continuous Patching to work +CONTINUOUSPATCH_TASK_PATCHIMAGE_NAME = "cssc-patch-image" +CONTINUOUSPATCH_TASK_PATCHIMAGE_DESCRIPTION = "This task will patch the OS vulnerabilities on a given image using Copacetic." +CONTINUOUSPATCH_TASK_SCANIMAGE_NAME = "cssc-scan-image" +CONTINUOUSPATCH_TASK_SCANIMAGE_DESCRIPTION = f"This task will perform vulnerability OS scan on a given image using Trivy. If there are any vulnerabilities found, it will trigger the patching task using {CONTINUOUSPATCH_TASK_PATCHIMAGE_NAME} task." +CONTINUOUSPATCH_TASK_SCANREGISTRY_NAME = "cssc-trigger-workflow" +CONTINUOUSPATCH_TASK_SCANREGISTRY_DESCRIPTION = f"This task will trigger the continuous patching workflow based on the schedule set during the creation. It will match the filter repositories set with config parameter and schedule vulnerability scan check using {CONTINUOUSPATCH_TASK_SCANIMAGE_NAME} task." +CONTINUOUS_PATCHING_WORKFLOW_NAME = CSSCTaskTypes.ContinuousPatchV1.value +DESCRIPTION = "Description" + +CONTINUOUSPATCH_ALL_TASK_NAMES = [ + CONTINUOUSPATCH_TASK_PATCHIMAGE_NAME, + CONTINUOUSPATCH_TASK_SCANIMAGE_NAME, + CONTINUOUSPATCH_TASK_SCANREGISTRY_NAME +] + +WORKFLOW_STATUS_NOT_AVAILABLE = "---Not Available---" +WORKFLOW_STATUS_PATCH_NOT_AVAILABLE = "---No patch image available---" +WORKFLOW_VALIDATION_MESSAGE="Validating configuration" + +ERROR_MESSAGE_INVALID_TASK = "Workflow type is invalid" +ERROR_MESSAGE_INVALID_TIMESPAN_VALUE = "Schedule value is invalid. " +ERROR_MESSAGE_INVALID_TIMESPAN_FORMAT = "Schedule format is invalid. " +RECOMMENDATION_SCHEDULE = "Schedule must be in the format of where unit is d for days. Example: 1d. Max value for d is 30d." +# this dictionary can be expanded to handle more configuration of the tasks regarding continuous patching +# if this gets out of hand, or more types of tasks are supported, this should be a class on its own +CONTINUOUSPATCH_TASK_DEFINITION = { + CONTINUOUSPATCH_TASK_PATCHIMAGE_NAME: + { + "parameter_name": "imagePatchingEncodedTask", + "template_file": "task/cssc_patch_image.yaml", + DESCRIPTION: CONTINUOUSPATCH_TASK_PATCHIMAGE_DESCRIPTION + }, + CONTINUOUSPATCH_TASK_SCANIMAGE_NAME: + { + "parameter_name": "imageScanningEncodedTask", + "template_file": "task/cssc_scan_image.yaml", + DESCRIPTION: CONTINUOUSPATCH_TASK_SCANIMAGE_DESCRIPTION + }, + CONTINUOUSPATCH_TASK_SCANREGISTRY_NAME: + { + "parameter_name": "registryScanningEncodedTask", + "template_file": "task/cssc_trigger_workflow.yaml", + DESCRIPTION: CONTINUOUSPATCH_TASK_SCANREGISTRY_DESCRIPTION + }, +} +CONTINUOUSPATCH_CONFIG_SCHEMA_SIZE_LIMIT = 1024 * 1024 * 10 # 10MB, we don't want to allow huge files +CONTINUOUSPATCH_CONFIG_SCHEMA_V1 = { + "type": "object", + "properties": { + "version": { + "type": "string", + "pattern": "^(v1)$" + }, + "tag-convention": { + "type": "string", + "pattern": "^(floating|incremental)$" + }, + "repositories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "repository": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": True, + "minItems": 1 + }, + "enabled": { + "type": "boolean" + } + }, + "required": ["repository", "tags"] + } + } + }, + "required": ["repositories", "version"] +} diff --git a/src/acrcssc/azext_acrcssc/helper/_deployment.py b/src/acrcssc/azext_acrcssc/helper/_deployment.py new file mode 100644 index 00000000000..e263822ced8 --- /dev/null +++ b/src/acrcssc/azext_acrcssc/helper/_deployment.py @@ -0,0 +1,150 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +"""A module to handle deployment functions related to tasks.""" +import os +from typing import Optional +from .._client_factory import cf_resources +from azure.cli.core.util import get_file_json +from azure.cli.core.azclierror import AzCLIError +from azure.cli.core.commands import LongRunningOperation +from azure.mgmt.resource.resources.models import ( + DeploymentExtended, + DeploymentProperties, + DeploymentMode, + Deployment +) +from knack.log import get_logger +# pylint: disable=line-too-long +# pylint: disable=logging-fstring-interpolation +logger = get_logger(__name__) + + +def validate_and_deploy_template(cmd_ctx, + registry, + resource_group: str, + deployment_name: str, + template_file_name: str, + parameters: dict, + dryrun: Optional[bool] = False): + logger.debug(f'Working with resource group {resource_group}, registry {registry} template {template_file_name}') + + deployment_path = os.path.dirname( + os.path.join( + os.path.dirname( + os.path.abspath(__file__)), + "../templates/")) # needs to be a constant + + arm_path = os.path.join(deployment_path, "arm") + template_path = os.path.join(arm_path, template_file_name) + template = DeploymentProperties( + template=get_file_json(template_path), + parameters=parameters, + mode=DeploymentMode.incremental) + try: + validate_template(cmd_ctx, resource_group, deployment_name, template) + if (dryrun): + logger.debug("Dry run, skipping deployment") + return None + + return deploy_template(cmd_ctx, resource_group, deployment_name, template) + except Exception as exception: + logger.debug(f'Failed to validate and deploy template: {exception}') + raise AzCLIError(f'Failed to validate and deploy template: {exception}') + + +def validate_template(cmd_ctx, resource_group, deployment_name, template): + # Validation is automatically re-attempted in live runs, but not in test + # playback, causing them to fail. This explicitly re-attempts validation to + # ensure the tests pass + api_clients = cf_resources(cmd_ctx) + validation_res = None + deployment = Deployment( + properties=template, + # tags = { "test": CSSC_TAGS }, #we need to know if tagging + # is something that will help ust, tasks are proxy resources, + # so not sure how that would work + ) + + for validation_attempt in range(2): + try: + validation = ( + api_clients.deployments.begin_validate( + resource_group_name=resource_group, + deployment_name=deployment_name, + parameters=deployment + ) + ) + validation_res = LongRunningOperation( + cmd_ctx, "Validating ARM template..." + )(validation) + break + except Exception: # pylint: disable=broad-except + if validation_attempt == 1: + raise + + if not validation_res: + # Don't expect to hit this but it appeases mypy + raise RuntimeError(f"Validation of template {template} failed.") + + logger.debug(f"Validation Result {validation_res}") + if validation_res.error: + # Validation failed so don't even try to deploy + logger.error( + ( + f"Template for resource group {resource_group} has failed validation. The message" + " was: {validation_res.error.message}. See logs for additional details." + ) + ) + logger.debug( + ( + f"Template for resource group {resource_group} failed validation." + " Full error details: {validation_res.error}" + ) + ) + raise RuntimeError("Azure template validation failed.") + + # Validation succeeded so proceed with deployment + logger.debug("Successfully validated resources for {resource_group}") + + +def deploy_template(cmd_ctx, resource_group, deployment_name, template): + api_client = cf_resources(cmd_ctx) + + deployment = Deployment( + properties=template, + # tags = { "test": CSSC_TAGS }, + # we need to know if tagging is something that will help us, + # tasks are proxy resources, so not sure how that would work + ) + + poller = api_client.deployments.begin_create_or_update( + resource_group_name=resource_group, + deployment_name=deployment_name, + parameters=deployment) + logger.debug(poller) + + # Wait for the deployment to complete and get the outputs + deployment: DeploymentExtended = LongRunningOperation( + cmd_ctx + )(poller) + logger.debug("Finished deploying") + + if deployment.properties is not None: + depl_props = deployment.properties + else: + raise RuntimeError("The deployment has no properties.\nAborting") + logger.debug(f"Deployed: {deployment.name} {deployment.id} {depl_props}") + + if depl_props.provisioning_state != "Succeeded": + logger.debug(f"Failed to provision: {depl_props}") + raise RuntimeError( + "Deploy of template to resource group" + f" {resource_group} proceeded but the provisioning" + f" state returned is {depl_props.provisioning_state}." + "\nAborting" + ) + logger.debug(f"Provisioning state of deployment {resource_group} : {depl_props.provisioning_state}") + + return depl_props.outputs diff --git a/src/acrcssc/azext_acrcssc/helper/_ociartifactoperations.py b/src/acrcssc/azext_acrcssc/helper/_ociartifactoperations.py new file mode 100644 index 00000000000..e83f88dabdf --- /dev/null +++ b/src/acrcssc/azext_acrcssc/helper/_ociartifactoperations.py @@ -0,0 +1,230 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +"""A module to handle ORAS calls to the registry.""" +# pylint: disable=line-too-long +# pylint: disable=logging-fstring-interpolation +import os +import dataclasses +import tempfile +import shutil +import subprocess + +from oras.client import OrasClient +from azure.cli.core.azclierror import AzCLIError +from azure.cli.command_modules.acr.repository import acr_repository_delete +from azure.mgmt.core.tools import parse_resource_id +from jsonschema.exceptions import ValidationError +from knack.log import get_logger +from ._constants import ( + BEARER_TOKEN_USERNAME, + CONTINUOUSPATCH_CONFIG_SCHEMA_V1, + CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG, + CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG_TAG_V1, + CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG_TAG_DRYRUN, + CONTINUOUSPATCH_TASK_SCANREGISTRY_NAME, + CSSC_WORKFLOW_POLICY_REPOSITORY, + SUBSCRIPTION +) +from ._utility import ( + convert_cron_to_schedule, + get_task +) + +logger = get_logger(__name__) + + +def create_oci_artifact_continuous_patch(registry, cssc_config_file, dryrun): + logger.debug(f"Entering create_oci_artifact_continuouspatching with parameters: {registry.name} {cssc_config_file} {dryrun}") + + try: + oras_client = _oras_client(registry) + # we might have to handle the tag lock/unlock for the cssc config file, + # to make it harder for the user to change it by mistake + + # the ORAS client can only work with files under the current directory + temp_artifact = tempfile.NamedTemporaryFile( + prefix="cssc_config_tmp_", + mode="w+b", + dir=os.getcwd(), + delete=False) + + temp_artifact_name = temp_artifact.name + user_artifact = open(cssc_config_file, "rb") + shutil.copyfileobj(user_artifact, temp_artifact) + temp_artifact.close() + + if dryrun: + oci_target_name = f"{CSSC_WORKFLOW_POLICY_REPOSITORY}/{CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG}:{CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG_TAG_DRYRUN}" + else: + oci_target_name = f"{CSSC_WORKFLOW_POLICY_REPOSITORY}/{CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG}:{CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG_TAG_V1}" + + oras_client.push( + target=oci_target_name, + files=[temp_artifact.name]) + except Exception as exception: + raise AzCLIError(f"Failed to push OCI artifact to ACR: {exception}") + finally: + oras_client.logout(hostname=str.lower(registry.login_server)) + if os.path.exists(temp_artifact_name): + os.remove(temp_artifact_name) + + +def get_oci_artifact_continuous_patch(cmd, registry): + logger.debug("Entering get_oci_artifact_continuous_patch with parameter: %s", registry.login_server) + config = None + file_name = None + try: + oras_client = _oras_client(registry) + + oci_target_name = f"{CSSC_WORKFLOW_POLICY_REPOSITORY}/{CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG}:{CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG_TAG_V1}" + + oci_artifacts = oras_client.pull( + target=oci_target_name, + stream=True) + trigger_task = get_task(cmd, registry, CONTINUOUSPATCH_TASK_SCANREGISTRY_NAME) + file_name = oci_artifacts[0] + config = ContinuousPatchConfig().from_file(file_name, trigger_task) + except Exception as exception: + raise AzCLIError(f"Failed to get OCI artifact from ACR: {exception}") + finally: + oras_client.logout(hostname=str.lower(registry.login_server)) + + return config, file_name + + +def delete_oci_artifact_continuous_patch(cmd, registry, dryrun): + logger.debug(f"Entering delete_oci_artifact_continuous_patch with parameters {registry} {dryrun}") + resourceid = parse_resource_id(registry.id) + subscription = resourceid[SUBSCRIPTION] + + if dryrun: + logger.warning("Dry run flag is set, no changes will be made") + return + try: + token = _get_acr_token(registry.name, subscription) + + # Delete repository, removing only image isn't deleting the repository always (Bug) + acr_repository_delete( + cmd=cmd, + registry_name=registry.name, + repository=f"{CSSC_WORKFLOW_POLICY_REPOSITORY}/{CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG}", + username=BEARER_TOKEN_USERNAME, + password=token, + yes=not dryrun) + logger.debug("Call to acr_repository_delete completed successfully") + except Exception as exception: + logger.debug(exception) + logger.error(f"{CSSC_WORKFLOW_POLICY_REPOSITORY}/{CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG}:{CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG_TAG_V1} might not exist or attempt to delete failed.") + raise + + +def _oras_client(registry): + resourceid = parse_resource_id(registry.id) + subscription = resourceid[SUBSCRIPTION] + + try: + token = _get_acr_token(registry.name, subscription) + client = OrasClient(hostname=str.lower(registry.login_server)) + client.login(BEARER_TOKEN_USERNAME, token) + except Exception as exception: + raise AzCLIError(f"Failed to login to Artifact Store ACR {registry.name}: {exception}") + + return client + + +# Need to check on this method once, if there's alternative to this +def _get_acr_token(registry_name, subscription): + logger.debug(f"Using CLI user credentials to log into {registry_name}") + acr_login_with_token_cmd = [ + str(shutil.which("az")), + "acr", "login", + "--name", registry_name, + "--subscription", subscription, + "--expose-token", + "--output", "tsv", + "--query", "accessToken", + ] + + try: + proc = subprocess.Popen( + acr_login_with_token_cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + token = proc.stdout.read().strip().decode("utf-8") + # this suppresses the 'login' warning from the ACR request, if we need the error and does not come from the exception we can take it from here + # error_stderr=proc.stderr.read() + except subprocess.CalledProcessError as error: + unauthorized = ( + error.stderr + and (" 401" in error.stderr or "unauthorized" in error.stderr) + ) or ( + error.stdout + and (" 401" in error.stdout or "unauthorized" in error.stdout) + ) + + if unauthorized: + # As we shell out the the subprocess, I think checking for these + # strings is the best check we can do for permission failures. + raise AzCLIError( + " Failed to login to Artifact Store ACR.\n" + " It looks like you do not have permissions. You need to have" + " the AcrPush role over the" + " registry in order to be able to upload to the new" + " artifact store." + ) from error + + return token + + +class ContinuousPatchConfig: + def __init__(self): + self.version = "" + self.repositories = [] + self.schedule = None + + def from_file(self, file_path, trigger_task=None): + with open(file_path, "r") as file: + return self.from_json(file.read(), trigger_task) + + def from_json(self, json_str, trigger_task=None): + import json + from jsonschema import validate + + try: + json_config = json.loads(json_str) + validate(json_config, CONTINUOUSPATCH_CONFIG_SCHEMA_V1) + except ValidationError as e: + logger.error("Error validating the continuous patch config file: %s", e) + return None + + self.version = json_config.get("version", "") + repositories = json_config.get("repositories", []) + for repo in repositories: + enabled = repo.get("enabled", True) # optional field, default to True + repository = Repository(repo["repository"], repo["tags"], enabled) + self.repositories.append(repository) + + if trigger_task: + trigger = trigger_task.trigger + if trigger and trigger.timer_triggers: + self.schedule = convert_cron_to_schedule(trigger.timer_triggers[0].schedule, just_days=True) + + return self + + def get_enabled_images(self): + enabled_images = [] + for repository in self.repositories: + if repository.enabled: + for tag in repository.tags: + image = f"{repository.repository}:{tag}" + enabled_images.append(image) + return enabled_images + + +@dataclasses.dataclass +class Repository: + repository: str + tags: list[str] + enabled: bool diff --git a/src/acrcssc/azext_acrcssc/helper/_taskoperations.py b/src/acrcssc/azext_acrcssc/helper/_taskoperations.py new file mode 100644 index 00000000000..e4b8654cf57 --- /dev/null +++ b/src/acrcssc/azext_acrcssc/helper/_taskoperations.py @@ -0,0 +1,559 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# pylint: disable=line-too-long +# pylint: disable=broad-exception-caught +# pylint: disable=logging-fstring-interpolation +import base64 +import os +import tempfile +import time +import logging +from knack.log import get_logger +from knack.util import CLIError +from ._constants import ( + CONTINUOUSPATCH_DEPLOYMENT_NAME, + CONTINUOUSPATCH_DEPLOYMENT_TEMPLATE, + CONTINUOUSPATCH_ALL_TASK_NAMES, + CONTINUOUSPATCH_TASK_DEFINITION, + CONTINUOUSPATCH_TASK_SCANREGISTRY_NAME, + RESOURCE_GROUP, + CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG, + CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG_TAG_V1, + TMP_DRY_RUN_FILE_NAME, + CONTINUOUS_PATCHING_WORKFLOW_NAME, + CSSC_WORKFLOW_POLICY_REPOSITORY, + CONTINUOUSPATCH_TASK_PATCHIMAGE_NAME, + CONTINUOUSPATCH_TASK_SCANIMAGE_NAME, + DESCRIPTION, + WORKFLOW_VALIDATION_MESSAGE, + TaskRunStatus) +from azure.cli.core.azclierror import AzCLIError, InvalidArgumentValueError +from azure.cli.core.commands import LongRunningOperation +from azure.cli.core.commands.progress import IndeterminateProgressBar +from azure.cli.command_modules.acr._utils import prepare_source_location +from azure.cli.command_modules.acr._archive_utils import logger as acr_archive_utils_logger +from azure.core.exceptions import ResourceNotFoundError, HttpResponseError +from azure.mgmt.core.tools import parse_resource_id +from azext_acrcssc._client_factory import cf_acr_tasks, cf_authorization, cf_acr_registries_tasks, cf_acr_runs +from azext_acrcssc.helper._deployment import validate_and_deploy_template +from azext_acrcssc._validators import check_continuous_task_exists, check_continuous_task_config_exists +from datetime import datetime, timezone, timedelta +from ._utility import convert_timespan_to_cron, convert_cron_to_schedule, create_temporary_dry_run_file, delete_temporary_dry_run_file +from azext_acrcssc.helper._ociartifactoperations import create_oci_artifact_continuous_patch, get_oci_artifact_continuous_patch, delete_oci_artifact_continuous_patch +from ._workflow_status import WorkflowTaskStatus + +logger = get_logger(__name__) + + +def create_update_continuous_patch_v1(cmd, + registry, + cssc_config_file, + schedule, + dryrun, + run_immediately, + is_create_workflow=True): + + logger.debug(f"Entering continuousPatchV1_creation {cssc_config_file} {dryrun} {run_immediately}") + + resource_group = parse_resource_id(registry.id)[RESOURCE_GROUP] + schedule_cron_expression = None + cssc_tasks_exists, task_list = check_continuous_task_exists(cmd, registry) + + if schedule is not None: + schedule_cron_expression = convert_timespan_to_cron(schedule) + logger.debug(f"converted schedule to cron expression: {schedule_cron_expression}") + + cssc_tasks_exists, task_list = check_continuous_task_exists(cmd, registry) + if is_create_workflow: + if cssc_tasks_exists: + raise AzCLIError(f"{CONTINUOUS_PATCHING_WORKFLOW_NAME} workflow task already exists. Use 'az acr supply-chain workflow update' command to perform updates.") + _create_cssc_workflow(cmd, registry, schedule_cron_expression, resource_group, dryrun) + else: + if not cssc_tasks_exists: + raise AzCLIError(f"{CONTINUOUS_PATCHING_WORKFLOW_NAME} workflow task does not exist. Use 'az acr supply-chain workflow create' command to create {CONTINUOUS_PATCHING_WORKFLOW_NAME} workflow.") + + _update_cssc_workflow(cmd, registry, schedule_cron_expression, resource_group, dryrun, task_list) + + if cssc_config_file is not None: + create_oci_artifact_continuous_patch(registry, cssc_config_file, dryrun) + logger.debug(f"Uploading of {cssc_config_file} completed successfully.") + + # on 'update' schedule is optional + if schedule is None: + trigger_task = next(task for task in task_list if task.name == CONTINUOUSPATCH_TASK_SCANREGISTRY_NAME) + trigger = trigger_task.trigger + if trigger and trigger.timer_triggers: + schedule_cron_expression = trigger.timer_triggers[0].schedule + + _eval_trigger_run(cmd, registry, resource_group, run_immediately) + next_date = get_next_date(schedule_cron_expression) + print(f"Continuous Patching workflow scheduled to run next at: {next_date} UTC") + + +def _create_cssc_workflow(cmd, registry, schedule_cron_expression, resource_group, dry_run, silent_execution=False): + parameters = { + "AcrName": {"value": registry.name}, + "AcrLocation": {"value": registry.location}, + "taskSchedule": {"value": schedule_cron_expression} + } + + for task in CONTINUOUSPATCH_TASK_DEFINITION: + encoded_task = {"value": _create_encoded_task(CONTINUOUSPATCH_TASK_DEFINITION[task]["template_file"])} + param_name = CONTINUOUSPATCH_TASK_DEFINITION[task]["parameter_name"] + parameters[param_name] = encoded_task + + validate_and_deploy_template( + cmd.cli_ctx, + registry, + resource_group, + CONTINUOUSPATCH_DEPLOYMENT_NAME, + CONTINUOUSPATCH_DEPLOYMENT_TEMPLATE, + parameters, + dry_run + ) + + if not silent_execution: + print(f"Deployment of {CONTINUOUS_PATCHING_WORKFLOW_NAME} tasks completed successfully.") + + +def _update_cssc_workflow(cmd, registry, schedule_cron_expression, resource_group, dry_run, task_list): + # compare the task definition to the existing tasks, if there is a difference, we need to update the tasks + # if we need to update the tasks, we will update the cron expression from it + # if not we just update the cron expression from the given parameter + acr_task_client = cf_acr_tasks(cmd.cli_ctx) + for task in task_list: + deployed_task = task.step.encoded_task_content + extension_task = _create_encoded_task(CONTINUOUSPATCH_TASK_DEFINITION[task.name]["template_file"]) + if deployed_task != extension_task: + logger.debug(f"Task {task.name} is different from the extension task, updating the task") + _update_task_yaml(cmd, acr_task_client, registry, resource_group, task, extension_task) + + if schedule_cron_expression is not None: + _update_task_schedule(cmd, acr_task_client, registry, resource_group, schedule_cron_expression, dry_run) + + +def _eval_trigger_run(cmd, registry, resource_group, run_immediately): + if run_immediately: + logger.warning(f'Triggering the {CONTINUOUSPATCH_TASK_SCANREGISTRY_NAME} to run immediately') + # Seen Managed Identity taking time, see if there can be an alternative (one alternative is to schedule the cron expression with delay) + # NEED TO SKIP THE TIME.SLEEP IN UNIT TEST CASE OR FIND AN ALTERNATIVE SOLUITION TO MI COMPLETE + time.sleep(30) + _trigger_task_run(cmd, registry, resource_group, CONTINUOUSPATCH_TASK_SCANREGISTRY_NAME) + + +def delete_continuous_patch_v1(cmd, registry, dryrun): + logger.debug("Entering delete_continuous_patch_v1") + cssc_tasks_exists, _ = check_continuous_task_exists(cmd, registry) + cssc_config_exists = check_continuous_task_config_exists(cmd, registry) + if not dryrun and (cssc_tasks_exists or cssc_config_exists): + cssc_tasks = ', '.join(CONTINUOUSPATCH_ALL_TASK_NAMES) + logger.warning(f"All of these tasks will be deleted: {cssc_tasks}") + for taskname in CONTINUOUSPATCH_ALL_TASK_NAMES: + # bug: if one of the deletion fails, the others will not be attempted, we need to attempt to delete all of them + _delete_task(cmd, registry, taskname, dryrun) + logger.warning(f"Task {taskname} deleted.") + logger.warning(f"Deleting {CSSC_WORKFLOW_POLICY_REPOSITORY}/{CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG}:{CONTINUOUSPATCH_OCI_ARTIFACT_CONFIG_TAG_V1}") + delete_oci_artifact_continuous_patch(cmd, registry, dryrun) + + if not cssc_tasks_exists: + logger.warning(f"{CONTINUOUS_PATCHING_WORKFLOW_NAME} workflow does not exist") + + +def list_continuous_patch_v1(cmd, registry): + logger.debug("Entering list_continuous_patch_v1") + cssc_tasks_exists, _ = check_continuous_task_exists(cmd, registry) + if not cssc_tasks_exists: + logger.warning(f"{CONTINUOUS_PATCHING_WORKFLOW_NAME} workflow task does not exist. Run 'az acr supply-chain workflow create' to create workflow tasks") + return + + acr_task_client = cf_acr_tasks(cmd.cli_ctx) + resource_group_name = parse_resource_id(registry.id)[RESOURCE_GROUP] + tasks_list = acr_task_client.list(resource_group_name, registry.name) + filtered_cssc_tasks = _transform_task_list(tasks_list) + return filtered_cssc_tasks + + +def acr_cssc_dry_run(cmd, registry, config_file_path, is_create=True, remove_internal_statements=True): + logger.debug(f"Entering acr_cssc_dry_run with parameters: {registry} {config_file_path}") + cssc_tasks_exists, _ = check_continuous_task_exists(cmd, registry) + + if config_file_path is None: + if not cssc_tasks_exists: + raise InvalidArgumentValueError("--config parameter is needed to perform dry-run check.") + + # attempt to get the config file from the registry, since the configuration should exist + logger.debug("Retrieving the configuration file from the registry.") + _, config_file_path = get_oci_artifact_continuous_patch(cmd, registry) + if config_file_path is None: + raise AzCLIError("Failed to retrieve the configuration file from the registry.") + + if is_create and cssc_tasks_exists: + raise AzCLIError(f"{CONTINUOUS_PATCHING_WORKFLOW_NAME} workflow task already exists. Use 'az acr supply-chain workflow update' command to perform updates.") + + file_name = None + tmp_folder = None + acr_archive_utils_logger_level = acr_archive_utils_logger.getEffectiveLevel() + try: + file_name = os.path.basename(config_file_path) + tmp_folder = os.path.join(os.getcwd(), tempfile.mkdtemp(prefix="cli_temp_cssc")) + create_temporary_dry_run_file(config_file_path, tmp_folder) + + resource_group_name = parse_resource_id(registry.id)[RESOURCE_GROUP] + acr_registries_task_client = cf_acr_registries_tasks(cmd.cli_ctx) + acr_run_client = cf_acr_runs(cmd.cli_ctx) + + # This removes the internal logging from the acr module, reenables it after the setup is completed. + # Because it is an external logger, the only way to control the output is by changing the level + try: + if remove_internal_statements: + acr_archive_utils_logger.setLevel(logging.ERROR) + + source_location = prepare_source_location( + cmd, + tmp_folder, + acr_registries_task_client, + registry.name, + resource_group_name) + + except CLIError as cli_error: + raise AzCLIError(f"Failed to prepare source to trigger ACR task: {cli_error}") + finally: + if remove_internal_statements: + acr_archive_utils_logger.setLevel(acr_archive_utils_logger_level) + + OS = acr_run_client.models.OS + Architecture = acr_run_client.models.Architecture + + # TODO: when the extension merges back into the acr module, we need to reuse the 'get_validate_platform()' from ACR modules (src\azure-cli\azure\cli\command_modules\acr\_utils.py) + platform_os = OS.linux.value + platform_arch = Architecture.amd64.value + platform_variant = None + + value_pair = [{"name": "CONFIGPATH", "value": f"{file_name}"}] + request = acr_registries_task_client.models.FileTaskRunRequest( + task_file_path=TMP_DRY_RUN_FILE_NAME, + values_file_path=None, + values=value_pair, + source_location=source_location, + timeout=None, + platform=acr_registries_task_client.models.PlatformProperties( + os=platform_os, + architecture=platform_arch, + variant=platform_variant + ), + credentials=_get_custom_registry_credentials(cmd, auth_mode=None), + agent_pool_name=None, + log_template=None + ) + queued = LongRunningOperation(cmd.cli_ctx, start_msg=WORKFLOW_VALIDATION_MESSAGE)(acr_registries_task_client.begin_schedule_run( + resource_group_name=resource_group_name, + registry_name=registry.name, + run_request=request)) + run_id = queued.run_id + logger.info("Performing dry-run check for filter policy using acr task run id: %s", run_id) + return WorkflowTaskStatus.remove_internal_acr_statements( + WorkflowTaskStatus.generate_logs( + cmd, + acr_run_client, + run_id, + registry.name, + resource_group_name, + await_task_run=True, + await_task_message=WORKFLOW_VALIDATION_MESSAGE)) + finally: + delete_temporary_dry_run_file(tmp_folder) + + +def cancel_continuous_patch_runs(cmd, resource_group_name, registry_name): + logger.debug("Entering cancel_continuous_patch_v1") + acr_task_run_client = cf_acr_runs(cmd.cli_ctx) + running_tasks = WorkflowTaskStatus.get_taskruns_with_filter( + acr_task_run_client, + registry_name=registry_name, + resource_group_name=resource_group_name, + status_filter=[TaskRunStatus.Running.value, TaskRunStatus.Queued.value, TaskRunStatus.Started.value], + taskname_filter=[CONTINUOUSPATCH_TASK_SCANREGISTRY_NAME, CONTINUOUSPATCH_TASK_SCANIMAGE_NAME, CONTINUOUSPATCH_TASK_PATCHIMAGE_NAME]) + + for task in running_tasks: + logger.warning("Sending request to cancel task %s", task.name) + acr_task_run_client.begin_cancel(resource_group_name, registry_name, task.name) + logger.warning("All active running workflow tasks have been cancelled.") + + +def track_scan_progress(cmd, resource_group_name, registry, status): + logger.debug("Entering track_scan_progress") + + config, _ = get_oci_artifact_continuous_patch(cmd, registry) + + return _retrieve_logs_for_image(cmd, registry, resource_group_name, config.schedule, status) + + +def _retrieve_logs_for_image(cmd, registry, resource_group_name, schedule, workflow_status=None): + image_status = [] + acr_task_run_client = cf_acr_runs(cmd.cli_ctx) + + # get all the tasks executed since the last schedule, add a day to make sure we are not running into and edge case with the date + today = datetime.now(timezone.utc) + # delta = datetime.timedelta(days=int(schedule) + 1) + delta = timedelta(days=int(schedule)) # use the schedule as is, we are running into issues we are querying too much and take time to filter + previous_date = today - delta + previous_date_filter = previous_date.strftime('%Y-%m-%dT%H:%M:%SZ') + + # th API returns an iterator, if we want to be able to modify it, we need to convert it to a list + scan_taskruns = WorkflowTaskStatus.get_taskruns_with_filter( + acr_task_run_client, + registry.name, + resource_group_name, + taskname_filter=[CONTINUOUSPATCH_TASK_SCANIMAGE_NAME], + date_filter=previous_date_filter) + + patch_taskruns = WorkflowTaskStatus.get_taskruns_with_filter( + acr_task_run_client, + registry.name, + resource_group_name, + taskname_filter=[CONTINUOUSPATCH_TASK_PATCHIMAGE_NAME], + date_filter=previous_date_filter) + + start_time = time.time() + + progress_indicator = IndeterminateProgressBar(cmd.cli_ctx, message="Retrieving logs for images") + progress_indicator.begin() + + image_status = WorkflowTaskStatus.from_taskrun(cmd, acr_task_run_client, registry, scan_taskruns, patch_taskruns, progress_indicator=progress_indicator) + if workflow_status: + filtered_image_status = [image for image in image_status if image["patch_status"] == workflow_status or image["scan_status"] == workflow_status] + image_status = filtered_image_status + + end_time = time.time() + execution_time = end_time - start_time + logger.debug(f"Execution time: {execution_time} seconds / tasks filtered: {len(scan_taskruns)} + {len(patch_taskruns)}") + + progress_indicator.end() + + return image_status + + +def _trigger_task_run(cmd, registry, resource_group, task_name): + acr_task_registries_client = cf_acr_registries_tasks(cmd.cli_ctx) + request = acr_task_registries_client.models.TaskRunRequest( + task_id=f"{registry.id}/tasks/{task_name}") + queued_run = LongRunningOperation(cmd.cli_ctx)( + acr_task_registries_client.begin_schedule_run( + resource_group, + registry.name, + request)) + run_id = queued_run.run_id + print(f"Queued {CONTINUOUS_PATCHING_WORKFLOW_NAME} workflow task '{task_name}' with run ID: {run_id}. Use 'az acr task logs --registry {registry.name} --run-id {run_id}' to view the logs.") + + +def _create_encoded_task(task_file): + # this is a bit of a hack, but we need to fix the path to the task's yaml, + # relative paths don't work because we don't control where the az cli is running from + templates_path = os.path.dirname( + os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "../templates/")) + + with open(os.path.join(templates_path, task_file), "rb") as f: + base64_content = base64.b64encode(f.read()) + return base64_content.decode('utf-8') + + +def _update_task_yaml(cmd, acr_task_client, registry, resource_group_name, task, encoded_task): + logger.debug("Entering update_task_yaml for task %s", task.name) + try: + taskUpdateParameters = acr_task_client.models.TaskUpdateParameters( + step=acr_task_client.models.EncodedTaskStepUpdateParameters( + encoded_task_content=encoded_task)) + + result = acr_task_client.begin_update(resource_group_name, + registry.name, + task.name, + taskUpdateParameters) + + logger.debug(f"Task {task.name} updated successfully") + except HttpResponseError as exception: + logger.warning(f"Failed to update task {task.name} in registry {registry.name}: {exception}") + + +def _update_task_schedule(cmd, acr_task_client, registry, resource_group_name, cron_expression, dryrun): + logger.debug(f"Using cron_expression: {cron_expression}") + taskUpdateParameters = acr_task_client.models.TaskUpdateParameters( + trigger=acr_task_client.models.TriggerUpdateParameters( + timer_triggers=[ + acr_task_client.models.TimerTriggerUpdateParameters( + name='azcli_defined_schedule', + schedule=cron_expression) + ])) + + if dryrun: + logger.debug("Dry run, skipping the update of the task schedule") + return + try: + result = acr_task_client.begin_update(resource_group_name, + registry.name, + CONTINUOUSPATCH_TASK_SCANREGISTRY_NAME, + taskUpdateParameters) + print("Schedule has been successfully updated.") + except HttpResponseError as exception: + raise AzCLIError(f"Failed to update the task schedule: {exception}") + + +def _delete_task(cmd, registry, task_name, dryrun): + logger.debug("Entering delete_task") + resource_group = parse_resource_id(registry.id)[RESOURCE_GROUP] + + try: + acr_tasks_client = cf_acr_tasks(cmd.cli_ctx) + _delete_task_role_assignment(cmd.cli_ctx, acr_tasks_client, registry, resource_group, task_name, dryrun) + + if dryrun: + logger.debug(f"Dry run, skipping deletion of the task: {task_name}") + return None + logger.debug(f"Deleting task {task_name}") + LongRunningOperation(cmd.cli_ctx)( + acr_tasks_client.begin_delete( + resource_group, + registry.name, + task_name)) + + except Exception as exception: + raise AzCLIError(f"Failed to delete task {task_name} from registry {registry.name} : {exception}") + + logger.debug(f"Task {task_name} deleted successfully") + + +def _delete_task_role_assignment(cli_ctx, acrtask_client, registry, resource_group, task_name, dryrun): + role_client = cf_authorization(cli_ctx) + acrtask_client = cf_acr_tasks(cli_ctx) + + try: + task = acrtask_client.get(resource_group, registry.name, task_name) + except ResourceNotFoundError: + logger.debug(f"Task {task_name} does not exist in registry {registry.name}") + logger.debug("Continuing with deletion") + return None + + identity = task.identity + + if identity: + assigned_roles = role_client.role_assignments.list_for_scope( + registry.id, + filter=f"principalId eq '{identity.principal_id}'" + ) + + for role in assigned_roles: + if dryrun: + logger.debug(f"Dry run, skipping deletion of role assignments, task: {task_name}, role name: {role.name}") + return None + logger.debug(f"Deleting role assignments of task {task_name} from the registry") + role_client.role_assignments.delete( + scope=registry.id, + role_assignment_name=role.name + ) + + +def _transform_task_list(tasks): + transformed = [] + for task in tasks: + if task.name not in CONTINUOUSPATCH_ALL_TASK_NAMES: + continue + + transformed_obj = { + "creationDate": task.creation_date, + "location": task.location, + "name": task.name, + "provisioningState": task.provisioning_state, + "systemData": task.system_data, + "schedule": None, + "description": CONTINUOUSPATCH_TASK_DEFINITION[task.name][DESCRIPTION] + } + + trigger = task.trigger + if trigger and trigger.timer_triggers: + # convert the cron expression to a 'days' format to keep consistent with the command's options + transformed_obj["schedule"] = convert_cron_to_schedule(trigger.timer_triggers[0].schedule) + + # add a 'nextOccurrence' field to the task, only for the scheduling task + transformed_obj["nextOccurrence"] = get_next_date(task.trigger.timer_triggers[0].schedule) + transformed.append(transformed_obj) + + return transformed + + +def _get_custom_registry_credentials(cmd, + auth_mode=None, + login_server=None, + username=None, + password=None, + identity=None, + is_remove=False): + """Get the credential object from the input + :param str auth_mode: The login mode for the source registry + :param str login_server: The login server of custom registry + :param str username: The username for custom registry (plain text or a key vault secret URI) + :param str password: The password for custom registry (plain text or a key vault secret URI) + :param str identity: The task managed identity used for the credential + """ + acr_tasks_client = cf_acr_tasks(cmd.cli_ctx) + source_registry_credentials = None + if auth_mode: + source_registry_credentials = acr_tasks_client.models.SourceRegistryCredentials( + login_mode=auth_mode) + + custom_registries = None + if login_server: + # if null username and password (or identity), then remove the credential + custom_reg_credential = None + + is_identity_credential = False + if not username and not password: + is_identity_credential = identity is not None + + if not is_remove: + if is_identity_credential: + custom_reg_credential = acr_tasks_client.models.CustomRegistryCredentials( + identity=identity + ) + else: + custom_reg_credential = acr_tasks_client.models.CustomRegistryCredentials( + user_name=acr_tasks_client.models.SecretObject( + type=acr_tasks_client.models.SecretObjectType.vaultsecret if _is_vault_secret( + cmd, username)else acr_tasks_client.models.SecretObjectType.opaque, + value=username + ), + password=acr_tasks_client.models.SecretObject( + type=acr_tasks_client.models.SecretObjectType.vaultsecret if _is_vault_secret( + cmd, password) else acr_tasks_client.models.SecretObjectType.opaque, + value=password + ), + identity=identity + ) + + custom_registries = {login_server: custom_reg_credential} + + return acr_tasks_client.models.Credentials( + source_registry=source_registry_credentials, + custom_registries=custom_registries + ) + + +def _is_vault_secret(cmd, credential): + keyvault_dns = None + try: + keyvault_dns = cmd.cli_ctx.cloud.suffixes.keyvault_dns + except Exception: + return False + if credential is not None: + return keyvault_dns.upper() in credential.upper() + return False + + +def get_next_date(cron_expression): + from croniter import croniter + now = datetime.now(timezone.utc) + cron = croniter(cron_expression, now, expand_from_start_time=False) + next_date = cron.get_next(datetime) + return str(next_date) diff --git a/src/acrcssc/azext_acrcssc/helper/_utility.py b/src/acrcssc/azext_acrcssc/helper/_utility.py new file mode 100644 index 00000000000..25177f26bb7 --- /dev/null +++ b/src/acrcssc/azext_acrcssc/helper/_utility.py @@ -0,0 +1,97 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import os +import re +from knack.log import get_logger +from datetime import (datetime, timezone) +import shutil +from azure.cli.core.azclierror import InvalidArgumentValueError, ResourceNotFoundError +from azure.mgmt.core.tools import parse_resource_id +from ._constants import ERROR_MESSAGE_INVALID_TIMESPAN_VALUE, TMP_DRY_RUN_FILE_NAME +from ._constants import RESOURCE_GROUP +from .._client_factory import cf_acr_tasks + +logger = get_logger(__name__) +# pylint: disable=logging-fstring-interpolation + + +def convert_timespan_to_cron(schedule, date_time=None): + # Regex to look for pattern 1d, 2d, 3d, etc. + match = re.match(r'(\d+)([d])', schedule) + if match is None: + raise InvalidArgumentValueError(error_msg=ERROR_MESSAGE_INVALID_TIMESPAN_VALUE) + + value = int(match.group(1)) + unit = match.group(2) + + if date_time is None: + date_time = datetime.now(timezone.utc) + + cron_hour = date_time.hour + cron_minute = date_time.minute + cron_expression = "" + + if unit == 'd': # day of the month + if value < 1 or value > 30: + raise InvalidArgumentValueError(error_msg=ERROR_MESSAGE_INVALID_TIMESPAN_VALUE) + cron_expression = f'{cron_minute} {cron_hour} */{value} * *' + + return cron_expression + + +def convert_cron_to_schedule(cron_expression, just_days=False): + try: + parts = cron_expression.split() + # The third part of the cron expression for 'day of the month' + third_part = parts[2] + + match = re.search(r'^\*/(\d+)$', third_part) + + if match: + if just_days: + return match.group(1) + return match.group(1) + 'd' + + return None + except IndexError: + return None + + +def create_temporary_dry_run_file(file_location, tmp_folder): + templates_path = os.path.dirname( + os.path.join( + os.path.dirname( + os.path.abspath(__file__)), + "../templates/")) + logger.debug(f"templates_path: {templates_path}") + + os.makedirs(tmp_folder, exist_ok=True) + file_template_copy = templates_path + "/" + TMP_DRY_RUN_FILE_NAME + + shutil.copy2(file_template_copy, tmp_folder) + shutil.copy2(file_location, tmp_folder) + folder_contents = os.listdir(tmp_folder) + logger.debug(f"Copied dry run file {folder_contents}") + + +def delete_temporary_dry_run_file(tmp_folder): + if tmp_folder is None or not os.path.exists(tmp_folder): + return + logger.debug(f"Deleting contents and directory {tmp_folder}") + shutil.rmtree(tmp_folder) + + +def get_task(cmd, registry, task_name, task_client=None): + if task_client is None: + task_client = cf_acr_tasks(cmd.cli_ctx) + + resourceid = parse_resource_id(registry.id) + resource_group = resourceid[RESOURCE_GROUP] + + try: + return task_client.get(resource_group, registry.name, task_name) + except ResourceNotFoundError as exception: + logger.debug("Failed to find task %s from registry %s : %s", task_name, registry.name, exception) + return None diff --git a/src/acrcssc/azext_acrcssc/helper/_workflow_status.py b/src/acrcssc/azext_acrcssc/helper/_workflow_status.py new file mode 100644 index 00000000000..c8975e5ab9f --- /dev/null +++ b/src/acrcssc/azext_acrcssc/helper/_workflow_status.py @@ -0,0 +1,535 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +# want it to be something where we can store the status of a image processed by the workflow +# contain both the image, scanning status and patching status + +import time +import re +from ._constants import ( + RESOURCE_GROUP, + CSSCTaskTypes, + TaskRunStatus, + WORKFLOW_STATUS_NOT_AVAILABLE, + WORKFLOW_STATUS_PATCH_NOT_AVAILABLE) +from azure.cli.core.profiles import ResourceType, get_sdk +from azure.cli.core.azclierror import AzCLIError, ResourceNotFoundError +from azure.mgmt.core.tools import parse_resource_id +from knack.log import get_logger +from enum import Enum +from msrestazure.azure_exceptions import CloudError +from azure.cli.core.commands import LongRunningOperation +from azure.core.polling import PollingMethod +from azure.cli.core.commands.progress import IndeterminateProgressBar + +logger = get_logger(__name__) + + +class WorkflowTaskState(Enum): + RUNNING = "Running" + SUCCEEDED = "Succeeded" + FAILED = "Failed" + QUEUED = "Queued" + SKIPPED = "Skipped" + CANCELED = "Canceled" + UNKNOWN = "Unknown" + + +class WorkflowTaskStatus: + def __init__(self, image): + repo = image.split(':') + # string to represent the repository + self.repository = repo[0] + + # string to represent the tag + self.tag = repo[1] + + # latest taskrun object for the scan task. If none, means no scan task has been run + self.scan_task = None + + # not sure if we should be proactive to get the logs for the scan task, but it will also be that we don't + # know that the scan task has been run until we check for logs + self.scan_logs = "" + + # latest taskrun object for the patch task, if none, means no patch task has been run + self.patch_task = None + + # ditto for patch logs, we don't know if the patch task has been run until we check for logs + self.patch_logs = "" + + def image(self): + return f"{self.repository}:{self.tag}" + + # task run status from src\ACR.Build.Contracts\src\Status.cs + @staticmethod + def _task_status_to_workflow_status(task): + if task is None: + return WorkflowTaskState.UNKNOWN.value + + status = task.status.lower() + status_mapping = { + TaskRunStatus.Succeeded.value.lower(): WorkflowTaskState.SUCCEEDED.value, + TaskRunStatus.Running.value.lower(): WorkflowTaskState.RUNNING.value, + TaskRunStatus.Started.value.lower(): WorkflowTaskState.RUNNING.value, + TaskRunStatus.Queued.value.lower(): WorkflowTaskState.QUEUED.value, + TaskRunStatus.Canceled.value.lower(): WorkflowTaskState.CANCELED.value, + TaskRunStatus.Failed.value.lower(): WorkflowTaskState.FAILED.value, + TaskRunStatus.Error.value.lower(): WorkflowTaskState.FAILED.value, + TaskRunStatus.Timeout.value.lower(): WorkflowTaskState.FAILED.value, + } + + return status_mapping.get(status, WorkflowTaskState.UNKNOWN.value) + + @staticmethod + def _workflow_status_to_task_status(status): + status_mapping = { + WorkflowTaskState.SUCCEEDED.value: [TaskRunStatus.Succeeded.value], + WorkflowTaskState.SKIPPED.value: [TaskRunStatus.Succeeded.value], + WorkflowTaskState.RUNNING.value: [TaskRunStatus.Running.value, + TaskRunStatus.Started.value], + WorkflowTaskState.QUEUED.value: [TaskRunStatus.Queued.value], + WorkflowTaskState.CANCELED.value: [TaskRunStatus.Canceled.value], + WorkflowTaskState.FAILED.value: [TaskRunStatus.Failed.value, + TaskRunStatus.Error.value, + TaskRunStatus.Timeout.value], + } + + return status_mapping.get(status, None) + + def scan_status(self): + return WorkflowTaskStatus._task_status_to_workflow_status(self.scan_task) + + def patch_status(self): + # this one is a bit more complicated, because the patch status depends on the scan status + # or more correctly, the patch status is the scan status if there is no patch task + # and the whole workflow status is both the scan and patch status + if self.patch_task is None and self.scan_status() == WorkflowTaskState.SUCCEEDED.value: + return WorkflowTaskState.SKIPPED.value + return WorkflowTaskStatus._task_status_to_workflow_status(self.patch_task) + + def status(self): + if self.patch_task is None: + return self.scan_status() + + return self.patch_status() + + # this extracts the image from the copacetic task logs, using this when we only have a repository + # name and a wildcard tag + @staticmethod + def _get_image_from_tasklog(logs): + match = re.search(r'Scanning repo: (\S+), Tag:\S+, OriginalTag:(\S+)', logs) + if match: + repository = match.group(1) + original_tag = match.group(2) + return f"{repository}:{original_tag}" + + match = re.search(r'Scanning image for vulnerability(?: and patch)? (\S+) for tag (\S+)', logs) + if match: + patched_image = match.group(1) + original_tag = match.group(2) + repository = patched_image.split(':')[0] + return f"{repository}:{original_tag}" + + match = re.search(r'Patching OS vulnerabilities for image (\S+):(\S+)', logs) + if match: + repository = match.group(1) + original_tag = match.group(2) + return f"{repository}:{original_tag}" + return None + + def get_patch_task_from_scan_tasklog(self): + if self.scan_task is None: + return None + + match = re.search(r'WARNING: Queued a run with ID: (\S+)', self.scan_logs) + if match: + return match.group(1) + return None + + def _get_scanning_repo_from_scan_task(self): + if self.scan_task is None: + return None + + if self.patch_status() == WorkflowTaskState.SKIPPED.value or \ + self.patch_status() == WorkflowTaskState.SUCCEEDED.value: + match = re.search( + r'PATCHING task scheduled for image (\S+):(\S+), new patch tag will be (\S+)', + self.scan_logs) + if match: + repository = match.group(1) + original_tag = match.group(2) + patched_tag = match.group(3) + return repository, patched_tag, original_tag + + match = re.search(r'Scanning repo: (\S+), Tag:(\S+), OriginalTag:(\S+)', self.scan_logs) + if match: + repository = match.group(1) + patched_tag = match.group(2) + original_tag = match.group(3) + return repository, patched_tag, original_tag + return None + + def _get_patch_skip_reason_from_tasklog(self): + if self.scan_task is None: + return None + match = re.search(r'PATCHING will be skipped as (.+)\n', self.scan_logs) + if match: + return match.group(1) + + def _get_patch_error_reason_from_tasklog(self): + if self.patch_task is None: + return None + return self._get_errors_from_tasklog(self.patch_logs) + + def _get_scan_error_reason_from_tasklog(self): + if self.scan_task is None: + return None + return self._get_errors_from_tasklog(self.scan_logs) + + def _get_errors_from_tasklog(self, tasklog): + match = re.findall(r'(?i)\b(error\b.*)', tasklog) + # TODO: should we filter out any error? + if match: + # retrieve only unique errors, sort them to make the output deterministic + return str.join("\n", sorted(set(match))) + return None + + def _get_patched_image_name_from_tasklog(self): + if self.scan_task is None: + return None + + repository, patched_tag, _ = self._get_scanning_repo_from_scan_task() + if repository is not None and patched_tag is not None: + return f"{repository}:{patched_tag}" + + if self.patch_task is None: + return None + + match = re.search(r'Patched image pushed to (\S+)', self.patch_logs) + if match: + return match.group(1) + return None + + @staticmethod + def _latest_task(this_task, this_log, that_task, that_log): + if this_task is None: + return (that_task, that_log) + if that_task is None: + return (this_task, this_log) + return (this_task, this_log) if this_task.create_time > that_task.create_time else (that_task, that_log) + + @staticmethod + def _retrieve_all_tasklogs(cmd, taskrun_client, registry, taskruns, progress_indicator=None): + import concurrent.futures + resource_group = parse_resource_id(registry.id)[RESOURCE_GROUP] + + def process_taskrun(taskrun): + try: + tasklog = WorkflowTaskStatus.generate_logs(cmd, + taskrun_client, + taskrun.run_id, + registry.name, + resource_group, + await_task_run=False) + if tasklog == "": + logger.debug("Taskrun: %s has no logs, silent failure", taskrun.run_id) + taskrun.task_log_result = tasklog + except ResourceNotFoundError as e: + logger.debug("Failed to get logs for taskrun: %s with exception: %s", taskrun.run_id, e) + + with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor: + futures = [] + for taskrun in taskruns: + if progress_indicator: + progress_indicator.update_progress() + + future = executor.submit(process_taskrun, taskrun) + futures.append(future) + + # Wait for all threads to complete + concurrent.futures.wait(futures) + + @staticmethod + def from_taskrun(cmd, taskrun_client, registry, scan_taskruns, patch_taskruns, progress_indicator=None): + WorkflowTaskStatus._retrieve_all_tasklogs(cmd, taskrun_client, registry, scan_taskruns, progress_indicator) + all_status = {} + + # we only retrieve logs for scan taskruns in the begining, we will need to retrieved the logs for + # failed patch taskruns to populate patch_error_reason. It will be faster to retrieve in a batch + failed_patch_tasklog_retrieval = [] + + for scan in scan_taskruns: + if progress_indicator: + progress_indicator.update_progress() + if hasattr(progress_indicator, 'hook') and \ + hasattr(progress_indicator.hook, 'active_progress') and \ + hasattr(progress_indicator.hook.active_progress, 'spinner'): + progress_indicator.hook.active_progress.spinner.step(label=progress_indicator.message) + if not hasattr(scan, 'task_log_result'): + logger.debug("Scan Taskrun: %s has no logs, silent failure", scan.run_id) + continue + + image = WorkflowTaskStatus._get_image_from_tasklog(scan.task_log_result) + + if not image: + continue + + # need to check if we have the latest scan task + task = scan + logs = scan.task_log_result + + if image in all_status: + task, logs = WorkflowTaskStatus._latest_task(all_status[image].scan_task, + all_status[image].scan_logs, + scan, scan.task_log_result) + else: + all_status[image] = WorkflowTaskStatus(image) + all_status[image].scan_task = task + all_status[image].scan_logs = logs + patch_task_id = all_status[image].get_patch_task_from_scan_tasklog() + # missing the patch task id means that the scan either failed, or succeeded and patching is not needed. + # this is important, because patching status depends on both the patching task status (if it exists) + # and the scan task status + if patch_task_id is not None: + # it is possible for the patch task to be mentioned in the logs, but the API has not returned the + # taskrun for it yet, attempt to retrieve it from client + try: + patch_task = next((task for task in patch_taskruns if task.run_id == patch_task_id)) + except StopIteration: + patch_task = WorkflowTaskStatus._get_missing_taskrun(taskrun_client, registry, patch_task_id) + + all_status[image].patch_task = patch_task + if WorkflowTaskStatus._task_status_to_workflow_status(patch_task) == WorkflowTaskState.FAILED.value: + failed_patch_tasklog_retrieval.append(all_status[image]) + + if len(failed_patch_tasklog_retrieval) > 0: + taskrunList = [task.patch_task for task in failed_patch_tasklog_retrieval] + WorkflowTaskStatus._retrieve_all_tasklogs(cmd, taskrun_client, registry, taskrunList, progress_indicator) + for workflow_status in failed_patch_tasklog_retrieval: + workflow_status.patch_logs = workflow_status.patch_task.task_log_result + + return [status.get_status() for status in all_status.values()] + + def get_status(self): + scan_status = self.scan_status() + scan_date = WORKFLOW_STATUS_NOT_AVAILABLE if self.scan_task is None else self.scan_task.create_time + scan_task_id = WORKFLOW_STATUS_NOT_AVAILABLE if self.scan_task is None else self.scan_task.run_id + patch_status = self.patch_status() + patch_date = WORKFLOW_STATUS_NOT_AVAILABLE if self.patch_task is None else self.patch_task.create_time + patch_task_id = WORKFLOW_STATUS_NOT_AVAILABLE if self.patch_task is None else self.patch_task.run_id + patched_image = self._get_patched_image_name_from_tasklog() + workflow_type = CSSCTaskTypes.ContinuousPatchV1.value + patch_skipped_reason = "" + scan_error_reason = "" + patch_error_reason = "" + + # this situation means that we don't have a patched image + if self.patch_status() == WorkflowTaskState.SKIPPED.value: + patch_skipped_reason = self._get_patch_skip_reason_from_tasklog() + + if self.scan_status() == WorkflowTaskState.FAILED.value: + scan_error_reason = self._get_scan_error_reason_from_tasklog() + + if self.patch_status() == WorkflowTaskState.FAILED.value: + patch_error_reason = self._get_patch_error_reason_from_tasklog() + + if patched_image == self.image(): + patched_image = WORKFLOW_STATUS_PATCH_NOT_AVAILABLE + + result = { + "image": f"{self.repository}:{self.tag}", + "scan_status": scan_status, + "scan_date": scan_date, + "scan_task_ID": scan_task_id, + "patch_status": patch_status + } + + if patch_skipped_reason != "": + result["patch_skipped_reason"] = patch_skipped_reason + + if scan_error_reason != "": + result["scan_error_reason"] = scan_error_reason + + if patch_error_reason != "": + result["patch_error_reason"] = patch_error_reason + + result["patch_date"] = patch_date + result["patch_task_ID"] = patch_task_id + result["last_patched_image"] = patched_image + result["workflow_type"] = workflow_type + + return result + + def __str__(self) -> str: + status = self.get_status() + result = f"image: {status.repository}:{status.tag}\n" \ + f"\tscan status: {status.scan_status}\n" \ + f"\tscan date: {status.scan_date}\n" \ + f"\tscan task ID: {status.scan_task_id}\n" + + if hasattr(status, "scan_error_reason"): + result += f"\tscan error reason: {status.scan_error_reason}\n" + + result += f"\tpatch status: {status.patch_status}\n" + + if hasattr(status, "patch_error_reason"): + result += f"\tpatch error reason: {status.patch_error_reason}\n" + + if hasattr(status, "patch_skipped_reason"): + result += f"\tpatch skipped reason: {status.patch_skipped_reason}\n" + + result += f"\tpatch date: {status.patch_date}\n" \ + f"\tpatch task ID: {status.patch_task_id}\n" \ + f"\tlast patched image: {status.patched_image}\n" \ + f"\tworkflow type: {status.workflow_type}" + + return result + + @staticmethod + def generate_logs(cmd, client, run_id, registry_name, resource_group_name, await_task_run=True, await_task_message=None): + + log_file_sas = None + error_msg = "Could not get logs for ID: {}".format(run_id) + try: + response = client.get_log_sas_url( + resource_group_name=resource_group_name, + registry_name=registry_name, + run_id=run_id) + log_file_sas = response.log_link + except (AttributeError, CloudError) as e: + logger.debug("%s Exception: %s", error_msg, e) + raise AzCLIError(error_msg) + except ResourceNotFoundError as e: + logger.debug("log file not found for run_id: %s, registry: %s, " + "resource_group: %s -- exception: %s", + run_id, registry_name, resource_group_name, e) + + if await_task_run: + polling_method = WorkflowLogPollingMethod(client, resource_group_name, registry_name, run_id) + LongRunningOperation(cmd.cli_ctx, progress_bar=IndeterminateProgressBar(cmd.cli_ctx, message=await_task_message))(polling_method) + + blobClient = get_sdk(cmd.cli_ctx, ResourceType.DATA_STORAGE_BLOB, '_blob_client#BlobClient') + return WorkflowTaskStatus._download_logs(blobClient.from_blob_url(log_file_sas)) + + @staticmethod + def _evaluate_task_run_nonterminal_state(run_status): + return run_status != TaskRunStatus.Succeeded.value and run_status != TaskRunStatus.Failed.value + + @staticmethod + def _get_run_status_local(client, resource_group_name, registry_name, run_id): + try: + response = client.get(resource_group_name, registry_name, run_id) + return response.status + except (AttributeError, CloudError): + return None + + @staticmethod + def _download_logs(blob_service): + blob = blob_service.download_blob() + blob_text = blob.readall().decode('utf-8') + + return blob_text + + @staticmethod + def remove_internal_acr_statements(blob_content): + lines = blob_content.split("\n") + starting_identifier = "DRY RUN mode enabled" + terminating_identifier = "Total matches found" + print_line = False + output = "" + + for line in lines: + if line.startswith(starting_identifier): + print_line = True + elif line.startswith(terminating_identifier): + output += "\n" + line + print_line = False + + if print_line: + output += "\n" + line + + return output + + @staticmethod + def _get_missing_taskrun(taskrun_client, registry, run_id): + try: + resourceid = parse_resource_id(registry.id) + resource_group = resourceid[RESOURCE_GROUP] + runs = WorkflowTaskStatus.get_taskruns_with_filter(taskrun_client, + registry_name=registry.name, + resource_group_name=resource_group, + runId_filter=run_id) + return runs[0] + except ResourceNotFoundError as e: + logger.debug("Failed to find taskrun %s from registry %s: %s", run_id, registry.name, e) + return None + + @staticmethod + def get_taskruns_with_filter(acr_task_run_client, + registry_name, + resource_group_name, + taskname_filter=None, + runId_filter=None, + date_filter=None, + status_filter=None, + top=1000): + # filters based on OData, found in ACR.BuildRP.DataModels - RunFilter.cs + filter_str = "" + if taskname_filter: + taskname_filter_str = "', '".join(taskname_filter) + filter_str += f"TaskName in ('{taskname_filter_str}')" + + if runId_filter: + if filter_str != "": + filter_str += " and " + filter_str += f"runId eq '{runId_filter}'" + + if date_filter: + if filter_str != "": + filter_str += " and " + filter_str += f"createTime ge {date_filter}" + + if status_filter: + if filter_str != "": + filter_str += " and " + status_filter_str = "', '".join(status_filter) + filter_str += f"Status in ('{status_filter_str}')" + + taskruns = acr_task_run_client.list(resource_group_name, registry_name, filter=filter_str, top=top) + return list(taskruns) + +class WorkflowLogPollingMethod(PollingMethod): + def __init__(self, client, resource_group_name, registry_name, run_id): + self.client = client + self.resource_group_name = resource_group_name + self.registry_name = registry_name + self.run_id = run_id + self.run_status = TaskRunStatus.Running.value + + def initialize(self, client, initial_response, deserialization_callback): + pass + + def run(self): + + while WorkflowTaskStatus._evaluate_task_run_nonterminal_state(self.run_status): + self.run_status = WorkflowTaskStatus._get_run_status_local(self.client, self.resource_group_name, self.registry_name, self.run_id) + if WorkflowTaskStatus._evaluate_task_run_nonterminal_state(self.run_status): + logger.debug("Waiting for the task run to complete. Current status: %s", self.run_status) + time.sleep(2) + + def status(self): + return WorkflowTaskStatus._get_run_status_local(self.client, self.resource_group_name, self.registry_name, self.run_id) + + def finished(self): + return not WorkflowTaskStatus._evaluate_task_run_nonterminal_state(self.status()) + + def done(self): + return self.finished() + + def result(self): + return self.run_status + + def resource(self): + return self.status() diff --git a/src/acrcssc/azext_acrcssc/templates/acrcli/artifact.json b/src/acrcssc/azext_acrcssc/templates/acrcli/artifact.json new file mode 100644 index 00000000000..57b2dbb85c4 --- /dev/null +++ b/src/acrcssc/azext_acrcssc/templates/acrcli/artifact.json @@ -0,0 +1,12 @@ +{ + "repositories": [ + { + "enabled": true, + "repository": "python", + "tags": [ + "*" + ] + } + ], + "version": "v1" +} \ No newline at end of file diff --git a/src/acrcssc/azext_acrcssc/templates/arm/CSSC-AutoImagePatching-encodedtasks.json b/src/acrcssc/azext_acrcssc/templates/arm/CSSC-AutoImagePatching-encodedtasks.json new file mode 100644 index 00000000000..0c09d4ab7f0 --- /dev/null +++ b/src/acrcssc/azext_acrcssc/templates/arm/CSSC-AutoImagePatching-encodedtasks.json @@ -0,0 +1,147 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "AcrName": { + "type": "string" + }, + "AcrLocation": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "taskSchedule": { + "type": "string" + }, + "imagePatchingEncodedTask": { + "type": "string" + }, + "imageScanningEncodedTask": { + "type": "string" + }, + "registryScanningEncodedTask": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.ContainerRegistry/registries/tasks", + "apiVersion": "2019-06-01-preview", + "name": "[format('{0}/{1}', parameters('AcrName'), 'cssc-patch-image')]", + "location": "[parameters('AcrLocation')]", + "tags": { + "cssc": "true", + "clienttracking": "true" + }, + "properties": { + "platform": { + "os": "linux", + "architecture": "amd64" + }, + "agentConfiguration": { + "cpu": 2 + }, + "timeout": 3600, + "step": { + "type": "EncodedTask", + "encodedTaskContent": "[parameters('imagePatchingEncodedTask')]", + "values": [] + }, + "isSystemTask": false + } + }, + { + "type": "Microsoft.ContainerRegistry/registries/tasks", + "apiVersion": "2019-06-01-preview", + "name": "[format('{0}/{1}', parameters('AcrName'), 'cssc-scan-image')]", + "location": "[parameters('AcrLocation')]", + "identity": { + "type": "SystemAssigned" + }, + "tags": { + "cssc": "true" + }, + "properties": { + "platform": { + "os": "linux", + "architecture": "amd64" + }, + "agentConfiguration": { + "cpu": 2 + }, + "timeout": 3600, + "step": { + "type": "EncodedTask", + "encodedTaskContent": "[parameters('imageScanningEncodedTask')]", + "values": [] + }, + "isSystemTask": false + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('AcrName'))]", + "name": "[guid(resourceId('Microsoft.ContainerRegistry/registries/tasks', parameters('AcrName'), 'cssc-scan-image'), subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c'))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "principalId": "[reference(resourceId('Microsoft.ContainerRegistry/registries/tasks', parameters('AcrName'), 'cssc-scan-image'), '2019-06-01-preview', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerRegistry/registries/tasks', parameters('AcrName'), 'cssc-scan-image')]" + ] + }, + { + "type": "Microsoft.ContainerRegistry/registries/tasks", + "apiVersion": "2019-06-01-preview", + "name": "[format('{0}/{1}', parameters('AcrName'), 'cssc-trigger-workflow')]", + "location": "[parameters('AcrLocation')]", + "identity": { + "type": "SystemAssigned" + }, + "tags": { + "cssc": "true", + "clienttracking": "true" + }, + "properties": { + "platform": { + "os": "linux", + "architecture": "amd64" + }, + "agentConfiguration": { + "cpu": 2 + }, + "timeout": 3600, + "status": "Enabled", + "step": { + "type": "EncodedTask", + "encodedTaskContent": "[parameters('registryScanningEncodedTask')]", + "values": [] + }, + "isSystemTask": false, + "trigger": { + "timerTriggers": [ + { + "name": "azcli_defined_schedule", + "schedule": "[parameters('taskSchedule')]" + } + ] + } + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('AcrName'))]", + "name": "[guid(resourceId('Microsoft.ContainerRegistry/registries/tasks', parameters('AcrName'), 'cssc-trigger-workflow'), subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c'))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "principalId": "[reference(resourceId('Microsoft.ContainerRegistry/registries/tasks', parameters('AcrName'), 'cssc-trigger-workflow'), '2019-06-01-preview', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerRegistry/registries/tasks', parameters('AcrName'), 'cssc-trigger-workflow')]" + ] + } + ] +} \ No newline at end of file diff --git a/src/acrcssc/azext_acrcssc/templates/task/cssc_patch_image.yaml b/src/acrcssc/azext_acrcssc/templates/task/cssc_patch_image.yaml new file mode 100644 index 00000000000..8f19e1b75ba --- /dev/null +++ b/src/acrcssc/azext_acrcssc/templates/task/cssc_patch_image.yaml @@ -0,0 +1,62 @@ +version: v1.1.0 +alias: + values: + ScanReport : os-vulnerability-report_trivy_{{ regexReplaceAll "[^a-zA-Z0-9]" .Values.SOURCE_REPOSITORY "-" }}_{{.Values.SOURCE_IMAGE_TAG}}_$(date "+%Y-%m-%d").json + cssc : mcr.microsoft.com/acr/cssc:9fb281c +steps: + - id: print-inputs + cmd: | + bash -c 'echo "Patching OS vulnerabilities for image {{.Values.SOURCE_REPOSITORY}}:{{.Values.SOURCE_IMAGE_TAG}}"' + bash -c 'echo "Patching repo: {{.Values.SOURCE_REPOSITORY}}, Tag:{{.Values.SOURCE_IMAGE_TAG}}, NewPatchTag:{{.Values.SOURCE_IMAGE_NEWPATCH_TAG}}"' + + - id: check-patch-tag + cmd: | + bash -c 'echo "New Patch tag is {{.Values.SOURCE_IMAGE_NEWPATCH_TAG}}" + if [ "{{.Values.SOURCE_IMAGE_NEWPATCH_TAG}}" != "patched" ] && [ {{.Values.SOURCE_IMAGE_NEWPATCH_TAG}} -gt 999 ]; then + echo "New Patch tag is greater than 999. No more than 1000 patches can be created for a tag. Exiting the patching workflow." + exit 1 + fi' + + - id: setup-data-dir + cmd: bash mkdir ./data + - id: generate-trivy-report + retries: 3 + retryDelay: 5 + timeout: 1800 + cmd: | + cssc trivy image \ + {{.Run.Registry}}/{{.Values.SOURCE_REPOSITORY}}:{{.Values.SOURCE_IMAGE_TAG}} \ + --pkg-types os \ + --ignore-unfixed \ + --format json \ + --timeout 30m \ + --scanners vuln \ + --db-repository "ghcr.io/aquasecurity/trivy-db:2","public.ecr.aws/aquasecurity/trivy-db" \ + --output /workspace/data/$ScanReport + + - id: buildkitd + cmd: mobybuildkit --addr tcp://0.0.0.0:8888 + entrypoint: buildkitd + detach: true + privileged: true + ports: ["127.0.0.1:8888:8888/tcp"] + + - id: patch-image + retries: 3 + retryDelay: 5 + timeout: 1800 + cmd: | + cssc copa patch \ + -i "{{.Run.Registry}}/{{.Values.SOURCE_REPOSITORY}}:{{.Values.SOURCE_IMAGE_TAG}}" \ + -r ./data/$ScanReport \ + -t "{{.Values.SOURCE_IMAGE_TAG}}-{{.Values.SOURCE_IMAGE_NEWPATCH_TAG}}" \ + --timeout 30m \ + --addr tcp://127.0.0.1:8888 + network: host + + - id: push-image + retries: 3 + retryDelay: 5 + timeout: 1800 + cmd: docker push {{.Run.Registry}}/{{.Values.SOURCE_REPOSITORY}}:{{.Values.SOURCE_IMAGE_TAG}}-{{.Values.SOURCE_IMAGE_NEWPATCH_TAG}} + - cmd: bash echo "Patched image pushed to {{.Run.Registry}}/{{.Values.SOURCE_REPOSITORY}}:{{.Values.SOURCE_IMAGE_TAG}}-{{.Values.SOURCE_IMAGE_NEWPATCH_TAG}}" \ No newline at end of file diff --git a/src/acrcssc/azext_acrcssc/templates/task/cssc_scan_image.yaml b/src/acrcssc/azext_acrcssc/templates/task/cssc_scan_image.yaml new file mode 100644 index 00000000000..9b33634a93c --- /dev/null +++ b/src/acrcssc/azext_acrcssc/templates/task/cssc_scan_image.yaml @@ -0,0 +1,55 @@ +version: v1.1.0 +alias: + values: + patchimagetask: cssc-patch-image + DATE: $(date "+%Y-%m-%d") + cssc : mcr.microsoft.com/acr/cssc:9fb281c +steps: + - id: print-inputs + cmd: | + bash -c 'echo "Scanning image for vulnerability {{.Values.SOURCE_REPOSITORY}}:{{.Values.SOURCE_IMAGE_TAG}} for tag {{.Values.SOURCE_IMAGE_ORIGINAL_TAG}}"' + bash -c 'echo "Scanning repo: {{.Values.SOURCE_REPOSITORY}}, Tag:{{.Values.SOURCE_IMAGE_TAG}}, OriginalTag:{{.Values.SOURCE_IMAGE_ORIGINAL_TAG}}"' + - id: setup-data-dir + cmd: bash mkdir ./data + + - id: generate-trivy-report + retries: 3 + retryDelay: 5 + timeout: 1800 + cmd: | + cssc trivy image \ + {{.Run.Registry}}/{{.Values.SOURCE_REPOSITORY}}:{{.Values.SOURCE_IMAGE_TAG}} \ + --pkg-types os \ + --ignore-unfixed \ + --format json \ + --timeout 30m \ + --scanners vuln \ + --db-repository "ghcr.io/aquasecurity/trivy-db:2","public.ecr.aws/aquasecurity/trivy-db" \ + --output /workspace/data/vulnerability-report_trivy_$DATE.json + + - cmd: cssc jq 'if .Results == null or (.Results | length) == 0 then 0 else [.Results[] | select(.Vulnerabilities != null) | .Vulnerabilities | length] | add end' /workspace/data/vulnerability-report_trivy_$DATE.json > /workspace/data/vulCount.txt + - cmd: cssc jq 'if .Metadata.OS.EOSL == null then false else .Metadata.OS.EOSL end' /workspace/data/vulnerability-report_trivy_$DATE.json > /workspace/data/eosl.txt + - cmd: | + bash -c 'echo "Vulnerabilities found for image {{.Values.SOURCE_REPOSITORY}}:{{.Values.SOURCE_IMAGE_TAG}} -> $(cat /workspace/data/vulCount.txt)"' + bash -c 'echo "EOSL for the image {{.Values.SOURCE_REPOSITORY}}:{{.Values.SOURCE_IMAGE_TAG}} -> $(cat /workspace/data/eosl.txt)"' + + - id: trigger-patch-task + retries: 3 + retryDelay: 10 + timeout: 1800 + cmd: | + az -c ' + vulCount=$(cat /workspace/data/vulCount.txt) && \ + eoslValue=$(cat /workspace/data/eosl.txt) && \ + if ! [[ "$vulCount" =~ ^[0-9]+$ ]]; then vulCount=0; fi && \ + if [ "$eoslValue" = "true" ]; then \ + echo "PATCHING will be skipped as EOSL is $eoslValue for image {{.Values.SOURCE_REPOSITORY}}:{{.Values.SOURCE_IMAGE_TAG}}"; \ + elif [ $vulCount -gt 0 ]; then \ + az login --identity; \ + echo "Total OS vulnerabilities found -> $vulCount"; \ + echo "PATCHING task scheduled for image {{.Values.SOURCE_REPOSITORY}}:{{.Values.SOURCE_IMAGE_TAG}}, new patch tag will be {{.Values.SOURCE_IMAGE_ORIGINAL_TAG}}-{{.Values.SOURCE_IMAGE_NEWPATCH_TAG}}"; \ + az acr task run --name $patchimagetask --registry $RegistryName --set SOURCE_REPOSITORY={{.Values.SOURCE_REPOSITORY}} --set SOURCE_IMAGE_TAG={{.Values.SOURCE_IMAGE_ORIGINAL_TAG}} --set SOURCE_IMAGE_NEWPATCH_TAG={{.Values.SOURCE_IMAGE_NEWPATCH_TAG}} --no-wait; \ + else \ + echo "PATCHING will be skipped as no vulnerability found in the image {{.Values.SOURCE_REPOSITORY}}:{{.Values.SOURCE_IMAGE_TAG}}"; \ + fi' + entryPoint: /bin/bash \ No newline at end of file diff --git a/src/acrcssc/azext_acrcssc/templates/task/cssc_trigger_workflow.yaml b/src/acrcssc/azext_acrcssc/templates/task/cssc_trigger_workflow.yaml new file mode 100644 index 00000000000..d0f2c6f9e89 --- /dev/null +++ b/src/acrcssc/azext_acrcssc/templates/task/cssc_trigger_workflow.yaml @@ -0,0 +1,62 @@ +version: v1.1.0 +alias: + values: + ScanImageAndSchedulePatchTask: cssc-scan-image + cssc : mcr.microsoft.com/acr/cssc:9fb281c + maxLimit: 100 +steps: + - cmd: bash -c 'echo "Inside cssc-trigger-workflow task, getting list of images to be patched based on --filter-policy for Registry {{.Run.Registry}}."' + - cmd: cssc acr cssc patch --filter-policy csscpolicies/patchpolicy:v1 --dry-run > filterRepos.txt + env: + - ACR_EXPERIMENTAL_CSSC=true + - cmd: bash -c 'sed -n "/^Validating/,/^Total/ {/^Validating/b;/^Total/b;p}" filterRepos.txt' > filterReposToDisplay.txt + - cmd: | + bash -c ' + echo "Below images will be scanned and patched (if any os vulnerabilities found) based on --filter-policy.\n$(cat filterReposToDisplay.txt)" + totalImages=$(sed -n "s/^Matches found://p" filterReposToDisplay.txt | tr -d "[:space:]") + if [ $totalImages -gt $maxLimit ]; then + echo "You have exceeded the maximum limit of $maxLimit images that can be scheduled for continuous patching. Adjust the JSON filter to limit the number of images. Failing the workflow." + exit 1 + fi' + - cmd: cssc acr cssc patch --filter-policy csscpolicies/patchpolicy:v1 --show-patch-tags --dry-run> filterReposWithPatchTags.txt + env: + - ACR_EXPERIMENTAL_CSSC=true + - cmd: bash -c 'sed -n "/^Listing/,/^Matches/ {/^Listing/b;/^Matches/b;/^Repo/b;p}" filterReposWithPatchTags.txt' > filteredReposAndTags.txt + - cmd: bash -c 'sed -n "/^Configured Tag Convention:/p" filterReposWithPatchTags.txt' > tagConvention.txt + - cmd: az login --identity + - id: scan-and-schedule-patch + timeout: 1800 + cmd: | + az -c ' + counter=0; \ + batchSize=10; \ + sleepDuration=30; \ + RegistryName={{.Run.Registry}}; \ + while read line;do \ + IFS=',' read -r -a array <<< "${line}" + RepoName=${array[0]} + OriginalTag=${array[1]} + TagName=${array[2]} + IncrementedTagNumber="" + echo "Tag Convention details: $(cat tagConvention.txt)" + if grep -q "floating" tagConvention.txt; then + IncrementedTagNumber="patched" + else + IncrementedTagNumber="1" + fi + + if [ $TagName == "N/A" ]; then + TagName=$OriginalTag + elif [[ $TagName =~ -([0-9]{1,3})$ ]]; then + TagNumber=${BASH_REMATCH[1]} + IncrementedTagNumber=$((TagNumber+1)) + fi + echo "Scheduling $ScanImageAndSchedulePatchTask for $RegistryName/$RepoName, Tag:$TagName, OriginalTag:$OriginalTag, PatchTag:$OriginalTag-$IncrementedTagNumber"; \ + az acr task run --name $ScanImageAndSchedulePatchTask --registry $RegistryName --set SOURCE_REPOSITORY=$RepoName --set SOURCE_IMAGE_TAG=$TagName --set SOURCE_IMAGE_ORIGINAL_TAG=$OriginalTag --set SOURCE_IMAGE_NEWPATCH_TAG=$IncrementedTagNumber --no-wait; \ + counter=$((counter+1)); \ + if [ $((counter%batchSize)) -eq 0 ]; then \ + echo "Waiting for $sleepDuration seconds before scheduling scans for next batch of images"; \ + sleep $sleepDuration; \ + fi; \ + done < filteredReposAndTags.txt;' + entryPoint: /bin/bash \ No newline at end of file diff --git a/src/acrcssc/azext_acrcssc/templates/tmp_dry_run_template.yaml b/src/acrcssc/azext_acrcssc/templates/tmp_dry_run_template.yaml new file mode 100644 index 00000000000..7438cc9edf3 --- /dev/null +++ b/src/acrcssc/azext_acrcssc/templates/tmp_dry_run_template.yaml @@ -0,0 +1,17 @@ +version: v1.1.0 +alias: + values: + cssc : mcr.microsoft.com/acr/cssc:9fb281c + maxLimit: 100 +steps: + - id: acr-cli-filter + cmd: cssc acr cssc patch --dry-run --filter-policy-file {{.Values.CONFIGPATH}}> filterRepos.txt; + env: + - ACR_EXPERIMENTAL_CSSC=true + - cmd: | + bash -c ' + echo "$(cat filterRepos.txt)" + totalImages=$(sed -n "s/^Matches found://p" filterRepos.txt | tr -d "[:space:]") + if [ $totalImages -gt $maxLimit ]; then + echo "You have exceeded the maximum limit of $maxLimit images that can be scheduled for continuous patching. Adjust the JSON filter to limit the number of images." + fi' \ No newline at end of file diff --git a/src/acrcssc/azext_acrcssc/tests/__init__.py b/src/acrcssc/azext_acrcssc/tests/__init__.py new file mode 100644 index 00000000000..2dcf9bb68b3 --- /dev/null +++ b/src/acrcssc/azext_acrcssc/tests/__init__.py @@ -0,0 +1,5 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# ----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/acrcssc/azext_acrcssc/tests/latest/__init__.py b/src/acrcssc/azext_acrcssc/tests/latest/__init__.py new file mode 100644 index 00000000000..2dcf9bb68b3 --- /dev/null +++ b/src/acrcssc/azext_acrcssc/tests/latest/__init__.py @@ -0,0 +1,5 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# ----------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/acrcssc/azext_acrcssc/tests/latest/artifact.json b/src/acrcssc/azext_acrcssc/tests/latest/artifact.json new file mode 100644 index 00000000000..116f9ac33dd --- /dev/null +++ b/src/acrcssc/azext_acrcssc/tests/latest/artifact.json @@ -0,0 +1,12 @@ +{ + "repositories": [ + { + "enabled": true, + "repository": "python", + "tags": [ + "*" + ] + } + ], + "version": "v1" +} \ No newline at end of file diff --git a/src/acrcssc/azext_acrcssc/tests/latest/recordings/test_create_supplychain_workflow.yaml b/src/acrcssc/azext_acrcssc/tests/latest/recordings/test_create_supplychain_workflow.yaml new file mode 100644 index 00000000000..327733bfcdd --- /dev/null +++ b/src/acrcssc/azext_acrcssc/tests/latest/recordings/test_create_supplychain_workflow.yaml @@ -0,0 +1,1376 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - acr create + Connection: + - keep-alive + ParameterSetName: + - -g -n --sku + User-Agent: + - AZURECLI/2.61.0 azsdk-python-core/1.30.2 Python/3.8.10 (Windows-10-10.0.22631-SP0) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_acrcssc000001?api-version=2022-09-01 + response: + body: + string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001","name":"cli_test_acrcssc000001","type":"Microsoft.Resources/resourceGroups","location":"westus","tags":{"product":"azurecli","cause":"automation","test":"test_create_supplychain_workflow","date":"2024-06-17T18:18:13Z","module":"acrcssc"},"properties":{"provisioningState":"Succeeded"}}' + headers: + cache-control: + - no-cache + content-length: + - '383' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:18:14 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 0300B96CF668463EABE1B233514A7FC6 Ref B: CO6AA3150217023 Ref C: 2024-06-17T18:18:14Z' + status: + code: 200 + message: OK +- request: + body: '{"location": "westus", "sku": {"name": "Basic"}, "properties": {"adminUserEnabled": + false, "anonymousPullEnabled": false}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - acr create + Connection: + - keep-alive + Content-Length: + - '122' + Content-Type: + - application/json + ParameterSetName: + - -g -n --sku + User-Agent: + - AZURECLI/2.61.0 azsdk-python-core/1.30.2 Python/3.8.10 (Windows-10-10.0.22631-SP0) + method: PUT + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002?api-version=2023-11-01-preview + response: + body: + string: '{"sku":{"name":"Basic","tier":"Basic"},"type":"Microsoft.ContainerRegistry/registries","id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002","name":"cli000002","location":"westus","tags":{},"systemData":{"createdBy":"puwalech@microsoft.com","createdByType":"User","createdAt":"2024-06-17T18:18:15.4440214+00:00","lastModifiedBy":"puwalech@microsoft.com","lastModifiedByType":"User","lastModifiedAt":"2024-06-17T18:18:15.4440214+00:00"},"properties":{"loginServer":"cli000002.azurecr.io","creationDate":"2024-06-17T18:18:15.4440214Z","provisioningState":"Creating","adminUserEnabled":false,"policies":{"quarantinePolicy":{"status":"disabled"},"trustPolicy":{"type":"Notary","status":"disabled"},"retentionPolicy":{"days":7,"lastUpdatedTime":"2024-06-17T18:18:22.2291688+00:00","status":"disabled"},"exportPolicy":{"status":"enabled"},"azureADAuthenticationAsArmPolicy":{"status":"enabled"},"softDeletePolicy":{"retentionDays":7,"lastUpdatedTime":"2024-06-17T18:18:22.229206+00:00","status":"disabled"}},"encryption":{"status":"disabled"},"dataEndpointEnabled":false,"dataEndpointHostNames":[],"privateEndpointConnections":[],"publicNetworkAccess":"Enabled","networkRuleBypassOptions":"AzureServices","zoneRedundancy":"Disabled","anonymousPullEnabled":false,"metadataSearch":"Disabled"}}' + headers: + api-supported-versions: + - 2023-11-01-preview + azure-asyncoperation: + - https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/operationStatuses/registries-f6615e2e-2cd5-11ef-9a12-847b5765e231?api-version=2023-11-01-preview&t=638542451024440912&c=MIIHpTCCBo2gAwIBAgITfwM6vSxODJvqjFP4oQAEAzq9LDANBgkqhkiG9w0BAQsFADBEMRMwEQYKCZImiZPyLGQBGRYDR0JMMRMwEQYKCZImiZPyLGQBGRYDQU1FMRgwFgYDVQQDEw9BTUUgSW5mcmEgQ0EgMDIwHhcNMjQwNTE1MTI0NjQwWhcNMjUwNTEwMTI0NjQwWjBAMT4wPAYDVQQDEzVhc3luY29wZXJhdGlvbnNpZ25pbmdjZXJ0aWZpY2F0ZS5tYW5hZ2VtZW50LmF6dXJlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKa4cOTKf-wVpLKiG5Zei1-Oc5u5PvibFdqWIGFZDLmSA3G2jYrx6dKQ8NH10xxzVOMT_dqQOb2nPmPDhnS3CUlhwx_iI9VSftq8J182Ci01SlOzoieOj_kBg-1yQ4TB3DD7Rwgy40TMWgK-1lkliuLAgSHruwrRW8Kj8Q96A0oGxy1RQggyCNWVG8EsUp1ngtGu-yi1BZRa4Q-v_x9KFfbvtOc9KIfKRFs2r2zg4MWc4xCzQCYrRXIVfS-sFxEn1GbDqtYc4-y5T978_4OnKXidZCkJqT4v1ZRcgxKZpH8d4GmacrEfBoCqjg9ZayboCoIPz5wEIF9LOngoqXqnmYECAwEAAaOCBJIwggSOMCcGCSsGAQQBgjcVCgQaMBgwCgYIKwYBBQUHAwEwCgYIKwYBBQUHAwIwPQYJKwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIhpDjDYTVtHiE8Ys-hZvdFs6dEoFggvX2K4Py0SACAWQCAQowggHaBggrBgEFBQcBAQSCAcwwggHIMGYGCCsGAQUFBzAChlpodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpaW5mcmEvQ2VydHMvQkwyUEtJSU5UQ0EwMS5BTUUuR0JMX0FNRSUyMEluZnJhJTIwQ0ElMjAwMig0KS5jcnQwVgYIKwYBBQUHMAKGSmh0dHA6Ly9jcmwxLmFtZS5nYmwvYWlhL0JMMlBLSUlOVENBMDEuQU1FLkdCTF9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3J0MFYGCCsGAQUFBzAChkpodHRwOi8vY3JsMi5hbWUuZ2JsL2FpYS9CTDJQS0lJTlRDQTAxLkFNRS5HQkxfQU1FJTIwSW5mcmElMjBDQSUyMDAyKDQpLmNydDBWBggrBgEFBQcwAoZKaHR0cDovL2NybDMuYW1lLmdibC9haWEvQkwyUEtJSU5UQ0EwMS5BTUUuR0JMX0FNRSUyMEluZnJhJTIwQ0ElMjAwMig0KS5jcnQwVgYIKwYBBQUHMAKGSmh0dHA6Ly9jcmw0LmFtZS5nYmwvYWlhL0JMMlBLSUlOVENBMDEuQU1FLkdCTF9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3J0MB0GA1UdDgQWBBRCKTJWBui0JqrIiMW81zJdA9-tSDAOBgNVHQ8BAf8EBAMCBaAwggE1BgNVHR8EggEsMIIBKDCCASSgggEgoIIBHIZCaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraWluZnJhL0NSTC9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3JshjRodHRwOi8vY3JsMS5hbWUuZ2JsL2NybC9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3JshjRodHRwOi8vY3JsMi5hbWUuZ2JsL2NybC9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3JshjRodHRwOi8vY3JsMy5hbWUuZ2JsL2NybC9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3JshjRodHRwOi8vY3JsNC5hbWUuZ2JsL2NybC9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3JsMIGdBgNVHSAEgZUwgZIwDAYKKwYBBAGCN3sBATBmBgorBgEEAYI3ewICMFgwVgYIKwYBBQUHAgIwSh5IADMAMwBlADAAMQA5ADIAMQAtADQAZAA2ADQALQA0AGYAOABjAC0AYQAwADUANQAtADUAYgBkAGEAZgBmAGQANQBlADMAMwBkMAwGCisGAQQBgjd7AwEwDAYKKwYBBAGCN3sEATAfBgNVHSMEGDAWgBSuecJrXSWIEwb2BwnDl3x7l48dVTAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADggEBAGJpRHLgYBJ-Hg0664G6_TgQ8luNO24um3ktexLaPrnailsQdaNThyJ4w9TTpMvyG31DlS7euSnKy8IsfMzCDxu1mmgziF9Urf-OpUw3u-ze-9z_PmzXym0G-rk8OrPpWWdAeApaUIHmydJGO_yrSQURQDLY9ATNa4gS1c9rQLruie0ZkPwjhAJCwpdK615q7s9ssaQ_HZEXM9r3mojVMYMB6b7TQJcwlVHBvkRO5u4HnAI26O2e-pcDzgccXJ6mqM158VJM-AyU1D2gWCqHj4zml1U005Ot-Fx-C3N3HCVImLvAllBxeQdwzOTae6Br-eXo1NCFf1ahI2fP4G_nB7o&s=R5cpqV7w0WyOqpEINwIDuGRjnrxZN-rhFyTga9eHbWhIRSrIkN1CkPaV0r5lUam28i2s43W8Gmt2KNdhk-PE4n20u0uaT0leE4XzPkCzcCFvxydceX04PlsCkBARyXk20IuIXbcScNXz15AzZ7sNqFEmThhCBRrz8cL6LwZ580arTjcgb05uyRiC5CFqzYeOU-gkyJQAIoCR0y93xd6ydeZ_ZBEnnMee3h0n_Lzvckk22ipGd7KDXALxUgXiiGXM_IEN46qv4kRrWffGURGHFa1pXxT6-rpqks2K4qX6lURqQf5Uo3zhBHtBfgmearMLIHruZvESBJNc0J0H7e8Ubg&h=M_PyeBUvpDOuBCuSnWZcm6GScSaFG6CEpebJCZuuKeY + cache-control: + - no-cache + content-length: + - '1387' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:18:22 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: 5A45C8010E9A44FB883865F310F20113 Ref B: CO6AA3150219037 Ref C: 2024-06-17T18:18:14Z' + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - acr create + Connection: + - keep-alive + ParameterSetName: + - -g -n --sku + User-Agent: + - AZURECLI/2.61.0 azsdk-python-core/1.30.2 Python/3.8.10 (Windows-10-10.0.22631-SP0) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/operationStatuses/registries-f6615e2e-2cd5-11ef-9a12-847b5765e231?api-version=2023-11-01-preview&t=638542451024440912&c=MIIHpTCCBo2gAwIBAgITfwM6vSxODJvqjFP4oQAEAzq9LDANBgkqhkiG9w0BAQsFADBEMRMwEQYKCZImiZPyLGQBGRYDR0JMMRMwEQYKCZImiZPyLGQBGRYDQU1FMRgwFgYDVQQDEw9BTUUgSW5mcmEgQ0EgMDIwHhcNMjQwNTE1MTI0NjQwWhcNMjUwNTEwMTI0NjQwWjBAMT4wPAYDVQQDEzVhc3luY29wZXJhdGlvbnNpZ25pbmdjZXJ0aWZpY2F0ZS5tYW5hZ2VtZW50LmF6dXJlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKa4cOTKf-wVpLKiG5Zei1-Oc5u5PvibFdqWIGFZDLmSA3G2jYrx6dKQ8NH10xxzVOMT_dqQOb2nPmPDhnS3CUlhwx_iI9VSftq8J182Ci01SlOzoieOj_kBg-1yQ4TB3DD7Rwgy40TMWgK-1lkliuLAgSHruwrRW8Kj8Q96A0oGxy1RQggyCNWVG8EsUp1ngtGu-yi1BZRa4Q-v_x9KFfbvtOc9KIfKRFs2r2zg4MWc4xCzQCYrRXIVfS-sFxEn1GbDqtYc4-y5T978_4OnKXidZCkJqT4v1ZRcgxKZpH8d4GmacrEfBoCqjg9ZayboCoIPz5wEIF9LOngoqXqnmYECAwEAAaOCBJIwggSOMCcGCSsGAQQBgjcVCgQaMBgwCgYIKwYBBQUHAwEwCgYIKwYBBQUHAwIwPQYJKwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIhpDjDYTVtHiE8Ys-hZvdFs6dEoFggvX2K4Py0SACAWQCAQowggHaBggrBgEFBQcBAQSCAcwwggHIMGYGCCsGAQUFBzAChlpodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpaW5mcmEvQ2VydHMvQkwyUEtJSU5UQ0EwMS5BTUUuR0JMX0FNRSUyMEluZnJhJTIwQ0ElMjAwMig0KS5jcnQwVgYIKwYBBQUHMAKGSmh0dHA6Ly9jcmwxLmFtZS5nYmwvYWlhL0JMMlBLSUlOVENBMDEuQU1FLkdCTF9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3J0MFYGCCsGAQUFBzAChkpodHRwOi8vY3JsMi5hbWUuZ2JsL2FpYS9CTDJQS0lJTlRDQTAxLkFNRS5HQkxfQU1FJTIwSW5mcmElMjBDQSUyMDAyKDQpLmNydDBWBggrBgEFBQcwAoZKaHR0cDovL2NybDMuYW1lLmdibC9haWEvQkwyUEtJSU5UQ0EwMS5BTUUuR0JMX0FNRSUyMEluZnJhJTIwQ0ElMjAwMig0KS5jcnQwVgYIKwYBBQUHMAKGSmh0dHA6Ly9jcmw0LmFtZS5nYmwvYWlhL0JMMlBLSUlOVENBMDEuQU1FLkdCTF9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3J0MB0GA1UdDgQWBBRCKTJWBui0JqrIiMW81zJdA9-tSDAOBgNVHQ8BAf8EBAMCBaAwggE1BgNVHR8EggEsMIIBKDCCASSgggEgoIIBHIZCaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraWluZnJhL0NSTC9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3JshjRodHRwOi8vY3JsMS5hbWUuZ2JsL2NybC9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3JshjRodHRwOi8vY3JsMi5hbWUuZ2JsL2NybC9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3JshjRodHRwOi8vY3JsMy5hbWUuZ2JsL2NybC9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3JshjRodHRwOi8vY3JsNC5hbWUuZ2JsL2NybC9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3JsMIGdBgNVHSAEgZUwgZIwDAYKKwYBBAGCN3sBATBmBgorBgEEAYI3ewICMFgwVgYIKwYBBQUHAgIwSh5IADMAMwBlADAAMQA5ADIAMQAtADQAZAA2ADQALQA0AGYAOABjAC0AYQAwADUANQAtADUAYgBkAGEAZgBmAGQANQBlADMAMwBkMAwGCisGAQQBgjd7AwEwDAYKKwYBBAGCN3sEATAfBgNVHSMEGDAWgBSuecJrXSWIEwb2BwnDl3x7l48dVTAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADggEBAGJpRHLgYBJ-Hg0664G6_TgQ8luNO24um3ktexLaPrnailsQdaNThyJ4w9TTpMvyG31DlS7euSnKy8IsfMzCDxu1mmgziF9Urf-OpUw3u-ze-9z_PmzXym0G-rk8OrPpWWdAeApaUIHmydJGO_yrSQURQDLY9ATNa4gS1c9rQLruie0ZkPwjhAJCwpdK615q7s9ssaQ_HZEXM9r3mojVMYMB6b7TQJcwlVHBvkRO5u4HnAI26O2e-pcDzgccXJ6mqM158VJM-AyU1D2gWCqHj4zml1U005Ot-Fx-C3N3HCVImLvAllBxeQdwzOTae6Br-eXo1NCFf1ahI2fP4G_nB7o&s=R5cpqV7w0WyOqpEINwIDuGRjnrxZN-rhFyTga9eHbWhIRSrIkN1CkPaV0r5lUam28i2s43W8Gmt2KNdhk-PE4n20u0uaT0leE4XzPkCzcCFvxydceX04PlsCkBARyXk20IuIXbcScNXz15AzZ7sNqFEmThhCBRrz8cL6LwZ580arTjcgb05uyRiC5CFqzYeOU-gkyJQAIoCR0y93xd6ydeZ_ZBEnnMee3h0n_Lzvckk22ipGd7KDXALxUgXiiGXM_IEN46qv4kRrWffGURGHFa1pXxT6-rpqks2K4qX6lURqQf5Uo3zhBHtBfgmearMLIHruZvESBJNc0J0H7e8Ubg&h=M_PyeBUvpDOuBCuSnWZcm6GScSaFG6CEpebJCZuuKeY + response: + body: + string: '{"status":"Succeeded"}' + headers: + api-supported-versions: + - 2023-11-01-preview + azure-asyncoperation: + - https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/operationStatuses/registries-f6615e2e-2cd5-11ef-9a12-847b5765e231?api-version=2023-11-01-preview&t=638542451027320498&c=MIIHpTCCBo2gAwIBAgITfwM6vSxODJvqjFP4oQAEAzq9LDANBgkqhkiG9w0BAQsFADBEMRMwEQYKCZImiZPyLGQBGRYDR0JMMRMwEQYKCZImiZPyLGQBGRYDQU1FMRgwFgYDVQQDEw9BTUUgSW5mcmEgQ0EgMDIwHhcNMjQwNTE1MTI0NjQwWhcNMjUwNTEwMTI0NjQwWjBAMT4wPAYDVQQDEzVhc3luY29wZXJhdGlvbnNpZ25pbmdjZXJ0aWZpY2F0ZS5tYW5hZ2VtZW50LmF6dXJlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKa4cOTKf-wVpLKiG5Zei1-Oc5u5PvibFdqWIGFZDLmSA3G2jYrx6dKQ8NH10xxzVOMT_dqQOb2nPmPDhnS3CUlhwx_iI9VSftq8J182Ci01SlOzoieOj_kBg-1yQ4TB3DD7Rwgy40TMWgK-1lkliuLAgSHruwrRW8Kj8Q96A0oGxy1RQggyCNWVG8EsUp1ngtGu-yi1BZRa4Q-v_x9KFfbvtOc9KIfKRFs2r2zg4MWc4xCzQCYrRXIVfS-sFxEn1GbDqtYc4-y5T978_4OnKXidZCkJqT4v1ZRcgxKZpH8d4GmacrEfBoCqjg9ZayboCoIPz5wEIF9LOngoqXqnmYECAwEAAaOCBJIwggSOMCcGCSsGAQQBgjcVCgQaMBgwCgYIKwYBBQUHAwEwCgYIKwYBBQUHAwIwPQYJKwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIhpDjDYTVtHiE8Ys-hZvdFs6dEoFggvX2K4Py0SACAWQCAQowggHaBggrBgEFBQcBAQSCAcwwggHIMGYGCCsGAQUFBzAChlpodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpaW5mcmEvQ2VydHMvQkwyUEtJSU5UQ0EwMS5BTUUuR0JMX0FNRSUyMEluZnJhJTIwQ0ElMjAwMig0KS5jcnQwVgYIKwYBBQUHMAKGSmh0dHA6Ly9jcmwxLmFtZS5nYmwvYWlhL0JMMlBLSUlOVENBMDEuQU1FLkdCTF9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3J0MFYGCCsGAQUFBzAChkpodHRwOi8vY3JsMi5hbWUuZ2JsL2FpYS9CTDJQS0lJTlRDQTAxLkFNRS5HQkxfQU1FJTIwSW5mcmElMjBDQSUyMDAyKDQpLmNydDBWBggrBgEFBQcwAoZKaHR0cDovL2NybDMuYW1lLmdibC9haWEvQkwyUEtJSU5UQ0EwMS5BTUUuR0JMX0FNRSUyMEluZnJhJTIwQ0ElMjAwMig0KS5jcnQwVgYIKwYBBQUHMAKGSmh0dHA6Ly9jcmw0LmFtZS5nYmwvYWlhL0JMMlBLSUlOVENBMDEuQU1FLkdCTF9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3J0MB0GA1UdDgQWBBRCKTJWBui0JqrIiMW81zJdA9-tSDAOBgNVHQ8BAf8EBAMCBaAwggE1BgNVHR8EggEsMIIBKDCCASSgggEgoIIBHIZCaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraWluZnJhL0NSTC9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3JshjRodHRwOi8vY3JsMS5hbWUuZ2JsL2NybC9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3JshjRodHRwOi8vY3JsMi5hbWUuZ2JsL2NybC9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3JshjRodHRwOi8vY3JsMy5hbWUuZ2JsL2NybC9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3JshjRodHRwOi8vY3JsNC5hbWUuZ2JsL2NybC9BTUUlMjBJbmZyYSUyMENBJTIwMDIoNCkuY3JsMIGdBgNVHSAEgZUwgZIwDAYKKwYBBAGCN3sBATBmBgorBgEEAYI3ewICMFgwVgYIKwYBBQUHAgIwSh5IADMAMwBlADAAMQA5ADIAMQAtADQAZAA2ADQALQA0AGYAOABjAC0AYQAwADUANQAtADUAYgBkAGEAZgBmAGQANQBlADMAMwBkMAwGCisGAQQBgjd7AwEwDAYKKwYBBAGCN3sEATAfBgNVHSMEGDAWgBSuecJrXSWIEwb2BwnDl3x7l48dVTAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADggEBAGJpRHLgYBJ-Hg0664G6_TgQ8luNO24um3ktexLaPrnailsQdaNThyJ4w9TTpMvyG31DlS7euSnKy8IsfMzCDxu1mmgziF9Urf-OpUw3u-ze-9z_PmzXym0G-rk8OrPpWWdAeApaUIHmydJGO_yrSQURQDLY9ATNa4gS1c9rQLruie0ZkPwjhAJCwpdK615q7s9ssaQ_HZEXM9r3mojVMYMB6b7TQJcwlVHBvkRO5u4HnAI26O2e-pcDzgccXJ6mqM158VJM-AyU1D2gWCqHj4zml1U005Ot-Fx-C3N3HCVImLvAllBxeQdwzOTae6Br-eXo1NCFf1ahI2fP4G_nB7o&s=QxQdFJsep4sUkby_ljEalzA2Jt1iWRsIb5KgOvJdd8ZkmzOahs6hDp4cwX7RbJePwEhmBOhA7FpjSNN46lqBRNj9MMwdsYKaYoIa6aIY7x1LdBm85COcxA0mA_3NN4FOfmNY9YVfHEEEjqsgv4cj7d7HgAqoF5lKhJ4Qds8aIQ-a7oxMiHow1a_UFu2M58Qg8gnGfCXjRRTvYfYkYURirSXiWZLT8BHgdvolSrON2r7hzSpgXah8Li90I-opqlqKZkCs3drINl8b7ltpRwKfDGAjEULLBe2-o-S6MJMZellpUCfXEAmv2GfapEOKw0Q5yg9jgjClWckHYsUHLgaR2g&h=6NntHTGrGLIMEOCPE8qhCPe8KcjTLK4Yg219EIVzIKU + cache-control: + - no-cache + content-length: + - '22' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:18:22 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: F1E8F31146254712B135DEFC18BF88E8 Ref B: CO6AA3150219037 Ref C: 2024-06-17T18:18:22Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - acr create + Connection: + - keep-alive + ParameterSetName: + - -g -n --sku + User-Agent: + - AZURECLI/2.61.0 azsdk-python-core/1.30.2 Python/3.8.10 (Windows-10-10.0.22631-SP0) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002?api-version=2023-11-01-preview + response: + body: + string: '{"sku":{"name":"Basic","tier":"Basic"},"type":"Microsoft.ContainerRegistry/registries","id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002","name":"cli000002","location":"westus","tags":{},"systemData":{"createdBy":"puwalech@microsoft.com","createdByType":"User","createdAt":"2024-06-17T18:18:15.4440214+00:00","lastModifiedBy":"puwalech@microsoft.com","lastModifiedByType":"User","lastModifiedAt":"2024-06-17T18:18:15.4440214+00:00"},"properties":{"loginServer":"cli000002.azurecr.io","creationDate":"2024-06-17T18:18:15.4440214Z","provisioningState":"Succeeded","adminUserEnabled":false,"policies":{"quarantinePolicy":{"status":"disabled"},"trustPolicy":{"type":"Notary","status":"disabled"},"retentionPolicy":{"days":7,"lastUpdatedTime":"2024-06-17T18:18:22.2291688+00:00","status":"disabled"},"exportPolicy":{"status":"enabled"},"azureADAuthenticationAsArmPolicy":{"status":"enabled"},"softDeletePolicy":{"retentionDays":7,"lastUpdatedTime":"2024-06-17T18:18:22.229206+00:00","status":"disabled"}},"encryption":{"status":"disabled"},"dataEndpointEnabled":false,"dataEndpointHostNames":[],"privateEndpointConnections":[],"publicNetworkAccess":"Enabled","networkRuleBypassOptions":"AzureServices","zoneRedundancy":"Disabled","anonymousPullEnabled":false,"metadataSearch":"Disabled"}}' + headers: + api-supported-versions: + - 2023-11-01-preview + cache-control: + - no-cache + content-length: + - '1388' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:18:22 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 7D7C0772A2264B4C8A9CD1A8581583DD Ref B: CO6AA3150219037 Ref C: 2024-06-17T18:18:22Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - acr supply-chain workflow create + Connection: + - keep-alive + ParameterSetName: + - -g -t -r --config --schedule + User-Agent: + - AZURECLI/2.61.0 azsdk-python-core/1.30.2 Python/3.8.10 (Windows-10-10.0.22631-SP0) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002?api-version=2023-01-01-preview + response: + body: + string: '{"sku":{"name":"Basic","tier":"Basic"},"type":"Microsoft.ContainerRegistry/registries","id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002","name":"cli000002","location":"westus","tags":{},"systemData":{"createdBy":"puwalech@microsoft.com","createdByType":"User","createdAt":"2024-06-17T18:18:15.4440214+00:00","lastModifiedBy":"puwalech@microsoft.com","lastModifiedByType":"User","lastModifiedAt":"2024-06-17T18:18:15.4440214+00:00"},"properties":{"loginServer":"cli000002.azurecr.io","creationDate":"2024-06-17T18:18:15.4440214Z","provisioningState":"Succeeded","adminUserEnabled":false,"policies":{"quarantinePolicy":{"status":"disabled"},"trustPolicy":{"type":"Notary","status":"disabled"},"retentionPolicy":{"days":7,"lastUpdatedTime":"2024-06-17T18:18:22.2291688+00:00","status":"disabled"},"exportPolicy":{"status":"enabled"},"azureADAuthenticationAsArmPolicy":{"status":"enabled"},"softDeletePolicy":{"retentionDays":7,"lastUpdatedTime":"2024-06-17T18:18:22.229206+00:00","status":"disabled"}},"encryption":{"status":"disabled"},"dataEndpointEnabled":false,"dataEndpointHostNames":[],"privateEndpointConnections":[],"publicNetworkAccess":"Enabled","networkRuleBypassOptions":"AzureServices","zoneRedundancy":"Disabled","anonymousPullEnabled":false}}' + headers: + api-supported-versions: + - 2023-01-01-preview + cache-control: + - no-cache + content-length: + - '1360' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:18:22 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 17CF5134DC0844858A46647D0B07A9FA Ref B: CO6AA3150217039 Ref C: 2024-06-17T18:18:23Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - acr supply-chain workflow create + Connection: + - keep-alive + ParameterSetName: + - -g -t -r --config --schedule + User-Agent: + - AZURECLI/2.61.0 azsdk-python-core/1.30.2 Python/3.8.10 (Windows-10-10.0.22631-SP0) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-patch-image?api-version=2019-06-01-preview + response: + body: + string: '{"error":{"code":"ResourceNotFound","message":"The Resource ''Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-patch-image'' + under resource group ''cli_test_acrcssc000001'' was not found. For more details + please go to https://aka.ms/ARMResourceNotFoundFix"}}' + headers: + cache-control: + - no-cache + content-length: + - '265' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:18:23 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-failure-cause: + - gateway + x-msedge-ref: + - 'Ref A: A7DAA40832B54A7CBBF996671152A177 Ref B: CO6AA3150217051 Ref C: 2024-06-17T18:18:23Z' + status: + code: 404 + message: Not Found +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - acr supply-chain workflow create + Connection: + - keep-alive + ParameterSetName: + - -g -t -r --config --schedule + User-Agent: + - AZURECLI/2.61.0 azsdk-python-core/1.30.2 Python/3.8.10 (Windows-10-10.0.22631-SP0) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-scan-image-schedule-patch?api-version=2019-06-01-preview + response: + body: + string: '{"error":{"code":"ResourceNotFound","message":"The Resource ''Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-scan-image-schedule-patch'' + under resource group ''cli_test_acrcssc000001'' was not found. For more details + please go to https://aka.ms/ARMResourceNotFoundFix"}}' + headers: + cache-control: + - no-cache + content-length: + - '279' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:18:23 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-failure-cause: + - gateway + x-msedge-ref: + - 'Ref A: CDCDCB73F8674078BA98A0A6E74760FF Ref B: CO6AA3150220047 Ref C: 2024-06-17T18:18:23Z' + status: + code: 404 + message: Not Found +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - acr supply-chain workflow create + Connection: + - keep-alive + ParameterSetName: + - -g -t -r --config --schedule + User-Agent: + - AZURECLI/2.61.0 azsdk-python-core/1.30.2 Python/3.8.10 (Windows-10-10.0.22631-SP0) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-trigger-scan?api-version=2019-06-01-preview + response: + body: + string: '{"error":{"code":"ResourceNotFound","message":"The Resource ''Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-trigger-scan'' + under resource group ''cli_test_acrcssc000001'' was not found. For more details + please go to https://aka.ms/ARMResourceNotFoundFix"}}' + headers: + cache-control: + - no-cache + content-length: + - '266' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:18:23 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-failure-cause: + - gateway + x-msedge-ref: + - 'Ref A: B649550196314257A109C6E0CBCC0F8C Ref B: CO6AA3150217025 Ref C: 2024-06-17T18:18:23Z' + status: + code: 404 + message: Not Found +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "parameters": {"AcrName": {"type": "string"}, "AcrLocation": + {"type": "string", "defaultValue": "[resourceGroup().location]"}, "taskSchedule": + {"type": "string"}, "imagePatchingEncodedTask": {"type": "string"}, "imageScanningEncodedTask": + {"type": "string"}, "registryScanningEncodedTask": {"type": "string"}}, "resources": + [{"type": "Microsoft.ContainerRegistry/registries/tasks", "apiVersion": "2019-06-01-preview", + "name": "[format(''{0}/{1}'', parameters(''AcrName''), ''cssc-patch-image'')]", + "location": "[parameters(''AcrLocation'')]", "tags": {"cssc": "true", "clienttracking": + "true"}, "properties": {"platform": {"os": "linux", "architecture": "amd64"}, + "agentConfiguration": {"cpu": 2}, "timeout": 3600, "step": {"type": "EncodedTask", + "encodedTaskContent": "[parameters(''imagePatchingEncodedTask'')]", "values": + []}, "isSystemTask": false}}, {"type": "Microsoft.ContainerRegistry/registries/tasks", + "apiVersion": "2019-06-01-preview", "name": "[format(''{0}/{1}'', parameters(''AcrName''), + ''cssc-scan-image-schedule-patch'')]", "location": "[parameters(''AcrLocation'')]", + "identity": {"type": "SystemAssigned"}, "tags": {"cssc": "true"}, "properties": + {"platform": {"os": "linux", "architecture": "amd64"}, "agentConfiguration": + {"cpu": 2}, "timeout": 3600, "step": {"type": "EncodedTask", "encodedTaskContent": + "[parameters(''imageScanningEncodedTask'')]", "values": []}, "isSystemTask": + false}}, {"type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", + "scope": "[format(''Microsoft.ContainerRegistry/registries/{0}'', parameters(''AcrName''))]", + "name": "[guid(resourceId(''Microsoft.ContainerRegistry/registries/tasks'', + parameters(''AcrName''), ''cssc-scan-image-schedule-patch''), subscriptionResourceId(''Microsoft.Authorization/roleDefinitions'', + ''b24988ac-6180-42a0-ab88-20f7382dd24c''))]", "properties": {"roleDefinitionId": + "[subscriptionResourceId(''Microsoft.Authorization/roleDefinitions'', ''b24988ac-6180-42a0-ab88-20f7382dd24c'')]", + "principalId": "[reference(resourceId(''Microsoft.ContainerRegistry/registries/tasks'', + parameters(''AcrName''), ''cssc-scan-image-schedule-patch''), ''2019-06-01-preview'', + ''full'').identity.principalId]", "principalType": "ServicePrincipal"}, "dependsOn": + ["[resourceId(''Microsoft.ContainerRegistry/registries/tasks'', parameters(''AcrName''), + ''cssc-scan-image-schedule-patch'')]"]}, {"type": "Microsoft.ContainerRegistry/registries/tasks", + "apiVersion": "2019-06-01-preview", "name": "[format(''{0}/{1}'', parameters(''AcrName''), + ''cssc-trigger-scan'')]", "location": "[parameters(''AcrLocation'')]", "identity": + {"type": "SystemAssigned"}, "tags": {"cssc": "true", "clienttracking": "true"}, + "properties": {"platform": {"os": "linux", "architecture": "amd64"}, "agentConfiguration": + {"cpu": 2}, "timeout": 3600, "status": "Enabled", "step": {"type": "EncodedTask", + "encodedTaskContent": "[parameters(''registryScanningEncodedTask'')]", "values": + []}, "isSystemTask": false, "trigger": {"timerTriggers": [{"name": "azcli_defined_schedule", + "schedule": "[parameters(''taskSchedule'')]"}]}}}, {"type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", "scope": "[format(''Microsoft.ContainerRegistry/registries/{0}'', + parameters(''AcrName''))]", "name": "[guid(resourceId(''Microsoft.ContainerRegistry/registries/tasks'', + parameters(''AcrName''), ''cssc-trigger-scan''), subscriptionResourceId(''Microsoft.Authorization/roleDefinitions'', + ''b24988ac-6180-42a0-ab88-20f7382dd24c''))]", "properties": {"roleDefinitionId": + "[subscriptionResourceId(''Microsoft.Authorization/roleDefinitions'', ''b24988ac-6180-42a0-ab88-20f7382dd24c'')]", + "principalId": "[reference(resourceId(''Microsoft.ContainerRegistry/registries/tasks'', + parameters(''AcrName''), ''cssc-trigger-scan''), ''2019-06-01-preview'', ''full'').identity.principalId]", + "principalType": "ServicePrincipal"}, "dependsOn": ["[resourceId(''Microsoft.ContainerRegistry/registries/tasks'', + parameters(''AcrName''), ''cssc-trigger-scan'')]"]}]}, "parameters": {"AcrName": + {"value": "cli000002"}, "AcrLocation": {"value": "westus"}, "taskSchedule": + {"value": "18 18 */1 * *"}, "imagePatchingEncodedTask": {"value": "dmVyc2lvbjogdjEuMS4wCmFsaWFzOgogIHZhbHVlczoKICAgIFNjYW5SZXBvcnQgOiBvcy12dWxuZXJhYmlsaXR5LXJlcG9ydF90cml2eV97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fV97ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319XyQoZGF0ZSAiKyVZLSVtLSVkIikuanNvbgpzdGVwczoKICAjIFN0ZXAgIzE6IFBlcmZvcm0gdGhlIHZ1bG5lcmFiaWxpdHkgc2NhbgogIC0gaWQ6IHByaW50LWlucHV0cwogICAgY21kOiB8CiAgICAgICAgYmFzaCAtYyAnZWNobyAiU2NhbiwgVXBsb2FkIHNjYW4gcmVwb3J0IGFuZCBTY2hlZHVsZSBQYXRjaCBmb3Ige3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fSInCiAgLSBpZDogc2V0dXAtZGF0YS1kaXIKICAgIGNtZDogYmFzaCBta2RpciAuL2RhdGEKICAtIGlkOiBnZW5lcmF0ZS10cml2eS1yZXBvcnQKICAgIGNtZDogfAogICAgICBnaGNyLmlvL2FxdWFzZWN1cml0eS90cml2eSBpbWFnZSBcCiAgICAgIHt7LlJ1bi5SZWdpc3RyeX19L3t7LlZhbHVlcy5TT1VSQ0VfUkVQT1NJVE9SWX19Ont7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfVEFHfX0gXAogICAgICAtLXZ1bG4tdHlwZSBvcyBcCiAgICAgIC0taWdub3JlLXVuZml4ZWQgXAogICAgICAtLWZvcm1hdCBqc29uIFwKICAgICAgLS1vdXRwdXQgL3dvcmtzcGFjZS9kYXRhLyRTY2FuUmVwb3J0CgogICMgU3RlcCAyOiBBdHRhY2ggdGhlIHZ1bG5lcmFiaWxpdHkgc2NhbiByZXBvcnQgdG8gdGhlIGltYWdlCiAgLSBpZDogdXBsb2FkLXRyaXZ5LXJlcG9ydAogICAgY21kOiB8CiAgICAgIGdoY3IuaW8vb3Jhcy1wcm9qZWN0L29yYXM6djEuMS4wIGF0dGFjaCBcCiAgICAgIC0tYXJ0aWZhY3QtdHlwZSB2dWxuZXJhYmlsaXR5U2Nhbi9yZXBvcnQgXAogICAgICB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IFwKICAgICAgLi9kYXRhLyRTY2FuUmVwb3J0CgogIC0gY21kOiBiYXNoIGVjaG8gIlVwbG9hZGVkIHZ1bG5lcmFiaWxpdHkgcmVwb3J0ICRTY2FuUmVwb3J0IHRvIHRoZSBpbWFnZSB7ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IgoKICAtIGlkOiBidWlsZGtpdGQKICAgIGNtZDogbW9ieS9idWlsZGtpdCAtLWFkZHIgdGNwOi8vMC4wLjAuMDo4ODg4CiAgICBlbnRyeXBvaW50OiBidWlsZGtpdGQKICAgIGRldGFjaDogdHJ1ZQogICAgcHJpdmlsZWdlZDogdHJ1ZQogICAgcG9ydHM6IFsiMTI3LjAuMC4xOjg4ODg6ODg4OC90Y3AiXQogIAogIC0gaWQ6IGxpc3Qtb3V0cHV0LWZpbGUKICAgIGNtZDogYmFzaCBscyAtbCAvd29ya3NwYWNlL2RhdGEKICAKICAjIFN0ZXAgMzogUGF0Y2ggdGhlIGltYWdlIHdpdGggQ29wYWNldGljCiAgLSBpZDogcGF0Y2gtd2l0aF9jb3BhCiAgICBjbWQ6IHwKICAgICAgZ2hjci5pby90b2RkeXNtL2Nzc2MtZnJhbWV3b3JrL2NvcGFjZXRpYzoxLjAgXAogICAgICB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IFwKICAgICAgJFNjYW5SZXBvcnQgXAogICAgICB7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319LXBhdGNoZWQKICAgIG5ldHdvcms6IGhvc3QKCiAgLSBpZDogcHVzaC1pbWFnZQogICAgY21kOiBkb2NrZXIgcHVzaCB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319LXBhdGNoZWQKCiAgLSBjbWQ6IGJhc2ggZWNobyAiUGF0Y2hlZCBpbWFnZSBwdXNoZWQgdG8ge3suUnVuLlJlZ2lzdHJ5fX0ve3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fS1wYXRjaGVkIg=="}, + "imageScanningEncodedTask": {"value": "dmVyc2lvbjogdjEuMS4wCmFsaWFzOgogIHZhbHVlczoKICAgIHBhdGNoaW1hZ2V0YXNrOiBjc3NjLXBhdGNoLWltYWdlCiAgICBEQVRFOiAkKGRhdGUgIislWS0lbS0lZCIpCnN0ZXBzOgogIC0gaWQ6IHByaW50LWlucHV0cwogICAgY21kOiB8CiAgICAgICAgYmFzaCAtYyAnZWNobyAiU2Nhbm5pbmcgaW1hZ2UgZm9yIHZ1bG5lcmFiaWxpdHkgYW5kIHBhdGNoIHt7LlZhbHVlcy5TT1VSQ0VfUkVQT1NJVE9SWX19Ont7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfVEFHfX0gZm9yIHRhZyB7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX09SSUdJTkFMX1RBR319IicKICAtIGlkOiBzZXR1cC1kYXRhLWRpcgogICAgY21kOiBiYXNoIG1rZGlyIC4vZGF0YQoKICAtIGlkOiBnZW5lcmF0ZS10cml2eS1yZXBvcnQKICAgIGNtZDogfAogICAgICBnaGNyLmlvL2FxdWFzZWN1cml0eS90cml2eSBpbWFnZSBcCiAgICAgIHt7LlJ1bi5SZWdpc3RyeX19L3t7LlZhbHVlcy5TT1VSQ0VfUkVQT1NJVE9SWX19Ont7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfVEFHfX0gXAogICAgICAtLXZ1bG4tdHlwZSBvcyBcCiAgICAgIC0taWdub3JlLXVuZml4ZWQgXAogICAgICAtLWZvcm1hdCBqc29uIFwKICAgICAgLS1vdXRwdXQgL3dvcmtzcGFjZS9kYXRhL3Z1bG5lcmFiaWxpdHktcmVwb3J0X3RyaXZ5XyREQVRFLmpzb24KICAtIGNtZDogbWNyLm1pY3Jvc29mdC5jb20vYXp1cmUtY2xpIGJhc2ggLWMgJ2pxICJbLlJlc3VsdHNbXS5WdWxuZXJhYmlsaXRpZXMgfCBsZW5ndGhdIHwgYWRkIiAvd29ya3NwYWNlL2RhdGEvdnVsbmVyYWJpbGl0eS1yZXBvcnRfdHJpdnlfJERBVEUuanNvbiA+IC93b3Jrc3BhY2UvZGF0YS92dWxDb3VudC50eHQnCiAgLSBjbWQ6IGJhc2ggZWNobyAiR2VuZXJhdGVkIHZ1bG5lcmFiaWxpdHkgcmVwb3J0IGF0IC93b3Jrc3BhY2UvZGF0YS92dWxuZXJhYmlsaXR5LXJlcG9ydF90cml2eV8kREFURS5qc29uIiAKICAtIGNtZDogYXogbG9naW4gLS1pZGVudGl0eQogIC0gY21kOiBiYXNoIGVjaG8gIlZ1bG5lcmFiaWxpdGllcyBmb3VuZCBmb3IgaW1hZ2Uge3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fSAtPiAkKGNhdCAvd29ya3NwYWNlL2RhdGEvdnVsQ291bnQudHh0KSIKICAtIGNtZDogfCAKICAgICAgbWNyLm1pY3Jvc29mdC5jb20vYXp1cmUtY2xpIGJhc2ggLWMgJ3Z1bENvdW50PSQoY2F0IC93b3Jrc3BhY2UvZGF0YS92dWxDb3VudC50eHQpICYmIFwKICAgICAgWyAkdnVsQ291bnQgLWd0IDAgXSAmJiBcCiAgICAgIGF6IGFjciB0YXNrIHJ1biAtLW5hbWUgJHBhdGNoaW1hZ2V0YXNrIC0tcmVnaXN0cnkgJFJlZ2lzdHJ5TmFtZSAtLXNldCBTT1VSQ0VfUkVQT1NJVE9SWT17ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fSAtLXNldCBTT1VSQ0VfSU1BR0VfVEFHPXt7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfT1JJR0lOQUxfVEFHfX0gLS1uby13YWl0IFwKICAgICAgfHwgZWNobyAiTm8gdnVsbmVyYWJpbGl0eSBpbiB0aGUgaW1hZ2Uge3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fSIn"}, + "registryScanningEncodedTask": {"value": "dmVyc2lvbjogdjEuMS4wCmFsaWFzOgogIHZhbHVlczoKICAgIFNjYW5JbWFnZUFuZFNjaGVkdWxlUGF0Y2hUYXNrOiBjc3NjLXNjYW4taW1hZ2Utc2NoZWR1bGUtcGF0Y2gKc3RlcHM6CiAgLSBjbWQ6IGJhc2ggLWMgJ2VjaG8gIkluc2lkZSBDU1NDLVRyaWdnZXJTY2FuLCBnZXR0aW5nIGltYWdlcyB0byBiZSBwYXRjaGVkIGJhc2VkIG9uIC0tZmlsdGVyLXBvbGljeSBmb3IgUmVnaXN0cnkge3suUnVuLlJlZ2lzdHJ5fX0uIicKICAtIGNtZDogbWNyLm1pY3Jvc29mdC5jb20vYWNyL2Fjci1jbGk6MC4xMSBjc3NjIHBhdGNoIC0tZmlsdGVyLXBvbGljeSBjc3NjcG9saWNpZXMvcGF0Y2hwb2xpY3k6djEgLS1kcnktcnVuID4gZmlsdGVyUmVwb3MudHh0CiAgICBlbnY6CiAgICAgIC0gQUNSX0VYUEVSSU1FTlRBTF9DU1NDPXRydWUKICAtIGNtZDogYmFzaCAtYyAnc2VkIC1uICIvXkxpc3RpbmcvLC9eVG90YWwvIHsvXkxpc3RpbmcvYjsvXlRvdGFsL2I7cH0iIGZpbHRlclJlcG9zLnR4dCcgPiBmaWx0ZXJSZXBvc1RvRGlzcGxheS50eHQKICAtIGNtZDogYmFzaCAtYyAnZWNobyAtZSAiQmVsb3cgaW1hZ2VzIHdpbGwgYmUgc2Nhbm5lZCBhbmQgcGF0Y2hlZCAoaWYgYW55IG9zIHZ1bG5lcmFiaWxpdGllcyBmb3VuZCkgYmFzZWQgb24gLS1maWx0ZXItcG9saWN5LlxuJChjYXQgZmlsdGVyUmVwb3NUb0Rpc3BsYXkudHh0KSInCiAgLSBjbWQ6IG1jci5taWNyb3NvZnQuY29tL2Fjci9hY3ItY2xpOjAuMTEgY3NzYyBwYXRjaCAtLWZpbHRlci1wb2xpY3kgY3NzY3BvbGljaWVzL3BhdGNocG9saWN5OnYxIC0tc2hvdy1wYXRjaC10YWdzIC0tZHJ5LXJ1bj4gZmlsdGVyUmVwb3NXaXRoUGF0Y2hUYWdzLnR4dAogICAgZW52OgogICAgICAtIEFDUl9FWFBFUklNRU5UQUxfQ1NTQz10cnVlCiAgLSBjbWQ6IGJhc2ggLWMgJ3NlZCAtbiAiL15MaXN0aW5nLywvXlRvdGFsLyB7L15MaXN0aW5nL2I7L15Ub3RhbC9iO3B9IiBmaWx0ZXJSZXBvc1dpdGhQYXRjaFRhZ3MudHh0JyA+IGZpbHRlcmVkUmVwb3NBbmRUYWdzLnR4dAogIC0gY21kOiBheiBsb2dpbiAtLWlkZW50aXR5IAogIC0gY21kOiB8CiAgICAgICAgbWNyLm1pY3Jvc29mdC5jb20vYXp1cmUtY2xpIGJhc2ggLWMgJ3doaWxlIHJlYWQgbGluZTtkbyBcCiAgICAgICAgSUZTPScvJyByZWFkIC1yIC1hIGFycmF5MSA8PDwgIiRsaW5lIgogICAgICAgIElGUz0nOicgcmVhZCAtciAtYSBhcnJheTIgPDw8ICIke2FycmF5MVsxXX0iCiAgICAgICAgSUZTPScsJyByZWFkIC1yIC1hIGFycmF5MyA8PDwgIiR7YXJyYXkyWzFdfSIKICAgICAgICBSZWdpc3RyeU5hbWU9JHthcnJheTFbMF19OwogICAgICAgIFJlcG9OYW1lPSR7YXJyYXkyWzBdfTsKICAgICAgICBPcmlnaW5hbFRhZz0ke2FycmF5M1swXX07CiAgICAgICAgVGFnTmFtZT0ke2FycmF5M1sxXX07CiAgICAgICAgZWNobyAiU2NoZWR1bGluZyAkU2NhbkltYWdlQW5kU2NoZWR1bGVQYXRjaFRhc2sgZm9yICRSZWdpc3RyeU5hbWUvJFJlcG9OYW1lLCBUYWc6JFRhZ05hbWUsIE9yaWdpbmFsVGFnOiRPcmlnaW5hbFRhZyI7CiAgICAgICAgYXogYWNyIHRhc2sgcnVuIC0tbmFtZSAkU2NhbkltYWdlQW5kU2NoZWR1bGVQYXRjaFRhc2sgLS1yZWdpc3RyeSAkUmVnaXN0cnlOYW1lIC0tc2V0IFNPVVJDRV9SRVBPU0lUT1JZPSRSZXBvTmFtZSAtLXNldCBTT1VSQ0VfSU1BR0VfVEFHPSRUYWdOYW1lIC0tc2V0IFNPVVJDRV9JTUFHRV9PUklHSU5BTF9UQUc9JE9yaWdpbmFsVGFnIC0tbm8td2FpdDsgXAogICAgICAgIGRvbmUgPCBmaWx0ZXJlZFJlcG9zQW5kVGFncy50eHQ7Jw=="}}, + "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - acr supply-chain workflow create + Connection: + - keep-alive + Content-Length: + - '11445' + Content-Type: + - application/json + ParameterSetName: + - -g -t -r --config --schedule + User-Agent: + - AZURECLI/2.61.0 azsdk-python-core/1.30.2 Python/3.8.10 (Windows-10-10.0.22631-SP0) + method: POST + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_acrcssc000001/providers/Microsoft.Resources/deployments/mock-deployment/validate?api-version=2022-09-01 + response: + body: + string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.Resources/deployments/continuouspatchingdeployment","name":"continuouspatchingdeployment","type":"Microsoft.Resources/deployments","properties":{"templateHash":"1103119532525797693","parameters":{"acrName":{"type":"String","value":"cli000002"},"acrLocation":{"type":"String","value":"westus"},"taskSchedule":{"type":"String","value":"18 + 18 */1 * *"},"imagePatchingEncodedTask":{"type":"String","value":"dmVyc2lvbjogdjEuMS4wCmFsaWFzOgogIHZhbHVlczoKICAgIFNjYW5SZXBvcnQgOiBvcy12dWxuZXJhYmlsaXR5LXJlcG9ydF90cml2eV97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fV97ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319XyQoZGF0ZSAiKyVZLSVtLSVkIikuanNvbgpzdGVwczoKICAjIFN0ZXAgIzE6IFBlcmZvcm0gdGhlIHZ1bG5lcmFiaWxpdHkgc2NhbgogIC0gaWQ6IHByaW50LWlucHV0cwogICAgY21kOiB8CiAgICAgICAgYmFzaCAtYyAnZWNobyAiU2NhbiwgVXBsb2FkIHNjYW4gcmVwb3J0IGFuZCBTY2hlZHVsZSBQYXRjaCBmb3Ige3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fSInCiAgLSBpZDogc2V0dXAtZGF0YS1kaXIKICAgIGNtZDogYmFzaCBta2RpciAuL2RhdGEKICAtIGlkOiBnZW5lcmF0ZS10cml2eS1yZXBvcnQKICAgIGNtZDogfAogICAgICBnaGNyLmlvL2FxdWFzZWN1cml0eS90cml2eSBpbWFnZSBcCiAgICAgIHt7LlJ1bi5SZWdpc3RyeX19L3t7LlZhbHVlcy5TT1VSQ0VfUkVQT1NJVE9SWX19Ont7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfVEFHfX0gXAogICAgICAtLXZ1bG4tdHlwZSBvcyBcCiAgICAgIC0taWdub3JlLXVuZml4ZWQgXAogICAgICAtLWZvcm1hdCBqc29uIFwKICAgICAgLS1vdXRwdXQgL3dvcmtzcGFjZS9kYXRhLyRTY2FuUmVwb3J0CgogICMgU3RlcCAyOiBBdHRhY2ggdGhlIHZ1bG5lcmFiaWxpdHkgc2NhbiByZXBvcnQgdG8gdGhlIGltYWdlCiAgLSBpZDogdXBsb2FkLXRyaXZ5LXJlcG9ydAogICAgY21kOiB8CiAgICAgIGdoY3IuaW8vb3Jhcy1wcm9qZWN0L29yYXM6djEuMS4wIGF0dGFjaCBcCiAgICAgIC0tYXJ0aWZhY3QtdHlwZSB2dWxuZXJhYmlsaXR5U2Nhbi9yZXBvcnQgXAogICAgICB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IFwKICAgICAgLi9kYXRhLyRTY2FuUmVwb3J0CgogIC0gY21kOiBiYXNoIGVjaG8gIlVwbG9hZGVkIHZ1bG5lcmFiaWxpdHkgcmVwb3J0ICRTY2FuUmVwb3J0IHRvIHRoZSBpbWFnZSB7ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IgoKICAtIGlkOiBidWlsZGtpdGQKICAgIGNtZDogbW9ieS9idWlsZGtpdCAtLWFkZHIgdGNwOi8vMC4wLjAuMDo4ODg4CiAgICBlbnRyeXBvaW50OiBidWlsZGtpdGQKICAgIGRldGFjaDogdHJ1ZQogICAgcHJpdmlsZWdlZDogdHJ1ZQogICAgcG9ydHM6IFsiMTI3LjAuMC4xOjg4ODg6ODg4OC90Y3AiXQogIAogIC0gaWQ6IGxpc3Qtb3V0cHV0LWZpbGUKICAgIGNtZDogYmFzaCBscyAtbCAvd29ya3NwYWNlL2RhdGEKICAKICAjIFN0ZXAgMzogUGF0Y2ggdGhlIGltYWdlIHdpdGggQ29wYWNldGljCiAgLSBpZDogcGF0Y2gtd2l0aF9jb3BhCiAgICBjbWQ6IHwKICAgICAgZ2hjci5pby90b2RkeXNtL2Nzc2MtZnJhbWV3b3JrL2NvcGFjZXRpYzoxLjAgXAogICAgICB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IFwKICAgICAgJFNjYW5SZXBvcnQgXAogICAgICB7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319LXBhdGNoZWQKICAgIG5ldHdvcms6IGhvc3QKCiAgLSBpZDogcHVzaC1pbWFnZQogICAgY21kOiBkb2NrZXIgcHVzaCB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319LXBhdGNoZWQKCiAgLSBjbWQ6IGJhc2ggZWNobyAiUGF0Y2hlZCBpbWFnZSBwdXNoZWQgdG8ge3suUnVuLlJlZ2lzdHJ5fX0ve3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fS1wYXRjaGVkIg=="},"imageScanningEncodedTask":{"type":"String","value":"dmVyc2lvbjogdjEuMS4wCmFsaWFzOgogIHZhbHVlczoKICAgIHBhdGNoaW1hZ2V0YXNrOiBjc3NjLXBhdGNoLWltYWdlCiAgICBEQVRFOiAkKGRhdGUgIislWS0lbS0lZCIpCnN0ZXBzOgogIC0gaWQ6IHByaW50LWlucHV0cwogICAgY21kOiB8CiAgICAgICAgYmFzaCAtYyAnZWNobyAiU2Nhbm5pbmcgaW1hZ2UgZm9yIHZ1bG5lcmFiaWxpdHkgYW5kIHBhdGNoIHt7LlZhbHVlcy5TT1VSQ0VfUkVQT1NJVE9SWX19Ont7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfVEFHfX0gZm9yIHRhZyB7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX09SSUdJTkFMX1RBR319IicKICAtIGlkOiBzZXR1cC1kYXRhLWRpcgogICAgY21kOiBiYXNoIG1rZGlyIC4vZGF0YQoKICAtIGlkOiBnZW5lcmF0ZS10cml2eS1yZXBvcnQKICAgIGNtZDogfAogICAgICBnaGNyLmlvL2FxdWFzZWN1cml0eS90cml2eSBpbWFnZSBcCiAgICAgIHt7LlJ1bi5SZWdpc3RyeX19L3t7LlZhbHVlcy5TT1VSQ0VfUkVQT1NJVE9SWX19Ont7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfVEFHfX0gXAogICAgICAtLXZ1bG4tdHlwZSBvcyBcCiAgICAgIC0taWdub3JlLXVuZml4ZWQgXAogICAgICAtLWZvcm1hdCBqc29uIFwKICAgICAgLS1vdXRwdXQgL3dvcmtzcGFjZS9kYXRhL3Z1bG5lcmFiaWxpdHktcmVwb3J0X3RyaXZ5XyREQVRFLmpzb24KICAtIGNtZDogbWNyLm1pY3Jvc29mdC5jb20vYXp1cmUtY2xpIGJhc2ggLWMgJ2pxICJbLlJlc3VsdHNbXS5WdWxuZXJhYmlsaXRpZXMgfCBsZW5ndGhdIHwgYWRkIiAvd29ya3NwYWNlL2RhdGEvdnVsbmVyYWJpbGl0eS1yZXBvcnRfdHJpdnlfJERBVEUuanNvbiA+IC93b3Jrc3BhY2UvZGF0YS92dWxDb3VudC50eHQnCiAgLSBjbWQ6IGJhc2ggZWNobyAiR2VuZXJhdGVkIHZ1bG5lcmFiaWxpdHkgcmVwb3J0IGF0IC93b3Jrc3BhY2UvZGF0YS92dWxuZXJhYmlsaXR5LXJlcG9ydF90cml2eV8kREFURS5qc29uIiAKICAtIGNtZDogYXogbG9naW4gLS1pZGVudGl0eQogIC0gY21kOiBiYXNoIGVjaG8gIlZ1bG5lcmFiaWxpdGllcyBmb3VuZCBmb3IgaW1hZ2Uge3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fSAtPiAkKGNhdCAvd29ya3NwYWNlL2RhdGEvdnVsQ291bnQudHh0KSIKICAtIGNtZDogfCAKICAgICAgbWNyLm1pY3Jvc29mdC5jb20vYXp1cmUtY2xpIGJhc2ggLWMgJ3Z1bENvdW50PSQoY2F0IC93b3Jrc3BhY2UvZGF0YS92dWxDb3VudC50eHQpICYmIFwKICAgICAgWyAkdnVsQ291bnQgLWd0IDAgXSAmJiBcCiAgICAgIGF6IGFjciB0YXNrIHJ1biAtLW5hbWUgJHBhdGNoaW1hZ2V0YXNrIC0tcmVnaXN0cnkgJFJlZ2lzdHJ5TmFtZSAtLXNldCBTT1VSQ0VfUkVQT1NJVE9SWT17ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fSAtLXNldCBTT1VSQ0VfSU1BR0VfVEFHPXt7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfT1JJR0lOQUxfVEFHfX0gLS1uby13YWl0IFwKICAgICAgfHwgZWNobyAiTm8gdnVsbmVyYWJpbGl0eSBpbiB0aGUgaW1hZ2Uge3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fSIn"},"registryScanningEncodedTask":{"type":"String","value":"dmVyc2lvbjogdjEuMS4wCmFsaWFzOgogIHZhbHVlczoKICAgIFNjYW5JbWFnZUFuZFNjaGVkdWxlUGF0Y2hUYXNrOiBjc3NjLXNjYW4taW1hZ2Utc2NoZWR1bGUtcGF0Y2gKc3RlcHM6CiAgLSBjbWQ6IGJhc2ggLWMgJ2VjaG8gIkluc2lkZSBDU1NDLVRyaWdnZXJTY2FuLCBnZXR0aW5nIGltYWdlcyB0byBiZSBwYXRjaGVkIGJhc2VkIG9uIC0tZmlsdGVyLXBvbGljeSBmb3IgUmVnaXN0cnkge3suUnVuLlJlZ2lzdHJ5fX0uIicKICAtIGNtZDogbWNyLm1pY3Jvc29mdC5jb20vYWNyL2Fjci1jbGk6MC4xMSBjc3NjIHBhdGNoIC0tZmlsdGVyLXBvbGljeSBjc3NjcG9saWNpZXMvcGF0Y2hwb2xpY3k6djEgLS1kcnktcnVuID4gZmlsdGVyUmVwb3MudHh0CiAgICBlbnY6CiAgICAgIC0gQUNSX0VYUEVSSU1FTlRBTF9DU1NDPXRydWUKICAtIGNtZDogYmFzaCAtYyAnc2VkIC1uICIvXkxpc3RpbmcvLC9eVG90YWwvIHsvXkxpc3RpbmcvYjsvXlRvdGFsL2I7cH0iIGZpbHRlclJlcG9zLnR4dCcgPiBmaWx0ZXJSZXBvc1RvRGlzcGxheS50eHQKICAtIGNtZDogYmFzaCAtYyAnZWNobyAtZSAiQmVsb3cgaW1hZ2VzIHdpbGwgYmUgc2Nhbm5lZCBhbmQgcGF0Y2hlZCAoaWYgYW55IG9zIHZ1bG5lcmFiaWxpdGllcyBmb3VuZCkgYmFzZWQgb24gLS1maWx0ZXItcG9saWN5LlxuJChjYXQgZmlsdGVyUmVwb3NUb0Rpc3BsYXkudHh0KSInCiAgLSBjbWQ6IG1jci5taWNyb3NvZnQuY29tL2Fjci9hY3ItY2xpOjAuMTEgY3NzYyBwYXRjaCAtLWZpbHRlci1wb2xpY3kgY3NzY3BvbGljaWVzL3BhdGNocG9saWN5OnYxIC0tc2hvdy1wYXRjaC10YWdzIC0tZHJ5LXJ1bj4gZmlsdGVyUmVwb3NXaXRoUGF0Y2hUYWdzLnR4dAogICAgZW52OgogICAgICAtIEFDUl9FWFBFUklNRU5UQUxfQ1NTQz10cnVlCiAgLSBjbWQ6IGJhc2ggLWMgJ3NlZCAtbiAiL15MaXN0aW5nLywvXlRvdGFsLyB7L15MaXN0aW5nL2I7L15Ub3RhbC9iO3B9IiBmaWx0ZXJSZXBvc1dpdGhQYXRjaFRhZ3MudHh0JyA+IGZpbHRlcmVkUmVwb3NBbmRUYWdzLnR4dAogIC0gY21kOiBheiBsb2dpbiAtLWlkZW50aXR5IAogIC0gY21kOiB8CiAgICAgICAgbWNyLm1pY3Jvc29mdC5jb20vYXp1cmUtY2xpIGJhc2ggLWMgJ3doaWxlIHJlYWQgbGluZTtkbyBcCiAgICAgICAgSUZTPScvJyByZWFkIC1yIC1hIGFycmF5MSA8PDwgIiRsaW5lIgogICAgICAgIElGUz0nOicgcmVhZCAtciAtYSBhcnJheTIgPDw8ICIke2FycmF5MVsxXX0iCiAgICAgICAgSUZTPScsJyByZWFkIC1yIC1hIGFycmF5MyA8PDwgIiR7YXJyYXkyWzFdfSIKICAgICAgICBSZWdpc3RyeU5hbWU9JHthcnJheTFbMF19OwogICAgICAgIFJlcG9OYW1lPSR7YXJyYXkyWzBdfTsKICAgICAgICBPcmlnaW5hbFRhZz0ke2FycmF5M1swXX07CiAgICAgICAgVGFnTmFtZT0ke2FycmF5M1sxXX07CiAgICAgICAgZWNobyAiU2NoZWR1bGluZyAkU2NhbkltYWdlQW5kU2NoZWR1bGVQYXRjaFRhc2sgZm9yICRSZWdpc3RyeU5hbWUvJFJlcG9OYW1lLCBUYWc6JFRhZ05hbWUsIE9yaWdpbmFsVGFnOiRPcmlnaW5hbFRhZyI7CiAgICAgICAgYXogYWNyIHRhc2sgcnVuIC0tbmFtZSAkU2NhbkltYWdlQW5kU2NoZWR1bGVQYXRjaFRhc2sgLS1yZWdpc3RyeSAkUmVnaXN0cnlOYW1lIC0tc2V0IFNPVVJDRV9SRVBPU0lUT1JZPSRSZXBvTmFtZSAtLXNldCBTT1VSQ0VfSU1BR0VfVEFHPSRUYWdOYW1lIC0tc2V0IFNPVVJDRV9JTUFHRV9PUklHSU5BTF9UQUc9JE9yaWdpbmFsVGFnIC0tbm8td2FpdDsgXAogICAgICAgIGRvbmUgPCBmaWx0ZXJlZFJlcG9zQW5kVGFncy50eHQ7Jw=="}},"mode":"Incremental","provisioningState":"Succeeded","timestamp":"0001-01-01T00:00:00Z","duration":"PT0S","correlationId":"dd015d7c-098c-44c5-b5e9-2f8f077648d4","providers":[{"namespace":"Microsoft.ContainerRegistry","resourceTypes":[{"resourceType":"registries/tasks","locations":["westus"]}]},{"namespace":"Microsoft.Authorization","resourceTypes":[{"resourceType":"roleAssignments","locations":[null]}]}],"dependencies":[{"dependsOn":[{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-scan-image-schedule-patch","resourceType":"Microsoft.ContainerRegistry/registries/tasks","resourceName":"cli000002/cssc-scan-image-schedule-patch"},{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-scan-image-schedule-patch","resourceType":"Microsoft.ContainerRegistry/registries/tasks","resourceName":"cli000002/cssc-scan-image-schedule-patch","apiVersion":"2019-06-01-preview"}],"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/providers/Microsoft.Authorization/roleAssignments/4d82eda5-7482-55f4-81d9-d8a19d2d9ce2","resourceType":"Microsoft.Authorization/roleAssignments","resourceName":"4d82eda5-7482-55f4-81d9-d8a19d2d9ce2"},{"dependsOn":[{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-trigger-scan","resourceType":"Microsoft.ContainerRegistry/registries/tasks","resourceName":"cli000002/cssc-trigger-scan"},{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-trigger-scan","resourceType":"Microsoft.ContainerRegistry/registries/tasks","resourceName":"cli000002/cssc-trigger-scan","apiVersion":"2019-06-01-preview"}],"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/providers/Microsoft.Authorization/roleAssignments/3dfab287-54f3-51aa-8165-175f40dd8b00","resourceType":"Microsoft.Authorization/roleAssignments","resourceName":"3dfab287-54f3-51aa-8165-175f40dd8b00"}],"validatedResources":[{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-patch-image"},{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-scan-image-schedule-patch"},{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/providers/Microsoft.Authorization/roleAssignments/4d82eda5-7482-55f4-81d9-d8a19d2d9ce2"},{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-trigger-scan"},{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/providers/Microsoft.Authorization/roleAssignments/3dfab287-54f3-51aa-8165-175f40dd8b00"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '11244' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:18:24 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: 0702E595442241D787BC3D1C8E327172 Ref B: CO6AA3150220027 Ref C: 2024-06-17T18:18:24Z' + status: + code: 200 + message: OK +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "parameters": {"AcrName": {"type": "string"}, "AcrLocation": + {"type": "string", "defaultValue": "[resourceGroup().location]"}, "taskSchedule": + {"type": "string"}, "imagePatchingEncodedTask": {"type": "string"}, "imageScanningEncodedTask": + {"type": "string"}, "registryScanningEncodedTask": {"type": "string"}}, "resources": + [{"type": "Microsoft.ContainerRegistry/registries/tasks", "apiVersion": "2019-06-01-preview", + "name": "[format(''{0}/{1}'', parameters(''AcrName''), ''cssc-patch-image'')]", + "location": "[parameters(''AcrLocation'')]", "tags": {"cssc": "true", "clienttracking": + "true"}, "properties": {"platform": {"os": "linux", "architecture": "amd64"}, + "agentConfiguration": {"cpu": 2}, "timeout": 3600, "step": {"type": "EncodedTask", + "encodedTaskContent": "[parameters(''imagePatchingEncodedTask'')]", "values": + []}, "isSystemTask": false}}, {"type": "Microsoft.ContainerRegistry/registries/tasks", + "apiVersion": "2019-06-01-preview", "name": "[format(''{0}/{1}'', parameters(''AcrName''), + ''cssc-scan-image-schedule-patch'')]", "location": "[parameters(''AcrLocation'')]", + "identity": {"type": "SystemAssigned"}, "tags": {"cssc": "true"}, "properties": + {"platform": {"os": "linux", "architecture": "amd64"}, "agentConfiguration": + {"cpu": 2}, "timeout": 3600, "step": {"type": "EncodedTask", "encodedTaskContent": + "[parameters(''imageScanningEncodedTask'')]", "values": []}, "isSystemTask": + false}}, {"type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", + "scope": "[format(''Microsoft.ContainerRegistry/registries/{0}'', parameters(''AcrName''))]", + "name": "[guid(resourceId(''Microsoft.ContainerRegistry/registries/tasks'', + parameters(''AcrName''), ''cssc-scan-image-schedule-patch''), subscriptionResourceId(''Microsoft.Authorization/roleDefinitions'', + ''b24988ac-6180-42a0-ab88-20f7382dd24c''))]", "properties": {"roleDefinitionId": + "[subscriptionResourceId(''Microsoft.Authorization/roleDefinitions'', ''b24988ac-6180-42a0-ab88-20f7382dd24c'')]", + "principalId": "[reference(resourceId(''Microsoft.ContainerRegistry/registries/tasks'', + parameters(''AcrName''), ''cssc-scan-image-schedule-patch''), ''2019-06-01-preview'', + ''full'').identity.principalId]", "principalType": "ServicePrincipal"}, "dependsOn": + ["[resourceId(''Microsoft.ContainerRegistry/registries/tasks'', parameters(''AcrName''), + ''cssc-scan-image-schedule-patch'')]"]}, {"type": "Microsoft.ContainerRegistry/registries/tasks", + "apiVersion": "2019-06-01-preview", "name": "[format(''{0}/{1}'', parameters(''AcrName''), + ''cssc-trigger-scan'')]", "location": "[parameters(''AcrLocation'')]", "identity": + {"type": "SystemAssigned"}, "tags": {"cssc": "true", "clienttracking": "true"}, + "properties": {"platform": {"os": "linux", "architecture": "amd64"}, "agentConfiguration": + {"cpu": 2}, "timeout": 3600, "status": "Enabled", "step": {"type": "EncodedTask", + "encodedTaskContent": "[parameters(''registryScanningEncodedTask'')]", "values": + []}, "isSystemTask": false, "trigger": {"timerTriggers": [{"name": "azcli_defined_schedule", + "schedule": "[parameters(''taskSchedule'')]"}]}}}, {"type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", "scope": "[format(''Microsoft.ContainerRegistry/registries/{0}'', + parameters(''AcrName''))]", "name": "[guid(resourceId(''Microsoft.ContainerRegistry/registries/tasks'', + parameters(''AcrName''), ''cssc-trigger-scan''), subscriptionResourceId(''Microsoft.Authorization/roleDefinitions'', + ''b24988ac-6180-42a0-ab88-20f7382dd24c''))]", "properties": {"roleDefinitionId": + "[subscriptionResourceId(''Microsoft.Authorization/roleDefinitions'', ''b24988ac-6180-42a0-ab88-20f7382dd24c'')]", + "principalId": "[reference(resourceId(''Microsoft.ContainerRegistry/registries/tasks'', + parameters(''AcrName''), ''cssc-trigger-scan''), ''2019-06-01-preview'', ''full'').identity.principalId]", + "principalType": "ServicePrincipal"}, "dependsOn": ["[resourceId(''Microsoft.ContainerRegistry/registries/tasks'', + parameters(''AcrName''), ''cssc-trigger-scan'')]"]}]}, "parameters": {"AcrName": + {"value": "cli000002"}, "AcrLocation": {"value": "westus"}, "taskSchedule": + {"value": "18 18 */1 * *"}, "imagePatchingEncodedTask": {"value": "dmVyc2lvbjogdjEuMS4wCmFsaWFzOgogIHZhbHVlczoKICAgIFNjYW5SZXBvcnQgOiBvcy12dWxuZXJhYmlsaXR5LXJlcG9ydF90cml2eV97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fV97ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319XyQoZGF0ZSAiKyVZLSVtLSVkIikuanNvbgpzdGVwczoKICAjIFN0ZXAgIzE6IFBlcmZvcm0gdGhlIHZ1bG5lcmFiaWxpdHkgc2NhbgogIC0gaWQ6IHByaW50LWlucHV0cwogICAgY21kOiB8CiAgICAgICAgYmFzaCAtYyAnZWNobyAiU2NhbiwgVXBsb2FkIHNjYW4gcmVwb3J0IGFuZCBTY2hlZHVsZSBQYXRjaCBmb3Ige3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fSInCiAgLSBpZDogc2V0dXAtZGF0YS1kaXIKICAgIGNtZDogYmFzaCBta2RpciAuL2RhdGEKICAtIGlkOiBnZW5lcmF0ZS10cml2eS1yZXBvcnQKICAgIGNtZDogfAogICAgICBnaGNyLmlvL2FxdWFzZWN1cml0eS90cml2eSBpbWFnZSBcCiAgICAgIHt7LlJ1bi5SZWdpc3RyeX19L3t7LlZhbHVlcy5TT1VSQ0VfUkVQT1NJVE9SWX19Ont7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfVEFHfX0gXAogICAgICAtLXZ1bG4tdHlwZSBvcyBcCiAgICAgIC0taWdub3JlLXVuZml4ZWQgXAogICAgICAtLWZvcm1hdCBqc29uIFwKICAgICAgLS1vdXRwdXQgL3dvcmtzcGFjZS9kYXRhLyRTY2FuUmVwb3J0CgogICMgU3RlcCAyOiBBdHRhY2ggdGhlIHZ1bG5lcmFiaWxpdHkgc2NhbiByZXBvcnQgdG8gdGhlIGltYWdlCiAgLSBpZDogdXBsb2FkLXRyaXZ5LXJlcG9ydAogICAgY21kOiB8CiAgICAgIGdoY3IuaW8vb3Jhcy1wcm9qZWN0L29yYXM6djEuMS4wIGF0dGFjaCBcCiAgICAgIC0tYXJ0aWZhY3QtdHlwZSB2dWxuZXJhYmlsaXR5U2Nhbi9yZXBvcnQgXAogICAgICB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IFwKICAgICAgLi9kYXRhLyRTY2FuUmVwb3J0CgogIC0gY21kOiBiYXNoIGVjaG8gIlVwbG9hZGVkIHZ1bG5lcmFiaWxpdHkgcmVwb3J0ICRTY2FuUmVwb3J0IHRvIHRoZSBpbWFnZSB7ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IgoKICAtIGlkOiBidWlsZGtpdGQKICAgIGNtZDogbW9ieS9idWlsZGtpdCAtLWFkZHIgdGNwOi8vMC4wLjAuMDo4ODg4CiAgICBlbnRyeXBvaW50OiBidWlsZGtpdGQKICAgIGRldGFjaDogdHJ1ZQogICAgcHJpdmlsZWdlZDogdHJ1ZQogICAgcG9ydHM6IFsiMTI3LjAuMC4xOjg4ODg6ODg4OC90Y3AiXQogIAogIC0gaWQ6IGxpc3Qtb3V0cHV0LWZpbGUKICAgIGNtZDogYmFzaCBscyAtbCAvd29ya3NwYWNlL2RhdGEKICAKICAjIFN0ZXAgMzogUGF0Y2ggdGhlIGltYWdlIHdpdGggQ29wYWNldGljCiAgLSBpZDogcGF0Y2gtd2l0aF9jb3BhCiAgICBjbWQ6IHwKICAgICAgZ2hjci5pby90b2RkeXNtL2Nzc2MtZnJhbWV3b3JrL2NvcGFjZXRpYzoxLjAgXAogICAgICB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IFwKICAgICAgJFNjYW5SZXBvcnQgXAogICAgICB7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319LXBhdGNoZWQKICAgIG5ldHdvcms6IGhvc3QKCiAgLSBpZDogcHVzaC1pbWFnZQogICAgY21kOiBkb2NrZXIgcHVzaCB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319LXBhdGNoZWQKCiAgLSBjbWQ6IGJhc2ggZWNobyAiUGF0Y2hlZCBpbWFnZSBwdXNoZWQgdG8ge3suUnVuLlJlZ2lzdHJ5fX0ve3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fS1wYXRjaGVkIg=="}, + "imageScanningEncodedTask": {"value": "dmVyc2lvbjogdjEuMS4wCmFsaWFzOgogIHZhbHVlczoKICAgIHBhdGNoaW1hZ2V0YXNrOiBjc3NjLXBhdGNoLWltYWdlCiAgICBEQVRFOiAkKGRhdGUgIislWS0lbS0lZCIpCnN0ZXBzOgogIC0gaWQ6IHByaW50LWlucHV0cwogICAgY21kOiB8CiAgICAgICAgYmFzaCAtYyAnZWNobyAiU2Nhbm5pbmcgaW1hZ2UgZm9yIHZ1bG5lcmFiaWxpdHkgYW5kIHBhdGNoIHt7LlZhbHVlcy5TT1VSQ0VfUkVQT1NJVE9SWX19Ont7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfVEFHfX0gZm9yIHRhZyB7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX09SSUdJTkFMX1RBR319IicKICAtIGlkOiBzZXR1cC1kYXRhLWRpcgogICAgY21kOiBiYXNoIG1rZGlyIC4vZGF0YQoKICAtIGlkOiBnZW5lcmF0ZS10cml2eS1yZXBvcnQKICAgIGNtZDogfAogICAgICBnaGNyLmlvL2FxdWFzZWN1cml0eS90cml2eSBpbWFnZSBcCiAgICAgIHt7LlJ1bi5SZWdpc3RyeX19L3t7LlZhbHVlcy5TT1VSQ0VfUkVQT1NJVE9SWX19Ont7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfVEFHfX0gXAogICAgICAtLXZ1bG4tdHlwZSBvcyBcCiAgICAgIC0taWdub3JlLXVuZml4ZWQgXAogICAgICAtLWZvcm1hdCBqc29uIFwKICAgICAgLS1vdXRwdXQgL3dvcmtzcGFjZS9kYXRhL3Z1bG5lcmFiaWxpdHktcmVwb3J0X3RyaXZ5XyREQVRFLmpzb24KICAtIGNtZDogbWNyLm1pY3Jvc29mdC5jb20vYXp1cmUtY2xpIGJhc2ggLWMgJ2pxICJbLlJlc3VsdHNbXS5WdWxuZXJhYmlsaXRpZXMgfCBsZW5ndGhdIHwgYWRkIiAvd29ya3NwYWNlL2RhdGEvdnVsbmVyYWJpbGl0eS1yZXBvcnRfdHJpdnlfJERBVEUuanNvbiA+IC93b3Jrc3BhY2UvZGF0YS92dWxDb3VudC50eHQnCiAgLSBjbWQ6IGJhc2ggZWNobyAiR2VuZXJhdGVkIHZ1bG5lcmFiaWxpdHkgcmVwb3J0IGF0IC93b3Jrc3BhY2UvZGF0YS92dWxuZXJhYmlsaXR5LXJlcG9ydF90cml2eV8kREFURS5qc29uIiAKICAtIGNtZDogYXogbG9naW4gLS1pZGVudGl0eQogIC0gY21kOiBiYXNoIGVjaG8gIlZ1bG5lcmFiaWxpdGllcyBmb3VuZCBmb3IgaW1hZ2Uge3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fSAtPiAkKGNhdCAvd29ya3NwYWNlL2RhdGEvdnVsQ291bnQudHh0KSIKICAtIGNtZDogfCAKICAgICAgbWNyLm1pY3Jvc29mdC5jb20vYXp1cmUtY2xpIGJhc2ggLWMgJ3Z1bENvdW50PSQoY2F0IC93b3Jrc3BhY2UvZGF0YS92dWxDb3VudC50eHQpICYmIFwKICAgICAgWyAkdnVsQ291bnQgLWd0IDAgXSAmJiBcCiAgICAgIGF6IGFjciB0YXNrIHJ1biAtLW5hbWUgJHBhdGNoaW1hZ2V0YXNrIC0tcmVnaXN0cnkgJFJlZ2lzdHJ5TmFtZSAtLXNldCBTT1VSQ0VfUkVQT1NJVE9SWT17ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fSAtLXNldCBTT1VSQ0VfSU1BR0VfVEFHPXt7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfT1JJR0lOQUxfVEFHfX0gLS1uby13YWl0IFwKICAgICAgfHwgZWNobyAiTm8gdnVsbmVyYWJpbGl0eSBpbiB0aGUgaW1hZ2Uge3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fSIn"}, + "registryScanningEncodedTask": {"value": "dmVyc2lvbjogdjEuMS4wCmFsaWFzOgogIHZhbHVlczoKICAgIFNjYW5JbWFnZUFuZFNjaGVkdWxlUGF0Y2hUYXNrOiBjc3NjLXNjYW4taW1hZ2Utc2NoZWR1bGUtcGF0Y2gKc3RlcHM6CiAgLSBjbWQ6IGJhc2ggLWMgJ2VjaG8gIkluc2lkZSBDU1NDLVRyaWdnZXJTY2FuLCBnZXR0aW5nIGltYWdlcyB0byBiZSBwYXRjaGVkIGJhc2VkIG9uIC0tZmlsdGVyLXBvbGljeSBmb3IgUmVnaXN0cnkge3suUnVuLlJlZ2lzdHJ5fX0uIicKICAtIGNtZDogbWNyLm1pY3Jvc29mdC5jb20vYWNyL2Fjci1jbGk6MC4xMSBjc3NjIHBhdGNoIC0tZmlsdGVyLXBvbGljeSBjc3NjcG9saWNpZXMvcGF0Y2hwb2xpY3k6djEgLS1kcnktcnVuID4gZmlsdGVyUmVwb3MudHh0CiAgICBlbnY6CiAgICAgIC0gQUNSX0VYUEVSSU1FTlRBTF9DU1NDPXRydWUKICAtIGNtZDogYmFzaCAtYyAnc2VkIC1uICIvXkxpc3RpbmcvLC9eVG90YWwvIHsvXkxpc3RpbmcvYjsvXlRvdGFsL2I7cH0iIGZpbHRlclJlcG9zLnR4dCcgPiBmaWx0ZXJSZXBvc1RvRGlzcGxheS50eHQKICAtIGNtZDogYmFzaCAtYyAnZWNobyAtZSAiQmVsb3cgaW1hZ2VzIHdpbGwgYmUgc2Nhbm5lZCBhbmQgcGF0Y2hlZCAoaWYgYW55IG9zIHZ1bG5lcmFiaWxpdGllcyBmb3VuZCkgYmFzZWQgb24gLS1maWx0ZXItcG9saWN5LlxuJChjYXQgZmlsdGVyUmVwb3NUb0Rpc3BsYXkudHh0KSInCiAgLSBjbWQ6IG1jci5taWNyb3NvZnQuY29tL2Fjci9hY3ItY2xpOjAuMTEgY3NzYyBwYXRjaCAtLWZpbHRlci1wb2xpY3kgY3NzY3BvbGljaWVzL3BhdGNocG9saWN5OnYxIC0tc2hvdy1wYXRjaC10YWdzIC0tZHJ5LXJ1bj4gZmlsdGVyUmVwb3NXaXRoUGF0Y2hUYWdzLnR4dAogICAgZW52OgogICAgICAtIEFDUl9FWFBFUklNRU5UQUxfQ1NTQz10cnVlCiAgLSBjbWQ6IGJhc2ggLWMgJ3NlZCAtbiAiL15MaXN0aW5nLywvXlRvdGFsLyB7L15MaXN0aW5nL2I7L15Ub3RhbC9iO3B9IiBmaWx0ZXJSZXBvc1dpdGhQYXRjaFRhZ3MudHh0JyA+IGZpbHRlcmVkUmVwb3NBbmRUYWdzLnR4dAogIC0gY21kOiBheiBsb2dpbiAtLWlkZW50aXR5IAogIC0gY21kOiB8CiAgICAgICAgbWNyLm1pY3Jvc29mdC5jb20vYXp1cmUtY2xpIGJhc2ggLWMgJ3doaWxlIHJlYWQgbGluZTtkbyBcCiAgICAgICAgSUZTPScvJyByZWFkIC1yIC1hIGFycmF5MSA8PDwgIiRsaW5lIgogICAgICAgIElGUz0nOicgcmVhZCAtciAtYSBhcnJheTIgPDw8ICIke2FycmF5MVsxXX0iCiAgICAgICAgSUZTPScsJyByZWFkIC1yIC1hIGFycmF5MyA8PDwgIiR7YXJyYXkyWzFdfSIKICAgICAgICBSZWdpc3RyeU5hbWU9JHthcnJheTFbMF19OwogICAgICAgIFJlcG9OYW1lPSR7YXJyYXkyWzBdfTsKICAgICAgICBPcmlnaW5hbFRhZz0ke2FycmF5M1swXX07CiAgICAgICAgVGFnTmFtZT0ke2FycmF5M1sxXX07CiAgICAgICAgZWNobyAiU2NoZWR1bGluZyAkU2NhbkltYWdlQW5kU2NoZWR1bGVQYXRjaFRhc2sgZm9yICRSZWdpc3RyeU5hbWUvJFJlcG9OYW1lLCBUYWc6JFRhZ05hbWUsIE9yaWdpbmFsVGFnOiRPcmlnaW5hbFRhZyI7CiAgICAgICAgYXogYWNyIHRhc2sgcnVuIC0tbmFtZSAkU2NhbkltYWdlQW5kU2NoZWR1bGVQYXRjaFRhc2sgLS1yZWdpc3RyeSAkUmVnaXN0cnlOYW1lIC0tc2V0IFNPVVJDRV9SRVBPU0lUT1JZPSRSZXBvTmFtZSAtLXNldCBTT1VSQ0VfSU1BR0VfVEFHPSRUYWdOYW1lIC0tc2V0IFNPVVJDRV9JTUFHRV9PUklHSU5BTF9UQUc9JE9yaWdpbmFsVGFnIC0tbm8td2FpdDsgXAogICAgICAgIGRvbmUgPCBmaWx0ZXJlZFJlcG9zQW5kVGFncy50eHQ7Jw=="}}, + "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - acr supply-chain workflow create + Connection: + - keep-alive + Content-Length: + - '11445' + Content-Type: + - application/json + ParameterSetName: + - -g -t -r --config --schedule + User-Agent: + - AZURECLI/2.61.0 azsdk-python-core/1.30.2 Python/3.8.10 (Windows-10-10.0.22631-SP0) + method: PUT + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_acrcssc000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.Resources/deployments/continuouspatchingdeployment","name":"continuouspatchingdeployment","type":"Microsoft.Resources/deployments","properties":{"templateHash":"1103119532525797693","parameters":{"acrName":{"type":"String","value":"cli000002"},"acrLocation":{"type":"String","value":"westus"},"taskSchedule":{"type":"String","value":"18 + 18 */1 * *"},"imagePatchingEncodedTask":{"type":"String","value":"dmVyc2lvbjogdjEuMS4wCmFsaWFzOgogIHZhbHVlczoKICAgIFNjYW5SZXBvcnQgOiBvcy12dWxuZXJhYmlsaXR5LXJlcG9ydF90cml2eV97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fV97ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319XyQoZGF0ZSAiKyVZLSVtLSVkIikuanNvbgpzdGVwczoKICAjIFN0ZXAgIzE6IFBlcmZvcm0gdGhlIHZ1bG5lcmFiaWxpdHkgc2NhbgogIC0gaWQ6IHByaW50LWlucHV0cwogICAgY21kOiB8CiAgICAgICAgYmFzaCAtYyAnZWNobyAiU2NhbiwgVXBsb2FkIHNjYW4gcmVwb3J0IGFuZCBTY2hlZHVsZSBQYXRjaCBmb3Ige3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fSInCiAgLSBpZDogc2V0dXAtZGF0YS1kaXIKICAgIGNtZDogYmFzaCBta2RpciAuL2RhdGEKICAtIGlkOiBnZW5lcmF0ZS10cml2eS1yZXBvcnQKICAgIGNtZDogfAogICAgICBnaGNyLmlvL2FxdWFzZWN1cml0eS90cml2eSBpbWFnZSBcCiAgICAgIHt7LlJ1bi5SZWdpc3RyeX19L3t7LlZhbHVlcy5TT1VSQ0VfUkVQT1NJVE9SWX19Ont7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfVEFHfX0gXAogICAgICAtLXZ1bG4tdHlwZSBvcyBcCiAgICAgIC0taWdub3JlLXVuZml4ZWQgXAogICAgICAtLWZvcm1hdCBqc29uIFwKICAgICAgLS1vdXRwdXQgL3dvcmtzcGFjZS9kYXRhLyRTY2FuUmVwb3J0CgogICMgU3RlcCAyOiBBdHRhY2ggdGhlIHZ1bG5lcmFiaWxpdHkgc2NhbiByZXBvcnQgdG8gdGhlIGltYWdlCiAgLSBpZDogdXBsb2FkLXRyaXZ5LXJlcG9ydAogICAgY21kOiB8CiAgICAgIGdoY3IuaW8vb3Jhcy1wcm9qZWN0L29yYXM6djEuMS4wIGF0dGFjaCBcCiAgICAgIC0tYXJ0aWZhY3QtdHlwZSB2dWxuZXJhYmlsaXR5U2Nhbi9yZXBvcnQgXAogICAgICB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IFwKICAgICAgLi9kYXRhLyRTY2FuUmVwb3J0CgogIC0gY21kOiBiYXNoIGVjaG8gIlVwbG9hZGVkIHZ1bG5lcmFiaWxpdHkgcmVwb3J0ICRTY2FuUmVwb3J0IHRvIHRoZSBpbWFnZSB7ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IgoKICAtIGlkOiBidWlsZGtpdGQKICAgIGNtZDogbW9ieS9idWlsZGtpdCAtLWFkZHIgdGNwOi8vMC4wLjAuMDo4ODg4CiAgICBlbnRyeXBvaW50OiBidWlsZGtpdGQKICAgIGRldGFjaDogdHJ1ZQogICAgcHJpdmlsZWdlZDogdHJ1ZQogICAgcG9ydHM6IFsiMTI3LjAuMC4xOjg4ODg6ODg4OC90Y3AiXQogIAogIC0gaWQ6IGxpc3Qtb3V0cHV0LWZpbGUKICAgIGNtZDogYmFzaCBscyAtbCAvd29ya3NwYWNlL2RhdGEKICAKICAjIFN0ZXAgMzogUGF0Y2ggdGhlIGltYWdlIHdpdGggQ29wYWNldGljCiAgLSBpZDogcGF0Y2gtd2l0aF9jb3BhCiAgICBjbWQ6IHwKICAgICAgZ2hjci5pby90b2RkeXNtL2Nzc2MtZnJhbWV3b3JrL2NvcGFjZXRpYzoxLjAgXAogICAgICB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IFwKICAgICAgJFNjYW5SZXBvcnQgXAogICAgICB7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319LXBhdGNoZWQKICAgIG5ldHdvcms6IGhvc3QKCiAgLSBpZDogcHVzaC1pbWFnZQogICAgY21kOiBkb2NrZXIgcHVzaCB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319LXBhdGNoZWQKCiAgLSBjbWQ6IGJhc2ggZWNobyAiUGF0Y2hlZCBpbWFnZSBwdXNoZWQgdG8ge3suUnVuLlJlZ2lzdHJ5fX0ve3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fS1wYXRjaGVkIg=="},"imageScanningEncodedTask":{"type":"String","value":"dmVyc2lvbjogdjEuMS4wCmFsaWFzOgogIHZhbHVlczoKICAgIHBhdGNoaW1hZ2V0YXNrOiBjc3NjLXBhdGNoLWltYWdlCiAgICBEQVRFOiAkKGRhdGUgIislWS0lbS0lZCIpCnN0ZXBzOgogIC0gaWQ6IHByaW50LWlucHV0cwogICAgY21kOiB8CiAgICAgICAgYmFzaCAtYyAnZWNobyAiU2Nhbm5pbmcgaW1hZ2UgZm9yIHZ1bG5lcmFiaWxpdHkgYW5kIHBhdGNoIHt7LlZhbHVlcy5TT1VSQ0VfUkVQT1NJVE9SWX19Ont7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfVEFHfX0gZm9yIHRhZyB7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX09SSUdJTkFMX1RBR319IicKICAtIGlkOiBzZXR1cC1kYXRhLWRpcgogICAgY21kOiBiYXNoIG1rZGlyIC4vZGF0YQoKICAtIGlkOiBnZW5lcmF0ZS10cml2eS1yZXBvcnQKICAgIGNtZDogfAogICAgICBnaGNyLmlvL2FxdWFzZWN1cml0eS90cml2eSBpbWFnZSBcCiAgICAgIHt7LlJ1bi5SZWdpc3RyeX19L3t7LlZhbHVlcy5TT1VSQ0VfUkVQT1NJVE9SWX19Ont7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfVEFHfX0gXAogICAgICAtLXZ1bG4tdHlwZSBvcyBcCiAgICAgIC0taWdub3JlLXVuZml4ZWQgXAogICAgICAtLWZvcm1hdCBqc29uIFwKICAgICAgLS1vdXRwdXQgL3dvcmtzcGFjZS9kYXRhL3Z1bG5lcmFiaWxpdHktcmVwb3J0X3RyaXZ5XyREQVRFLmpzb24KICAtIGNtZDogbWNyLm1pY3Jvc29mdC5jb20vYXp1cmUtY2xpIGJhc2ggLWMgJ2pxICJbLlJlc3VsdHNbXS5WdWxuZXJhYmlsaXRpZXMgfCBsZW5ndGhdIHwgYWRkIiAvd29ya3NwYWNlL2RhdGEvdnVsbmVyYWJpbGl0eS1yZXBvcnRfdHJpdnlfJERBVEUuanNvbiA+IC93b3Jrc3BhY2UvZGF0YS92dWxDb3VudC50eHQnCiAgLSBjbWQ6IGJhc2ggZWNobyAiR2VuZXJhdGVkIHZ1bG5lcmFiaWxpdHkgcmVwb3J0IGF0IC93b3Jrc3BhY2UvZGF0YS92dWxuZXJhYmlsaXR5LXJlcG9ydF90cml2eV8kREFURS5qc29uIiAKICAtIGNtZDogYXogbG9naW4gLS1pZGVudGl0eQogIC0gY21kOiBiYXNoIGVjaG8gIlZ1bG5lcmFiaWxpdGllcyBmb3VuZCBmb3IgaW1hZ2Uge3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fSAtPiAkKGNhdCAvd29ya3NwYWNlL2RhdGEvdnVsQ291bnQudHh0KSIKICAtIGNtZDogfCAKICAgICAgbWNyLm1pY3Jvc29mdC5jb20vYXp1cmUtY2xpIGJhc2ggLWMgJ3Z1bENvdW50PSQoY2F0IC93b3Jrc3BhY2UvZGF0YS92dWxDb3VudC50eHQpICYmIFwKICAgICAgWyAkdnVsQ291bnQgLWd0IDAgXSAmJiBcCiAgICAgIGF6IGFjciB0YXNrIHJ1biAtLW5hbWUgJHBhdGNoaW1hZ2V0YXNrIC0tcmVnaXN0cnkgJFJlZ2lzdHJ5TmFtZSAtLXNldCBTT1VSQ0VfUkVQT1NJVE9SWT17ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fSAtLXNldCBTT1VSQ0VfSU1BR0VfVEFHPXt7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfT1JJR0lOQUxfVEFHfX0gLS1uby13YWl0IFwKICAgICAgfHwgZWNobyAiTm8gdnVsbmVyYWJpbGl0eSBpbiB0aGUgaW1hZ2Uge3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fSIn"},"registryScanningEncodedTask":{"type":"String","value":"dmVyc2lvbjogdjEuMS4wCmFsaWFzOgogIHZhbHVlczoKICAgIFNjYW5JbWFnZUFuZFNjaGVkdWxlUGF0Y2hUYXNrOiBjc3NjLXNjYW4taW1hZ2Utc2NoZWR1bGUtcGF0Y2gKc3RlcHM6CiAgLSBjbWQ6IGJhc2ggLWMgJ2VjaG8gIkluc2lkZSBDU1NDLVRyaWdnZXJTY2FuLCBnZXR0aW5nIGltYWdlcyB0byBiZSBwYXRjaGVkIGJhc2VkIG9uIC0tZmlsdGVyLXBvbGljeSBmb3IgUmVnaXN0cnkge3suUnVuLlJlZ2lzdHJ5fX0uIicKICAtIGNtZDogbWNyLm1pY3Jvc29mdC5jb20vYWNyL2Fjci1jbGk6MC4xMSBjc3NjIHBhdGNoIC0tZmlsdGVyLXBvbGljeSBjc3NjcG9saWNpZXMvcGF0Y2hwb2xpY3k6djEgLS1kcnktcnVuID4gZmlsdGVyUmVwb3MudHh0CiAgICBlbnY6CiAgICAgIC0gQUNSX0VYUEVSSU1FTlRBTF9DU1NDPXRydWUKICAtIGNtZDogYmFzaCAtYyAnc2VkIC1uICIvXkxpc3RpbmcvLC9eVG90YWwvIHsvXkxpc3RpbmcvYjsvXlRvdGFsL2I7cH0iIGZpbHRlclJlcG9zLnR4dCcgPiBmaWx0ZXJSZXBvc1RvRGlzcGxheS50eHQKICAtIGNtZDogYmFzaCAtYyAnZWNobyAtZSAiQmVsb3cgaW1hZ2VzIHdpbGwgYmUgc2Nhbm5lZCBhbmQgcGF0Y2hlZCAoaWYgYW55IG9zIHZ1bG5lcmFiaWxpdGllcyBmb3VuZCkgYmFzZWQgb24gLS1maWx0ZXItcG9saWN5LlxuJChjYXQgZmlsdGVyUmVwb3NUb0Rpc3BsYXkudHh0KSInCiAgLSBjbWQ6IG1jci5taWNyb3NvZnQuY29tL2Fjci9hY3ItY2xpOjAuMTEgY3NzYyBwYXRjaCAtLWZpbHRlci1wb2xpY3kgY3NzY3BvbGljaWVzL3BhdGNocG9saWN5OnYxIC0tc2hvdy1wYXRjaC10YWdzIC0tZHJ5LXJ1bj4gZmlsdGVyUmVwb3NXaXRoUGF0Y2hUYWdzLnR4dAogICAgZW52OgogICAgICAtIEFDUl9FWFBFUklNRU5UQUxfQ1NTQz10cnVlCiAgLSBjbWQ6IGJhc2ggLWMgJ3NlZCAtbiAiL15MaXN0aW5nLywvXlRvdGFsLyB7L15MaXN0aW5nL2I7L15Ub3RhbC9iO3B9IiBmaWx0ZXJSZXBvc1dpdGhQYXRjaFRhZ3MudHh0JyA+IGZpbHRlcmVkUmVwb3NBbmRUYWdzLnR4dAogIC0gY21kOiBheiBsb2dpbiAtLWlkZW50aXR5IAogIC0gY21kOiB8CiAgICAgICAgbWNyLm1pY3Jvc29mdC5jb20vYXp1cmUtY2xpIGJhc2ggLWMgJ3doaWxlIHJlYWQgbGluZTtkbyBcCiAgICAgICAgSUZTPScvJyByZWFkIC1yIC1hIGFycmF5MSA8PDwgIiRsaW5lIgogICAgICAgIElGUz0nOicgcmVhZCAtciAtYSBhcnJheTIgPDw8ICIke2FycmF5MVsxXX0iCiAgICAgICAgSUZTPScsJyByZWFkIC1yIC1hIGFycmF5MyA8PDwgIiR7YXJyYXkyWzFdfSIKICAgICAgICBSZWdpc3RyeU5hbWU9JHthcnJheTFbMF19OwogICAgICAgIFJlcG9OYW1lPSR7YXJyYXkyWzBdfTsKICAgICAgICBPcmlnaW5hbFRhZz0ke2FycmF5M1swXX07CiAgICAgICAgVGFnTmFtZT0ke2FycmF5M1sxXX07CiAgICAgICAgZWNobyAiU2NoZWR1bGluZyAkU2NhbkltYWdlQW5kU2NoZWR1bGVQYXRjaFRhc2sgZm9yICRSZWdpc3RyeU5hbWUvJFJlcG9OYW1lLCBUYWc6JFRhZ05hbWUsIE9yaWdpbmFsVGFnOiRPcmlnaW5hbFRhZyI7CiAgICAgICAgYXogYWNyIHRhc2sgcnVuIC0tbmFtZSAkU2NhbkltYWdlQW5kU2NoZWR1bGVQYXRjaFRhc2sgLS1yZWdpc3RyeSAkUmVnaXN0cnlOYW1lIC0tc2V0IFNPVVJDRV9SRVBPU0lUT1JZPSRSZXBvTmFtZSAtLXNldCBTT1VSQ0VfSU1BR0VfVEFHPSRUYWdOYW1lIC0tc2V0IFNPVVJDRV9JTUFHRV9PUklHSU5BTF9UQUc9JE9yaWdpbmFsVGFnIC0tbm8td2FpdDsgXAogICAgICAgIGRvbmUgPCBmaWx0ZXJlZFJlcG9zQW5kVGFncy50eHQ7Jw=="}},"mode":"Incremental","provisioningState":"Accepted","timestamp":"2024-06-17T18:18:26.5983479Z","duration":"PT0.000142S","correlationId":"2a4902b2-d2f0-4522-b4d3-ca521cd0f59d","providers":[{"namespace":"Microsoft.ContainerRegistry","resourceTypes":[{"resourceType":"registries/tasks","locations":["westus"]}]},{"namespace":"Microsoft.Authorization","resourceTypes":[{"resourceType":"roleAssignments","locations":[null]}]}],"dependencies":[{"dependsOn":[{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-scan-image-schedule-patch","resourceType":"Microsoft.ContainerRegistry/registries/tasks","resourceName":"cli000002/cssc-scan-image-schedule-patch"},{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-scan-image-schedule-patch","resourceType":"Microsoft.ContainerRegistry/registries/tasks","resourceName":"cli000002/cssc-scan-image-schedule-patch","apiVersion":"2019-06-01-preview"}],"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/providers/Microsoft.Authorization/roleAssignments/4d82eda5-7482-55f4-81d9-d8a19d2d9ce2","resourceType":"Microsoft.Authorization/roleAssignments","resourceName":"4d82eda5-7482-55f4-81d9-d8a19d2d9ce2"},{"dependsOn":[{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-trigger-scan","resourceType":"Microsoft.ContainerRegistry/registries/tasks","resourceName":"cli000002/cssc-trigger-scan"},{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-trigger-scan","resourceType":"Microsoft.ContainerRegistry/registries/tasks","resourceName":"cli000002/cssc-trigger-scan","apiVersion":"2019-06-01-preview"}],"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/providers/Microsoft.Authorization/roleAssignments/3dfab287-54f3-51aa-8165-175f40dd8b00","resourceType":"Microsoft.Authorization/roleAssignments","resourceName":"3dfab287-54f3-51aa-8165-175f40dd8b00"}]}}' + headers: + azure-asyncoperation: + - https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_acrcssc000001/providers/Microsoft.Resources/deployments/continuouspatchingdeployment/operationStatuses/08584829585798070804?api-version=2022-09-01 + cache-control: + - no-cache + content-length: + - '10187' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:18:26 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: F98AB0E70D204A55B0C5B367926483AE Ref B: CO6AA3150218047 Ref C: 2024-06-17T18:18:25Z' + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - acr supply-chain workflow create + Connection: + - keep-alive + ParameterSetName: + - -g -t -r --config --schedule + User-Agent: + - AZURECLI/2.61.0 azsdk-python-core/1.30.2 Python/3.8.10 (Windows-10-10.0.22631-SP0) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_acrcssc000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584829585798070804?api-version=2022-09-01 + response: + body: + string: '{"status":"Accepted"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:18:26 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 3FD9B593B7D04698ACF1688F332C29B1 Ref B: CO6AA3150218047 Ref C: 2024-06-17T18:18:26Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - acr supply-chain workflow create + Connection: + - keep-alive + ParameterSetName: + - -g -t -r --config --schedule + User-Agent: + - AZURECLI/2.61.0 azsdk-python-core/1.30.2 Python/3.8.10 (Windows-10-10.0.22631-SP0) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_acrcssc000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584829585798070804?api-version=2022-09-01 + response: + body: + string: '{"status":"Succeeded"}' + headers: + cache-control: + - no-cache + content-length: + - '22' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:18:56 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 112ADFCB7521403199D82415AEAC3ECE Ref B: CO6AA3150218047 Ref C: 2024-06-17T18:18:57Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - acr supply-chain workflow create + Connection: + - keep-alive + ParameterSetName: + - -g -t -r --config --schedule + User-Agent: + - AZURECLI/2.61.0 azsdk-python-core/1.30.2 Python/3.8.10 (Windows-10-10.0.22631-SP0) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_acrcssc000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.Resources/deployments/continuouspatchingdeployment","name":"continuouspatchingdeployment","type":"Microsoft.Resources/deployments","properties":{"templateHash":"1103119532525797693","parameters":{"acrName":{"type":"String","value":"cli000002"},"acrLocation":{"type":"String","value":"westus"},"taskSchedule":{"type":"String","value":"18 + 18 */1 * *"},"imagePatchingEncodedTask":{"type":"String","value":"dmVyc2lvbjogdjEuMS4wCmFsaWFzOgogIHZhbHVlczoKICAgIFNjYW5SZXBvcnQgOiBvcy12dWxuZXJhYmlsaXR5LXJlcG9ydF90cml2eV97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fV97ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319XyQoZGF0ZSAiKyVZLSVtLSVkIikuanNvbgpzdGVwczoKICAjIFN0ZXAgIzE6IFBlcmZvcm0gdGhlIHZ1bG5lcmFiaWxpdHkgc2NhbgogIC0gaWQ6IHByaW50LWlucHV0cwogICAgY21kOiB8CiAgICAgICAgYmFzaCAtYyAnZWNobyAiU2NhbiwgVXBsb2FkIHNjYW4gcmVwb3J0IGFuZCBTY2hlZHVsZSBQYXRjaCBmb3Ige3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fSInCiAgLSBpZDogc2V0dXAtZGF0YS1kaXIKICAgIGNtZDogYmFzaCBta2RpciAuL2RhdGEKICAtIGlkOiBnZW5lcmF0ZS10cml2eS1yZXBvcnQKICAgIGNtZDogfAogICAgICBnaGNyLmlvL2FxdWFzZWN1cml0eS90cml2eSBpbWFnZSBcCiAgICAgIHt7LlJ1bi5SZWdpc3RyeX19L3t7LlZhbHVlcy5TT1VSQ0VfUkVQT1NJVE9SWX19Ont7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfVEFHfX0gXAogICAgICAtLXZ1bG4tdHlwZSBvcyBcCiAgICAgIC0taWdub3JlLXVuZml4ZWQgXAogICAgICAtLWZvcm1hdCBqc29uIFwKICAgICAgLS1vdXRwdXQgL3dvcmtzcGFjZS9kYXRhLyRTY2FuUmVwb3J0CgogICMgU3RlcCAyOiBBdHRhY2ggdGhlIHZ1bG5lcmFiaWxpdHkgc2NhbiByZXBvcnQgdG8gdGhlIGltYWdlCiAgLSBpZDogdXBsb2FkLXRyaXZ5LXJlcG9ydAogICAgY21kOiB8CiAgICAgIGdoY3IuaW8vb3Jhcy1wcm9qZWN0L29yYXM6djEuMS4wIGF0dGFjaCBcCiAgICAgIC0tYXJ0aWZhY3QtdHlwZSB2dWxuZXJhYmlsaXR5U2Nhbi9yZXBvcnQgXAogICAgICB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IFwKICAgICAgLi9kYXRhLyRTY2FuUmVwb3J0CgogIC0gY21kOiBiYXNoIGVjaG8gIlVwbG9hZGVkIHZ1bG5lcmFiaWxpdHkgcmVwb3J0ICRTY2FuUmVwb3J0IHRvIHRoZSBpbWFnZSB7ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IgoKICAtIGlkOiBidWlsZGtpdGQKICAgIGNtZDogbW9ieS9idWlsZGtpdCAtLWFkZHIgdGNwOi8vMC4wLjAuMDo4ODg4CiAgICBlbnRyeXBvaW50OiBidWlsZGtpdGQKICAgIGRldGFjaDogdHJ1ZQogICAgcHJpdmlsZWdlZDogdHJ1ZQogICAgcG9ydHM6IFsiMTI3LjAuMC4xOjg4ODg6ODg4OC90Y3AiXQogIAogIC0gaWQ6IGxpc3Qtb3V0cHV0LWZpbGUKICAgIGNtZDogYmFzaCBscyAtbCAvd29ya3NwYWNlL2RhdGEKICAKICAjIFN0ZXAgMzogUGF0Y2ggdGhlIGltYWdlIHdpdGggQ29wYWNldGljCiAgLSBpZDogcGF0Y2gtd2l0aF9jb3BhCiAgICBjbWQ6IHwKICAgICAgZ2hjci5pby90b2RkeXNtL2Nzc2MtZnJhbWV3b3JrL2NvcGFjZXRpYzoxLjAgXAogICAgICB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IFwKICAgICAgJFNjYW5SZXBvcnQgXAogICAgICB7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319LXBhdGNoZWQKICAgIG5ldHdvcms6IGhvc3QKCiAgLSBpZDogcHVzaC1pbWFnZQogICAgY21kOiBkb2NrZXIgcHVzaCB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319LXBhdGNoZWQKCiAgLSBjbWQ6IGJhc2ggZWNobyAiUGF0Y2hlZCBpbWFnZSBwdXNoZWQgdG8ge3suUnVuLlJlZ2lzdHJ5fX0ve3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fS1wYXRjaGVkIg=="},"imageScanningEncodedTask":{"type":"String","value":"dmVyc2lvbjogdjEuMS4wCmFsaWFzOgogIHZhbHVlczoKICAgIHBhdGNoaW1hZ2V0YXNrOiBjc3NjLXBhdGNoLWltYWdlCiAgICBEQVRFOiAkKGRhdGUgIislWS0lbS0lZCIpCnN0ZXBzOgogIC0gaWQ6IHByaW50LWlucHV0cwogICAgY21kOiB8CiAgICAgICAgYmFzaCAtYyAnZWNobyAiU2Nhbm5pbmcgaW1hZ2UgZm9yIHZ1bG5lcmFiaWxpdHkgYW5kIHBhdGNoIHt7LlZhbHVlcy5TT1VSQ0VfUkVQT1NJVE9SWX19Ont7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfVEFHfX0gZm9yIHRhZyB7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX09SSUdJTkFMX1RBR319IicKICAtIGlkOiBzZXR1cC1kYXRhLWRpcgogICAgY21kOiBiYXNoIG1rZGlyIC4vZGF0YQoKICAtIGlkOiBnZW5lcmF0ZS10cml2eS1yZXBvcnQKICAgIGNtZDogfAogICAgICBnaGNyLmlvL2FxdWFzZWN1cml0eS90cml2eSBpbWFnZSBcCiAgICAgIHt7LlJ1bi5SZWdpc3RyeX19L3t7LlZhbHVlcy5TT1VSQ0VfUkVQT1NJVE9SWX19Ont7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfVEFHfX0gXAogICAgICAtLXZ1bG4tdHlwZSBvcyBcCiAgICAgIC0taWdub3JlLXVuZml4ZWQgXAogICAgICAtLWZvcm1hdCBqc29uIFwKICAgICAgLS1vdXRwdXQgL3dvcmtzcGFjZS9kYXRhL3Z1bG5lcmFiaWxpdHktcmVwb3J0X3RyaXZ5XyREQVRFLmpzb24KICAtIGNtZDogbWNyLm1pY3Jvc29mdC5jb20vYXp1cmUtY2xpIGJhc2ggLWMgJ2pxICJbLlJlc3VsdHNbXS5WdWxuZXJhYmlsaXRpZXMgfCBsZW5ndGhdIHwgYWRkIiAvd29ya3NwYWNlL2RhdGEvdnVsbmVyYWJpbGl0eS1yZXBvcnRfdHJpdnlfJERBVEUuanNvbiA+IC93b3Jrc3BhY2UvZGF0YS92dWxDb3VudC50eHQnCiAgLSBjbWQ6IGJhc2ggZWNobyAiR2VuZXJhdGVkIHZ1bG5lcmFiaWxpdHkgcmVwb3J0IGF0IC93b3Jrc3BhY2UvZGF0YS92dWxuZXJhYmlsaXR5LXJlcG9ydF90cml2eV8kREFURS5qc29uIiAKICAtIGNtZDogYXogbG9naW4gLS1pZGVudGl0eQogIC0gY21kOiBiYXNoIGVjaG8gIlZ1bG5lcmFiaWxpdGllcyBmb3VuZCBmb3IgaW1hZ2Uge3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fSAtPiAkKGNhdCAvd29ya3NwYWNlL2RhdGEvdnVsQ291bnQudHh0KSIKICAtIGNtZDogfCAKICAgICAgbWNyLm1pY3Jvc29mdC5jb20vYXp1cmUtY2xpIGJhc2ggLWMgJ3Z1bENvdW50PSQoY2F0IC93b3Jrc3BhY2UvZGF0YS92dWxDb3VudC50eHQpICYmIFwKICAgICAgWyAkdnVsQ291bnQgLWd0IDAgXSAmJiBcCiAgICAgIGF6IGFjciB0YXNrIHJ1biAtLW5hbWUgJHBhdGNoaW1hZ2V0YXNrIC0tcmVnaXN0cnkgJFJlZ2lzdHJ5TmFtZSAtLXNldCBTT1VSQ0VfUkVQT1NJVE9SWT17ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fSAtLXNldCBTT1VSQ0VfSU1BR0VfVEFHPXt7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfT1JJR0lOQUxfVEFHfX0gLS1uby13YWl0IFwKICAgICAgfHwgZWNobyAiTm8gdnVsbmVyYWJpbGl0eSBpbiB0aGUgaW1hZ2Uge3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fSIn"},"registryScanningEncodedTask":{"type":"String","value":"dmVyc2lvbjogdjEuMS4wCmFsaWFzOgogIHZhbHVlczoKICAgIFNjYW5JbWFnZUFuZFNjaGVkdWxlUGF0Y2hUYXNrOiBjc3NjLXNjYW4taW1hZ2Utc2NoZWR1bGUtcGF0Y2gKc3RlcHM6CiAgLSBjbWQ6IGJhc2ggLWMgJ2VjaG8gIkluc2lkZSBDU1NDLVRyaWdnZXJTY2FuLCBnZXR0aW5nIGltYWdlcyB0byBiZSBwYXRjaGVkIGJhc2VkIG9uIC0tZmlsdGVyLXBvbGljeSBmb3IgUmVnaXN0cnkge3suUnVuLlJlZ2lzdHJ5fX0uIicKICAtIGNtZDogbWNyLm1pY3Jvc29mdC5jb20vYWNyL2Fjci1jbGk6MC4xMSBjc3NjIHBhdGNoIC0tZmlsdGVyLXBvbGljeSBjc3NjcG9saWNpZXMvcGF0Y2hwb2xpY3k6djEgLS1kcnktcnVuID4gZmlsdGVyUmVwb3MudHh0CiAgICBlbnY6CiAgICAgIC0gQUNSX0VYUEVSSU1FTlRBTF9DU1NDPXRydWUKICAtIGNtZDogYmFzaCAtYyAnc2VkIC1uICIvXkxpc3RpbmcvLC9eVG90YWwvIHsvXkxpc3RpbmcvYjsvXlRvdGFsL2I7cH0iIGZpbHRlclJlcG9zLnR4dCcgPiBmaWx0ZXJSZXBvc1RvRGlzcGxheS50eHQKICAtIGNtZDogYmFzaCAtYyAnZWNobyAtZSAiQmVsb3cgaW1hZ2VzIHdpbGwgYmUgc2Nhbm5lZCBhbmQgcGF0Y2hlZCAoaWYgYW55IG9zIHZ1bG5lcmFiaWxpdGllcyBmb3VuZCkgYmFzZWQgb24gLS1maWx0ZXItcG9saWN5LlxuJChjYXQgZmlsdGVyUmVwb3NUb0Rpc3BsYXkudHh0KSInCiAgLSBjbWQ6IG1jci5taWNyb3NvZnQuY29tL2Fjci9hY3ItY2xpOjAuMTEgY3NzYyBwYXRjaCAtLWZpbHRlci1wb2xpY3kgY3NzY3BvbGljaWVzL3BhdGNocG9saWN5OnYxIC0tc2hvdy1wYXRjaC10YWdzIC0tZHJ5LXJ1bj4gZmlsdGVyUmVwb3NXaXRoUGF0Y2hUYWdzLnR4dAogICAgZW52OgogICAgICAtIEFDUl9FWFBFUklNRU5UQUxfQ1NTQz10cnVlCiAgLSBjbWQ6IGJhc2ggLWMgJ3NlZCAtbiAiL15MaXN0aW5nLywvXlRvdGFsLyB7L15MaXN0aW5nL2I7L15Ub3RhbC9iO3B9IiBmaWx0ZXJSZXBvc1dpdGhQYXRjaFRhZ3MudHh0JyA+IGZpbHRlcmVkUmVwb3NBbmRUYWdzLnR4dAogIC0gY21kOiBheiBsb2dpbiAtLWlkZW50aXR5IAogIC0gY21kOiB8CiAgICAgICAgbWNyLm1pY3Jvc29mdC5jb20vYXp1cmUtY2xpIGJhc2ggLWMgJ3doaWxlIHJlYWQgbGluZTtkbyBcCiAgICAgICAgSUZTPScvJyByZWFkIC1yIC1hIGFycmF5MSA8PDwgIiRsaW5lIgogICAgICAgIElGUz0nOicgcmVhZCAtciAtYSBhcnJheTIgPDw8ICIke2FycmF5MVsxXX0iCiAgICAgICAgSUZTPScsJyByZWFkIC1yIC1hIGFycmF5MyA8PDwgIiR7YXJyYXkyWzFdfSIKICAgICAgICBSZWdpc3RyeU5hbWU9JHthcnJheTFbMF19OwogICAgICAgIFJlcG9OYW1lPSR7YXJyYXkyWzBdfTsKICAgICAgICBPcmlnaW5hbFRhZz0ke2FycmF5M1swXX07CiAgICAgICAgVGFnTmFtZT0ke2FycmF5M1sxXX07CiAgICAgICAgZWNobyAiU2NoZWR1bGluZyAkU2NhbkltYWdlQW5kU2NoZWR1bGVQYXRjaFRhc2sgZm9yICRSZWdpc3RyeU5hbWUvJFJlcG9OYW1lLCBUYWc6JFRhZ05hbWUsIE9yaWdpbmFsVGFnOiRPcmlnaW5hbFRhZyI7CiAgICAgICAgYXogYWNyIHRhc2sgcnVuIC0tbmFtZSAkU2NhbkltYWdlQW5kU2NoZWR1bGVQYXRjaFRhc2sgLS1yZWdpc3RyeSAkUmVnaXN0cnlOYW1lIC0tc2V0IFNPVVJDRV9SRVBPU0lUT1JZPSRSZXBvTmFtZSAtLXNldCBTT1VSQ0VfSU1BR0VfVEFHPSRUYWdOYW1lIC0tc2V0IFNPVVJDRV9JTUFHRV9PUklHSU5BTF9UQUc9JE9yaWdpbmFsVGFnIC0tbm8td2FpdDsgXAogICAgICAgIGRvbmUgPCBmaWx0ZXJlZFJlcG9zQW5kVGFncy50eHQ7Jw=="}},"mode":"Incremental","provisioningState":"Succeeded","timestamp":"2024-06-17T18:18:40.1999809Z","duration":"PT13.601775S","correlationId":"2a4902b2-d2f0-4522-b4d3-ca521cd0f59d","providers":[{"namespace":"Microsoft.ContainerRegistry","resourceTypes":[{"resourceType":"registries/tasks","locations":["westus"]}]},{"namespace":"Microsoft.Authorization","resourceTypes":[{"resourceType":"roleAssignments","locations":[null]}]}],"dependencies":[{"dependsOn":[{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-scan-image-schedule-patch","resourceType":"Microsoft.ContainerRegistry/registries/tasks","resourceName":"cli000002/cssc-scan-image-schedule-patch"},{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-scan-image-schedule-patch","resourceType":"Microsoft.ContainerRegistry/registries/tasks","resourceName":"cli000002/cssc-scan-image-schedule-patch","apiVersion":"2019-06-01-preview"}],"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/providers/Microsoft.Authorization/roleAssignments/4d82eda5-7482-55f4-81d9-d8a19d2d9ce2","resourceType":"Microsoft.Authorization/roleAssignments","resourceName":"4d82eda5-7482-55f4-81d9-d8a19d2d9ce2"},{"dependsOn":[{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-trigger-scan","resourceType":"Microsoft.ContainerRegistry/registries/tasks","resourceName":"cli000002/cssc-trigger-scan"},{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-trigger-scan","resourceType":"Microsoft.ContainerRegistry/registries/tasks","resourceName":"cli000002/cssc-trigger-scan","apiVersion":"2019-06-01-preview"}],"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/providers/Microsoft.Authorization/roleAssignments/3dfab287-54f3-51aa-8165-175f40dd8b00","resourceType":"Microsoft.Authorization/roleAssignments","resourceName":"3dfab287-54f3-51aa-8165-175f40dd8b00"}],"outputResources":[{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/providers/Microsoft.Authorization/roleAssignments/3dfab287-54f3-51aa-8165-175f40dd8b00"},{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/providers/Microsoft.Authorization/roleAssignments/4d82eda5-7482-55f4-81d9-d8a19d2d9ce2"},{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-patch-image"},{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-scan-image-schedule-patch"},{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-trigger-scan"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '11257' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:18:57 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 30D842C59225492682692EA93F3839D5 Ref B: CO6AA3150218047 Ref C: 2024-06-17T18:18:57Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/octet-stream + User-Agent: + - python-requests/2.32.3 + method: POST + uri: https://cli000002.azurecr.io/v2/csscpolicies/patchpolicy/blobs/uploads/ + response: + body: + string: '{"errors":[{"code":"UNAUTHORIZED","message":"authentication required, + visit https://aka.ms/acr/authorization for more information.","detail":[{"Type":"repository","Name":"csscpolicies/patchpolicy","Action":"pull"},{"Type":"repository","Name":"csscpolicies/patchpolicy","Action":"push"}]}]} + + ' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '290' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:19:00 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer realm="https://clivrewxyvnmnvd2kexvenki.azurecr.io/oauth2/token",service="clivrewxyvnmnvd2kexvenki.azurecr.io",scope="repository:csscpolicies/patchpolicy:push,pull" + x-content-type-options: + - nosniff + status: + code: 401 + message: Unauthorized +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Service: + - clivrewxyvnmnvd2kexvenki.azurecr.io + User-Agent: + - oras-py + method: GET + uri: https://cli000002.azurecr.io/oauth2/token?service=cli000002.azurecr.io&scope=repository%3Acsscpolicies%2Fpatchpolicy%3Apush%2Cpull + response: + body: + string: '{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IklFTVI6TTdFRzpVV1JUOllIUEs6T1BZUTpZQjZNOjVUQ1M6S1RYRjpaQUhDOlZIRUw6RVVMUTo0SU1LIn0.eyJqdGkiOiI2MDkyMTJlMy01YjYzLTQ0YWQtYTllYi03NmQ5NWUxYjkzOTAiLCJzdWIiOiJwdXdhbGVjaEBtaWNyb3NvZnQuY29tIiwibmJmIjoxNzE4NjQ3NDQwLCJleHAiOjE3MTg2NTE5NDAsImlhdCI6MTcxODY0NzQ0MCwiaXNzIjoiQXp1cmUgQ29udGFpbmVyIFJlZ2lzdHJ5IiwiYXVkIjoiY2xpdnJld3h5dm5tbnZkMmtleHZlbmtpLmF6dXJlY3IuaW8iLCJ2ZXJzaW9uIjoiMS4wIiwicmlkIjoiNDUzNzdkOTJjOTFiNDc4OWE2YmFmMzgzOWI3NzFmZTUiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6ImNzc2Nwb2xpY2llcy9wYXRjaHBvbGljeSIsImFjdGlvbnMiOlsicHVzaCIsInB1bGwiXX1dLCJyb2xlcyI6W10sImdyYW50X3R5cGUiOiJhY2Nlc3NfdG9rZW4iLCJhcHBpZCI6IjA0YjA3Nzk1LThkZGItNDYxYS1iYmVlLTAyZjllMWJmN2I0NiJ9.SEZRk7NewyaI8RrHPwRZBH05qm0GLYeZ8b6dRVNEq2d9oLckUamBvT4cUowuHMc5IFzIYc1DXGZCDWa9WQcIjWo0rO8AbCganLY5uK2tC3GiaLu0ca-qe6nt7Bljn677tZ3mwLGheHDqXSOCKapYEEXfjJ-9bkqaf7KN0mIQZtArn4bxFW9ZC6En51Xg5f_3zrq6e5vtympayYvnD4YavIVKETGvo7yTIo6o1Pa3k95axouzKkk6kwLTR5othJ83F1KxEqYxgBby5y2JmvvYXLtS4WtTPKwebLIpShscSnFEOpQ-5HOHGBXWYxwn20ncU5b5paSZtN4RBHUgq_Rq0A","refresh_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IklFTVI6TTdFRzpVV1JUOllIUEs6T1BZUTpZQjZNOjVUQ1M6S1RYRjpaQUhDOlZIRUw6RVVMUTo0SU1LIn0.eyJqdGkiOiIwNWQ3Y2U4Mi05YTkwLTQxOGYtOGQxMi04ZWY3ZDk1NzI4MTQiLCJzdWIiOiJwdXdhbGVjaEBtaWNyb3NvZnQuY29tIiwibmJmIjoxNzE4NjQ3NDQwLCJleHAiOjE3MTg2NTkxNDAsImlhdCI6MTcxODY0NzQ0MCwiaXNzIjoiQXp1cmUgQ29udGFpbmVyIFJlZ2lzdHJ5IiwiYXVkIjoiY2xpdnJld3h5dm5tbnZkMmtleHZlbmtpLmF6dXJlY3IuaW8iLCJ2ZXJzaW9uIjoiMS4wIiwicmlkIjoiNDUzNzdkOTJjOTFiNDc4OWE2YmFmMzgzOWI3NzFmZTUiLCJncmFudF90eXBlIjoicmVmcmVzaF90b2tlbiIsImFwcGlkIjoiMDRiMDc3OTUtOGRkYi00NjFhLWJiZWUtMDJmOWUxYmY3YjQ2IiwidGVuYW50IjoiNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3IiwicGVybWlzc2lvbnMiOnsiYWN0aW9ucyI6WyJyZWFkIiwid3JpdGUiLCJkZWxldGUiLCJtZXRhZGF0YS9yZWFkIiwibWV0YWRhdGEvd3JpdGUiLCJkZWxldGVkL3JlYWQiLCJkZWxldGVkL3Jlc3RvcmUvYWN0aW9uIl19LCJyb2xlcyI6W119.GwLXXNwV1G-Juicy6UcK-iwzYRM1h5ZWxf_4dKYkWPe3_e20jbt6FqQ1fHMUOjoH4j6sn1GBwq7wc4HFYj7KJW27il3ZtwuTVb7xPE68e9j64xZz97N-mgpS-5hGC3AYw_USPHoZXwnoJevnyWdu1L9KDnDZdD61QdmxaF7hzl8O7KNqefZZOFaqt8pRzzXfv9djppont38F0axly6M5Mn7hykQsrjxRm-8z7ufVgZXBAZp1mWe6YqR_ItnnGtVWav9kdkIRJ3O9MQcgse5IawbqjSluwmzNpxqtp-cZOXNMY8cSztEMkahIfihnjI0gFkh5ZQC7PM9-wpe0DjhZpA"}' + headers: + connection: + - keep-alive + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:19:00 GMT + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + x-ms-ratelimit-remaining-calls-per-second: + - '166.65' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/octet-stream + User-Agent: + - python-requests/2.32.3 + method: POST + uri: https://cli000002.azurecr.io/v2/csscpolicies/patchpolicy/blobs/uploads/ + response: + body: + string: '' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '0' + date: + - Mon, 17 Jun 2024 18:19:01 GMT + docker-distribution-api-version: + - registry/2.0 + docker-upload-uuid: + - 59de5123-9a38-4a0e-be3a-d23c5ba5a9e3 + location: + - /v2/csscpolicies/patchpolicy/blobs/uploads/59de5123-9a38-4a0e-be3a-d23c5ba5a9e3?_nouploadcache=false&_state=x8fkfxAO7sRtYsMeHOjRaXSxmecQjZEnTbBJV9y_Uep7Ik5hbWUiOiJjc3NjcG9saWNpZXMvcGF0Y2hwb2xpY3kiLCJVVUlEIjoiNTlkZTUxMjMtOWEzOC00YTBlLWJlM2EtZDIzYzViYTVhOWUzIiwiT2Zmc2V0IjowLCJTdGFydGVkQXQiOiIyMDI0LTA2LTE3VDE4OjE5OjAxLjE2MjIzNjg5MVoifQ%3D%3D + range: + - 0-0 + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 202 + message: Accepted +- request: + body: "{\r\n \"repositories\": [\r\n\t{\r\n \"enabled\": true,\r\n + \ \"repository\": \"python\",\r\n \"tags\": [\r\n \"*\"\r\n + \ ]\r\n }\r\n\t],\r\n \"version\": \"v1\"\r\n}" + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '194' + Content-Type: + - application/octet-stream + User-Agent: + - python-requests/2.32.3 + method: PUT + uri: https://cli000002.azurecr.io/v2/csscpolicies/patchpolicy/blobs/uploads/59de5123-9a38-4a0e-be3a-d23c5ba5a9e3?_nouploadcache=false&_state=x8fkfxAO7sRtYsMeHOjRaXSxmecQjZEnTbBJV9y_Uep7Ik5hbWUiOiJjc3NjcG9saWNpZXMvcGF0Y2hwb2xpY3kiLCJVVUlEIjoiNTlkZTUxMjMtOWEzOC00YTBlLWJlM2EtZDIzYzViYTVhOWUzIiwiT2Zmc2V0IjowLCJTdGFydGVkQXQiOiIyMDI0LTA2LTE3VDE4OjE5OjAxLjE2MjIzNjg5MVoifQ%3D%3D&digest=sha256%3A202b8e6a12528252f53b2920ec271b8531d21221013109597d260ccead4b95bf + response: + body: + string: '' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '0' + date: + - Mon, 17 Jun 2024 18:19:01 GMT + docker-content-digest: + - sha256:202b8e6a12528252f53b2920ec271b8531d21221013109597d260ccead4b95bf + docker-distribution-api-version: + - registry/2.0 + location: + - /v2/csscpolicies/patchpolicy/blobs/sha256:202b8e6a12528252f53b2920ec271b8531d21221013109597d260ccead4b95bf + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/octet-stream + User-Agent: + - python-requests/2.32.3 + method: POST + uri: https://cli000002.azurecr.io/v2/csscpolicies/patchpolicy/blobs/uploads/ + response: + body: + string: '{"errors":[{"code":"UNAUTHORIZED","message":"authentication required, + visit https://aka.ms/acr/authorization for more information.","detail":[{"Type":"repository","Name":"csscpolicies/patchpolicy","Action":"pull"},{"Type":"repository","Name":"csscpolicies/patchpolicy","Action":"push"}]}]} + + ' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '290' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:19:01 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer realm="https://clivrewxyvnmnvd2kexvenki.azurecr.io/oauth2/token",service="clivrewxyvnmnvd2kexvenki.azurecr.io",scope="repository:csscpolicies/patchpolicy:pull,push" + x-content-type-options: + - nosniff + status: + code: 401 + message: Unauthorized +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Service: + - clivrewxyvnmnvd2kexvenki.azurecr.io + User-Agent: + - oras-py + method: GET + uri: https://cli000002.azurecr.io/oauth2/token?service=cli000002.azurecr.io&scope=repository%3Acsscpolicies%2Fpatchpolicy%3Apull%2Cpush + response: + body: + string: '{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IklFTVI6TTdFRzpVV1JUOllIUEs6T1BZUTpZQjZNOjVUQ1M6S1RYRjpaQUhDOlZIRUw6RVVMUTo0SU1LIn0.eyJqdGkiOiIxOWJjOWIxNS05OTY0LTRjMzQtYTU4YS0xZjU0ZWMyYjQ2OTAiLCJzdWIiOiJwdXdhbGVjaEBtaWNyb3NvZnQuY29tIiwibmJmIjoxNzE4NjQ3NDQxLCJleHAiOjE3MTg2NTE5NDEsImlhdCI6MTcxODY0NzQ0MSwiaXNzIjoiQXp1cmUgQ29udGFpbmVyIFJlZ2lzdHJ5IiwiYXVkIjoiY2xpdnJld3h5dm5tbnZkMmtleHZlbmtpLmF6dXJlY3IuaW8iLCJ2ZXJzaW9uIjoiMS4wIiwicmlkIjoiNDUzNzdkOTJjOTFiNDc4OWE2YmFmMzgzOWI3NzFmZTUiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6ImNzc2Nwb2xpY2llcy9wYXRjaHBvbGljeSIsImFjdGlvbnMiOlsicHVsbCIsInB1c2giXX1dLCJyb2xlcyI6W10sImdyYW50X3R5cGUiOiJhY2Nlc3NfdG9rZW4iLCJhcHBpZCI6IjA0YjA3Nzk1LThkZGItNDYxYS1iYmVlLTAyZjllMWJmN2I0NiJ9.YTFmlvk0wjz4ROfbuZiGEwy4Ckv8GvG-vHjRpby3G10vkMYkCCbFw7x-iUmRIqpUCywHGNvx3udGJ5kaL9OKQFYBmTIUt3vmJKmUivUhnn8YYg-kEErcS96kQAggi7Hy7scDsuR-rX0bJMHb5A7YVhuD7vNyJgROMF3rzE1eeXK79cLCF37FrZhiHHBPb5a20E6JPRxVQUmlEoIRAsdofRWXqhK81DGPSgY7KHHk11FAbfQFbXdH9KyPgSwCmGfqxvuFoRIOB7FGqkTREpCnlcD5D4CQzDRMxiUirWCF_h7qIOROEjVBM_Ac0mwOqgDlKseLVrBX1TTNuyFw-NaP8g","refresh_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IklFTVI6TTdFRzpVV1JUOllIUEs6T1BZUTpZQjZNOjVUQ1M6S1RYRjpaQUhDOlZIRUw6RVVMUTo0SU1LIn0.eyJqdGkiOiIwNWQ3Y2U4Mi05YTkwLTQxOGYtOGQxMi04ZWY3ZDk1NzI4MTQiLCJzdWIiOiJwdXdhbGVjaEBtaWNyb3NvZnQuY29tIiwibmJmIjoxNzE4NjQ3NDQwLCJleHAiOjE3MTg2NTkxNDAsImlhdCI6MTcxODY0NzQ0MCwiaXNzIjoiQXp1cmUgQ29udGFpbmVyIFJlZ2lzdHJ5IiwiYXVkIjoiY2xpdnJld3h5dm5tbnZkMmtleHZlbmtpLmF6dXJlY3IuaW8iLCJ2ZXJzaW9uIjoiMS4wIiwicmlkIjoiNDUzNzdkOTJjOTFiNDc4OWE2YmFmMzgzOWI3NzFmZTUiLCJncmFudF90eXBlIjoicmVmcmVzaF90b2tlbiIsImFwcGlkIjoiMDRiMDc3OTUtOGRkYi00NjFhLWJiZWUtMDJmOWUxYmY3YjQ2IiwidGVuYW50IjoiNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3IiwicGVybWlzc2lvbnMiOnsiYWN0aW9ucyI6WyJyZWFkIiwid3JpdGUiLCJkZWxldGUiLCJtZXRhZGF0YS9yZWFkIiwibWV0YWRhdGEvd3JpdGUiLCJkZWxldGVkL3JlYWQiLCJkZWxldGVkL3Jlc3RvcmUvYWN0aW9uIl19LCJyb2xlcyI6W119.GwLXXNwV1G-Juicy6UcK-iwzYRM1h5ZWxf_4dKYkWPe3_e20jbt6FqQ1fHMUOjoH4j6sn1GBwq7wc4HFYj7KJW27il3ZtwuTVb7xPE68e9j64xZz97N-mgpS-5hGC3AYw_USPHoZXwnoJevnyWdu1L9KDnDZdD61QdmxaF7hzl8O7KNqefZZOFaqt8pRzzXfv9djppont38F0axly6M5Mn7hykQsrjxRm-8z7ufVgZXBAZp1mWe6YqR_ItnnGtVWav9kdkIRJ3O9MQcgse5IawbqjSluwmzNpxqtp-cZOXNMY8cSztEMkahIfihnjI0gFkh5ZQC7PM9-wpe0DjhZpA"}' + headers: + connection: + - keep-alive + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:19:01 GMT + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + x-ms-ratelimit-remaining-calls-per-second: + - '166.633333' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/octet-stream + User-Agent: + - python-requests/2.32.3 + method: POST + uri: https://cli000002.azurecr.io/v2/csscpolicies/patchpolicy/blobs/uploads/ + response: + body: + string: '' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '0' + date: + - Mon, 17 Jun 2024 18:19:01 GMT + docker-distribution-api-version: + - registry/2.0 + docker-upload-uuid: + - 6cd0fa8f-8b4c-41fa-933f-7e5a93f8f6fe + location: + - /v2/csscpolicies/patchpolicy/blobs/uploads/6cd0fa8f-8b4c-41fa-933f-7e5a93f8f6fe?_nouploadcache=false&_state=ARgG-9KmG42Tkki_i8S7U30bjC3_KCknGlf2ptPWaY57Ik5hbWUiOiJjc3NjcG9saWNpZXMvcGF0Y2hwb2xpY3kiLCJVVUlEIjoiNmNkMGZhOGYtOGI0Yy00MWZhLTkzM2YtN2U1YTkzZjhmNmZlIiwiT2Zmc2V0IjowLCJTdGFydGVkQXQiOiIyMDI0LTA2LTE3VDE4OjE5OjAxLjU0NTkwMjI4N1oifQ%3D%3D + range: + - 0-0 + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 202 + message: Accepted +- request: + body: '{}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/octet-stream + User-Agent: + - python-requests/2.32.3 + method: PUT + uri: https://cli000002.azurecr.io/v2/csscpolicies/patchpolicy/blobs/uploads/6cd0fa8f-8b4c-41fa-933f-7e5a93f8f6fe?_nouploadcache=false&_state=ARgG-9KmG42Tkki_i8S7U30bjC3_KCknGlf2ptPWaY57Ik5hbWUiOiJjc3NjcG9saWNpZXMvcGF0Y2hwb2xpY3kiLCJVVUlEIjoiNmNkMGZhOGYtOGI0Yy00MWZhLTkzM2YtN2U1YTkzZjhmNmZlIiwiT2Zmc2V0IjowLCJTdGFydGVkQXQiOiIyMDI0LTA2LTE3VDE4OjE5OjAxLjU0NTkwMjI4N1oifQ%3D%3D&digest=sha256%3A44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a + response: + body: + string: '' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '0' + date: + - Mon, 17 Jun 2024 18:19:01 GMT + docker-content-digest: + - sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a + docker-distribution-api-version: + - registry/2.0 + location: + - /v2/csscpolicies/patchpolicy/blobs/sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 201 + message: Created +- request: + body: '{"schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": {"mediaType": "application/vnd.unknown.config.v1+json", "size": 2, + "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"}, + "layers": [{"mediaType": "application/vnd.oci.image.layer.v1.tar", "size": 194, + "digest": "sha256:202b8e6a12528252f53b2920ec271b8531d21221013109597d260ccead4b95bf", + "annotations": {"org.opencontainers.image.title": "cssc_config_tmp_303gml09"}}], + "annotations": {}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '507' + Content-Type: + - application/vnd.oci.image.manifest.v1+json + User-Agent: + - python-requests/2.32.3 + method: PUT + uri: https://cli000002.azurecr.io/v2/csscpolicies/patchpolicy/manifests/v1 + response: + body: + string: '{"errors":[{"code":"UNAUTHORIZED","message":"authentication required, + visit https://aka.ms/acr/authorization for more information.","detail":[{"Type":"repository","Name":"csscpolicies/patchpolicy","Action":"pull"},{"Type":"repository","Name":"csscpolicies/patchpolicy","Action":"push"}]}]} + + ' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '290' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:19:01 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer realm="https://clivrewxyvnmnvd2kexvenki.azurecr.io/oauth2/token",service="clivrewxyvnmnvd2kexvenki.azurecr.io",scope="repository:csscpolicies/patchpolicy:pull,push" + x-content-type-options: + - nosniff + status: + code: 401 + message: Unauthorized +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Service: + - clivrewxyvnmnvd2kexvenki.azurecr.io + User-Agent: + - oras-py + method: GET + uri: https://cli000002.azurecr.io/oauth2/token?service=cli000002.azurecr.io&scope=repository%3Acsscpolicies%2Fpatchpolicy%3Apull%2Cpush + response: + body: + string: '{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IklFTVI6TTdFRzpVV1JUOllIUEs6T1BZUTpZQjZNOjVUQ1M6S1RYRjpaQUhDOlZIRUw6RVVMUTo0SU1LIn0.eyJqdGkiOiIwYWJhYWJkYi0xMWQ2LTQ3MGUtYjI0Ny1iNGIxOGY1YjZhMzQiLCJzdWIiOiJwdXdhbGVjaEBtaWNyb3NvZnQuY29tIiwibmJmIjoxNzE4NjQ3NDQxLCJleHAiOjE3MTg2NTE5NDEsImlhdCI6MTcxODY0NzQ0MSwiaXNzIjoiQXp1cmUgQ29udGFpbmVyIFJlZ2lzdHJ5IiwiYXVkIjoiY2xpdnJld3h5dm5tbnZkMmtleHZlbmtpLmF6dXJlY3IuaW8iLCJ2ZXJzaW9uIjoiMS4wIiwicmlkIjoiNDUzNzdkOTJjOTFiNDc4OWE2YmFmMzgzOWI3NzFmZTUiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6ImNzc2Nwb2xpY2llcy9wYXRjaHBvbGljeSIsImFjdGlvbnMiOlsicHVsbCIsInB1c2giXX1dLCJyb2xlcyI6W10sImdyYW50X3R5cGUiOiJhY2Nlc3NfdG9rZW4iLCJhcHBpZCI6IjA0YjA3Nzk1LThkZGItNDYxYS1iYmVlLTAyZjllMWJmN2I0NiJ9.OKpQjHohyXogcDamsBSfBf4fmYbYXnT4b6ofr54tGAfskCVHpwe0M--z3F4T2U7UK_mnn_uk_PUO97aaRa58p2MFdCiTju9Oz1bATfczyX8S5fK7_H52IQUsWtjwEJtLkn3CG1RkzO1i7d7TEAkNNRzurbcacIwKXxWFDacZgVLu3E8t3gWl-e5zo4MQBJ2jW8E7RvaANTJXgK5w-7ln0SE85F5vko0zFv4BFdaViEGTl4PA31df3tZnxBL9WoUu1hANquRfhDpSYo-OcZ9C2JNtKlJgyA20uS8ihQuoieYxDp1kXuosOjQJerDE_ba5N-DQIp3DSdLTCQ8wezrfgg","refresh_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IklFTVI6TTdFRzpVV1JUOllIUEs6T1BZUTpZQjZNOjVUQ1M6S1RYRjpaQUhDOlZIRUw6RVVMUTo0SU1LIn0.eyJqdGkiOiIwNWQ3Y2U4Mi05YTkwLTQxOGYtOGQxMi04ZWY3ZDk1NzI4MTQiLCJzdWIiOiJwdXdhbGVjaEBtaWNyb3NvZnQuY29tIiwibmJmIjoxNzE4NjQ3NDQwLCJleHAiOjE3MTg2NTkxNDAsImlhdCI6MTcxODY0NzQ0MCwiaXNzIjoiQXp1cmUgQ29udGFpbmVyIFJlZ2lzdHJ5IiwiYXVkIjoiY2xpdnJld3h5dm5tbnZkMmtleHZlbmtpLmF6dXJlY3IuaW8iLCJ2ZXJzaW9uIjoiMS4wIiwicmlkIjoiNDUzNzdkOTJjOTFiNDc4OWE2YmFmMzgzOWI3NzFmZTUiLCJncmFudF90eXBlIjoicmVmcmVzaF90b2tlbiIsImFwcGlkIjoiMDRiMDc3OTUtOGRkYi00NjFhLWJiZWUtMDJmOWUxYmY3YjQ2IiwidGVuYW50IjoiNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3IiwicGVybWlzc2lvbnMiOnsiYWN0aW9ucyI6WyJyZWFkIiwid3JpdGUiLCJkZWxldGUiLCJtZXRhZGF0YS9yZWFkIiwibWV0YWRhdGEvd3JpdGUiLCJkZWxldGVkL3JlYWQiLCJkZWxldGVkL3Jlc3RvcmUvYWN0aW9uIl19LCJyb2xlcyI6W119.GwLXXNwV1G-Juicy6UcK-iwzYRM1h5ZWxf_4dKYkWPe3_e20jbt6FqQ1fHMUOjoH4j6sn1GBwq7wc4HFYj7KJW27il3ZtwuTVb7xPE68e9j64xZz97N-mgpS-5hGC3AYw_USPHoZXwnoJevnyWdu1L9KDnDZdD61QdmxaF7hzl8O7KNqefZZOFaqt8pRzzXfv9djppont38F0axly6M5Mn7hykQsrjxRm-8z7ufVgZXBAZp1mWe6YqR_ItnnGtVWav9kdkIRJ3O9MQcgse5IawbqjSluwmzNpxqtp-cZOXNMY8cSztEMkahIfihnjI0gFkh5ZQC7PM9-wpe0DjhZpA"}' + headers: + connection: + - keep-alive + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:19:01 GMT + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + x-ms-ratelimit-remaining-calls-per-second: + - '166.616667' + status: + code: 200 + message: OK +- request: + body: '{"schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": {"mediaType": "application/vnd.unknown.config.v1+json", "size": 2, + "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"}, + "layers": [{"mediaType": "application/vnd.oci.image.layer.v1.tar", "size": 194, + "digest": "sha256:202b8e6a12528252f53b2920ec271b8531d21221013109597d260ccead4b95bf", + "annotations": {"org.opencontainers.image.title": "cssc_config_tmp_303gml09"}}], + "annotations": {}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '507' + Content-Type: + - application/vnd.oci.image.manifest.v1+json + User-Agent: + - python-requests/2.32.3 + method: PUT + uri: https://cli000002.azurecr.io/v2/csscpolicies/patchpolicy/manifests/v1 + response: + body: + string: '' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '0' + date: + - Mon, 17 Jun 2024 18:19:02 GMT + docker-content-digest: + - sha256:e08a6fa0f19da7c0f3dee3306c47e9798727a840f25ceae7a449635b68728e52 + docker-distribution-api-version: + - registry/2.0 + location: + - /v2/csscpolicies/patchpolicy/manifests/sha256:e08a6fa0f19da7c0f3dee3306c47e9798727a840f25ceae7a449635b68728e52 + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - acr supply-chain workflow show + Connection: + - keep-alive + ParameterSetName: + - -g -t -r + User-Agent: + - AZURECLI/2.61.0 azsdk-python-core/1.30.2 Python/3.8.10 (Windows-10-10.0.22631-SP0) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002?api-version=2023-01-01-preview + response: + body: + string: '{"sku":{"name":"Basic","tier":"Basic"},"type":"Microsoft.ContainerRegistry/registries","id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002","name":"cli000002","location":"westus","tags":{},"systemData":{"createdBy":"puwalech@microsoft.com","createdByType":"User","createdAt":"2024-06-17T18:18:15.4440214+00:00","lastModifiedBy":"puwalech@microsoft.com","lastModifiedByType":"User","lastModifiedAt":"2024-06-17T18:18:15.4440214+00:00"},"properties":{"loginServer":"cli000002.azurecr.io","creationDate":"2024-06-17T18:18:15.4440214Z","provisioningState":"Succeeded","adminUserEnabled":false,"policies":{"quarantinePolicy":{"status":"disabled"},"trustPolicy":{"type":"Notary","status":"disabled"},"retentionPolicy":{"days":7,"lastUpdatedTime":"2024-06-17T18:18:22.2291688+00:00","status":"disabled"},"exportPolicy":{"status":"enabled"},"azureADAuthenticationAsArmPolicy":{"status":"enabled"},"softDeletePolicy":{"retentionDays":7,"lastUpdatedTime":"2024-06-17T18:18:22.229206+00:00","status":"disabled"}},"encryption":{"status":"disabled"},"dataEndpointEnabled":false,"dataEndpointHostNames":[],"privateEndpointConnections":[],"publicNetworkAccess":"Enabled","networkRuleBypassOptions":"AzureServices","zoneRedundancy":"Disabled","anonymousPullEnabled":false}}' + headers: + api-supported-versions: + - 2023-01-01-preview + cache-control: + - no-cache + content-length: + - '1360' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:19:02 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: DC9704301BFA496B96965F4D1F1F41BD Ref B: CO6AA3150219017 Ref C: 2024-06-17T18:19:02Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - acr supply-chain workflow show + Connection: + - keep-alive + ParameterSetName: + - -g -t -r + User-Agent: + - AZURECLI/2.61.0 azsdk-python-core/1.30.2 Python/3.8.10 (Windows-10-10.0.22631-SP0) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-patch-image?api-version=2019-06-01-preview + response: + body: + string: '{"type":"Microsoft.ContainerRegistry/registries/tasks","properties":{"provisioningState":"Succeeded","creationDate":"2024-06-17T18:18:29.5498562+00:00","platform":{"os":"linux","architecture":"amd64"},"agentConfiguration":{"cpu":2},"timeout":3600,"step":{"type":"EncodedTask","encodedTaskContent":"dmVyc2lvbjogdjEuMS4wCmFsaWFzOgogIHZhbHVlczoKICAgIFNjYW5SZXBvcnQgOiBvcy12dWxuZXJhYmlsaXR5LXJlcG9ydF90cml2eV97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fV97ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319XyQoZGF0ZSAiKyVZLSVtLSVkIikuanNvbgpzdGVwczoKICAjIFN0ZXAgIzE6IFBlcmZvcm0gdGhlIHZ1bG5lcmFiaWxpdHkgc2NhbgogIC0gaWQ6IHByaW50LWlucHV0cwogICAgY21kOiB8CiAgICAgICAgYmFzaCAtYyAnZWNobyAiU2NhbiwgVXBsb2FkIHNjYW4gcmVwb3J0IGFuZCBTY2hlZHVsZSBQYXRjaCBmb3Ige3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fSInCiAgLSBpZDogc2V0dXAtZGF0YS1kaXIKICAgIGNtZDogYmFzaCBta2RpciAuL2RhdGEKICAtIGlkOiBnZW5lcmF0ZS10cml2eS1yZXBvcnQKICAgIGNtZDogfAogICAgICBnaGNyLmlvL2FxdWFzZWN1cml0eS90cml2eSBpbWFnZSBcCiAgICAgIHt7LlJ1bi5SZWdpc3RyeX19L3t7LlZhbHVlcy5TT1VSQ0VfUkVQT1NJVE9SWX19Ont7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfVEFHfX0gXAogICAgICAtLXZ1bG4tdHlwZSBvcyBcCiAgICAgIC0taWdub3JlLXVuZml4ZWQgXAogICAgICAtLWZvcm1hdCBqc29uIFwKICAgICAgLS1vdXRwdXQgL3dvcmtzcGFjZS9kYXRhLyRTY2FuUmVwb3J0CgogICMgU3RlcCAyOiBBdHRhY2ggdGhlIHZ1bG5lcmFiaWxpdHkgc2NhbiByZXBvcnQgdG8gdGhlIGltYWdlCiAgLSBpZDogdXBsb2FkLXRyaXZ5LXJlcG9ydAogICAgY21kOiB8CiAgICAgIGdoY3IuaW8vb3Jhcy1wcm9qZWN0L29yYXM6djEuMS4wIGF0dGFjaCBcCiAgICAgIC0tYXJ0aWZhY3QtdHlwZSB2dWxuZXJhYmlsaXR5U2Nhbi9yZXBvcnQgXAogICAgICB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IFwKICAgICAgLi9kYXRhLyRTY2FuUmVwb3J0CgogIC0gY21kOiBiYXNoIGVjaG8gIlVwbG9hZGVkIHZ1bG5lcmFiaWxpdHkgcmVwb3J0ICRTY2FuUmVwb3J0IHRvIHRoZSBpbWFnZSB7ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IgoKICAtIGlkOiBidWlsZGtpdGQKICAgIGNtZDogbW9ieS9idWlsZGtpdCAtLWFkZHIgdGNwOi8vMC4wLjAuMDo4ODg4CiAgICBlbnRyeXBvaW50OiBidWlsZGtpdGQKICAgIGRldGFjaDogdHJ1ZQogICAgcHJpdmlsZWdlZDogdHJ1ZQogICAgcG9ydHM6IFsiMTI3LjAuMC4xOjg4ODg6ODg4OC90Y3AiXQogIAogIC0gaWQ6IGxpc3Qtb3V0cHV0LWZpbGUKICAgIGNtZDogYmFzaCBscyAtbCAvd29ya3NwYWNlL2RhdGEKICAKICAjIFN0ZXAgMzogUGF0Y2ggdGhlIGltYWdlIHdpdGggQ29wYWNldGljCiAgLSBpZDogcGF0Y2gtd2l0aF9jb3BhCiAgICBjbWQ6IHwKICAgICAgZ2hjci5pby90b2RkeXNtL2Nzc2MtZnJhbWV3b3JrL2NvcGFjZXRpYzoxLjAgXAogICAgICB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IFwKICAgICAgJFNjYW5SZXBvcnQgXAogICAgICB7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319LXBhdGNoZWQKICAgIG5ldHdvcms6IGhvc3QKCiAgLSBpZDogcHVzaC1pbWFnZQogICAgY21kOiBkb2NrZXIgcHVzaCB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319LXBhdGNoZWQKCiAgLSBjbWQ6IGJhc2ggZWNobyAiUGF0Y2hlZCBpbWFnZSBwdXNoZWQgdG8ge3suUnVuLlJlZ2lzdHJ5fX0ve3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fS1wYXRjaGVkIg==","values":[]},"isSystemTask":false},"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-patch-image","name":"cssc-patch-image","tags":{"cssc":"true","clienttracking":"true"},"location":"westus","systemData":{"createdBy":"puwalech@microsoft.com","createdByType":"User","createdAt":"2024-06-17T18:18:29.3152526+00:00","lastModifiedBy":"puwalech@microsoft.com","lastModifiedByType":"User","lastModifiedAt":"2024-06-17T18:18:29.3152526+00:00"}}' + headers: + cache-control: + - no-cache + content-length: + - '3449' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:19:02 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: FE2D469485A04E38BB3F75563F422108 Ref B: CO6AA3150217023 Ref C: 2024-06-17T18:19:02Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - acr supply-chain workflow show + Connection: + - keep-alive + ParameterSetName: + - -g -t -r + User-Agent: + - AZURECLI/2.61.0 azsdk-python-core/1.30.2 Python/3.8.10 (Windows-10-10.0.22631-SP0) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks?api-version=2019-06-01-preview + response: + body: + string: '{"value":[{"type":"Microsoft.ContainerRegistry/registries/tasks","properties":{"provisioningState":"Succeeded","creationDate":"2024-06-17T18:18:29.5498562+00:00","platform":{"os":"linux","architecture":"amd64"},"agentConfiguration":{"cpu":2},"timeout":3600,"step":{"type":"EncodedTask","encodedTaskContent":"dmVyc2lvbjogdjEuMS4wCmFsaWFzOgogIHZhbHVlczoKICAgIFNjYW5SZXBvcnQgOiBvcy12dWxuZXJhYmlsaXR5LXJlcG9ydF90cml2eV97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fV97ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319XyQoZGF0ZSAiKyVZLSVtLSVkIikuanNvbgpzdGVwczoKICAjIFN0ZXAgIzE6IFBlcmZvcm0gdGhlIHZ1bG5lcmFiaWxpdHkgc2NhbgogIC0gaWQ6IHByaW50LWlucHV0cwogICAgY21kOiB8CiAgICAgICAgYmFzaCAtYyAnZWNobyAiU2NhbiwgVXBsb2FkIHNjYW4gcmVwb3J0IGFuZCBTY2hlZHVsZSBQYXRjaCBmb3Ige3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fSInCiAgLSBpZDogc2V0dXAtZGF0YS1kaXIKICAgIGNtZDogYmFzaCBta2RpciAuL2RhdGEKICAtIGlkOiBnZW5lcmF0ZS10cml2eS1yZXBvcnQKICAgIGNtZDogfAogICAgICBnaGNyLmlvL2FxdWFzZWN1cml0eS90cml2eSBpbWFnZSBcCiAgICAgIHt7LlJ1bi5SZWdpc3RyeX19L3t7LlZhbHVlcy5TT1VSQ0VfUkVQT1NJVE9SWX19Ont7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfVEFHfX0gXAogICAgICAtLXZ1bG4tdHlwZSBvcyBcCiAgICAgIC0taWdub3JlLXVuZml4ZWQgXAogICAgICAtLWZvcm1hdCBqc29uIFwKICAgICAgLS1vdXRwdXQgL3dvcmtzcGFjZS9kYXRhLyRTY2FuUmVwb3J0CgogICMgU3RlcCAyOiBBdHRhY2ggdGhlIHZ1bG5lcmFiaWxpdHkgc2NhbiByZXBvcnQgdG8gdGhlIGltYWdlCiAgLSBpZDogdXBsb2FkLXRyaXZ5LXJlcG9ydAogICAgY21kOiB8CiAgICAgIGdoY3IuaW8vb3Jhcy1wcm9qZWN0L29yYXM6djEuMS4wIGF0dGFjaCBcCiAgICAgIC0tYXJ0aWZhY3QtdHlwZSB2dWxuZXJhYmlsaXR5U2Nhbi9yZXBvcnQgXAogICAgICB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IFwKICAgICAgLi9kYXRhLyRTY2FuUmVwb3J0CgogIC0gY21kOiBiYXNoIGVjaG8gIlVwbG9hZGVkIHZ1bG5lcmFiaWxpdHkgcmVwb3J0ICRTY2FuUmVwb3J0IHRvIHRoZSBpbWFnZSB7ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IgoKICAtIGlkOiBidWlsZGtpdGQKICAgIGNtZDogbW9ieS9idWlsZGtpdCAtLWFkZHIgdGNwOi8vMC4wLjAuMDo4ODg4CiAgICBlbnRyeXBvaW50OiBidWlsZGtpdGQKICAgIGRldGFjaDogdHJ1ZQogICAgcHJpdmlsZWdlZDogdHJ1ZQogICAgcG9ydHM6IFsiMTI3LjAuMC4xOjg4ODg6ODg4OC90Y3AiXQogIAogIC0gaWQ6IGxpc3Qtb3V0cHV0LWZpbGUKICAgIGNtZDogYmFzaCBscyAtbCAvd29ya3NwYWNlL2RhdGEKICAKICAjIFN0ZXAgMzogUGF0Y2ggdGhlIGltYWdlIHdpdGggQ29wYWNldGljCiAgLSBpZDogcGF0Y2gtd2l0aF9jb3BhCiAgICBjbWQ6IHwKICAgICAgZ2hjci5pby90b2RkeXNtL2Nzc2MtZnJhbWV3b3JrL2NvcGFjZXRpYzoxLjAgXAogICAgICB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319IFwKICAgICAgJFNjYW5SZXBvcnQgXAogICAgICB7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319LXBhdGNoZWQKICAgIG5ldHdvcms6IGhvc3QKCiAgLSBpZDogcHVzaC1pbWFnZQogICAgY21kOiBkb2NrZXIgcHVzaCB7ey5SdW4uUmVnaXN0cnl9fS97ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fTp7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX1RBR319LXBhdGNoZWQKCiAgLSBjbWQ6IGJhc2ggZWNobyAiUGF0Y2hlZCBpbWFnZSBwdXNoZWQgdG8ge3suUnVuLlJlZ2lzdHJ5fX0ve3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fS1wYXRjaGVkIg==","values":[]},"isSystemTask":false},"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-patch-image","name":"cssc-patch-image","tags":{"cssc":"true","clienttracking":"true"},"location":"westus","systemData":{"createdBy":"puwalech@microsoft.com","createdByType":"User","createdAt":"2024-06-17T18:18:29.3152526+00:00","lastModifiedBy":"puwalech@microsoft.com","lastModifiedByType":"User","lastModifiedAt":"2024-06-17T18:18:29.3152526+00:00"}},{"type":"Microsoft.ContainerRegistry/registries/tasks","identity":{"principalId":"4d396699-cc25-49d3-b4c5-30611ef874a5","tenantId":"72f988bf-86f1-41af-91ab-2d7cd011db47","type":"SystemAssigned"},"properties":{"provisioningState":"Succeeded","creationDate":"2024-06-17T18:18:29.9167439+00:00","status":"Enabled","platform":{"os":"linux","architecture":"amd64"},"agentConfiguration":{"cpu":2},"timeout":3600,"step":{"type":"EncodedTask","encodedTaskContent":"dmVyc2lvbjogdjEuMS4wCmFsaWFzOgogIHZhbHVlczoKICAgIFNjYW5JbWFnZUFuZFNjaGVkdWxlUGF0Y2hUYXNrOiBjc3NjLXNjYW4taW1hZ2Utc2NoZWR1bGUtcGF0Y2gKc3RlcHM6CiAgLSBjbWQ6IGJhc2ggLWMgJ2VjaG8gIkluc2lkZSBDU1NDLVRyaWdnZXJTY2FuLCBnZXR0aW5nIGltYWdlcyB0byBiZSBwYXRjaGVkIGJhc2VkIG9uIC0tZmlsdGVyLXBvbGljeSBmb3IgUmVnaXN0cnkge3suUnVuLlJlZ2lzdHJ5fX0uIicKICAtIGNtZDogbWNyLm1pY3Jvc29mdC5jb20vYWNyL2Fjci1jbGk6MC4xMSBjc3NjIHBhdGNoIC0tZmlsdGVyLXBvbGljeSBjc3NjcG9saWNpZXMvcGF0Y2hwb2xpY3k6djEgLS1kcnktcnVuID4gZmlsdGVyUmVwb3MudHh0CiAgICBlbnY6CiAgICAgIC0gQUNSX0VYUEVSSU1FTlRBTF9DU1NDPXRydWUKICAtIGNtZDogYmFzaCAtYyAnc2VkIC1uICIvXkxpc3RpbmcvLC9eVG90YWwvIHsvXkxpc3RpbmcvYjsvXlRvdGFsL2I7cH0iIGZpbHRlclJlcG9zLnR4dCcgPiBmaWx0ZXJSZXBvc1RvRGlzcGxheS50eHQKICAtIGNtZDogYmFzaCAtYyAnZWNobyAtZSAiQmVsb3cgaW1hZ2VzIHdpbGwgYmUgc2Nhbm5lZCBhbmQgcGF0Y2hlZCAoaWYgYW55IG9zIHZ1bG5lcmFiaWxpdGllcyBmb3VuZCkgYmFzZWQgb24gLS1maWx0ZXItcG9saWN5LlxuJChjYXQgZmlsdGVyUmVwb3NUb0Rpc3BsYXkudHh0KSInCiAgLSBjbWQ6IG1jci5taWNyb3NvZnQuY29tL2Fjci9hY3ItY2xpOjAuMTEgY3NzYyBwYXRjaCAtLWZpbHRlci1wb2xpY3kgY3NzY3BvbGljaWVzL3BhdGNocG9saWN5OnYxIC0tc2hvdy1wYXRjaC10YWdzIC0tZHJ5LXJ1bj4gZmlsdGVyUmVwb3NXaXRoUGF0Y2hUYWdzLnR4dAogICAgZW52OgogICAgICAtIEFDUl9FWFBFUklNRU5UQUxfQ1NTQz10cnVlCiAgLSBjbWQ6IGJhc2ggLWMgJ3NlZCAtbiAiL15MaXN0aW5nLywvXlRvdGFsLyB7L15MaXN0aW5nL2I7L15Ub3RhbC9iO3B9IiBmaWx0ZXJSZXBvc1dpdGhQYXRjaFRhZ3MudHh0JyA+IGZpbHRlcmVkUmVwb3NBbmRUYWdzLnR4dAogIC0gY21kOiBheiBsb2dpbiAtLWlkZW50aXR5IAogIC0gY21kOiB8CiAgICAgICAgbWNyLm1pY3Jvc29mdC5jb20vYXp1cmUtY2xpIGJhc2ggLWMgJ3doaWxlIHJlYWQgbGluZTtkbyBcCiAgICAgICAgSUZTPScvJyByZWFkIC1yIC1hIGFycmF5MSA8PDwgIiRsaW5lIgogICAgICAgIElGUz0nOicgcmVhZCAtciAtYSBhcnJheTIgPDw8ICIke2FycmF5MVsxXX0iCiAgICAgICAgSUZTPScsJyByZWFkIC1yIC1hIGFycmF5MyA8PDwgIiR7YXJyYXkyWzFdfSIKICAgICAgICBSZWdpc3RyeU5hbWU9JHthcnJheTFbMF19OwogICAgICAgIFJlcG9OYW1lPSR7YXJyYXkyWzBdfTsKICAgICAgICBPcmlnaW5hbFRhZz0ke2FycmF5M1swXX07CiAgICAgICAgVGFnTmFtZT0ke2FycmF5M1sxXX07CiAgICAgICAgZWNobyAiU2NoZWR1bGluZyAkU2NhbkltYWdlQW5kU2NoZWR1bGVQYXRjaFRhc2sgZm9yICRSZWdpc3RyeU5hbWUvJFJlcG9OYW1lLCBUYWc6JFRhZ05hbWUsIE9yaWdpbmFsVGFnOiRPcmlnaW5hbFRhZyI7CiAgICAgICAgYXogYWNyIHRhc2sgcnVuIC0tbmFtZSAkU2NhbkltYWdlQW5kU2NoZWR1bGVQYXRjaFRhc2sgLS1yZWdpc3RyeSAkUmVnaXN0cnlOYW1lIC0tc2V0IFNPVVJDRV9SRVBPU0lUT1JZPSRSZXBvTmFtZSAtLXNldCBTT1VSQ0VfSU1BR0VfVEFHPSRUYWdOYW1lIC0tc2V0IFNPVVJDRV9JTUFHRV9PUklHSU5BTF9UQUc9JE9yaWdpbmFsVGFnIC0tbm8td2FpdDsgXAogICAgICAgIGRvbmUgPCBmaWx0ZXJlZFJlcG9zQW5kVGFncy50eHQ7Jw==","values":[]},"trigger":{"timerTriggers":[{"schedule":"18 + 18 */1 * *","status":"Enabled","name":"azcli_defined_schedule"}]},"isSystemTask":false},"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-trigger-scan","name":"cssc-trigger-scan","tags":{"cssc":"true","clienttracking":"true"},"location":"westus","systemData":{"createdBy":"puwalech@microsoft.com","createdByType":"User","createdAt":"2024-06-17T18:18:29.3152526+00:00","lastModifiedBy":"puwalech@microsoft.com","lastModifiedByType":"User","lastModifiedAt":"2024-06-17T18:18:29.3152526+00:00"}},{"type":"Microsoft.ContainerRegistry/registries/tasks","identity":{"principalId":"125a2e49-d3fb-4d0c-8921-8b44835bc4f6","tenantId":"72f988bf-86f1-41af-91ab-2d7cd011db47","type":"SystemAssigned"},"properties":{"provisioningState":"Succeeded","creationDate":"2024-06-17T18:18:30.6480357+00:00","platform":{"os":"linux","architecture":"amd64"},"agentConfiguration":{"cpu":2},"timeout":3600,"step":{"type":"EncodedTask","encodedTaskContent":"dmVyc2lvbjogdjEuMS4wCmFsaWFzOgogIHZhbHVlczoKICAgIHBhdGNoaW1hZ2V0YXNrOiBjc3NjLXBhdGNoLWltYWdlCiAgICBEQVRFOiAkKGRhdGUgIislWS0lbS0lZCIpCnN0ZXBzOgogIC0gaWQ6IHByaW50LWlucHV0cwogICAgY21kOiB8CiAgICAgICAgYmFzaCAtYyAnZWNobyAiU2Nhbm5pbmcgaW1hZ2UgZm9yIHZ1bG5lcmFiaWxpdHkgYW5kIHBhdGNoIHt7LlZhbHVlcy5TT1VSQ0VfUkVQT1NJVE9SWX19Ont7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfVEFHfX0gZm9yIHRhZyB7ey5WYWx1ZXMuU09VUkNFX0lNQUdFX09SSUdJTkFMX1RBR319IicKICAtIGlkOiBzZXR1cC1kYXRhLWRpcgogICAgY21kOiBiYXNoIG1rZGlyIC4vZGF0YQoKICAtIGlkOiBnZW5lcmF0ZS10cml2eS1yZXBvcnQKICAgIGNtZDogfAogICAgICBnaGNyLmlvL2FxdWFzZWN1cml0eS90cml2eSBpbWFnZSBcCiAgICAgIHt7LlJ1bi5SZWdpc3RyeX19L3t7LlZhbHVlcy5TT1VSQ0VfUkVQT1NJVE9SWX19Ont7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfVEFHfX0gXAogICAgICAtLXZ1bG4tdHlwZSBvcyBcCiAgICAgIC0taWdub3JlLXVuZml4ZWQgXAogICAgICAtLWZvcm1hdCBqc29uIFwKICAgICAgLS1vdXRwdXQgL3dvcmtzcGFjZS9kYXRhL3Z1bG5lcmFiaWxpdHktcmVwb3J0X3RyaXZ5XyREQVRFLmpzb24KICAtIGNtZDogbWNyLm1pY3Jvc29mdC5jb20vYXp1cmUtY2xpIGJhc2ggLWMgJ2pxICJbLlJlc3VsdHNbXS5WdWxuZXJhYmlsaXRpZXMgfCBsZW5ndGhdIHwgYWRkIiAvd29ya3NwYWNlL2RhdGEvdnVsbmVyYWJpbGl0eS1yZXBvcnRfdHJpdnlfJERBVEUuanNvbiA+IC93b3Jrc3BhY2UvZGF0YS92dWxDb3VudC50eHQnCiAgLSBjbWQ6IGJhc2ggZWNobyAiR2VuZXJhdGVkIHZ1bG5lcmFiaWxpdHkgcmVwb3J0IGF0IC93b3Jrc3BhY2UvZGF0YS92dWxuZXJhYmlsaXR5LXJlcG9ydF90cml2eV8kREFURS5qc29uIiAKICAtIGNtZDogYXogbG9naW4gLS1pZGVudGl0eQogIC0gY21kOiBiYXNoIGVjaG8gIlZ1bG5lcmFiaWxpdGllcyBmb3VuZCBmb3IgaW1hZ2Uge3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fSAtPiAkKGNhdCAvd29ya3NwYWNlL2RhdGEvdnVsQ291bnQudHh0KSIKICAtIGNtZDogfCAKICAgICAgbWNyLm1pY3Jvc29mdC5jb20vYXp1cmUtY2xpIGJhc2ggLWMgJ3Z1bENvdW50PSQoY2F0IC93b3Jrc3BhY2UvZGF0YS92dWxDb3VudC50eHQpICYmIFwKICAgICAgWyAkdnVsQ291bnQgLWd0IDAgXSAmJiBcCiAgICAgIGF6IGFjciB0YXNrIHJ1biAtLW5hbWUgJHBhdGNoaW1hZ2V0YXNrIC0tcmVnaXN0cnkgJFJlZ2lzdHJ5TmFtZSAtLXNldCBTT1VSQ0VfUkVQT1NJVE9SWT17ey5WYWx1ZXMuU09VUkNFX1JFUE9TSVRPUll9fSAtLXNldCBTT1VSQ0VfSU1BR0VfVEFHPXt7LlZhbHVlcy5TT1VSQ0VfSU1BR0VfT1JJR0lOQUxfVEFHfX0gLS1uby13YWl0IFwKICAgICAgfHwgZWNobyAiTm8gdnVsbmVyYWJpbGl0eSBpbiB0aGUgaW1hZ2Uge3suVmFsdWVzLlNPVVJDRV9SRVBPU0lUT1JZfX06e3suVmFsdWVzLlNPVVJDRV9JTUFHRV9UQUd9fSIn","values":[]},"isSystemTask":false},"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_acrcssc000001/providers/Microsoft.ContainerRegistry/registries/cli000002/tasks/cssc-scan-image-schedule-patch","name":"cssc-scan-image-schedule-patch","tags":{"cssc":"true"},"location":"westus","systemData":{"createdBy":"puwalech@microsoft.com","createdByType":"User","createdAt":"2024-06-17T18:18:29.3152526+00:00","lastModifiedBy":"puwalech@microsoft.com","lastModifiedByType":"User","lastModifiedAt":"2024-06-17T18:18:29.3152526+00:00"}}]}' + headers: + cache-control: + - no-cache + content-length: + - '10080' + content-type: + - application/json; charset=utf-8 + date: + - Mon, 17 Jun 2024 18:19:03 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 85DBCC2B62D945A2BB43DE86C028E221 Ref B: CO6AA3150218053 Ref C: 2024-06-17T18:19:03Z' + status: + code: 200 + message: OK +version: 1 diff --git a/src/acrcssc/azext_acrcssc/tests/latest/test_helper_ociartifactoperations.py b/src/acrcssc/azext_acrcssc/tests/latest/test_helper_ociartifactoperations.py new file mode 100644 index 00000000000..5e2c37c2cd1 --- /dev/null +++ b/src/acrcssc/azext_acrcssc/tests/latest/test_helper_ociartifactoperations.py @@ -0,0 +1,121 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import tempfile +import unittest +from unittest import mock +from unittest.mock import MagicMock, patch +from azext_acrcssc.helper._ociartifactoperations import create_oci_artifact_continuous_patch, delete_oci_artifact_continuous_patch +from azure.cli.core.mock import DummyCli +from azure.cli.core.azclierror import AzCLIError + + +class TestCreateOciArtifactContinuousPatch(unittest.TestCase): + @patch('azext_acrcssc.helper._ociartifactoperations._oras_client') + @patch('azext_acrcssc.helper._ociartifactoperations.tempfile.NamedTemporaryFile') + def test_create_oci_artifact_continuous_patch(self, mock_NamedTemporaryFile, mock_oras_client): + # Mock the necessary dependencies + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + temp_file_path = temp_file.name + registry = MagicMock() + registry.login_server = "test@azurecr.io" + cssc_config_file = temp_file_path + dryrun = False + oras_client = MagicMock() + mock_oras_client.return_value = oras_client + temp_artifact = MagicMock() + mock_NamedTemporaryFile.return_value = temp_artifact + + # Call the function + with patch('os.path.exists', return_value=True), patch('os.remove', return_value=True): + create_oci_artifact_continuous_patch(registry, cssc_config_file, dryrun) + + # Assert that the necessary functions were called with the correct arguments + mock_oras_client.assert_called_once_with(registry) + oras_client.push.assert_called_once_with(target='csscpolicies/patchpolicy:v1', files=[temp_artifact.name]) + + @mock.patch('azext_acrcssc.helper._ociartifactoperations._get_acr_token') + @mock.patch('azext_acrcssc.helper._ociartifactoperations.logger') + @mock.patch('azext_acrcssc.helper._ociartifactoperations.parse_resource_id') + @mock.patch('azext_acrcssc.helper._ociartifactoperations.acr_repository_delete') + def test_delete_oci_artifact_continuous_patch(self, mock_acr_repository_delete, mock_parse_resource_id, mock_logger, mock_get_acr_token): + # Mock the necessary dependencies + cmd = self._setup_cmd() + registry = MagicMock() + dryrun = False + mock_parse_resource_id.return_value = { + "resource_group": "test_rg", + "subscription": "test_subscription" + } + mock_get_acr_token.return_value = "test_token" + + # Call the function + delete_oci_artifact_continuous_patch(cmd, registry, dryrun) + + # Assert the function calls + mock_parse_resource_id.assert_called_once_with(registry.id) + mock_get_acr_token.assert_called_once_with(registry.name, "test_subscription") + mock_acr_repository_delete.assert_called_once_with( + cmd=cmd, + registry_name=registry.name, + repository="csscpolicies/patchpolicy", + username="00000000-0000-0000-0000-000000000000", + password="test_token", + yes=True + ) + mock_logger.warning.assert_not_called() + mock_acr_repository_delete.assert_called_once() + + @mock.patch('azext_acrcssc.helper._ociartifactoperations._get_acr_token') + @mock.patch('azext_acrcssc.helper._ociartifactoperations.parse_resource_id') + @mock.patch('azext_acrcssc.helper._ociartifactoperations.acr_repository_delete') + def test_delete_oci_artifact_continuous_patch_dryrun(self, mock_acr_repository_delete, mock_parse_resource_id, mock_get_acr_token): + # Mock the necessary dependencies + cmd = self._setup_cmd() + registry = MagicMock() + dryrun = True + mock_parse_resource_id.return_value = { + "resource_group": "test_rg", + "subscription": "test_subscription" + } + mock_get_acr_token.return_value = "test_token" + + # Run the function + delete_oci_artifact_continuous_patch(cmd, registry, dryrun) + + # Assert the function calls + mock_parse_resource_id.assert_called_once_with(registry.id) + mock_acr_repository_delete.assert_not_called() + + @mock.patch('azext_acrcssc.helper._ociartifactoperations._get_acr_token') + @mock.patch('azext_acrcssc.helper._ociartifactoperations.logger') + @mock.patch('azext_acrcssc.helper._ociartifactoperations.parse_resource_id') + @mock.patch('azext_acrcssc.helper._ociartifactoperations.acr_repository_delete') + def test_delete_oci_artifact_continuous_patch_exception(self, mock_acr_repository_delete, mock_parse_resource_id, mock_logger, mock_get_acr_token): + # Mock the necessary dependencies + cmd = self._setup_cmd() + registry = MagicMock() + dryrun = False + mock_parse_resource_id.return_value = { + "resource_group": "test_rg", + "subscription": "test_subscription" + } + + mock_get_acr_token.return_value = "test_token" + mock_acr_repository_delete.side_effect = Exception("Test exception") + + # Run the function and assert the exception + with (self.assertRaises(Exception)): + delete_oci_artifact_continuous_patch(cmd, registry, dryrun) + + # Assert the function calls + mock_parse_resource_id.assert_called_once_with(registry.id) + mock_get_acr_token.assert_called_once_with(registry.name, "test_subscription") + mock_logger.warning.assert_not_called() + + def _setup_cmd(self): + cmd = mock.MagicMock() + cmd.cli_ctx = DummyCli() + return cmd diff --git a/src/acrcssc/azext_acrcssc/tests/latest/test_helper_taskoperations.py b/src/acrcssc/azext_acrcssc/tests/latest/test_helper_taskoperations.py new file mode 100644 index 00000000000..a3d0ea482b1 --- /dev/null +++ b/src/acrcssc/azext_acrcssc/tests/latest/test_helper_taskoperations.py @@ -0,0 +1,176 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import tempfile +import unittest +from unittest import mock +from azure.cli.core.mock import DummyCli +from azext_acrcssc.helper._taskoperations import (create_update_continuous_patch_v1, delete_continuous_patch_v1) + + +class TestCreateContinuousPatchV1(unittest.TestCase): + @mock.patch("azext_acrcssc.helper._taskoperations.check_continuous_task_exists") + @mock.patch("azext_acrcssc.helper._taskoperations.convert_timespan_to_cron") + @mock.patch("azext_acrcssc.helper._taskoperations.create_oci_artifact_continuous_patch") + @mock.patch("azext_acrcssc.helper._taskoperations.parse_resource_id") + @mock.patch("azext_acrcssc.helper._taskoperations.validate_and_deploy_template") + @mock.patch("azext_acrcssc.helper._taskoperations._trigger_task_run") + def test_create_continuous_patch_v1(self, mock_trigger_task_run, mock_validate_and_deploy_template, mock_parse_resource_id, mock_create_oci_artifact_continuous_patch, mock_convert_timespan_to_cron, mock_check_continuoustask_exists): + # Mock the necessary dependencies + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + temp_file_path = temp_file.name + mock_check_continuoustask_exists.return_value = False, [] + mock_convert_timespan_to_cron.return_value = "0 0 * * *" + mock_parse_resource_id.return_value = {"resource_group": "test_rg"} + cmd = self._setup_cmd() + registry = mock.MagicMock() + registry.id = "/subscriptions/11111111-0000-0000-0000-0000000000006/resourceGroups/test-rg/providers/Microsoft.ContainerRegistry/registries/testregistry" + + # Call the function + create_update_continuous_patch_v1(cmd, registry, temp_file_path, "1d", False, False) + + # Assert that the dependencies were called with the correct arguments + mock_convert_timespan_to_cron.assert_called_once_with("1d") + mock_create_oci_artifact_continuous_patch.assert_called_once() + mock_validate_and_deploy_template.assert_called_once() + mock_trigger_task_run.assert_not_called() + + @mock.patch("azext_acrcssc.helper._taskoperations.check_continuous_task_exists") + @mock.patch("azext_acrcssc.helper._taskoperations.convert_timespan_to_cron") + @mock.patch("azext_acrcssc.helper._taskoperations.create_oci_artifact_continuous_patch") + @mock.patch("azext_acrcssc.helper._taskoperations.parse_resource_id") + @mock.patch("azext_acrcssc.helper._taskoperations.validate_and_deploy_template") + @mock.patch("azext_acrcssc.helper._taskoperations._trigger_task_run") + def test_create_continuous_patch_v1_create_run_immediately_triggers_task(self, mock_trigger_task_run, mock_validate_and_deploy_template, mock_parse_resource_id, mock_create_oci_artifact_continuous_patch, mock_convert_timespan_to_cron, mock_check_continuoustask_exists): + # Mock the necessary dependencies + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + temp_file_path = temp_file.name + mock_check_continuoustask_exists.return_value = False, [] + mock_convert_timespan_to_cron.return_value = "0 0 * * *" + mock_parse_resource_id.return_value = {"resource_group": "test_rg"} + cmd = self._setup_cmd() + registry = mock.MagicMock() + registry.id = "/subscriptions/11111111-0000-0000-0000-0000000000006/resourceGroups/test-rg/providers/Microsoft.ContainerRegistry/registries/testregistry" + + # Call the function + create_update_continuous_patch_v1(cmd, registry, temp_file_path, "1d", False, True) + + # Assert that the dependencies were called with the correct arguments + mock_convert_timespan_to_cron.assert_called_once_with("1d") + mock_create_oci_artifact_continuous_patch.assert_called_once() + mock_validate_and_deploy_template.assert_called_once() + mock_trigger_task_run.assert_called_once() + + @mock.patch("azext_acrcssc.helper._taskoperations._update_task_schedule") + @mock.patch("azext_acrcssc.helper._taskoperations.check_continuous_task_exists") + @mock.patch("azext_acrcssc.helper._taskoperations.convert_timespan_to_cron") + @mock.patch("azext_acrcssc.helper._taskoperations.create_oci_artifact_continuous_patch") + @mock.patch("azext_acrcssc.helper._taskoperations.parse_resource_id") + @mock.patch("azext_acrcssc.helper._taskoperations.validate_and_deploy_template") + @mock.patch("azext_acrcssc.helper._taskoperations._trigger_task_run") + def test_update_continuous_patch_v1_schedule_update_should_not_update_config(self, mock_trigger_task_run, mock_validate_and_deploy_template, mock_parse_resource_id, mock_create_oci_artifact_continuous_patch, mock_convert_timespan_to_cron, mock_check_continuoustask_exists, mock_update_task_schedule): + # Mock the necessary dependencies + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + temp_file_path = temp_file.name + mock_check_continuoustask_exists.return_value = True, [] + mock_convert_timespan_to_cron.return_value = "0 0 * * *" + mock_parse_resource_id.return_value = {"resource_group": "test_rg"} + cmd = self._setup_cmd() + registry = mock.MagicMock() + registry.id = "/subscriptions/11111111-0000-0000-0000-0000000000006/resourceGroups/test-rg/providers/Microsoft.ContainerRegistry/registries/testregistry" + + # Call the function + create_update_continuous_patch_v1(cmd, registry, None, "2d", False, False, False) + + # Assert that the dependencies were called with the correct arguments + mock_convert_timespan_to_cron.assert_called_once_with("2d") + mock_create_oci_artifact_continuous_patch.assert_not_called() + mock_validate_and_deploy_template.assert_not_called() + mock_trigger_task_run.assert_not_called() + mock_update_task_schedule.assert_called_once() + + @mock.patch("azext_acrcssc.helper._taskoperations.check_continuous_task_exists") + @mock.patch("azext_acrcssc.helper._taskoperations.convert_timespan_to_cron") + @mock.patch("azext_acrcssc.helper._taskoperations.create_oci_artifact_continuous_patch") + @mock.patch("azext_acrcssc.helper._taskoperations.parse_resource_id") + def test_update_continuous_patch_v1__update_without_tasks_workflow_should_fail(self, mock_parse_resource_id, mock_create_oci_artifact_continuous_patch, mock_convert_timespan_to_cron, mock_check_continuoustask_exists): + # Mock the necessary dependencies + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + temp_file_path = temp_file.name + mock_check_continuoustask_exists.return_value = False, [] + mock_convert_timespan_to_cron.return_value = "0 0 * * *" + mock_parse_resource_id.return_value = {"resource_group": "test_rg"} + cmd = self._setup_cmd() + registry = mock.MagicMock() + registry.id = "/subscriptions/11111111-0000-0000-0000-0000000000006/resourceGroups/test-rg/providers/Microsoft.ContainerRegistry/registries/testregistry" + + # Call the function + self.assertRaises(Exception,create_update_continuous_patch_v1,cmd, registry, None, "2d", False, False, False) + + # Assert that the dependencies were called with the correct arguments + mock_convert_timespan_to_cron.assert_called_once_with("2d") + mock_create_oci_artifact_continuous_patch.assert_not_called() + + @mock.patch("azext_acrcssc.helper._taskoperations._update_task_schedule") + @mock.patch("azext_acrcssc.helper._taskoperations.check_continuous_task_exists") + @mock.patch("azext_acrcssc.helper._taskoperations.convert_timespan_to_cron") + @mock.patch("azext_acrcssc.helper._taskoperations.create_oci_artifact_continuous_patch") + @mock.patch("azext_acrcssc.helper._taskoperations.parse_resource_id") + @mock.patch("azext_acrcssc.helper._taskoperations.validate_and_deploy_template") + @mock.patch("azext_acrcssc.helper._taskoperations._trigger_task_run") + def test_update_continuous_patch_v1_schedule_update_run_immediately_triggers_task(self, mock_trigger_task_run, mock_validate_and_deploy_template, mock_parse_resource_id, mock_create_oci_artifact_continuous_patch, mock_convert_timespan_to_cron, mock_check_continuoustask_exists, mock_update_task_schedule): + # Mock the necessary dependencies + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + temp_file_path = temp_file.name + mock_check_continuoustask_exists.return_value = True, [] + mock_convert_timespan_to_cron.return_value = "0 0 * * *" + mock_parse_resource_id.return_value = {"resource_group": "test_rg"} + cmd = self._setup_cmd() + registry = mock.MagicMock() + registry.id = "/subscriptions/11111111-0000-0000-0000-0000000000006/resourceGroups/test-rg/providers/Microsoft.ContainerRegistry/registries/testregistry" + + # Call the function + create_update_continuous_patch_v1(cmd, registry, None, "2d", False, True, False) + + # Assert that the dependencies were called with the correct arguments + mock_convert_timespan_to_cron.assert_called_once_with("2d") + mock_create_oci_artifact_continuous_patch.assert_not_called() + mock_validate_and_deploy_template.assert_not_called() + mock_trigger_task_run.assert_called_once() + mock_update_task_schedule.assert_called_once() + + @mock.patch("azext_acrcssc.helper._taskoperations.check_continuous_task_config_exists") + @mock.patch('azext_acrcssc.helper._taskoperations.delete_oci_artifact_continuous_patch') + @mock.patch("azext_acrcssc.helper._taskoperations.parse_resource_id") + @mock.patch("azext_acrcssc.helper._taskoperations.check_continuous_task_exists") + @mock.patch('azext_acrcssc.helper._taskoperations.cf_acr_tasks') + @mock.patch('azext_acrcssc.helper._taskoperations.cf_authorization') + def test_delete_continuous_patch_v1(self, mock_cf_authorization, mock_cf_acr_tasks, mock_check_continuoustask_exists, mock_parse_resource_id, mock_delete_oci_artifact_continuous_patch, mock_check_continuous_task_config_exists): + # Mock the necessary dependencies + cmd = self._setup_cmd() + mock_registry = mock.MagicMock() + mock_dryrun = False + mock_check_continuoustask_exists.return_value = True, [] + mock_check_continuous_task_config_exists.return_value = True + mock_registry.id = 'registry_id' + mock_resource_group = mock.MagicMock() + mock_resource_group.name = 'resource_group_name' + mock_registry.name = 'registry_name' + mock_acr_tasks_client = mock.MagicMock() + mock_cf_acr_tasks.return_value = mock_acr_tasks_client + mock_role_client = mock.MagicMock() + mock_cf_authorization.return_value = mock_role_client + mock_task = mock.MagicMock() + mock_task.identity = mock.MagicMock()(principal_id='principal_id') + mock_acr_tasks_client.get.return_value = mock_task + + delete_continuous_patch_v1(cmd, mock_registry, mock_dryrun) + ## Assert here + mock_delete_oci_artifact_continuous_patch.assert_called_once() + + def _setup_cmd(self): + cmd = mock.MagicMock() + cmd.cli_ctx = DummyCli() + return cmd diff --git a/src/acrcssc/azext_acrcssc/tests/latest/test_helper_utility.py b/src/acrcssc/azext_acrcssc/tests/latest/test_helper_utility.py new file mode 100644 index 00000000000..193f4143f39 --- /dev/null +++ b/src/acrcssc/azext_acrcssc/tests/latest/test_helper_utility.py @@ -0,0 +1,42 @@ +import unittest +from unittest.mock import MagicMock, patch +from azext_acrcssc.helper._utility import convert_cron_to_schedule, convert_timespan_to_cron +from azure.cli.core.azclierror import InvalidArgumentValueError + + +class TestCSSCUtilities(unittest.TestCase): + def test_convert_timespan_to_cron_valid(self): + # convert_timespan_to_cron() will return a cron that will include the current minute and hour, + # only match with the day of the month + test_cases = [ + ('1d', r'\d+ \d+ \*/1 \* \*'), + ('5d', r'\d+ \d+ \*/5 \* \*'), + ('10d', r'\d+ \d+ \*/10 \* \*') + ] + + for timespan, result in test_cases: + converted_value = convert_timespan_to_cron(timespan) + self.assertRegex(converted_value, result) + + def test_convert_timespan_to_cron_invalid(self): + test_cases = [('12'), ('0d'), ('99d'), ('dd'), ('d')] + + for timespan in test_cases: + self.assertRaises(InvalidArgumentValueError, convert_timespan_to_cron, timespan) + + def test_convert_cron_to_schedule_valid(self): + test_cases = [ + ('0 0 */1 * *', '1d'), + ('0 0 */5 * *', '5d'), + ('0 0 */10 * *', '10d') + ] + + for cron, result in test_cases: + self.assertEqual(convert_cron_to_schedule(cron), result) + + def test_convert_cron_to_schedule_invalid(self): + test_cases = [('*'), (''), ('* * * * *'), ('dd'), ('d')] + + for cron in test_cases: + self.assertEqual(convert_cron_to_schedule(cron), None) + diff --git a/src/acrcssc/azext_acrcssc/tests/latest/test_validators.py b/src/acrcssc/azext_acrcssc/tests/latest/test_validators.py new file mode 100644 index 00000000000..d75798e94ca --- /dev/null +++ b/src/acrcssc/azext_acrcssc/tests/latest/test_validators.py @@ -0,0 +1,180 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import tempfile +import unittest +from unittest import mock +from ..._validators import ( + _validate_schedule, check_continuous_task_exists, validate_continuouspatch_config_v1 +) + +from azure.cli.core.azclierror import AzCLIError, InvalidArgumentValueError +from azure.cli.core.mock import DummyCli +from unittest.mock import patch + + +class AcrCsscCommandsTests(unittest.TestCase): + + def test_validate_schedule_valid(self): + test_cases = [ + ('1d'), + ('5d'), + ('10d') + ] + + for timespan in test_cases: + with self.subTest(timespan=timespan): + _validate_schedule(timespan) + + def test_validate_schedule_invalid(self): + test_cases = [('df'),('12'),('dd'),('41d'), ('21dd')] + + for timespan in test_cases: + self.assertRaises(InvalidArgumentValueError, _validate_schedule, timespan) + + @patch('azext_acrcssc._validators.cf_acr_tasks') + def test_check_continuoustask_exists(self, mock_cf_acr_tasks): + cmd = self._setup_cmd() + registry = mock.MagicMock() + registry.id = "/subscriptions/11111111-0000-0000-0000-0000000000006/resourceGroups/test-rg/providers/Microsoft.ContainerRegistry/registries/testregistry" + cf_acr_tasks_mock = mock.MagicMock() + + mock_cf_acr_tasks.return_value = cf_acr_tasks_mock + cf_acr_tasks_mock.get.return_value = {"name": "my_task"} + exists, _ = check_continuous_task_exists(cmd, registry) + self.assertTrue(exists) + + @patch('azext_acrcssc._validators.cf_acr_tasks') + def test_task_does_not_exist(self, mock_cf_acr_tasks): + cmd = self._setup_cmd() + registry = mock.MagicMock() + registry.id = "/subscriptions/11111111-0000-0000-0000-0000000000006/resourceGroups/test-rg/providers/Microsoft.ContainerRegistry/registries/testregistry" + cf_acr_tasks_mock = mock.MagicMock() + + mock_cf_acr_tasks.return_value = cf_acr_tasks_mock + cf_acr_tasks_mock.get.return_value = None + + exists, _ = check_continuous_task_exists(cmd, registry) + self.assertFalse(exists) + + def test_validate_continuouspatch_file(self): + # Create a temporary file for testing + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + temp_file_path = temp_file.name + + # Test when the file does not exist + with patch('os.path.exists', return_value=False): + self.assertRaises(AzCLIError, validate_continuouspatch_config_v1, temp_file_path) + + # Test when the path is not a file + with patch('os.path.exists', return_value=True), \ + patch('os.path.isfile', return_value=False): + self.assertRaises(AzCLIError, validate_continuouspatch_config_v1, temp_file_path) + + # Test when the file size exceeds the limit + with patch('os.path.exists', return_value=True), \ + patch('os.path.isfile', return_value=True), \ + patch('os.path.getsize', return_value=10485761): + self.assertRaises(AzCLIError, validate_continuouspatch_config_v1, temp_file_path) + + # Test when the file is empty + with patch('os.path.exists', return_value=True), \ + patch('os.path.isfile', return_value=True), \ + patch('os.path.getsize', return_value=0): + self.assertRaises(AzCLIError, validate_continuouspatch_config_v1, temp_file_path) + + # Test when the file is not readable + with patch('os.path.exists', return_value=True), \ + patch('os.path.isfile', return_value=True), \ + patch('os.path.getsize', return_value=100), \ + patch('os.access', return_value=False): + self.assertRaises(AzCLIError, validate_continuouspatch_config_v1, temp_file_path) + + # Clean up the temporary file + os.remove(temp_file_path) + + @patch('azext_acrcssc._validators.json.load') + def test_validate_continuouspatch_json_valid_json_should_parse(self, mock_load): + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + temp_file_path = temp_file.name + + mock_config = { + "repositories": [ + { + "repository": "docker-local", + "tags": ["v1"], + "enabled": True + }], + "version": "v1" + } + mock_load.return_value = mock_config + + with patch('os.path.exists', return_value=True), \ + patch('os.path.isfile', return_value=True), \ + patch('os.path.getsize', return_value=100), \ + patch('os.access', return_value=True): + validate_continuouspatch_config_v1(temp_file_path) + mock_load.assert_called_once_with(mock.ANY) + + @patch('azext_acrcssc._validators.json.load') + def test_validate_continuouspatch_json_invalid_json_should_fail(self, mock_load): + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + temp_file_path = temp_file.name + + mock_invalid_config = { + "repositories": [ + { + "repository": "docker-local", + "tags": ["v1"], + }] + } + mock_load.return_value = mock_invalid_config + + with patch('os.path.exists', return_value=True), \ + patch('os.path.isfile', return_value=True), \ + patch('os.path.getsize', return_value=100), \ + patch('os.access', return_value=True): + self.assertRaises(AzCLIError, validate_continuouspatch_config_v1, temp_file_path) + + @patch('azext_acrcssc._validators.json.load') + def test_validate_continuouspatch_json_invalid_tags_should_fail(self, mock_load): + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + temp_file_path = temp_file.name + + mock_invalid_config = { + "repositories": [ + { + "repository": "docker-local", + "tags": ["v1-patched"], + }] + } + mock_load.return_value = mock_invalid_config + + with patch('os.path.exists', return_value=True), \ + patch('os.path.isfile', return_value=True), \ + patch('os.path.getsize', return_value=100), \ + patch('os.access', return_value=True): + self.assertRaises(AzCLIError, validate_continuouspatch_config_v1, temp_file_path) + + mock_invalid_config = { + "repositories": [ + { + "repository": "docker-local", + "tags": ["v1-999"], + }], + } + mock_load.return_value = mock_invalid_config + + with patch('os.path.exists', return_value=True), \ + patch('os.path.isfile', return_value=True), \ + patch('os.path.getsize', return_value=100), \ + patch('os.access', return_value=True): + self.assertRaises(AzCLIError, validate_continuouspatch_config_v1, temp_file_path) + + def _setup_cmd(self): + cmd = mock.MagicMock() + cmd.cli_ctx = DummyCli() + return cmd diff --git a/src/acrcssc/azext_acrcssc/tests/latest/test_workflow_Status.py b/src/acrcssc/azext_acrcssc/tests/latest/test_workflow_Status.py new file mode 100644 index 00000000000..9206d0becac --- /dev/null +++ b/src/acrcssc/azext_acrcssc/tests/latest/test_workflow_Status.py @@ -0,0 +1,272 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import unittest +from unittest import mock +from unittest.mock import MagicMock +from azure.cli.core.mock import DummyCli +from azext_acrcssc.helper._constants import TaskRunStatus +from azext_acrcssc.helper._workflow_status import WorkflowTaskStatus, WorkflowTaskState + + +class TestWorkflowTaskStatus(unittest.TestCase): + + def setUp(self): + self.image = "repository:tag" + self.workflow_task_status = WorkflowTaskStatus(self.image) + + def test_init(self): + self.assertEqual(self.workflow_task_status.repository, "repository") + self.assertEqual(self.workflow_task_status.tag, "tag") + self.assertIsNone(self.workflow_task_status.scan_task) + self.assertEqual(self.workflow_task_status.scan_logs, "") + self.assertIsNone(self.workflow_task_status.patch_task) + self.assertEqual(self.workflow_task_status.patch_logs, "") + + def test_image(self): + self.assertEqual(self.workflow_task_status.image(), "repository:tag") + + def test__task_status_to_workflow_status(self): + from azext_acrcssc.helper._constants import TaskRunStatus + task = mock.MagicMock() + task.status = TaskRunStatus.Succeeded.value + self.assertEqual(WorkflowTaskStatus._task_status_to_workflow_status(task), WorkflowTaskState.SUCCEEDED.value) + task.status = TaskRunStatus.Running.value + self.assertEqual(WorkflowTaskStatus._task_status_to_workflow_status(task), WorkflowTaskState.RUNNING.value) + task.status = TaskRunStatus.Started.value + self.assertEqual(WorkflowTaskStatus._task_status_to_workflow_status(task), WorkflowTaskState.RUNNING.value) + task.status = TaskRunStatus.Queued.value + self.assertEqual(WorkflowTaskStatus._task_status_to_workflow_status(task), WorkflowTaskState.QUEUED.value) + task.status = TaskRunStatus.Canceled.value + self.assertEqual(WorkflowTaskStatus._task_status_to_workflow_status(task), WorkflowTaskState.CANCELED.value) + task.status = TaskRunStatus.Failed.value + self.assertEqual(WorkflowTaskStatus._task_status_to_workflow_status(task), WorkflowTaskState.FAILED.value) + task.status = TaskRunStatus.Error.value + self.assertEqual(WorkflowTaskStatus._task_status_to_workflow_status(task), WorkflowTaskState.FAILED.value) + task.status = TaskRunStatus.Timeout.value + self.assertEqual(WorkflowTaskStatus._task_status_to_workflow_status(task), WorkflowTaskState.FAILED.value) + + self.assertEqual(WorkflowTaskStatus._task_status_to_workflow_status(None), WorkflowTaskState.UNKNOWN.value) + + def test_workflow_status(self): + workflow = WorkflowTaskStatus("mock_repo:mock_tag") + workflow.scan_task = mock.MagicMock() + workflow.patch_task = mock.MagicMock() + + workflow.scan_task.status = TaskRunStatus.Succeeded.value + workflow.patch_task.status = TaskRunStatus.Succeeded.value + self.assertEqual(workflow.status(), WorkflowTaskState.SUCCEEDED.value) + + workflow.scan_task.status = TaskRunStatus.Succeeded.value + workflow.patch_task.status = TaskRunStatus.Failed.value + self.assertEqual(workflow.status(), WorkflowTaskState.FAILED.value) + + workflow.scan_task.status = TaskRunStatus.Succeeded.value + workflow.patch_task.status = TaskRunStatus.Running.value + self.assertEqual(workflow.status(), WorkflowTaskState.RUNNING.value) + + workflow.scan_task.status = TaskRunStatus.Canceled.value + workflow.patch_task = None + self.assertEqual(workflow.status(), WorkflowTaskState.CANCELED.value) + + workflow.scan_task.status = TaskRunStatus.Succeeded.value + workflow.patch_task = None + self.assertEqual(workflow.status(), WorkflowTaskState.SUCCEEDED.value) + + workflow.scan_task = None + workflow.patch_task = None + self.assertEqual(workflow.status(), WorkflowTaskState.UNKNOWN.value) + + def test_get_image_from_tasklog(self): + logs = "Scanning image for vulnerability and patch mock-repo:mock-tag for tag mock-tag" + self.assertEqual(WorkflowTaskStatus._get_image_from_tasklog(logs), "mock-repo:mock-tag") + + logs = "Scanning repo: mock-repo, Tag:mock-tag, OriginalTag:mock-tag" + self.assertEqual(WorkflowTaskStatus._get_image_from_tasklog(logs), "mock-repo:mock-tag") + + # logs = "Scan, Upload scan report and Schedule Patch for mock-repo:mock-tag" + # self.assertEqual(WorkflowTaskStatus._get_image_from_tasklog(logs), "mock-repo:mock-tag") + + logs = "Patching OS vulnerabilities for image mock-repo:mock-tag" + self.assertEqual(WorkflowTaskStatus._get_image_from_tasklog(logs), "mock-repo:mock-tag") + + def test_get_patch_error_reason_from_tasklog(self): + error_logs = """2025/02/11 23:46:22 Launching container with name: patch-image +#1 resolve image config for docker-image://test.azurecr.io/repo:tag-9.9.99.20202020-amd64 +Error: unsupported osType azurelinux specified +2025/02/11 23:46:23 Container failed during run: patch-image. No retries remaining. +failed to run step ID: patch-image: exit status 1 + +Run ID: dt2rw failed after 37s. Error: failed during run, err: exit status 1""" + + assert_error_log = """Error: failed during run, err: exit status 1 +Error: unsupported osType azurelinux specified""" + error_output = self.workflow_task_status._get_errors_from_tasklog(error_logs) + self.assertEqual(error_output, assert_error_log) + + @mock.patch('azext_acrcssc.helper._workflow_status.WorkflowTaskStatus._get_missing_taskrun') + @mock.patch('azext_acrcssc.helper._workflow_status.WorkflowTaskStatus._retrieve_all_tasklogs') + def test_from_taskrun(self, mock_retrieve_all_tasklogs, mock_get_missing_taskrun): + cmd = mock.MagicMock() + cmd.cli_ctx = DummyCli() + taskrun_client = MagicMock() + registry = MagicMock() + scan_taskruns = [self._generate_test_taskrun(True, repository="mock1"), self._generate_test_taskrun(True, repository="mock2"), self._generate_test_taskrun(True, repository="mock3")] + patch_taskruns = [] + + mock_retrieve_all_tasklogs.return_value = scan_taskruns + mock_get_missing_taskrun.return_value = None + + # Test with 3 simple scan tasks and no patch tasks + result = WorkflowTaskStatus.from_taskrun(cmd, taskrun_client, registry, scan_taskruns, patch_taskruns) + self.assertIsInstance(result, list) + self.assertEqual(len(result), len(scan_taskruns)) + self.assertTrue(all("patch_status" in workflow for workflow in result)) + self.assertTrue(all(workflow["patch_status"] == WorkflowTaskState.SKIPPED.value for workflow in result)) + self.assertTrue(all(workflow["scan_status"] == WorkflowTaskState.SUCCEEDED.value for workflow in result)) + self.assertTrue(all("patch_skipped_reason" in workflow for workflow in result)) + self.assertTrue(all("scan_error_reason" not in workflow for workflow in result)) + self.assertTrue(all("patch_error_reason" not in workflow for workflow in result)) + + # Test with 1 scan task and 1 failed patch task + patch_task = self._generate_test_taskrun(True, status=TaskRunStatus.Failed.value) + scan_taskruns = [self._generate_test_taskrun(True, patch_taskid_in_scan=patch_task.run_id)] + patch_taskruns = [patch_task] + result = WorkflowTaskStatus.from_taskrun(cmd, taskrun_client, registry, scan_taskruns, patch_taskruns) + self.assertIsInstance(result, list) + self.assertEqual(len(result), 1) + self.assertTrue(all("patch_status" in workflow for workflow in result)) + self.assertTrue(all(workflow["patch_status"] == WorkflowTaskState.FAILED.value for workflow in result)) + self.assertTrue(all(workflow["scan_status"] == WorkflowTaskState.SUCCEEDED.value for workflow in result)) + self.assertTrue(all("scan_error_reason" not in workflow for workflow in result)) + self.assertTrue(all("patch_error_reason" in workflow for workflow in result)) + + # Test where the patch task is missing, but mentioned in the scan task logs + patch_task = self._generate_test_taskrun(True, status=TaskRunStatus.Succeeded.value) + scan_taskruns = [self._generate_test_taskrun(True, patch_taskid_in_scan=patch_task.run_id)] + patch_taskruns = [] + mock_get_missing_taskrun.return_value = patch_task + result = WorkflowTaskStatus.from_taskrun(cmd, taskrun_client, registry, scan_taskruns, patch_taskruns) + self.assertIsInstance(result, list) + self.assertEqual(len(result), len(scan_taskruns)) + mock_get_missing_taskrun.assert_called_once() + self.assertTrue("patch_status" in result[0]) + self.assertTrue("patch_task_ID" in result[0]) + self.assertTrue("last_patched_image" in result[0]) + self.assertTrue(not result[0]["last_patched_image"].startswith("---")) + self.assertTrue(result[0]["patch_status"] == WorkflowTaskState.SUCCEEDED.value) + + # Test with mixed scan and patch tasks status + patch_taskruns = [self._generate_test_taskrun(True, status=TaskRunStatus.Succeeded.value, tag="tag0"), + self._generate_test_taskrun(True, status=TaskRunStatus.Canceled.value, tag="tag1"), + self._generate_test_taskrun(True, status=TaskRunStatus.Queued.value, tag="tag2"), + self._generate_test_taskrun(True, status=TaskRunStatus.Running.value, tag="tag3"), + self._generate_test_taskrun(True, status=TaskRunStatus.Failed.value, tag="tag4")] + + scan_taskruns = [self._generate_test_taskrun(True, patch_taskid_in_scan=patch_taskruns[0].run_id, tag="tag0"), + self._generate_test_taskrun(True, patch_taskid_in_scan=patch_taskruns[1].run_id, tag="tag1"), + self._generate_test_taskrun(True, patch_taskid_in_scan=patch_taskruns[2].run_id, tag="tag2"), + self._generate_test_taskrun(True, patch_taskid_in_scan=patch_taskruns[3].run_id, tag="tag3"), + self._generate_test_taskrun(True, patch_taskid_in_scan=patch_taskruns[4].run_id, tag="tag4"), + self._generate_test_taskrun(True, status=TaskRunStatus.Failed.value, tag="tag5"), + self._generate_test_taskrun(True, status=TaskRunStatus.Canceled.value, tag="tag6"), + self._generate_test_taskrun(True, status=TaskRunStatus.Queued.value, tag="tag7"), + self._generate_test_taskrun(True, status=TaskRunStatus.Running.value, tag="tag8"),] + + mock_get_missing_taskrun.return_value = patch_task + result = WorkflowTaskStatus.from_taskrun(cmd, taskrun_client, registry, scan_taskruns, patch_taskruns) + self.assertIsInstance(result, list) + self.assertEqual(len(result), len(scan_taskruns)) + self.assertTrue(all("patch_status" in workflow for workflow in result)) + self.assertTrue(all("scan_status" in workflow for workflow in result)) + self.assertTrue(all("image" in workflow for workflow in result)) + self.assertTrue(all("scan_date" in workflow for workflow in result)) + self.assertTrue(all("scan_task_ID" in workflow for workflow in result)) + self.assertTrue(all("patch_date" in workflow for workflow in result)) + self.assertTrue(all("patch_task_ID" in workflow for workflow in result)) + self.assertTrue(all("last_patched_image" in workflow for workflow in result)) + self.assertTrue(all("workflow_type" in workflow for workflow in result)) + self.assertTrue(all(True if workflow["patch_status"] != TaskRunStatus.Failed.value or "patch_error_reason" in workflow else False for workflow in result)) + self.assertTrue(all(True if workflow["scan_status"] != TaskRunStatus.Failed.value or "scan_error_reason" in workflow else False for workflow in result)) + # test that a successful patch has a patched image reference + self.assertTrue(all(True if workflow["patch_status"] != TaskRunStatus.Succeeded.value or not workflow["last_patched_image"].startswith("---") else False for workflow in result)) + + # generate a random scan or patch taskrun with the desired properties + def _generate_test_taskrun(self, scan_task=True, status=TaskRunStatus.Succeeded.value, repository="mock-repo", tag="mock-tag", patch_taskid_in_scan=""): + import random + import string + import datetime + taskrun = MagicMock() + taskrun.status = status + taskrun.create_time = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ") + + letters = ''.join(random.choices(string.ascii_lowercase, k=3)) + number = random.randint(0, 9) + taskrun.run_id = f"{letters}{number}" + task_log_result = "" + + if scan_task: + task_log_result = f"Scanning image for vulnerability and patch {repository}:{tag} for tag {tag}" + task_log_result += f"\nScanning repo: {repository}, Tag:{tag}, OriginalTag:{tag}" + if taskrun.status == TaskRunStatus.Failed.value or taskrun.status == TaskRunStatus.Error.value: + task_log_result += "\nerror: mock error on scan" + elif taskrun.status == TaskRunStatus.Succeeded.value: + task_log_result += "\nmock patch logs" + else: + task_log_result += "\ngeneric mock scan logs" + if patch_taskid_in_scan != "": + task_log_result += f"\nPATCHING task scheduled for image {repository}:{tag}, new patch tag will be {tag}-patched" + task_log_result += f"\nWARNING: Queued a run with ID: {patch_taskid_in_scan}" + + else: + task_log_result += f"Patching OS vulnerabilities for image {repository}:{tag}" + if taskrun.status == TaskRunStatus.Failed.value or taskrun.status == TaskRunStatus.Error.value: + task_log_result += "\nerror: mock error on patch" + elif taskrun.status == TaskRunStatus.Succeeded.value: + task_log_result += f"\nPATCHING task scheduled for image {repository}:{tag}, new patch tag will be {tag}-patched" + else: + task_log_result += "\nmock patch logs" + + taskrun.task_log_result = task_log_result + + return taskrun + + @mock.patch('azext_acrcssc.helper._workflow_status.WorkflowTaskStatus._download_logs') + @mock.patch('azext_acrcssc.helper._workflow_status.get_sdk') + @mock.patch('azure.cli.core.profiles.get_sdk') + def test_generate_logs(self, mock_core_get_sdk, mock_wf_get_sdk, mock_download_logs): + cmd = mock.MagicMock() + cmd.cli_ctx = DummyCli() + client = mock.MagicMock() + run_id = "cgb5" + registry_name = "myregistry" + resource_group_name = "myresourcegroup" + + # Mock the response from client.get_log_sas_url() + response = mock.MagicMock() + response.log_link = "https://example.com/logs" + client.get_log_sas_url.return_value = response + + run_response = mock.MagicMock() + run_response.status = "Succeeded" + client.get.return_value = run_response + + # Create a mock for the blob client + mock_blob_client = mock.MagicMock() + #mock_blob_client.from_blob_url = mock.MagicMock() + mock_blob_client.from_blob_url.return_value = "mock_blob_client" + + mock_core_get_sdk.return_value = mock_blob_client + mock_wf_get_sdk.return_value = mock_blob_client + mock_download_logs.return_value = "mock logs" + + # Call the function + result = WorkflowTaskStatus.generate_logs(cmd, client, run_id, registry_name, resource_group_name) + + # Assert the function calls + mock_download_logs.assert_called() + mock_blob_client.from_blob_url.assert_called() + client.get_log_sas_url.assert_called() + self.assertEqual(result, "mock logs") diff --git a/src/acrcssc/setup.cfg b/src/acrcssc/setup.cfg new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/acrcssc/setup.py b/src/acrcssc/setup.py new file mode 100644 index 00000000000..ed9d94ebbe3 --- /dev/null +++ b/src/acrcssc/setup.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python + +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +from codecs import open +from setuptools import setup, find_packages +try: + from azure_bdist_wheel import cmdclass +except ImportError: + from distutils import log as logger + logger.warn("Wheel is not available, disabling bdist_wheel hook") + +# TODO: Confirm this is the right version number you want and it matches your +# HISTORY.rst entry. +VERSION = '1.1.1rc7' + +# The full list of classifiers is available at +# https://pypi.python.org/pypi?%3Aaction=list_classifiers +CLASSIFIERS = [ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Intended Audience :: System Administrators', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'License :: OSI Approved :: MIT License', +] + +# TODO: Add any additional SDK dependencies here +DEPENDENCIES = ["oras~=0.1.19", "croniter~=3.0.0"] + +with open('README.rst', 'r', encoding='utf-8') as f: + README = f.read() +with open('HISTORY.rst', 'r', encoding='utf-8') as f: + HISTORY = f.read() + +setup( + name='acrcssc', + version=VERSION, + description='Microsoft Azure Command-Line Tools Acrcssc Extension', + author='Microsoft Corporation', + author_email='azpycli@microsoft.com', + # TODO: change to your extension source code repo if the code will not be put in azure-cli-extensions repo + url='https://github.com/Azure/azure-cli-extensions/tree/master/src/acrcssc', + long_description=README + '\n\n' + HISTORY, + license='MIT', + classifiers=CLASSIFIERS, + packages=find_packages(), + install_requires=DEPENDENCIES, + package_data={ + 'azext_acrcssc': [ + "azext_metadata.json", + "templates/tmp_dry_run_template.yaml", + "templates/arm/*", + "templates/task/*" + ] + } +)