diff --git a/.gitignore b/.gitignore index d277be86..67ed941e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.swp *~ .idea +.vscode /docs/site bin build diff --git a/apis/v1alpha1/ack-generate-metadata.yaml b/apis/v1alpha1/ack-generate-metadata.yaml index bec6bd11..daf15c29 100755 --- a/apis/v1alpha1/ack-generate-metadata.yaml +++ b/apis/v1alpha1/ack-generate-metadata.yaml @@ -1,13 +1,13 @@ ack_generate_info: - build_date: "2025-07-22T22:13:44Z" - build_hash: b2dc0f44e0b08f041de14c3944a5cc005ba97c8f + build_date: "2025-07-25T17:47:22Z" + build_hash: 21224288556d986791b01b9caf69e2e20e0591ca go_version: go1.24.5 - version: v0.50.0 + version: v0.50.0-1-g2122428 api_directory_checksum: d162a6e9df2d4861d6c01d42047402b51f341293 api_version: v1alpha1 aws_sdk_go_version: v1.32.6 generator_config_info: - file_checksum: e6a6ff840d55df735215ac04826122ebf89eb79a + file_checksum: e3c9cd11651288747aece625d6883dfefa718864 original_file_name: generator.yaml last_modification: reason: API generation diff --git a/apis/v1alpha1/generator.yaml b/apis/v1alpha1/generator.yaml index e38b7397..c45a6747 100644 --- a/apis/v1alpha1/generator.yaml +++ b/apis/v1alpha1/generator.yaml @@ -250,12 +250,8 @@ operations: resource_name: VpcEndpoint CreateVpcEndpointServiceConfiguration: output_wrapper_field_path: ServiceConfiguration - DeleteVpcEndpointServiceConfigurations: - operation_type: - - Delete - resource_name: VpcEndpointServiceConfiguration - CreateVpcEndpointServiceConfiguration: - output_wrapper_field_path: ServiceConfiguration + DescribeVpcEndpointServiceConfigurations: + custom_check_required_fields_missing_method: checkForMissingRequiredFields DeleteVpcEndpointServiceConfigurations: operation_type: - Delete @@ -1068,7 +1064,10 @@ resources: when: - path: Status.ServiceState in: - - available + - Available + list_operation: + match_fields: + - ServiceId hooks: sdk_delete_post_build_request: template_path: hooks/vpc_endpoint_service_configuration/sdk_delete_post_build_request.go.tpl @@ -1078,6 +1077,8 @@ resources: template_path: hooks/vpc_endpoint_service_configuration/sdk_update_pre_build_request.go.tpl sdk_read_many_post_set_output: template_path: hooks/vpc_endpoint_service_configuration/sdk_read_many_post_set_output.go.tpl + sdk_read_many_post_build_request: + template_path: hooks/vpc_endpoint_service_configuration/sdk_read_many_post_build_request.go.tpl VpcPeeringConnection: fields: VpcId: diff --git a/generator.yaml b/generator.yaml index e38b7397..c45a6747 100644 --- a/generator.yaml +++ b/generator.yaml @@ -250,12 +250,8 @@ operations: resource_name: VpcEndpoint CreateVpcEndpointServiceConfiguration: output_wrapper_field_path: ServiceConfiguration - DeleteVpcEndpointServiceConfigurations: - operation_type: - - Delete - resource_name: VpcEndpointServiceConfiguration - CreateVpcEndpointServiceConfiguration: - output_wrapper_field_path: ServiceConfiguration + DescribeVpcEndpointServiceConfigurations: + custom_check_required_fields_missing_method: checkForMissingRequiredFields DeleteVpcEndpointServiceConfigurations: operation_type: - Delete @@ -1068,7 +1064,10 @@ resources: when: - path: Status.ServiceState in: - - available + - Available + list_operation: + match_fields: + - ServiceId hooks: sdk_delete_post_build_request: template_path: hooks/vpc_endpoint_service_configuration/sdk_delete_post_build_request.go.tpl @@ -1078,6 +1077,8 @@ resources: template_path: hooks/vpc_endpoint_service_configuration/sdk_update_pre_build_request.go.tpl sdk_read_many_post_set_output: template_path: hooks/vpc_endpoint_service_configuration/sdk_read_many_post_set_output.go.tpl + sdk_read_many_post_build_request: + template_path: hooks/vpc_endpoint_service_configuration/sdk_read_many_post_build_request.go.tpl VpcPeeringConnection: fields: VpcId: diff --git a/pkg/resource/vpc_endpoint_service_configuration/hooks.go b/pkg/resource/vpc_endpoint_service_configuration/hooks.go index fc91e0ae..58538063 100644 --- a/pkg/resource/vpc_endpoint_service_configuration/hooks.go +++ b/pkg/resource/vpc_endpoint_service_configuration/hooks.go @@ -157,4 +157,11 @@ func (rm *resourceManager) setAdditionalFields( return &resource{ko}, nil } +// checkForMissingRequiredFields validates that all fields required for making a ReadMany +// API call are present in the resource's object. Need to use a custom method a current code-gen +// implementation does not include fields marked with is_primary_key. +func (rm *resourceManager) checkForMissingRequiredFields(r *resource) bool { + return r.ko.Status.ServiceID == nil +} + var syncTags = tags.Sync diff --git a/pkg/resource/vpc_endpoint_service_configuration/manager.go b/pkg/resource/vpc_endpoint_service_configuration/manager.go index ff1edc87..a35b3884 100644 --- a/pkg/resource/vpc_endpoint_service_configuration/manager.go +++ b/pkg/resource/vpc_endpoint_service_configuration/manager.go @@ -271,7 +271,7 @@ func (rm *resourceManager) IsSynced(ctx context.Context, res acktypes.AWSResourc if r.ko.Status.ServiceState == nil { return false, nil } - serviceStateCandidates := []string{"available"} + serviceStateCandidates := []string{"Available"} if !ackutil.InStrings(*r.ko.Status.ServiceState, serviceStateCandidates) { return false, nil } diff --git a/pkg/resource/vpc_endpoint_service_configuration/sdk.go b/pkg/resource/vpc_endpoint_service_configuration/sdk.go index b96e2e2b..551ab798 100644 --- a/pkg/resource/vpc_endpoint_service_configuration/sdk.go +++ b/pkg/resource/vpc_endpoint_service_configuration/sdk.go @@ -74,6 +74,9 @@ func (rm *resourceManager) sdkFind( if err != nil { return nil, err } + if r.ko.Status.ServiceID != nil { + input.ServiceIds = []string{*r.ko.Status.ServiceID} + } var resp *svcsdk.DescribeVpcEndpointServiceConfigurationsOutput resp, err = rm.sdkapi.DescribeVpcEndpointServiceConfigurations(ctx, input) rm.metrics.RecordAPICall("READ_MANY", "DescribeVpcEndpointServiceConfigurations", err) @@ -150,6 +153,11 @@ func (rm *resourceManager) sdkFind( ko.Status.PrivateDNSNameConfiguration = nil } if elem.ServiceId != nil { + if ko.Status.ServiceID != nil { + if *elem.ServiceId != *ko.Status.ServiceID { + continue + } + } ko.Status.ServiceID = elem.ServiceId } else { ko.Status.ServiceID = nil @@ -224,7 +232,7 @@ func (rm *resourceManager) sdkFind( func (rm *resourceManager) requiredFieldsMissingFromReadManyInput( r *resource, ) bool { - return false + return rm.checkForMissingRequiredFields(r) } // newListRequestPayload returns SDK-specific struct for the HTTP request diff --git a/templates/hooks/vpc_endpoint_service_configuration/sdk_read_many_post_build_request.go.tpl b/templates/hooks/vpc_endpoint_service_configuration/sdk_read_many_post_build_request.go.tpl new file mode 100644 index 00000000..2522f7c8 --- /dev/null +++ b/templates/hooks/vpc_endpoint_service_configuration/sdk_read_many_post_build_request.go.tpl @@ -0,0 +1,3 @@ + if r.ko.Status.ServiceID != nil { + input.ServiceIds = []string{*r.ko.Status.ServiceID} + } \ No newline at end of file diff --git a/test/e2e/bootstrap_resources.py b/test/e2e/bootstrap_resources.py index c49bcd5e..138d08cd 100644 --- a/test/e2e/bootstrap_resources.py +++ b/test/e2e/bootstrap_resources.py @@ -19,6 +19,7 @@ from acktest.bootstrapping.s3 import Bucket from acktest.bootstrapping.vpc import VPC from acktest.bootstrapping.vpc import TransitGateway +from acktest.bootstrapping.vpc_endpoint_service import VpcEndpointServiceConfiguration from e2e import bootstrap_directory @dataclass @@ -28,6 +29,7 @@ class BootstrapResources(Resources): AdoptedVPC: VPC NetworkLoadBalancer: NetworkLoadBalancer TestTransitGateway: TransitGateway + AdoptedVpcEndpointService: VpcEndpointServiceConfiguration _bootstrap_resources = None diff --git a/test/e2e/requirements.txt b/test/e2e/requirements.txt index b16cc32a..a00c64cf 100644 --- a/test/e2e/requirements.txt +++ b/test/e2e/requirements.txt @@ -1 +1 @@ -acktest @ git+https://github.com/aws-controllers-k8s/test-infra.git@72e9d798ad4f22e0e1ff4e227cfd69f7e301479a \ No newline at end of file +acktest @ git+https://github.com/aws-controllers-k8s/test-infra.git@e7adf679cc3b78f7bc907fa7c11a13ce0d9c41a7 \ No newline at end of file diff --git a/test/e2e/resources/vpc_endpoint_service_adoption.yaml b/test/e2e/resources/vpc_endpoint_service_adoption.yaml new file mode 100644 index 00000000..0bf948b8 --- /dev/null +++ b/test/e2e/resources/vpc_endpoint_service_adoption.yaml @@ -0,0 +1,9 @@ +apiVersion: ec2.services.k8s.aws/v1alpha1 +kind: VPCEndpointServiceConfiguration +metadata: + name: $VPC_ENDPOINT_SERVICE_ADOPTED_NAME + annotations: + services.k8s.aws/adoption-policy: $ADOPTION_POLICY + services.k8s.aws/adoption-fields: "$ADOPTION_FIELDS" + services.k8s.aws/deletion-policy: retain +spec: {} \ No newline at end of file diff --git a/test/e2e/service_bootstrap.py b/test/e2e/service_bootstrap.py index 9231a954..119b14d7 100644 --- a/test/e2e/service_bootstrap.py +++ b/test/e2e/service_bootstrap.py @@ -19,6 +19,7 @@ from acktest.bootstrapping.elbv2 import NetworkLoadBalancer from acktest.bootstrapping.vpc import VPC from acktest.bootstrapping.vpc import TransitGateway +from acktest.bootstrapping.vpc_endpoint_service import VpcEndpointServiceConfiguration from acktest.bootstrapping.s3 import Bucket from e2e import bootstrap_directory from e2e.bootstrap_resources import BootstrapResources @@ -37,7 +38,8 @@ def service_bootstrap() -> Resources: ), NetworkLoadBalancer=NetworkLoadBalancer("e2e-vpc-ep-service-test"), AdoptedVPC=VPC(name_prefix="e2e-adopted-vpc", num_public_subnet=1, num_private_subnet=0), - TestTransitGateway=TransitGateway() + TestTransitGateway=TransitGateway(), + AdoptedVpcEndpointService=VpcEndpointServiceConfiguration(name_prefix="e2e-adopted-vpc-es"), ) try: diff --git a/test/e2e/tests/test_vpc_endpoint_service_configuration.py b/test/e2e/tests/test_vpc_endpoint_service_configuration.py index 34f8b4b6..26bdf299 100644 --- a/test/e2e/tests/test_vpc_endpoint_service_configuration.py +++ b/test/e2e/tests/test_vpc_endpoint_service_configuration.py @@ -100,6 +100,8 @@ def test_vpc_endpoint_service_configuration_create_delete(self, ec2_client, simp # Check VPC Endpoint Service exists in AWS ec2_validator = EC2Validator(ec2_client) ec2_validator.assert_vpc_endpoint_service_configuration(resource_id) + assert k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=5) + # Check that the allowedPrincipal is properly set allowed_principals = ec2_validator.get_vpc_endpoint_service_permissions(resource_id) diff --git a/test/e2e/tests/test_vpc_endpoint_service_configuration_adoption.py b/test/e2e/tests/test_vpc_endpoint_service_configuration_adoption.py new file mode 100644 index 00000000..7d70d123 --- /dev/null +++ b/test/e2e/tests/test_vpc_endpoint_service_configuration_adoption.py @@ -0,0 +1,129 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You may +# not use this file except in compliance with the License. A copy of the +# License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is distributed +# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +# express or implied. See the License for the specific language governing +# permissions and limitations under the License. + +"""Integration tests for the Vpc Endpoint Service Configuraion Adoption. +""" + +# Default to us-west-2 since that's where prow is deployed +import logging +from os import environ +import time + +import pytest + +from e2e import service_marker +from e2e import CRD_GROUP, CRD_VERSION, load_ec2_resource +from e2e.bootstrap_resources import get_bootstrap_resources +from e2e.replacement_values import REPLACEMENT_VALUES +from acktest.resources import random_suffix_name +from acktest.k8s import resource as k8s +from acktest import tags + +from e2e.tests.helper import EC2Validator + + +REGION = "us-west-2" if environ.get('AWS_DEFAULT_REGION') is None else environ.get('AWS_DEFAULT_REGION') +RESOURCE_PLURAL = "vpcendpointserviceconfigurations" + +CREATE_WAIT_AFTER_SECONDS = 10 +DELETE_WAIT_AFTER_SECONDS = 10 +MODIFY_WAIT_AFTER_SECONDS = 5 + +@pytest.fixture +def vpc_endpoint_service_adoption(): + replacements = REPLACEMENT_VALUES.copy() + resource_name = random_suffix_name("vpc-es-adoption", 24) + service_id = get_bootstrap_resources().AdoptedVpcEndpointService.service_id + assert service_id is not None + + replacements["VPC_ENDPOINT_SERVICE_ADOPTED_NAME"] = resource_name + replacements["ADOPTION_POLICY"] = "adopt" + replacements["ADOPTION_FIELDS"] = f"{{\\\"serviceID\\\": \\\"{service_id}\\\"}}" + + resource_data = load_ec2_resource( + "vpc_endpoint_service_adoption", + additional_replacements=replacements, + ) + logging.debug(resource_data) + + ref = k8s.CustomResourceReference( + CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL, + resource_name, namespace="default", + ) + + k8s.create_custom_resource(ref, resource_data) + time.sleep(CREATE_WAIT_AFTER_SECONDS) + + cr = k8s.wait_resource_consumed_by_controller(ref) + assert cr is not None + assert k8s.get_resource_exists(ref) + + yield (ref, cr) + + _, deleted = k8s.delete_custom_resource(ref, DELETE_WAIT_AFTER_SECONDS) + assert deleted + +@service_marker +@pytest.mark.canary +class TestVpcAdoption: + + def test_vpc_endpoint_service_configuration_adopt_update(self, ec2_client, vpc_endpoint_service_adoption): + (ref, cr) = vpc_endpoint_service_adoption + + time.sleep(CREATE_WAIT_AFTER_SECONDS) + + assert cr is not None + assert 'status' in cr + assert 'serviceID' in cr['status'] + resource_id = cr['status']['serviceID'] + + assert 'spec' in cr + assert 'tags' in cr['spec'] + + # Check VPC Endpoint Service exists in AWS + ec2_validator = EC2Validator(ec2_client) + ec2_validator.assert_vpc_endpoint_service_configuration(resource_id) + + updated_endpoint_service_config = ec2_validator.get_vpc_endpoint_service_configuration(resource_id) + + actual_tags = updated_endpoint_service_config['Tags'] + tags.assert_ack_system_tags(actual_tags) + + name_tag = next((tag for tag in actual_tags if tag['Key'] == 'Name'), None) + assert name_tag is not None + + name_tag = {'key': 'Name', 'value': name_tag['Value']} + new_tag = {'key': 'TestName', 'value': 'test-value'} + updates = { + "spec": {"tags": [name_tag, new_tag]} + } + + k8s.patch_custom_resource(ref, updates) + time.sleep(MODIFY_WAIT_AFTER_SECONDS) + + assert k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=5) + + updated_endpoint_service_config = ec2_validator.get_vpc_endpoint_service_configuration(resource_id) + assert updated_endpoint_service_config is not None + assert 'Tags' in updated_endpoint_service_config + + expected_tags = [{"Key": name_tag['key'], "Value": name_tag['value']}, {"Key": new_tag['key'], "Value": new_tag['value']}] + tags.assert_equal_without_ack_tags( + actual=updated_endpoint_service_config['Tags'], + expected=expected_tags, + ) + + + + +