diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a8813745b..2c2cc9455 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,55 @@ All notable changes to this project will be documented in this file. The format is based on `Keep a Changelog `__. +3.88.0 - 2026-06-23 +------------------- +Added +~~~~~ +* Support for global parameter federation-endpoint to override the auth service federation endpoint used with instance principal authentication + + * ``oci --auth instance_principal --federation-endpoint`` + +* Resource Scheduler service + + * Support for ADBD Backup support + + * ``oci resource-scheduler schedule create --action BACKUP_RESOURCE`` + * ``oci resource-scheduler resource-type-collection list-resource-types --action-type BACKUP_RESOURCE`` + * ``oci resource-scheduler schedule update --action BACKUP_RESOURCE`` + +* API Gateway service + + * Support for static redirect URLs in authentication policy plugins for deployments in create and update commands + + * ``oci api-gateway deployment create --specification`` + * ``oci api-gateway deployment update --specification`` + + * Support for 3 optional parameters in sdk update command + + * ``oci api-gateway sdk update --max-wait-seconds --wait-for-state --wait-interval-seconds`` + +* Container Engine service + + * Support for virtual node pool cycling + + * ``oci ce virtual-node-pool create --virtual-node-pool-cycling-details `` + * ``oci ce virtual-node-pool update --virtual-node-pool-cycling-details`` + +* DNS service + + * Support for Zero Trust Packet Routing (ZPR) security attributes, defined tags, and free-form tags for DNS resolver endpoint + + * ``oci dns resolver-endpoint create --defined-tags --freeform-tags --security-attributes`` + * ``oci dns resolver-endpoint update --defined-tags --freeform-tags --security-attributes`` + +Changed +~~~~~~~ +* API Gateway service + + * [BREAKING] SDK update command now returns an `Sdk` payload instead of `None` + + * ``oci api-gateway sdk update`` + 3.87.0 - 2026-06-16 ------------------- Added diff --git a/conftest.py b/conftest.py index df9688b73..f14fc1e95 100644 --- a/conftest.py +++ b/conftest.py @@ -43,8 +43,14 @@ if not os.path.exists(os.path.join('tests', 'temp')): os.makedirs(os.path.join('tests', 'temp')) -dynamic_loader.load_all_services() -final_command_processor.process() + +def _load_services_for_pytest(service_name): + if service_name == "all": + dynamic_loader.load_all_services() + elif service_name not in dynamic_loader.NON_SERVICE_TOP_LEVEL_COMMANDS: + dynamic_loader.load_service(service_name) + + final_command_processor.process() def pytest_addoption(parser): @@ -70,6 +76,7 @@ def add_test_option(parser, option, action, default, help): def pytest_configure(config): test_config_container.vcr_mode = config.getoption("--vcr-record-mode") + _load_services_for_pytest(config.getoption("service")) @pytest.fixture(scope="session", autouse=True) diff --git a/requirements.txt b/requirements.txt index 7bdef29f6..2ca405f3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ Jinja2>=3.1.5,<4.0.0; python_version >= '3.7' jmespath>=0.10.0,<=1.0.1 ndg-httpsclient==0.4.2 mock==2.0.0 -oci==2.179.0 +oci==2.180.0 packaging>=22.0,<25.0; python_version > '3.8' packaging==20.2; python_version <= '3.8' pluggy==0.13.0 diff --git a/services/apigateway/src/oci_cli_api_gateway/generated/apigateway_cli.py b/services/apigateway/src/oci_cli_api_gateway/generated/apigateway_cli.py index 11347cb74..b5457d9a8 100644 --- a/services/apigateway/src/oci_cli_api_gateway/generated/apigateway_cli.py +++ b/services/apigateway/src/oci_cli_api_gateway/generated/apigateway_cli.py @@ -1583,12 +1583,15 @@ def update_certificate(ctx, from_json, force, wait_for_state, max_wait_seconds, @cli_util.option('--if-match', help=u"""For optimistic concurrency control. In the PUT or DELETE call for a resource, set the `if-match` parameter to the value of the etag from a previous GET or POST response for that resource. The resource will be updated or deleted only if the etag you provide matches the resource's current etag value.""") @cli_util.option('--is-lock-override', type=click.BOOL, help=u"""Whether to override locks (if any exist).""") @cli_util.option('--force', help="""Perform update without prompting for confirmation.""", is_flag=True) +@cli_util.option('--wait-for-state', type=custom_types.CliCaseInsensitiveChoice(["CREATING", "ACTIVE", "FAILED", "DELETING", "DELETED"]), multiple=True, help="""This operation creates, modifies or deletes a resource that has a defined lifecycle state. Specify this option to perform the action and then wait until the resource reaches a given lifecycle state. Multiple states can be specified, returning on the first state. For example, --wait-for-state CREATING --wait-for-state DELETED would return on whichever lifecycle state is reached first. If timeout is reached, a return code of 2 is returned. For any other error, a return code of 1 is returned.""") +@cli_util.option('--max-wait-seconds', type=click.INT, help="""The maximum time to wait for the resource to reach the lifecycle state defined by --wait-for-state. Defaults to 1200 seconds.""") +@cli_util.option('--wait-interval-seconds', type=click.INT, help="""Check every --wait-interval-seconds to see whether the resource has reached the lifecycle state defined by --wait-for-state. Defaults to 30 seconds.""") @json_skeleton_utils.get_cli_json_input_option({'freeform-tags': {'module': 'apigateway', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'apigateway', 'class': 'dict(str, dict(str, object))'}}) @cli_util.help_option @click.pass_context -@json_skeleton_utils.json_skeleton_generation_handler(input_params_to_complex_types={'freeform-tags': {'module': 'apigateway', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'apigateway', 'class': 'dict(str, dict(str, object))'}}) +@json_skeleton_utils.json_skeleton_generation_handler(input_params_to_complex_types={'freeform-tags': {'module': 'apigateway', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'apigateway', 'class': 'dict(str, dict(str, object))'}}, output_type={'module': 'apigateway', 'class': 'Sdk'}) @cli_util.wrap_exceptions -def update_sdk(ctx, from_json, force, sdk_id, display_name, freeform_tags, defined_tags, if_match, is_lock_override): +def update_sdk(ctx, from_json, force, wait_for_state, max_wait_seconds, wait_interval_seconds, sdk_id, display_name, freeform_tags, defined_tags, if_match, is_lock_override): if isinstance(sdk_id, six.string_types) and len(sdk_id.strip()) == 0: raise click.UsageError('Parameter --sdk-id cannot be whitespace or empty string') @@ -1621,4 +1624,27 @@ def update_sdk(ctx, from_json, force, sdk_id, display_name, freeform_tags, defin update_sdk_details=_details, **kwargs ) + if wait_for_state: + + if hasattr(client, 'get_sdk') and callable(getattr(client, 'get_sdk')): + try: + wait_period_kwargs = {} + if max_wait_seconds is not None: + wait_period_kwargs['max_wait_seconds'] = max_wait_seconds + if wait_interval_seconds is not None: + wait_period_kwargs['max_interval_seconds'] = wait_interval_seconds + + click.echo('Action completed. Waiting until the resource has entered state: {}'.format(wait_for_state), file=sys.stderr) + result = oci.wait_until(client, client.get_sdk(result.data.id), 'lifecycle_state', wait_for_state, **wait_period_kwargs) + except oci.exceptions.MaximumWaitTimeExceeded as e: + # If we fail, we should show an error, but we should still provide the information to the customer + click.echo('Failed to wait until the resource entered the specified state. Outputting last known resource state', file=sys.stderr) + cli_util.render_response(result, ctx) + sys.exit(2) + except Exception: + click.echo('Encountered error while waiting for resource to enter the specified state. Outputting last known resource state', file=sys.stderr) + cli_util.render_response(result, ctx) + raise + else: + click.echo('Unable to wait for the resource to enter the specified state', file=sys.stderr) cli_util.render_response(result, ctx) diff --git a/services/container_engine/src/oci_cli_container_engine/generated/containerengine_cli.py b/services/container_engine/src/oci_cli_container_engine/generated/containerengine_cli.py index 212326e96..3e83602fd 100644 --- a/services/container_engine/src/oci_cli_container_engine/generated/containerengine_cli.py +++ b/services/container_engine/src/oci_cli_container_engine/generated/containerengine_cli.py @@ -675,15 +675,16 @@ def create_node_pool_node_source_via_image_details(ctx, from_json, wait_for_stat @cli_util.option('--freeform-tags', type=custom_types.CLI_COMPLEX_TYPE, help=u"""Free-form tags for this resource. Each tag is a simple key-value pair with no predefined name, type, or namespace. For more information, see [Resource Tags]. Example: `{\"Department\": \"Finance\"}`""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) @cli_util.option('--defined-tags', type=custom_types.CLI_COMPLEX_TYPE, help=u"""Defined tags for this resource. Each key is predefined and scoped to a namespace. For more information, see [Resource Tags]. Example: `{\"Operations\": {\"CostCenter\": \"42\"}}`""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) @cli_util.option('--virtual-node-tags', type=custom_types.CLI_COMPLEX_TYPE, help=u"""""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) +@cli_util.option('--virtual-node-pool-cycling-details', type=custom_types.CLI_COMPLEX_TYPE, help=u"""""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) @cli_util.option('--wait-for-state', type=custom_types.CliCaseInsensitiveChoice(["ACCEPTED", "IN_PROGRESS", "FAILED", "SUCCEEDED", "CANCELING", "CANCELED"]), multiple=True, help="""This operation asynchronously creates, modifies or deletes a resource and uses a work request to track the progress of the operation. Specify this option to perform the action and then wait until the work request reaches a certain state. Multiple states can be specified, returning on the first state. For example, --wait-for-state ACCEPTED --wait-for-state CANCELED would return on whichever lifecycle state is reached first. If timeout is reached, a return code of 2 is returned. For any other error, a return code of 1 is returned.""") @cli_util.option('--max-wait-seconds', type=click.INT, help="""The maximum time to wait for the work request to reach the state defined by --wait-for-state. Defaults to 1200 seconds.""") @cli_util.option('--wait-interval-seconds', type=click.INT, help="""Check every --wait-interval-seconds to see whether the work request has reached the state defined by --wait-for-state. Defaults to 30 seconds.""") -@json_skeleton_utils.get_cli_json_input_option({'initial-virtual-node-labels': {'module': 'container_engine', 'class': 'list[InitialVirtualNodeLabel]'}, 'taints': {'module': 'container_engine', 'class': 'list[Taint]'}, 'placement-configurations': {'module': 'container_engine', 'class': 'list[PlacementConfiguration]'}, 'nsg-ids': {'module': 'container_engine', 'class': 'list[string]'}, 'pod-configuration': {'module': 'container_engine', 'class': 'PodConfiguration'}, 'freeform-tags': {'module': 'container_engine', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'container_engine', 'class': 'dict(str, dict(str, object))'}, 'virtual-node-tags': {'module': 'container_engine', 'class': 'VirtualNodeTags'}}) +@json_skeleton_utils.get_cli_json_input_option({'initial-virtual-node-labels': {'module': 'container_engine', 'class': 'list[InitialVirtualNodeLabel]'}, 'taints': {'module': 'container_engine', 'class': 'list[Taint]'}, 'placement-configurations': {'module': 'container_engine', 'class': 'list[PlacementConfiguration]'}, 'nsg-ids': {'module': 'container_engine', 'class': 'list[string]'}, 'pod-configuration': {'module': 'container_engine', 'class': 'PodConfiguration'}, 'freeform-tags': {'module': 'container_engine', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'container_engine', 'class': 'dict(str, dict(str, object))'}, 'virtual-node-tags': {'module': 'container_engine', 'class': 'VirtualNodeTags'}, 'virtual-node-pool-cycling-details': {'module': 'container_engine', 'class': 'VirtualNodePoolCyclingDetails'}}) @cli_util.help_option @click.pass_context -@json_skeleton_utils.json_skeleton_generation_handler(input_params_to_complex_types={'initial-virtual-node-labels': {'module': 'container_engine', 'class': 'list[InitialVirtualNodeLabel]'}, 'taints': {'module': 'container_engine', 'class': 'list[Taint]'}, 'placement-configurations': {'module': 'container_engine', 'class': 'list[PlacementConfiguration]'}, 'nsg-ids': {'module': 'container_engine', 'class': 'list[string]'}, 'pod-configuration': {'module': 'container_engine', 'class': 'PodConfiguration'}, 'freeform-tags': {'module': 'container_engine', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'container_engine', 'class': 'dict(str, dict(str, object))'}, 'virtual-node-tags': {'module': 'container_engine', 'class': 'VirtualNodeTags'}}) +@json_skeleton_utils.json_skeleton_generation_handler(input_params_to_complex_types={'initial-virtual-node-labels': {'module': 'container_engine', 'class': 'list[InitialVirtualNodeLabel]'}, 'taints': {'module': 'container_engine', 'class': 'list[Taint]'}, 'placement-configurations': {'module': 'container_engine', 'class': 'list[PlacementConfiguration]'}, 'nsg-ids': {'module': 'container_engine', 'class': 'list[string]'}, 'pod-configuration': {'module': 'container_engine', 'class': 'PodConfiguration'}, 'freeform-tags': {'module': 'container_engine', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'container_engine', 'class': 'dict(str, dict(str, object))'}, 'virtual-node-tags': {'module': 'container_engine', 'class': 'VirtualNodeTags'}, 'virtual-node-pool-cycling-details': {'module': 'container_engine', 'class': 'VirtualNodePoolCyclingDetails'}}) @cli_util.wrap_exceptions -def create_virtual_node_pool(ctx, from_json, wait_for_state, max_wait_seconds, wait_interval_seconds, compartment_id, cluster_id, display_name, size, placement_configurations, pod_configuration, initial_virtual_node_labels, taints, nsg_ids, freeform_tags, defined_tags, virtual_node_tags): +def create_virtual_node_pool(ctx, from_json, wait_for_state, max_wait_seconds, wait_interval_seconds, compartment_id, cluster_id, display_name, size, placement_configurations, pod_configuration, initial_virtual_node_labels, taints, nsg_ids, freeform_tags, defined_tags, virtual_node_tags, virtual_node_pool_cycling_details): kwargs = {} kwargs['opc_request_id'] = cli_util.use_or_generate_request_id(ctx.obj['request_id']) @@ -714,6 +715,9 @@ def create_virtual_node_pool(ctx, from_json, wait_for_state, max_wait_seconds, w if virtual_node_tags is not None: _details['virtualNodeTags'] = cli_util.parse_json_parameter("virtual_node_tags", virtual_node_tags) + if virtual_node_pool_cycling_details is not None: + _details['virtualNodePoolCyclingDetails'] = cli_util.parse_json_parameter("virtual_node_pool_cycling_details", virtual_node_pool_cycling_details) + client = cli_util.build_client('container_engine', 'container_engine', ctx) result = client.create_virtual_node_pool( create_virtual_node_pool_details=_details, @@ -3048,23 +3052,24 @@ def update_node_pool_node_source_via_image_details(ctx, from_json, force, wait_f @cli_util.option('--freeform-tags', type=custom_types.CLI_COMPLEX_TYPE, help=u"""Free-form tags for this resource. Each tag is a simple key-value pair with no predefined name, type, or namespace. For more information, see [Resource Tags]. Example: `{\"Department\": \"Finance\"}`""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) @cli_util.option('--defined-tags', type=custom_types.CLI_COMPLEX_TYPE, help=u"""Defined tags for this resource. Each key is predefined and scoped to a namespace. For more information, see [Resource Tags]. Example: `{\"Operations\": {\"CostCenter\": \"42\"}}`""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) @cli_util.option('--virtual-node-tags', type=custom_types.CLI_COMPLEX_TYPE, help=u"""""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) +@cli_util.option('--virtual-node-pool-cycling-details', type=custom_types.CLI_COMPLEX_TYPE, help=u"""""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) @cli_util.option('--if-match', help=u"""For optimistic concurrency control. In the PUT or DELETE call for a resource, set the `if-match` parameter to the value of the etag from a previous GET or POST response for that resource. The resource will be updated or deleted only if the etag you provide matches the resource's current etag value.""") @cli_util.option('--force', help="""Perform update without prompting for confirmation.""", is_flag=True) @cli_util.option('--wait-for-state', type=custom_types.CliCaseInsensitiveChoice(["ACCEPTED", "IN_PROGRESS", "FAILED", "SUCCEEDED", "CANCELING", "CANCELED"]), multiple=True, help="""This operation asynchronously creates, modifies or deletes a resource and uses a work request to track the progress of the operation. Specify this option to perform the action and then wait until the work request reaches a certain state. Multiple states can be specified, returning on the first state. For example, --wait-for-state ACCEPTED --wait-for-state CANCELED would return on whichever lifecycle state is reached first. If timeout is reached, a return code of 2 is returned. For any other error, a return code of 1 is returned.""") @cli_util.option('--max-wait-seconds', type=click.INT, help="""The maximum time to wait for the work request to reach the state defined by --wait-for-state. Defaults to 1200 seconds.""") @cli_util.option('--wait-interval-seconds', type=click.INT, help="""Check every --wait-interval-seconds to see whether the work request has reached the state defined by --wait-for-state. Defaults to 30 seconds.""") -@json_skeleton_utils.get_cli_json_input_option({'initial-virtual-node-labels': {'module': 'container_engine', 'class': 'list[InitialVirtualNodeLabel]'}, 'taints': {'module': 'container_engine', 'class': 'list[Taint]'}, 'placement-configurations': {'module': 'container_engine', 'class': 'list[PlacementConfiguration]'}, 'nsg-ids': {'module': 'container_engine', 'class': 'list[string]'}, 'pod-configuration': {'module': 'container_engine', 'class': 'PodConfiguration'}, 'freeform-tags': {'module': 'container_engine', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'container_engine', 'class': 'dict(str, dict(str, object))'}, 'virtual-node-tags': {'module': 'container_engine', 'class': 'VirtualNodeTags'}}) +@json_skeleton_utils.get_cli_json_input_option({'initial-virtual-node-labels': {'module': 'container_engine', 'class': 'list[InitialVirtualNodeLabel]'}, 'taints': {'module': 'container_engine', 'class': 'list[Taint]'}, 'placement-configurations': {'module': 'container_engine', 'class': 'list[PlacementConfiguration]'}, 'nsg-ids': {'module': 'container_engine', 'class': 'list[string]'}, 'pod-configuration': {'module': 'container_engine', 'class': 'PodConfiguration'}, 'freeform-tags': {'module': 'container_engine', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'container_engine', 'class': 'dict(str, dict(str, object))'}, 'virtual-node-tags': {'module': 'container_engine', 'class': 'VirtualNodeTags'}, 'virtual-node-pool-cycling-details': {'module': 'container_engine', 'class': 'VirtualNodePoolCyclingDetails'}}) @cli_util.help_option @click.pass_context -@json_skeleton_utils.json_skeleton_generation_handler(input_params_to_complex_types={'initial-virtual-node-labels': {'module': 'container_engine', 'class': 'list[InitialVirtualNodeLabel]'}, 'taints': {'module': 'container_engine', 'class': 'list[Taint]'}, 'placement-configurations': {'module': 'container_engine', 'class': 'list[PlacementConfiguration]'}, 'nsg-ids': {'module': 'container_engine', 'class': 'list[string]'}, 'pod-configuration': {'module': 'container_engine', 'class': 'PodConfiguration'}, 'freeform-tags': {'module': 'container_engine', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'container_engine', 'class': 'dict(str, dict(str, object))'}, 'virtual-node-tags': {'module': 'container_engine', 'class': 'VirtualNodeTags'}}) +@json_skeleton_utils.json_skeleton_generation_handler(input_params_to_complex_types={'initial-virtual-node-labels': {'module': 'container_engine', 'class': 'list[InitialVirtualNodeLabel]'}, 'taints': {'module': 'container_engine', 'class': 'list[Taint]'}, 'placement-configurations': {'module': 'container_engine', 'class': 'list[PlacementConfiguration]'}, 'nsg-ids': {'module': 'container_engine', 'class': 'list[string]'}, 'pod-configuration': {'module': 'container_engine', 'class': 'PodConfiguration'}, 'freeform-tags': {'module': 'container_engine', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'container_engine', 'class': 'dict(str, dict(str, object))'}, 'virtual-node-tags': {'module': 'container_engine', 'class': 'VirtualNodeTags'}, 'virtual-node-pool-cycling-details': {'module': 'container_engine', 'class': 'VirtualNodePoolCyclingDetails'}}) @cli_util.wrap_exceptions -def update_virtual_node_pool(ctx, from_json, force, wait_for_state, max_wait_seconds, wait_interval_seconds, virtual_node_pool_id, display_name, initial_virtual_node_labels, taints, size, placement_configurations, nsg_ids, pod_configuration, freeform_tags, defined_tags, virtual_node_tags, if_match): +def update_virtual_node_pool(ctx, from_json, force, wait_for_state, max_wait_seconds, wait_interval_seconds, virtual_node_pool_id, display_name, initial_virtual_node_labels, taints, size, placement_configurations, nsg_ids, pod_configuration, freeform_tags, defined_tags, virtual_node_tags, virtual_node_pool_cycling_details, if_match): if isinstance(virtual_node_pool_id, six.string_types) and len(virtual_node_pool_id.strip()) == 0: raise click.UsageError('Parameter --virtual-node-pool-id cannot be whitespace or empty string') if not force: - if initial_virtual_node_labels or taints or placement_configurations or nsg_ids or pod_configuration or freeform_tags or defined_tags or virtual_node_tags: - if not click.confirm("WARNING: Updates to initial-virtual-node-labels and taints and placement-configurations and nsg-ids and pod-configuration and freeform-tags and defined-tags and virtual-node-tags will replace any existing values. Are you sure you want to continue?"): + if initial_virtual_node_labels or taints or placement_configurations or nsg_ids or pod_configuration or freeform_tags or defined_tags or virtual_node_tags or virtual_node_pool_cycling_details: + if not click.confirm("WARNING: Updates to initial-virtual-node-labels and taints and placement-configurations and nsg-ids and pod-configuration and freeform-tags and defined-tags and virtual-node-tags and virtual-node-pool-cycling-details will replace any existing values. Are you sure you want to continue?"): ctx.abort() kwargs = {} @@ -3104,6 +3109,9 @@ def update_virtual_node_pool(ctx, from_json, force, wait_for_state, max_wait_sec if virtual_node_tags is not None: _details['virtualNodeTags'] = cli_util.parse_json_parameter("virtual_node_tags", virtual_node_tags) + if virtual_node_pool_cycling_details is not None: + _details['virtualNodePoolCyclingDetails'] = cli_util.parse_json_parameter("virtual_node_pool_cycling_details", virtual_node_pool_cycling_details) + client = cli_util.build_client('container_engine', 'container_engine', ctx) result = client.update_virtual_node_pool( virtual_node_pool_id=virtual_node_pool_id, diff --git a/services/dns/src/oci_cli_dns/generated/dns_cli.py b/services/dns/src/oci_cli_dns/generated/dns_cli.py index eb8e69608..7a8819ad4 100644 --- a/services/dns/src/oci_cli_dns/generated/dns_cli.py +++ b/services/dns/src/oci_cli_dns/generated/dns_cli.py @@ -445,16 +445,18 @@ def change_zone_compartment(ctx, from_json, wait_for_state, max_wait_seconds, wa @cli_util.option('--endpoint-type', type=custom_types.CliCaseInsensitiveChoice(["VNIC"]), help=u"""The type of resolver endpoint. VNIC is currently the only supported type.""") @cli_util.option('--forwarding-address', help=u"""An IP address from which forwarded queries may be sent. For VNIC endpoints, this IP address must be part of the subnet and will be assigned by the system if unspecified when isForwarding is true.""") @cli_util.option('--listening-address', help=u"""An IP address to listen to queries on. For VNIC endpoints this IP address must be part of the subnet and will be assigned by the system if unspecified when isListening is true.""") +@cli_util.option('--freeform-tags', type=custom_types.CLI_COMPLEX_TYPE, help=u"""""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) +@cli_util.option('--defined-tags', type=custom_types.CLI_COMPLEX_TYPE, help=u"""""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) @cli_util.option('--scope', type=custom_types.CliCaseInsensitiveChoice(["GLOBAL", "PRIVATE"]), help=u"""Specifies to operate only on resources that have a matching DNS scope.""") @cli_util.option('--wait-for-state', type=custom_types.CliCaseInsensitiveChoice(["ACTIVE", "CREATING", "DELETED", "DELETING", "FAILED", "UPDATING"]), multiple=True, help="""This operation creates, modifies or deletes a resource that has a defined lifecycle state. Specify this option to perform the action and then wait until the resource reaches a given lifecycle state. Multiple states can be specified, returning on the first state. For example, --wait-for-state ACTIVE --wait-for-state UPDATING would return on whichever lifecycle state is reached first. If timeout is reached, a return code of 2 is returned. For any other error, a return code of 1 is returned.""") @cli_util.option('--max-wait-seconds', type=click.INT, help="""The maximum time to wait for the resource to reach the lifecycle state defined by --wait-for-state. Defaults to 1200 seconds.""") @cli_util.option('--wait-interval-seconds', type=click.INT, help="""Check every --wait-interval-seconds to see whether the resource has reached the lifecycle state defined by --wait-for-state. Defaults to 30 seconds.""") -@json_skeleton_utils.get_cli_json_input_option({}) +@json_skeleton_utils.get_cli_json_input_option({'freeform-tags': {'module': 'dns', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'dns', 'class': 'dict(str, dict(str, object))'}}) @cli_util.help_option @click.pass_context -@json_skeleton_utils.json_skeleton_generation_handler(input_params_to_complex_types={}, output_type={'module': 'dns', 'class': 'ResolverEndpoint'}) +@json_skeleton_utils.json_skeleton_generation_handler(input_params_to_complex_types={'freeform-tags': {'module': 'dns', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'dns', 'class': 'dict(str, dict(str, object))'}}, output_type={'module': 'dns', 'class': 'ResolverEndpoint'}) @cli_util.wrap_exceptions -def create_resolver_endpoint(ctx, from_json, wait_for_state, max_wait_seconds, wait_interval_seconds, resolver_id, name, is_forwarding, is_listening, endpoint_type, forwarding_address, listening_address, scope): +def create_resolver_endpoint(ctx, from_json, wait_for_state, max_wait_seconds, wait_interval_seconds, resolver_id, name, is_forwarding, is_listening, endpoint_type, forwarding_address, listening_address, freeform_tags, defined_tags, scope): if isinstance(resolver_id, six.string_types) and len(resolver_id.strip()) == 0: raise click.UsageError('Parameter --resolver-id cannot be whitespace or empty string') @@ -478,6 +480,12 @@ def create_resolver_endpoint(ctx, from_json, wait_for_state, max_wait_seconds, w if listening_address is not None: _details['listeningAddress'] = listening_address + if freeform_tags is not None: + _details['freeformTags'] = cli_util.parse_json_parameter("freeform_tags", freeform_tags) + + if defined_tags is not None: + _details['definedTags'] = cli_util.parse_json_parameter("defined_tags", defined_tags) + client = cli_util.build_client('dns', 'dns', ctx) result = client.create_resolver_endpoint( resolver_id=resolver_id, @@ -518,17 +526,20 @@ def create_resolver_endpoint(ctx, from_json, wait_for_state, max_wait_seconds, w @cli_util.option('--subnet-id', required=True, help=u"""The OCID of a subnet. Must be part of the VCN that the resolver is attached to.""") @cli_util.option('--forwarding-address', help=u"""An IP address from which forwarded queries may be sent. For VNIC endpoints, this IP address must be part of the subnet and will be assigned by the system if unspecified when isForwarding is true.""") @cli_util.option('--listening-address', help=u"""An IP address to listen to queries on. For VNIC endpoints this IP address must be part of the subnet and will be assigned by the system if unspecified when isListening is true.""") +@cli_util.option('--freeform-tags', type=custom_types.CLI_COMPLEX_TYPE, help=u"""""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) +@cli_util.option('--defined-tags', type=custom_types.CLI_COMPLEX_TYPE, help=u"""""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) @cli_util.option('--nsg-ids', type=custom_types.CLI_COMPLEX_TYPE, help=u"""An array of network security group OCIDs for the resolver endpoint. These must be part of the VCN that the resolver endpoint is a part of.""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) +@cli_util.option('--security-attributes', type=custom_types.CLI_COMPLEX_TYPE, help=u"""""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) @cli_util.option('--scope', type=custom_types.CliCaseInsensitiveChoice(["GLOBAL", "PRIVATE"]), help=u"""Specifies to operate only on resources that have a matching DNS scope.""") @cli_util.option('--wait-for-state', type=custom_types.CliCaseInsensitiveChoice(["ACTIVE", "CREATING", "DELETED", "DELETING", "FAILED", "UPDATING"]), multiple=True, help="""This operation creates, modifies or deletes a resource that has a defined lifecycle state. Specify this option to perform the action and then wait until the resource reaches a given lifecycle state. Multiple states can be specified, returning on the first state. For example, --wait-for-state ACTIVE --wait-for-state UPDATING would return on whichever lifecycle state is reached first. If timeout is reached, a return code of 2 is returned. For any other error, a return code of 1 is returned.""") @cli_util.option('--max-wait-seconds', type=click.INT, help="""The maximum time to wait for the resource to reach the lifecycle state defined by --wait-for-state. Defaults to 1200 seconds.""") @cli_util.option('--wait-interval-seconds', type=click.INT, help="""Check every --wait-interval-seconds to see whether the resource has reached the lifecycle state defined by --wait-for-state. Defaults to 30 seconds.""") -@json_skeleton_utils.get_cli_json_input_option({'nsg-ids': {'module': 'dns', 'class': 'list[string]'}}) +@json_skeleton_utils.get_cli_json_input_option({'freeform-tags': {'module': 'dns', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'dns', 'class': 'dict(str, dict(str, object))'}, 'nsg-ids': {'module': 'dns', 'class': 'list[string]'}, 'security-attributes': {'module': 'dns', 'class': 'dict(str, dict(str, object))'}}) @cli_util.help_option @click.pass_context -@json_skeleton_utils.json_skeleton_generation_handler(input_params_to_complex_types={'nsg-ids': {'module': 'dns', 'class': 'list[string]'}}, output_type={'module': 'dns', 'class': 'ResolverEndpoint'}) +@json_skeleton_utils.json_skeleton_generation_handler(input_params_to_complex_types={'freeform-tags': {'module': 'dns', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'dns', 'class': 'dict(str, dict(str, object))'}, 'nsg-ids': {'module': 'dns', 'class': 'list[string]'}, 'security-attributes': {'module': 'dns', 'class': 'dict(str, dict(str, object))'}}, output_type={'module': 'dns', 'class': 'ResolverEndpoint'}) @cli_util.wrap_exceptions -def create_resolver_endpoint_create_resolver_vnic_endpoint_details(ctx, from_json, wait_for_state, max_wait_seconds, wait_interval_seconds, resolver_id, name, is_forwarding, is_listening, subnet_id, forwarding_address, listening_address, nsg_ids, scope): +def create_resolver_endpoint_create_resolver_vnic_endpoint_details(ctx, from_json, wait_for_state, max_wait_seconds, wait_interval_seconds, resolver_id, name, is_forwarding, is_listening, subnet_id, forwarding_address, listening_address, freeform_tags, defined_tags, nsg_ids, security_attributes, scope): if isinstance(resolver_id, six.string_types) and len(resolver_id.strip()) == 0: raise click.UsageError('Parameter --resolver-id cannot be whitespace or empty string') @@ -550,9 +561,18 @@ def create_resolver_endpoint_create_resolver_vnic_endpoint_details(ctx, from_jso if listening_address is not None: _details['listeningAddress'] = listening_address + if freeform_tags is not None: + _details['freeformTags'] = cli_util.parse_json_parameter("freeform_tags", freeform_tags) + + if defined_tags is not None: + _details['definedTags'] = cli_util.parse_json_parameter("defined_tags", defined_tags) + if nsg_ids is not None: _details['nsgIds'] = cli_util.parse_json_parameter("nsg_ids", nsg_ids) + if security_attributes is not None: + _details['securityAttributes'] = cli_util.parse_json_parameter("security_attributes", security_attributes) + _details['endpointType'] = 'VNIC' client = cli_util.build_client('dns', 'dns', ctx) @@ -630,7 +650,7 @@ def create_resolver_endpoint_create_resolver_vnic_endpoint_details(ctx, from_jso This option is a JSON list with items of type SteeringPolicyRule. For documentation on SteeringPolicyRule please see our API reference: https://docs.oracle.com/en-us/iaas/api/#/en/dns/20180115/datatypes/SteeringPolicyRule.""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) @cli_util.option('--scope', type=custom_types.CliCaseInsensitiveChoice(["GLOBAL", "PRIVATE"]), help=u"""Specifies to operate only on resources that have a matching DNS scope.""") -@cli_util.option('--wait-for-state', type=custom_types.CliCaseInsensitiveChoice(["ACTIVE", "CREATING", "DELETED", "DELETING"]), multiple=True, help="""This operation creates, modifies or deletes a resource that has a defined lifecycle state. Specify this option to perform the action and then wait until the resource reaches a given lifecycle state. Multiple states can be specified, returning on the first state. For example, --wait-for-state ACTIVE --wait-for-state DELETING would return on whichever lifecycle state is reached first. If timeout is reached, a return code of 2 is returned. For any other error, a return code of 1 is returned.""") +@cli_util.option('--wait-for-state', type=custom_types.CliCaseInsensitiveChoice(["ACTIVE", "CREATING", "UPDATING", "DELETED", "DELETING"]), multiple=True, help="""This operation creates, modifies or deletes a resource that has a defined lifecycle state. Specify this option to perform the action and then wait until the resource reaches a given lifecycle state. Multiple states can be specified, returning on the first state. For example, --wait-for-state ACTIVE --wait-for-state DELETING would return on whichever lifecycle state is reached first. If timeout is reached, a return code of 2 is returned. For any other error, a return code of 1 is returned.""") @cli_util.option('--max-wait-seconds', type=click.INT, help="""The maximum time to wait for the resource to reach the lifecycle state defined by --wait-for-state. Defaults to 1200 seconds.""") @cli_util.option('--wait-interval-seconds', type=click.INT, help="""Check every --wait-interval-seconds to see whether the resource has reached the lifecycle state defined by --wait-for-state. Defaults to 30 seconds.""") @json_skeleton_utils.get_cli_json_input_option({'freeform-tags': {'module': 'dns', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'dns', 'class': 'dict(str, dict(str, object))'}, 'answers': {'module': 'dns', 'class': 'list[SteeringPolicyAnswer]'}, 'rules': {'module': 'dns', 'class': 'list[SteeringPolicyRule]'}}) @@ -1005,7 +1025,7 @@ def create_zone(ctx, from_json, wait_for_state, max_wait_seconds, wait_interval_ @cli_util.option('--external-downstreams', type=custom_types.CLI_COMPLEX_TYPE, help=u"""External secondary servers for the zone. This field is currently not supported when `zoneType` is `SECONDARY` or `scope` is `PRIVATE`. This option is a JSON list with items of type ExternalDownstream. For documentation on ExternalDownstream please see our API reference: https://docs.oracle.com/en-us/iaas/api/#/en/dns/20180115/datatypes/ExternalDownstream.""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) -@cli_util.option('--resolution-mode', type=custom_types.CliCaseInsensitiveChoice(["STATIC", "TRANSPARENT", "RTYPE_TRANSPARENT"]), help=u"""The resolution mode of a zone defines behavior related to how query responses can be handled.""") +@cli_util.option('--resolution-mode', type=custom_types.CliCaseInsensitiveChoice(["STATIC", "TRANSPARENT", "RTYPE_TRANSPARENT"]), help=u"""The resolution mode of a zone defines behavior related to how query responses can be handled. See [Private DNS Zone Transparency] for more information.""") @cli_util.option('--dnssec-state', type=custom_types.CliCaseInsensitiveChoice(["ENABLED", "DISABLED"]), help=u"""The state of DNSSEC on the zone. For DNSSEC to function, every parent zone in the DNS tree up to the top-level domain (or an independent trust anchor) must also have DNSSEC correctly set up. After enabling DNSSEC, you must add a DS record to the zone's parent zone containing the `KskDnssecKeyVersion` data. You can find the DS data in the `dsData` attribute of the `KskDnssecKeyVersion`. Then, use the `PromoteZoneDnssecKeyVersion` operation to promote the `KskDnssecKeyVersion`. @@ -2425,7 +2445,7 @@ def list_resolvers(ctx, from_json, all_pages, page_size, compartment_id, display @cli_util.option('--time-created-greater-than-or-equal-to', type=custom_types.CLI_DATETIME, help=u"""An [RFC 3339] timestamp that states all returned resources were created on or after the indicated time.""" + custom_types.CLI_DATETIME.VALID_DATETIME_CLI_HELP_MESSAGE) @cli_util.option('--time-created-less-than', type=custom_types.CLI_DATETIME, help=u"""An [RFC 3339] timestamp that states all returned resources were created before the indicated time.""" + custom_types.CLI_DATETIME.VALID_DATETIME_CLI_HELP_MESSAGE) @cli_util.option('--template', help=u"""Search by steering template type. Will match any resource whose template type matches the provided value.""") -@cli_util.option('--lifecycle-state', type=custom_types.CliCaseInsensitiveChoice(["ACTIVE", "CREATING", "DELETED", "DELETING"]), help=u"""The state of a resource.""") +@cli_util.option('--lifecycle-state', type=custom_types.CliCaseInsensitiveChoice(["ACTIVE", "CREATING", "UPDATING", "DELETED", "DELETING"]), help=u"""The state of a resource.""") @cli_util.option('--sort-by', type=custom_types.CliCaseInsensitiveChoice(["displayName", "timeCreated", "template"]), help=u"""The field by which to sort steering policies. If unspecified, defaults to `timeCreated`.""") @cli_util.option('--sort-order', type=custom_types.CliCaseInsensitiveChoice(["ASC", "DESC"]), help=u"""The order to sort the resources.""") @cli_util.option('--scope', type=custom_types.CliCaseInsensitiveChoice(["GLOBAL", "PRIVATE"]), help=u"""Specifies to operate only on resources that have a matching DNS scope.""") @@ -3317,24 +3337,31 @@ def update_resolver(ctx, from_json, force, wait_for_state, max_wait_seconds, wai @cli_util.option('--resolver-id', required=True, help=u"""The OCID of the target resolver.""") @cli_util.option('--resolver-endpoint-name', required=True, help=u"""The name of the target resolver endpoint.""") @cli_util.option('--endpoint-type', type=custom_types.CliCaseInsensitiveChoice(["VNIC"]), help=u"""The type of resolver endpoint. VNIC is currently the only supported type.""") +@cli_util.option('--freeform-tags', type=custom_types.CLI_COMPLEX_TYPE, help=u"""""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) +@cli_util.option('--defined-tags', type=custom_types.CLI_COMPLEX_TYPE, help=u"""""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) @cli_util.option('--if-match', help=u"""The `If-Match` header field makes the request method conditional on the existence of at least one current representation of the target resource, when the field-value is `*`, or having a current representation of the target resource that has an entity-tag matching a member of the list of entity-tags provided in the field-value.""") @cli_util.option('--if-unmodified-since', help=u"""The `If-Unmodified-Since` header field makes the request method conditional on the selected representation's last modification date being earlier than or equal to the date provided in the field-value. This field accomplishes the same purpose as If-Match for cases where the user agent does not have an entity-tag for the representation.""") @cli_util.option('--scope', type=custom_types.CliCaseInsensitiveChoice(["GLOBAL", "PRIVATE"]), help=u"""Specifies to operate only on resources that have a matching DNS scope.""") +@cli_util.option('--force', help="""Perform update without prompting for confirmation.""", is_flag=True) @cli_util.option('--wait-for-state', type=custom_types.CliCaseInsensitiveChoice(["ACTIVE", "CREATING", "DELETED", "DELETING", "FAILED", "UPDATING"]), multiple=True, help="""This operation creates, modifies or deletes a resource that has a defined lifecycle state. Specify this option to perform the action and then wait until the resource reaches a given lifecycle state. Multiple states can be specified, returning on the first state. For example, --wait-for-state ACTIVE --wait-for-state UPDATING would return on whichever lifecycle state is reached first. If timeout is reached, a return code of 2 is returned. For any other error, a return code of 1 is returned.""") @cli_util.option('--max-wait-seconds', type=click.INT, help="""The maximum time to wait for the resource to reach the lifecycle state defined by --wait-for-state. Defaults to 1200 seconds.""") @cli_util.option('--wait-interval-seconds', type=click.INT, help="""Check every --wait-interval-seconds to see whether the resource has reached the lifecycle state defined by --wait-for-state. Defaults to 30 seconds.""") -@json_skeleton_utils.get_cli_json_input_option({}) +@json_skeleton_utils.get_cli_json_input_option({'freeform-tags': {'module': 'dns', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'dns', 'class': 'dict(str, dict(str, object))'}}) @cli_util.help_option @click.pass_context -@json_skeleton_utils.json_skeleton_generation_handler(input_params_to_complex_types={}, output_type={'module': 'dns', 'class': 'ResolverEndpoint'}) +@json_skeleton_utils.json_skeleton_generation_handler(input_params_to_complex_types={'freeform-tags': {'module': 'dns', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'dns', 'class': 'dict(str, dict(str, object))'}}, output_type={'module': 'dns', 'class': 'ResolverEndpoint'}) @cli_util.wrap_exceptions -def update_resolver_endpoint(ctx, from_json, wait_for_state, max_wait_seconds, wait_interval_seconds, resolver_id, resolver_endpoint_name, endpoint_type, if_match, if_unmodified_since, scope): +def update_resolver_endpoint(ctx, from_json, force, wait_for_state, max_wait_seconds, wait_interval_seconds, resolver_id, resolver_endpoint_name, endpoint_type, freeform_tags, defined_tags, if_match, if_unmodified_since, scope): if isinstance(resolver_id, six.string_types) and len(resolver_id.strip()) == 0: raise click.UsageError('Parameter --resolver-id cannot be whitespace or empty string') if isinstance(resolver_endpoint_name, six.string_types) and len(resolver_endpoint_name.strip()) == 0: raise click.UsageError('Parameter --resolver-endpoint-name cannot be whitespace or empty string') + if not force: + if freeform_tags or defined_tags: + if not click.confirm("WARNING: Updates to freeform-tags and defined-tags will replace any existing values. Are you sure you want to continue?"): + ctx.abort() kwargs = {} if if_match is not None: @@ -3350,6 +3377,12 @@ def update_resolver_endpoint(ctx, from_json, wait_for_state, max_wait_seconds, w if endpoint_type is not None: _details['endpointType'] = endpoint_type + if freeform_tags is not None: + _details['freeformTags'] = cli_util.parse_json_parameter("freeform_tags", freeform_tags) + + if defined_tags is not None: + _details['definedTags'] = cli_util.parse_json_parameter("defined_tags", defined_tags) + client = cli_util.build_client('dns', 'dns', ctx) result = client.update_resolver_endpoint( resolver_id=resolver_id, @@ -3386,7 +3419,10 @@ def update_resolver_endpoint(ctx, from_json, wait_for_state, max_wait_seconds, w @resolver_endpoint_group.command(name=cli_util.override('dns.update_resolver_endpoint_update_resolver_vnic_endpoint_details.command_name', 'update-resolver-endpoint-update-resolver-vnic-endpoint-details'), help=u"""Updates the specified resolver endpoint with your new information. \n[Command Reference](updateResolverEndpoint)""") @cli_util.option('--resolver-id', required=True, help=u"""The OCID of the target resolver.""") @cli_util.option('--resolver-endpoint-name', required=True, help=u"""The name of the target resolver endpoint.""") +@cli_util.option('--freeform-tags', type=custom_types.CLI_COMPLEX_TYPE, help=u"""""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) +@cli_util.option('--defined-tags', type=custom_types.CLI_COMPLEX_TYPE, help=u"""""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) @cli_util.option('--nsg-ids', type=custom_types.CLI_COMPLEX_TYPE, help=u"""An array of network security group OCIDs for the resolver endpoint. These must be part of the VCN that the resolver endpoint is a part of.""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) +@cli_util.option('--security-attributes', type=custom_types.CLI_COMPLEX_TYPE, help=u"""""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) @cli_util.option('--if-match', help=u"""The `If-Match` header field makes the request method conditional on the existence of at least one current representation of the target resource, when the field-value is `*`, or having a current representation of the target resource that has an entity-tag matching a member of the list of entity-tags provided in the field-value.""") @cli_util.option('--if-unmodified-since', help=u"""The `If-Unmodified-Since` header field makes the request method conditional on the selected representation's last modification date being earlier than or equal to the date provided in the field-value. This field accomplishes the same purpose as If-Match for cases where the user agent does not have an entity-tag for the representation.""") @cli_util.option('--scope', type=custom_types.CliCaseInsensitiveChoice(["GLOBAL", "PRIVATE"]), help=u"""Specifies to operate only on resources that have a matching DNS scope.""") @@ -3394,12 +3430,12 @@ def update_resolver_endpoint(ctx, from_json, wait_for_state, max_wait_seconds, w @cli_util.option('--wait-for-state', type=custom_types.CliCaseInsensitiveChoice(["ACTIVE", "CREATING", "DELETED", "DELETING", "FAILED", "UPDATING"]), multiple=True, help="""This operation creates, modifies or deletes a resource that has a defined lifecycle state. Specify this option to perform the action and then wait until the resource reaches a given lifecycle state. Multiple states can be specified, returning on the first state. For example, --wait-for-state ACTIVE --wait-for-state UPDATING would return on whichever lifecycle state is reached first. If timeout is reached, a return code of 2 is returned. For any other error, a return code of 1 is returned.""") @cli_util.option('--max-wait-seconds', type=click.INT, help="""The maximum time to wait for the resource to reach the lifecycle state defined by --wait-for-state. Defaults to 1200 seconds.""") @cli_util.option('--wait-interval-seconds', type=click.INT, help="""Check every --wait-interval-seconds to see whether the resource has reached the lifecycle state defined by --wait-for-state. Defaults to 30 seconds.""") -@json_skeleton_utils.get_cli_json_input_option({'nsg-ids': {'module': 'dns', 'class': 'list[string]'}}) +@json_skeleton_utils.get_cli_json_input_option({'freeform-tags': {'module': 'dns', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'dns', 'class': 'dict(str, dict(str, object))'}, 'nsg-ids': {'module': 'dns', 'class': 'list[string]'}, 'security-attributes': {'module': 'dns', 'class': 'dict(str, dict(str, object))'}}) @cli_util.help_option @click.pass_context -@json_skeleton_utils.json_skeleton_generation_handler(input_params_to_complex_types={'nsg-ids': {'module': 'dns', 'class': 'list[string]'}}, output_type={'module': 'dns', 'class': 'ResolverEndpoint'}) +@json_skeleton_utils.json_skeleton_generation_handler(input_params_to_complex_types={'freeform-tags': {'module': 'dns', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'dns', 'class': 'dict(str, dict(str, object))'}, 'nsg-ids': {'module': 'dns', 'class': 'list[string]'}, 'security-attributes': {'module': 'dns', 'class': 'dict(str, dict(str, object))'}}, output_type={'module': 'dns', 'class': 'ResolverEndpoint'}) @cli_util.wrap_exceptions -def update_resolver_endpoint_update_resolver_vnic_endpoint_details(ctx, from_json, force, wait_for_state, max_wait_seconds, wait_interval_seconds, resolver_id, resolver_endpoint_name, nsg_ids, if_match, if_unmodified_since, scope): +def update_resolver_endpoint_update_resolver_vnic_endpoint_details(ctx, from_json, force, wait_for_state, max_wait_seconds, wait_interval_seconds, resolver_id, resolver_endpoint_name, freeform_tags, defined_tags, nsg_ids, security_attributes, if_match, if_unmodified_since, scope): if isinstance(resolver_id, six.string_types) and len(resolver_id.strip()) == 0: raise click.UsageError('Parameter --resolver-id cannot be whitespace or empty string') @@ -3407,8 +3443,8 @@ def update_resolver_endpoint_update_resolver_vnic_endpoint_details(ctx, from_jso if isinstance(resolver_endpoint_name, six.string_types) and len(resolver_endpoint_name.strip()) == 0: raise click.UsageError('Parameter --resolver-endpoint-name cannot be whitespace or empty string') if not force: - if nsg_ids: - if not click.confirm("WARNING: Updates to nsg-ids will replace any existing values. Are you sure you want to continue?"): + if freeform_tags or defined_tags or nsg_ids or security_attributes: + if not click.confirm("WARNING: Updates to freeform-tags and defined-tags and nsg-ids and security-attributes will replace any existing values. Are you sure you want to continue?"): ctx.abort() kwargs = {} @@ -3422,9 +3458,18 @@ def update_resolver_endpoint_update_resolver_vnic_endpoint_details(ctx, from_jso _details = {} + if freeform_tags is not None: + _details['freeformTags'] = cli_util.parse_json_parameter("freeform_tags", freeform_tags) + + if defined_tags is not None: + _details['definedTags'] = cli_util.parse_json_parameter("defined_tags", defined_tags) + if nsg_ids is not None: _details['nsgIds'] = cli_util.parse_json_parameter("nsg_ids", nsg_ids) + if security_attributes is not None: + _details['securityAttributes'] = cli_util.parse_json_parameter("security_attributes", security_attributes) + _details['endpointType'] = 'VNIC' client = cli_util.build_client('dns', 'dns', ctx) @@ -3572,7 +3617,7 @@ def update_rr_set(ctx, from_json, force, zone_name_or_id, domain, rtype, items, @cli_util.option('--if-unmodified-since', help=u"""The `If-Unmodified-Since` header field makes the request method conditional on the selected representation's last modification date being earlier than or equal to the date provided in the field-value. This field accomplishes the same purpose as If-Match for cases where the user agent does not have an entity-tag for the representation.""") @cli_util.option('--scope', type=custom_types.CliCaseInsensitiveChoice(["GLOBAL", "PRIVATE"]), help=u"""Specifies to operate only on resources that have a matching DNS scope.""") @cli_util.option('--force', help="""Perform update without prompting for confirmation.""", is_flag=True) -@cli_util.option('--wait-for-state', type=custom_types.CliCaseInsensitiveChoice(["ACTIVE", "CREATING", "DELETED", "DELETING"]), multiple=True, help="""This operation creates, modifies or deletes a resource that has a defined lifecycle state. Specify this option to perform the action and then wait until the resource reaches a given lifecycle state. Multiple states can be specified, returning on the first state. For example, --wait-for-state ACTIVE --wait-for-state DELETING would return on whichever lifecycle state is reached first. If timeout is reached, a return code of 2 is returned. For any other error, a return code of 1 is returned.""") +@cli_util.option('--wait-for-state', type=custom_types.CliCaseInsensitiveChoice(["ACTIVE", "CREATING", "UPDATING", "DELETED", "DELETING"]), multiple=True, help="""This operation creates, modifies or deletes a resource that has a defined lifecycle state. Specify this option to perform the action and then wait until the resource reaches a given lifecycle state. Multiple states can be specified, returning on the first state. For example, --wait-for-state ACTIVE --wait-for-state DELETING would return on whichever lifecycle state is reached first. If timeout is reached, a return code of 2 is returned. For any other error, a return code of 1 is returned.""") @cli_util.option('--max-wait-seconds', type=click.INT, help="""The maximum time to wait for the resource to reach the lifecycle state defined by --wait-for-state. Defaults to 1200 seconds.""") @cli_util.option('--wait-interval-seconds', type=click.INT, help="""Check every --wait-interval-seconds to see whether the resource has reached the lifecycle state defined by --wait-for-state. Defaults to 30 seconds.""") @json_skeleton_utils.get_cli_json_input_option({'freeform-tags': {'module': 'dns', 'class': 'dict(str, string)'}, 'defined-tags': {'module': 'dns', 'class': 'dict(str, dict(str, object))'}, 'answers': {'module': 'dns', 'class': 'list[SteeringPolicyAnswer]'}, 'rules': {'module': 'dns', 'class': 'list[SteeringPolicyRule]'}}) @@ -3891,7 +3936,7 @@ def update_view(ctx, from_json, force, wait_for_state, max_wait_seconds, wait_in @cli_util.option('--defined-tags', type=custom_types.CLI_COMPLEX_TYPE, help=u"""Defined tags for this resource. Each key is predefined and scoped to a namespace. For more information, see [Resource Tags]. **Example:** `{\"Operations\": {\"CostCenter\": \"42\"}}`""" + custom_types.cli_complex_type.COMPLEX_TYPE_HELP) -@cli_util.option('--resolution-mode', type=custom_types.CliCaseInsensitiveChoice(["STATIC", "TRANSPARENT", "RTYPE_TRANSPARENT"]), help=u"""The resolution mode of a zone defines behavior related to how query responses can be handled.""") +@cli_util.option('--resolution-mode', type=custom_types.CliCaseInsensitiveChoice(["STATIC", "TRANSPARENT", "RTYPE_TRANSPARENT"]), help=u"""The resolution mode of a zone defines behavior related to how query responses can be handled. See [Private DNS Zone Transparency] for more information.""") @cli_util.option('--dnssec-state', type=custom_types.CliCaseInsensitiveChoice(["ENABLED", "DISABLED"]), help=u"""The state of DNSSEC on the zone. For DNSSEC to function, every parent zone in the DNS tree up to the top-level domain (or an independent trust anchor) must also have DNSSEC correctly set up. After enabling DNSSEC, you must add a DS record to the zone's parent zone containing the `KskDnssecKeyVersion` data. You can find the DS data in the `dsData` attribute of the `KskDnssecKeyVersion`. Then, use the `PromoteZoneDnssecKeyVersion` operation to promote the `KskDnssecKeyVersion`. diff --git a/services/integration/src/oci_cli_integration_instance/generated/integrationinstance_cli.py b/services/integration/src/oci_cli_integration_instance/generated/integrationinstance_cli.py index 2e2f5c92d..60588e617 100644 --- a/services/integration/src/oci_cli_integration_instance/generated/integrationinstance_cli.py +++ b/services/integration/src/oci_cli_integration_instance/generated/integrationinstance_cli.py @@ -55,6 +55,7 @@ def work_request_group(): @integration_instance_group.command(name=cli_util.override('integration.add_log_analytics_log_group.command_name', 'add'), help=u"""Add LogGroup with specified ocid for Integration Instance to enable sending OIC Activity Stream to OCI Logging Analytics. \n[Command Reference](addLogAnalyticsLogGroup)""") @cli_util.option('--integration-instance-id', required=True, help=u"""Unique Integration Instance identifier.""") @cli_util.option('--log-group-id', required=True, help=u"""Log Group ocid.""") +@cli_util.option('--attachment-type', type=custom_types.CliCaseInsensitiveChoice(["PROCESS_AUTOMATION"]), help=u"""Type of attachment. Supported at this include PROCESS_AUTOMATION""") @cli_util.option('--if-match', help=u"""For optimistic concurrency control. In the PUT or DELETE call for a resource, set the `if-match` parameter to the value of the etag from a previous GET or POST response for that resource. The resource will be updated or deleted only if the etag you provide matches the resource's current etag value.""") @cli_util.option('--wait-for-state', type=custom_types.CliCaseInsensitiveChoice(["ACCEPTED", "IN_PROGRESS", "FAILED", "SUCCEEDED", "CANCELING", "CANCELED"]), multiple=True, help="""This operation asynchronously creates, modifies or deletes a resource and uses a work request to track the progress of the operation. Specify this option to perform the action and then wait until the work request reaches a certain state. Multiple states can be specified, returning on the first state. For example, --wait-for-state ACCEPTED --wait-for-state CANCELED would return on whichever lifecycle state is reached first. If timeout is reached, a return code of 2 is returned. For any other error, a return code of 1 is returned.""") @cli_util.option('--max-wait-seconds', type=click.INT, help="""The maximum time to wait for the work request to reach the state defined by --wait-for-state. Defaults to 1200 seconds.""") @@ -64,7 +65,7 @@ def work_request_group(): @click.pass_context @json_skeleton_utils.json_skeleton_generation_handler(input_params_to_complex_types={}) @cli_util.wrap_exceptions -def add_log_analytics_log_group(ctx, from_json, wait_for_state, max_wait_seconds, wait_interval_seconds, integration_instance_id, log_group_id, if_match): +def add_log_analytics_log_group(ctx, from_json, wait_for_state, max_wait_seconds, wait_interval_seconds, integration_instance_id, log_group_id, attachment_type, if_match): if isinstance(integration_instance_id, six.string_types) and len(integration_instance_id.strip()) == 0: raise click.UsageError('Parameter --integration-instance-id cannot be whitespace or empty string') @@ -77,6 +78,9 @@ def add_log_analytics_log_group(ctx, from_json, wait_for_state, max_wait_seconds _details = {} _details['logGroupId'] = log_group_id + if attachment_type is not None: + _details['attachmentType'] = attachment_type + client = cli_util.build_client('integration', 'integration_instance', ctx) result = client.add_log_analytics_log_group( integration_instance_id=integration_instance_id, @@ -1458,8 +1462,9 @@ def list_work_requests(ctx, from_json, all_pages, page_size, compartment_id, pag cli_util.render_response(result, ctx) -@integration_instance_group.command(name=cli_util.override('integration.remove_log_analytics_log_group.command_name', 'remove'), help=u"""Removes Log Analytics logGroup, if enabled for given integrationInstance. Since only single LogGroup can be enabled for integration instance, no additional details are required to be includes in the request. \n[Command Reference](removeLogAnalyticsLogGroup)""") +@integration_instance_group.command(name=cli_util.override('integration.remove_log_analytics_log_group.command_name', 'remove'), help=u"""Removes Log Analytics logGroup, if enabled for given integrationInstance. Also used for removing Log Analytics log Group for attached OPA instance. \n[Command Reference](removeLogAnalyticsLogGroup)""") @cli_util.option('--integration-instance-id', required=True, help=u"""Unique Integration Instance identifier.""") +@cli_util.option('--attachment-type', type=custom_types.CliCaseInsensitiveChoice(["PROCESS_AUTOMATION"]), help=u"""Type of attachment. Supported at this include PROCESS_AUTOMATION""") @cli_util.option('--if-match', help=u"""For optimistic concurrency control. In the PUT or DELETE call for a resource, set the `if-match` parameter to the value of the etag from a previous GET or POST response for that resource. The resource will be updated or deleted only if the etag you provide matches the resource's current etag value.""") @cli_util.option('--wait-for-state', type=custom_types.CliCaseInsensitiveChoice(["ACCEPTED", "IN_PROGRESS", "FAILED", "SUCCEEDED", "CANCELING", "CANCELED"]), multiple=True, help="""This operation asynchronously creates, modifies or deletes a resource and uses a work request to track the progress of the operation. Specify this option to perform the action and then wait until the work request reaches a certain state. Multiple states can be specified, returning on the first state. For example, --wait-for-state ACCEPTED --wait-for-state CANCELED would return on whichever lifecycle state is reached first. If timeout is reached, a return code of 2 is returned. For any other error, a return code of 1 is returned.""") @cli_util.option('--max-wait-seconds', type=click.INT, help="""The maximum time to wait for the work request to reach the state defined by --wait-for-state. Defaults to 1200 seconds.""") @@ -1469,7 +1474,7 @@ def list_work_requests(ctx, from_json, all_pages, page_size, compartment_id, pag @click.pass_context @json_skeleton_utils.json_skeleton_generation_handler(input_params_to_complex_types={}) @cli_util.wrap_exceptions -def remove_log_analytics_log_group(ctx, from_json, wait_for_state, max_wait_seconds, wait_interval_seconds, integration_instance_id, if_match): +def remove_log_analytics_log_group(ctx, from_json, wait_for_state, max_wait_seconds, wait_interval_seconds, integration_instance_id, attachment_type, if_match): if isinstance(integration_instance_id, six.string_types) and len(integration_instance_id.strip()) == 0: raise click.UsageError('Parameter --integration-instance-id cannot be whitespace or empty string') @@ -1478,9 +1483,16 @@ def remove_log_analytics_log_group(ctx, from_json, wait_for_state, max_wait_seco if if_match is not None: kwargs['if_match'] = if_match kwargs['opc_request_id'] = cli_util.use_or_generate_request_id(ctx.obj['request_id']) + + _details = {} + + if attachment_type is not None: + _details['attachmentType'] = attachment_type + client = cli_util.build_client('integration', 'integration_instance', ctx) result = client.remove_log_analytics_log_group( integration_instance_id=integration_instance_id, + remove_log_analytics_log_group_details=_details, **kwargs ) if wait_for_state: diff --git a/services/resource_scheduler/src/oci_cli_schedule/generated/schedule_cli.py b/services/resource_scheduler/src/oci_cli_schedule/generated/schedule_cli.py index 316b82f49..cd8ced8ae 100644 --- a/services/resource_scheduler/src/oci_cli_schedule/generated/schedule_cli.py +++ b/services/resource_scheduler/src/oci_cli_schedule/generated/schedule_cli.py @@ -138,7 +138,7 @@ def cancel_work_request(ctx, from_json, work_request_id, if_match): cli_util.render_response(result, ctx) -@schedule_group.command(name=cli_util.override('resource_scheduler.change_schedule_compartment.command_name', 'change-compartment'), help=u"""This API) moves a schedule into a different compartment within the same tenancy. For information about moving resources between compartments, see [Moving Resources to a Different Compartment]. \n[Command Reference](changeScheduleCompartment)""") +@schedule_group.command(name=cli_util.override('resource_scheduler.change_schedule_compartment.command_name', 'change-compartment'), help=u"""This API moves a schedule into a different compartment within the same tenancy. For information about moving resources between compartments, see [Moving Resources to a Different Compartment]. \n[Command Reference](changeScheduleCompartment)""") @cli_util.option('--schedule-id', required=True, help=u"""This is the [OCID] of the schedule.""") @cli_util.option('--compartment-id', required=True, help=u"""The [OCID] of the compartment to move the schedule to.""") @cli_util.option('--if-match', help=u"""This is used for optimistic concurrency control. In the PUT or DELETE call for a resource, set the `if-match` parameter to the value of the etag from a previous GET or POST response for that resource. The resource will be updated or deleted only if the etag you provide matches the resource's current etag value.""") @@ -201,7 +201,7 @@ def change_schedule_compartment(ctx, from_json, wait_for_state, max_wait_seconds @schedule_group.command(name=cli_util.override('resource_scheduler.create_schedule.command_name', 'create'), help=u"""This API creates a schedule. You must provide either resources or resourceFilters. \n[Command Reference](createSchedule)""") @cli_util.option('--compartment-id', required=True, help=u"""The [OCID] of the compartment in which the schedule is created""") -@cli_util.option('--action', required=True, type=custom_types.CliCaseInsensitiveChoice(["START_RESOURCE", "STOP_RESOURCE"]), help=u"""This is the action that will be executed by the schedule.""") +@cli_util.option('--action', required=True, type=custom_types.CliCaseInsensitiveChoice(["START_RESOURCE", "STOP_RESOURCE", "BACKUP_RESOURCE"]), help=u"""This is the action that will be executed by the schedule.""") @cli_util.option('--recurrence-details', required=True, help=u"""This is the frequency of recurrence of a schedule. The frequency field can either conform to RFC-5545 formatting or UNIX cron formatting for recurrences, based on the value specified by the recurrenceType field.""") @cli_util.option('--recurrence-type', required=True, type=custom_types.CliCaseInsensitiveChoice(["CRON", "ICAL"]), help=u"""Type of recurrence of a schedule""") @cli_util.option('--display-name', help=u"""This is a user-friendly name for the schedule. It does not have to be unique, and it's changeable.""") @@ -463,6 +463,7 @@ def get_work_request(ctx, from_json, work_request_id): @resource_type_collection_group.command(name=cli_util.override('resource_scheduler.list_resource_types.command_name', 'list-resource-types'), help=u"""This API gets a list of schedule resource types. \n[Command Reference](listResourceTypes)""") @cli_util.option('--compartment-id', help=u"""This is the [OCID] of the compartment in which to list resources.""") +@cli_util.option('--action-type', help=u"""This describes the Action Type""") @cli_util.option('--limit', type=click.INT, help=u"""For list pagination. The maximum number of results per page, or items to return in a paginated \"List\" call. For important details about how pagination works, see [List Pagination].""") @cli_util.option('--page', help=u"""This used for list pagination. The value of the opc-next-page response header from the previous \"List\" call. For important details about how pagination works, see [List Pagination].""") @cli_util.option('--all', 'all_pages', is_flag=True, help="""Fetches all pages of results. If you provide this option, then you cannot provide the --limit option.""") @@ -472,7 +473,7 @@ def get_work_request(ctx, from_json, work_request_id): @click.pass_context @json_skeleton_utils.json_skeleton_generation_handler(input_params_to_complex_types={}, output_type={'module': 'resource_scheduler', 'class': 'ResourceTypeCollection'}) @cli_util.wrap_exceptions -def list_resource_types(ctx, from_json, all_pages, page_size, compartment_id, limit, page): +def list_resource_types(ctx, from_json, all_pages, page_size, compartment_id, action_type, limit, page): if all_pages and limit: raise click.UsageError('If you provide the --all option you cannot provide the --limit option') @@ -480,6 +481,8 @@ def list_resource_types(ctx, from_json, all_pages, page_size, compartment_id, li kwargs = {} if compartment_id is not None: kwargs['compartment_id'] = compartment_id + if action_type is not None: + kwargs['action_type'] = action_type if limit is not None: kwargs['limit'] = limit if page is not None: @@ -756,7 +759,7 @@ def list_work_requests(ctx, from_json, all_pages, page_size, compartment_id, wor @cli_util.option('--schedule-id', required=True, help=u"""This is the [OCID] of the schedule.""") @cli_util.option('--display-name', help=u"""This is a user-friendly name for the schedule. It does not have to be unique, and it's changeable.""") @cli_util.option('--description', help=u"""This is the description of the schedule.""") -@cli_util.option('--action', type=custom_types.CliCaseInsensitiveChoice(["START_RESOURCE", "STOP_RESOURCE"]), help=u"""This is the action that will be executed by the schedule.""") +@cli_util.option('--action', type=custom_types.CliCaseInsensitiveChoice(["START_RESOURCE", "STOP_RESOURCE", "BACKUP_RESOURCE"]), help=u"""This is the action that will be executed by the schedule.""") @cli_util.option('--recurrence-details', help=u"""This is the frequency of recurrence of a schedule. The frequency field can either conform to RFC-5545 formatting or UNIX cron formatting for recurrences, based on the value specified by the recurrenceType field.""") @cli_util.option('--recurrence-type', type=custom_types.CliCaseInsensitiveChoice(["CRON", "ICAL"]), help=u"""Type of recurrence of a schedule""") @cli_util.option('--resource-filters', type=custom_types.CLI_COMPLEX_TYPE, help=u"""This is a list of resources filters. The schedule will be applied to resources matching all of them. diff --git a/setup.py b/setup.py index 3009136ca..f46c32cd3 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ def open_relative(*path): readme = f.read() requires = [ - 'oci==2.179.0', + 'oci==2.180.0', 'arrow>=1.0.0,<2.0.0', 'certifi>=2025.1.31,<2026.0.0', 'click<=8.1.2', diff --git a/src/oci_cli/cli_constants.py b/src/oci_cli/cli_constants.py index 3e5b32036..3bdfbfa88 100644 --- a/src/oci_cli/cli_constants.py +++ b/src/oci_cli/cli_constants.py @@ -36,6 +36,7 @@ OCI_CLI_CONFIG_FILE_ENV_VAR = 'OCI_CLI_CONFIG_FILE' OCI_CLI_RC_FILE_ENV_VAR = 'OCI_CLI_RC_FILE' OCI_CLI_CERT_BUNDLE_ENV_VAR = 'OCI_CLI_CERT_BUNDLE' +OCI_CLI_FEDERATION_ENDPOINT_ENV_VAR = 'OCI_CLI_FEDERATION_ENDPOINT' OCI_CLI_DELEGATION_TOKEN_FILE_ENV_VAR = 'OCI_CLI_DELEGATION_TOKEN_FILE' OCI_CLI_SECURITY_TOKEN_FILE_ENV_VAR = 'OCI_CLI_SECURITY_TOKEN_FILE' OCI_CLI_ENDPOINT_ENV_VAR = 'OCI_CLI_ENDPOINT' @@ -63,6 +64,7 @@ OCI_CLI_PARAM_TO_ENV_MAP = { 'region': OCI_CLI_REGION_ENV_VAR, 'endpoint': OCI_CLI_ENDPOINT_ENV_VAR, + 'federation_endpoint': OCI_CLI_FEDERATION_ENDPOINT_ENV_VAR, 'cert_bundle': OCI_CLI_CERT_BUNDLE_ENV_VAR, 'config_file': OCI_CLI_CONFIG_FILE_ENV_VAR, } diff --git a/src/oci_cli/cli_root.py b/src/oci_cli/cli_root.py index 619077b2e..c303cfd20 100644 --- a/src/oci_cli/cli_root.py +++ b/src/oci_cli/cli_root.py @@ -106,6 +106,18 @@ If the flag is used for a service that does not support such feature, the default endpoint will be used. """ +FEDERATION_ENDPOINT_HELP = """The full federation endpoint to use with --auth instance_principal. + +Example:: + oci iam region list --auth instance_principal --federation-endpoint https://auth.us-ashburn-1.ds.oraclecloud.com/v1/x509 + +Set this option only when you want to explicitly choose the federation endpoint. + +If you set both the command-line option (``--federation-endpoint``) and the environment variable (``OCI_CLI_FEDERATION_ENDPOINT``), the value passed with ``--federation-endpoint`` is used. + +If you do not set either one, the default federation endpoint behavior is used. +""" + GENERATE_PARAM_JSON_HELP = """Complex input, such as arrays and objects, are passed in JSON format. When passed the name of an option which takes complex input, this will print out example JSON of what needs to be passed to that option. @@ -428,6 +440,7 @@ def find_latest_release_version(ctx, param, value): @click.option('--realm-specific-endpoint', is_flag=True, help=REALM_SPECIFIC_ENDPOINT_HELP) @click.option('--connection-timeout', 'connection_timeout', type=click.INT, callback=read_values_from_env, help='The value of the connection timeout in seconds to make establish connection from sdk to services. This will override the default connection timeout value of 10 secs. ') @click.option('--enable-dual-stack', is_flag=True, help=ENABLE_DUAL_STACK_HELP) +@click.option('--federation-endpoint', callback=read_values_from_env, help=FEDERATION_ENDPOINT_HELP) @click.option('--read-timeout', 'read_timeout', type=click.INT, callback=read_values_from_env, help='The value of the read timeout in seconds to wait for service calls to send response to sdk. This will override the default read timeout value of 60 secs. ') @click.option('--cert-bundle', callback=read_values_from_env, help='The full path to a CA certificate bundle to be used for SSL verification. This will override the default CA certificate bundle.') @click.option('--output', type=click.Choice(choices=['json', 'table']), help='The output format. [Default is json]') @@ -456,7 +469,7 @@ def find_latest_release_version(ctx, param, value): @click.option('-?', '-h', '--help', is_flag=True, help='For detailed help on the individual OCI CLI command, enter --help.') @click.option('--enable-propagation', is_flag=False, type=click.Choice(choices=['True', 'False']), help='Enable propagation of opc-request-id') @click.pass_context -def cli(ctx, config_file, profile, cli_rc_file, request_id, region, endpoint, realm_specific_endpoint, cert_bundle, enable_dual_stack, output, query, raw_output, auth, auth_purpose, no_retry, max_retries, generate_full_command_json_input, generate_param_json_input, proxy, debug, cli_auto_prompt, connection_timeout, read_timeout, enable_propagation, help): +def cli(ctx, config_file, profile, cli_rc_file, request_id, region, endpoint, realm_specific_endpoint, cert_bundle, enable_dual_stack, federation_endpoint, output, query, raw_output, auth, auth_purpose, no_retry, max_retries, generate_full_command_json_input, generate_param_json_input, proxy, debug, cli_auto_prompt, connection_timeout, read_timeout, enable_propagation, help): click.exceptions.UsageError.show = cli_util.update_click_help_message if max_retries and no_retry: @@ -496,6 +509,7 @@ def cli(ctx, config_file, profile, cli_rc_file, request_id, region, endpoint, re 'realm_specific_endpoint': realm_specific_endpoint, 'connection_timeout': connection_timeout, 'enable_dual_stack': enable_dual_stack, + 'federation_endpoint': federation_endpoint, 'read_timeout': read_timeout, 'cert_bundle': cert_bundle, 'output': output, diff --git a/src/oci_cli/cli_util.py b/src/oci_cli/cli_util.py index a2e67e266..c42a9d833 100644 --- a/src/oci_cli/cli_util.py +++ b/src/oci_cli/cli_util.py @@ -275,6 +275,13 @@ def get_instance_principal_signer(ctx, client_config): signer_kwargs = {} if ctx.obj['cert_bundle']: signer_kwargs['federation_client_cert_bundle_verify'] = ctx.obj['cert_bundle'] + + federation_endpoint = ctx.obj.get('federation_endpoint') + if isinstance(federation_endpoint, str): + federation_endpoint = federation_endpoint.strip() + if federation_endpoint: + signer_kwargs['federation_endpoint'] = federation_endpoint + if ctx.obj['region']: # If we don't set this then constructed clients will try and pluck the region from the instance principals signer, which may # conflict with the caller intent (since they *DID* explicitly pass a region) @@ -512,6 +519,7 @@ def build_raw_requests_session(ctx): signer = oci.Signer.from_config(client_config) session = requests.Session() + session.trust_env = False session.auth = signer session.headers['opc-request-id'] = ctx.obj['request_id'] session.headers['user-agent'] = oci.base_client.build_user_agent(extra=client_config[ADDITIONAL_USER_AGENT]) @@ -1484,7 +1492,7 @@ def load_context_obj_values_from_defaults(ctx): if not ctx.obj['enable_dual_stack']: # False for enable-dual-stack means not provided, so just load it if there is a default value. If there's nothing there, then this'll be # None, which is still false-y - ctx.obj['realm_specific_endpoint'] = get_default_value_from_defaults_file(ctx, 'enable-dual-stack', click.BOOL, False) + ctx.obj['enable_dual_stack'] = get_default_value_from_defaults_file(ctx, 'enable-dual-stack', click.BOOL, False) else: populate_dict_key_with_default_value(ctx, 'enable_dual_stack', click.BOOL, param_name='enable-dual-stack') diff --git a/src/oci_cli/raw_request_cli.py b/src/oci_cli/raw_request_cli.py index 8ee996431..4c34512c8 100644 --- a/src/oci_cli/raw_request_cli.py +++ b/src/oci_cli/raw_request_cli.py @@ -50,11 +50,13 @@ def _make_raw_request(ctx, target_uri, http_method, request_body, request_header jmespath_expression = cli_util.get_jmespath_expression_from_context(ctx) - # Deliberately a bit open as we can permit an empty string through as an empty request body - parsed_request_body = '' - if request_body is not None and request_body.strip() != '': - request_body_as_dict = cli_util.parse_json_parameter('request_body', request_body, 'camelize_keys', False) - parsed_request_body = json.dumps(request_body_as_dict) + parsed_request_body = None + if request_body is not None: + # Deliberately a bit open as we can permit an empty string through as an empty request body + parsed_request_body = '' + if request_body.strip() != '': + request_body_as_dict = cli_util.parse_json_parameter('request_body', request_body, 'camelize_keys', False) + parsed_request_body = json.dumps(request_body_as_dict) additional_headers = {} if request_headers: diff --git a/src/oci_cli/version.py b/src/oci_cli/version.py index 87dd12b2d..b911c6e8b 100644 --- a/src/oci_cli/version.py +++ b/src/oci_cli/version.py @@ -2,4 +2,4 @@ # Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. # This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license. -__version__ = '3.87.0' +__version__ = '3.88.0' diff --git a/tests/test_root_options.py b/tests/test_root_options.py index dd99ffaf3..233759ae1 100644 --- a/tests/test_root_options.py +++ b/tests/test_root_options.py @@ -7,7 +7,7 @@ import os import six.moves import traceback -from mock import patch +from mock import mock_open, patch from oci_cli.cli_constants import OCI_CONFIG_REQUIRED_VARS as config_required_vars from conftest import runner @@ -185,10 +185,170 @@ def test_root_level_options_overrides_environment_variable(config_file): os.environ[oci_cli.cli_constants.OCI_CLI_CONFIG_FILE_ENV_VAR] = original_value -def test_auth_instance_principal_param(config_file): +def test_instance_principal_auth_uses_default_signer_without_federation_endpoint(config_file): with patch.object(oci.auth.signers.InstancePrincipalsSecurityTokenSigner, '__init__', return_value=None) as mock_init: result = invoke_example_operation(runner, ['--auth', 'instance_principal'], 'non-existent-config') assert mock_init.called + assert 'federation_endpoint' not in mock_init.call_args[1] + + +def test_federation_endpoint_option_is_passed_to_instance_principal_signer(config_file): + expected_federation_endpoint = 'https://auth.us-ashburn-1.ds.oraclecloud.com/v1/x509' + + with patch.object(oci.auth.signers.InstancePrincipalsSecurityTokenSigner, '__init__', return_value=None) as mock_init: + invoke_example_operation( + runner, + [ + '--auth', 'instance_principal', + '--federation-endpoint', expected_federation_endpoint + ], + 'non-existent-config' + ) + + assert mock_init.called + assert mock_init.call_args[1]['federation_endpoint'] == expected_federation_endpoint + + +def test_federation_endpoint_env_var_is_passed_to_instance_principal_signer(config_file): + expected_federation_endpoint = 'https://auth.us-phoenix-1.ds.oraclecloud.com/v1/x509' + + with patch.dict(os.environ, {oci_cli.cli_constants.OCI_CLI_FEDERATION_ENDPOINT_ENV_VAR: expected_federation_endpoint}, clear=False): + with patch.object(oci.auth.signers.InstancePrincipalsSecurityTokenSigner, '__init__', return_value=None) as mock_init: + invoke_example_operation(runner, ['--auth', 'instance_principal'], 'non-existent-config') + + assert mock_init.called + assert mock_init.call_args[1]['federation_endpoint'] == expected_federation_endpoint + + +def test_federation_endpoint_option_overrides_env_var_for_instance_principal_signer(config_file): + env_federation_endpoint = 'https://auth.us-phoenix-1.ds.oraclecloud.com/v1/x509' + option_federation_endpoint = 'https://auth.us-ashburn-1.ds.oraclecloud.com/v1/x509' + + with patch.dict(os.environ, {oci_cli.cli_constants.OCI_CLI_FEDERATION_ENDPOINT_ENV_VAR: env_federation_endpoint}, clear=False): + with patch.object(oci.auth.signers.InstancePrincipalsSecurityTokenSigner, '__init__', return_value=None) as mock_init: + invoke_example_operation( + runner, + [ + '--auth', 'instance_principal', + '--federation-endpoint', option_federation_endpoint + ], + 'non-existent-config' + ) + + assert mock_init.called + assert mock_init.call_args[1]['federation_endpoint'] == option_federation_endpoint + + +def test_empty_federation_endpoint_env_var_is_ignored_for_instance_principal_signer(config_file): + with patch.dict( + os.environ, + {oci_cli.cli_constants.OCI_CLI_FEDERATION_ENDPOINT_ENV_VAR: ''}, + clear=False + ): + with patch.object( + oci.auth.signers.InstancePrincipalsSecurityTokenSigner, + '__init__', + return_value=None + ) as mock_init: + invoke_example_operation( + runner, + ['--auth', 'instance_principal'], + 'non-existent-config' + ) + + assert mock_init.called + assert 'federation_endpoint' not in mock_init.call_args[1] + + +def test_whitespace_only_federation_endpoint_env_var_is_ignored_for_instance_principal_signer(config_file): + with patch.dict( + os.environ, + {oci_cli.cli_constants.OCI_CLI_FEDERATION_ENDPOINT_ENV_VAR: ' '}, + clear=False + ): + with patch.object( + oci.auth.signers.InstancePrincipalsSecurityTokenSigner, + '__init__', + return_value=None + ) as mock_init: + invoke_example_operation( + runner, + ['--auth', 'instance_principal'], + 'non-existent-config' + ) + + assert mock_init.called + assert 'federation_endpoint' not in mock_init.call_args[1] + + +def test_whitespace_only_federation_endpoint_option_is_ignored_for_instance_principal_signer(config_file): + with patch.object( + oci.auth.signers.InstancePrincipalsSecurityTokenSigner, + '__init__', + return_value=None + ) as mock_init: + invoke_example_operation( + runner, + [ + '--auth', 'instance_principal', + '--federation-endpoint', ' ' + ], + 'non-existent-config' + ) + + assert mock_init.called + assert 'federation_endpoint' not in mock_init.call_args[1] + + +def test_federation_endpoint_option_passes_through_non_ds_value_for_instance_principal_signer(config_file): + expected_federation_endpoint = 'https://auth.us-phoenix-1.oraclecloud.com/v1/x509' + + with patch.object( + oci.auth.signers.InstancePrincipalsSecurityTokenSigner, + '__init__', + return_value=None + ) as mock_init: + invoke_example_operation( + runner, + [ + '--auth', 'instance_principal', + '--federation-endpoint', expected_federation_endpoint + ], + 'non-existent-config' + ) + + assert mock_init.called + assert mock_init.call_args[1]['federation_endpoint'] == expected_federation_endpoint + + +def test_federation_endpoint_option_is_passed_to_instance_obo_user_signer(config_file): + expected_federation_endpoint = 'https://auth.us-ashburn-1.ds.oraclecloud.com/v1/x509' + delegation_token_file = '/tmp/delegation_token_file' + + mocked_config = { + 'delegation_token_file': delegation_token_file, + 'region': 'us-phoenix-1' + } + + with patch.object(oci_cli.cli_util, 'build_config', return_value=mocked_config): + with patch('os.path.exists', return_value=True): + with patch('builtins.open', mock_open(read_data='delegation-token')): + with patch.object( + oci.auth.signers.InstancePrincipalsDelegationTokenSigner, + '__init__', + return_value=None + ) as mock_init: + invoke_example_operation( + runner, + [ + '--auth', 'instance_obo_user', + '--federation-endpoint', expected_federation_endpoint + ], + 'internal_resources/config' + ) + + assert mock_init.called + assert mock_init.call_args[1]['federation_endpoint'] == expected_federation_endpoint def test_auth_instance_principal_env_var(config_file):