diff --git a/apis/v1alpha1/ack-generate-metadata.yaml b/apis/v1alpha1/ack-generate-metadata.yaml index 1de87cc7..1f189327 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-09-25T16:19:07Z" - build_hash: 6b4211163dcc34776b01da9a18217bac0f4103fd - go_version: go1.24.6 - version: v0.52.0 -api_directory_checksum: 5a5c93e3d4865ea08d8a47b2500551112ea831b9 + build_date: "2025-10-28T08:49:43Z" + build_hash: eaabefb6bd7b2be8a1baf4478f22b3310e6921c8 + go_version: go1.24.0 + version: v0.52.0-6-geaabefb +api_directory_checksum: b558bf9c1a38a2d20a3521ab1048281ad80d5b29 api_version: v1alpha1 aws_sdk_go_version: v1.32.6 generator_config_info: - file_checksum: 6d72b2e7d053535b6f6966348d3ac4e4535052cd + file_checksum: 32b9b52f3a9103c18e42ef8de5494bd046da56eb original_file_name: generator.yaml last_modification: reason: API generation diff --git a/apis/v1alpha1/generator.yaml b/apis/v1alpha1/generator.yaml index 12377b8a..dc52b657 100644 --- a/apis/v1alpha1/generator.yaml +++ b/apis/v1alpha1/generator.yaml @@ -113,6 +113,9 @@ ignore: - CreateLaunchTemplateInput.ClientToken - CreateLaunchTemplateInput.TagSpecifications - CreateLaunchTemplateVersionInput.LaunchTemplateData.TagSpecifications + - CreateManagedPrefixListInput.ClientToken + - CreateManagedPrefixListInput.TagSpecifications + - CreateManagedPrefixListInput.DryRun - CreateLaunchTemplateVersionOutput.LaunchTemplateVersion.DefaultVersion resource_names: - CapacityReservationBySplitting @@ -150,7 +153,7 @@ ignore: - LocalGatewayRouteTableVpcAssociation - LocalGatewayRouteTableVirtualInterfaceGroupAssociation - LocalGatewayRoute - - ManagedPrefixList + #- ManagedPrefixList #- NatGateway - NetworkAclEntry #- NetworkAcl @@ -608,6 +611,56 @@ resources: template_path: hooks/nat_gateway/sdk_file_end.go.tpl update_operation: custom_method_name: customUpdateNATGateway + ManagedPrefixList: + exceptions: + terminal_codes: + - InvalidParameterValue + - PrefixListMaxEntriesExceeded + fields: + PrefixListId: + is_primary_key: true + is_read_only: true + print: + path: Status.prefixListID + name: ID + PrefixListName: + print: + path: Spec.prefixListName + name: NAME + State: + is_read_only: true + print: + path: Status.state + name: STATE + Version: + is_read_only: true + print: + path: Status.version + name: VERSION + Entries: + custom_field: + list_of: AddPrefixListEntry + compare: + is_ignored: false + Tags: + from: + operation: CreateTags + path: Tags + synced: + when: + - path: Status.State + in: + - create-complete + - modify-complete + hooks: + sdk_create_post_build_request: + template_path: hooks/managed_prefix_list/sdk_create_post_build_request.go.tpl + sdk_read_many_post_build_request: + template_path: hooks/managed_prefix_list/sdk_read_many_post_build_request.go.tpl + sdk_read_many_post_set_output: + template_path: hooks/managed_prefix_list/sdk_read_many_post_set_output.go.tpl + update_operation: + custom_method_name: customUpdateManagedPrefixList RouteTable: fields: # RouteStatuses as Route to ensure diff --git a/apis/v1alpha1/managed_prefix_list.go b/apis/v1alpha1/managed_prefix_list.go new file mode 100644 index 00000000..706ee473 --- /dev/null +++ b/apis/v1alpha1/managed_prefix_list.go @@ -0,0 +1,105 @@ +// 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. + +// Code generated by ack-generate. DO NOT EDIT. + +package v1alpha1 + +import ( + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ManagedPrefixListSpec defines the desired state of ManagedPrefixList. +// +// Describes a managed prefix list. +type ManagedPrefixListSpec struct { + + // The IP address type. + // + // Valid Values: IPv4 | IPv6 + // +kubebuilder:validation:Required + AddressFamily *string `json:"addressFamily"` + Entries []*AddPrefixListEntry `json:"entries,omitempty"` + // The maximum number of entries for the prefix list. + // +kubebuilder:validation:Required + MaxEntries *int64 `json:"maxEntries"` + // A name for the prefix list. + // + // Constraints: Up to 255 characters in length. The name cannot start with com.amazonaws. + // +kubebuilder:validation:Required + PrefixListName *string `json:"prefixListName"` + // The tags. The value parameter is required, but if you don't want the tag + // to have a value, specify the parameter with no value, and we set the value + // to an empty string. + Tags []*Tag `json:"tags,omitempty"` +} + +// ManagedPrefixListStatus defines the observed state of ManagedPrefixList +type ManagedPrefixListStatus struct { + // All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + // that is used to contain resource sync state, account ownership, + // constructed ARN for the resource + // +kubebuilder:validation:Optional + ACKResourceMetadata *ackv1alpha1.ResourceMetadata `json:"ackResourceMetadata"` + // All CRs managed by ACK have a common `Status.Conditions` member that + // contains a collection of `ackv1alpha1.Condition` objects that describe + // the various terminal states of the CR and its backend AWS service API + // resource + // +kubebuilder:validation:Optional + Conditions []*ackv1alpha1.Condition `json:"conditions"` + // The ID of the owner of the prefix list. + // +kubebuilder:validation:Optional + OwnerID *string `json:"ownerID,omitempty"` + // The Amazon Resource Name (ARN) for the prefix list. + // +kubebuilder:validation:Optional + PrefixListARN *string `json:"prefixListARN,omitempty"` + // The ID of the prefix list. + // +kubebuilder:validation:Optional + PrefixListID *string `json:"prefixListID,omitempty"` + // The current state of the prefix list. + // +kubebuilder:validation:Optional + State *string `json:"state,omitempty"` + // The state message. + // +kubebuilder:validation:Optional + StateMessage *string `json:"stateMessage,omitempty"` + // The version of the prefix list. + // +kubebuilder:validation:Optional + Version *int64 `json:"version,omitempty"` +} + +// ManagedPrefixList is the Schema for the ManagedPrefixLists API +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="ID",type=string,priority=0,JSONPath=`.status.prefixListID` +// +kubebuilder:printcolumn:name="NAME",type=string,priority=0,JSONPath=`.spec.prefixListName` +// +kubebuilder:printcolumn:name="STATE",type=string,priority=0,JSONPath=`.status.state` +// +kubebuilder:printcolumn:name="VERSION",type=integer,priority=0,JSONPath=`.status.version` +type ManagedPrefixList struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec ManagedPrefixListSpec `json:"spec,omitempty"` + Status ManagedPrefixListStatus `json:"status,omitempty"` +} + +// ManagedPrefixListList contains a list of ManagedPrefixList +// +kubebuilder:object:root=true +type ManagedPrefixListList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ManagedPrefixList `json:"items"` +} + +func init() { + SchemeBuilder.Register(&ManagedPrefixList{}, &ManagedPrefixListList{}) +} diff --git a/apis/v1alpha1/types.go b/apis/v1alpha1/types.go index aa04695e..f8b4313f 100644 --- a/apis/v1alpha1/types.go +++ b/apis/v1alpha1/types.go @@ -218,6 +218,7 @@ type AnalysisPacketHeader struct { // Describes a route table route. type AnalysisRouteTableRoute struct { CarrierGatewayID *string `json:"carrierGatewayID,omitempty"` + CoreNetworkARN *string `json:"coreNetworkARN,omitempty"` DestinationCIDR *string `json:"destinationCIDR,omitempty"` DestinationPrefixListID *string `json:"destinationPrefixListID,omitempty"` EgressOnlyInternetGatewayID *string `json:"egressOnlyInternetGatewayID,omitempty"` @@ -250,6 +251,7 @@ type AssignedPrivateIPAddress struct { // Information about the associated IAM roles. type AssociatedRole struct { + AssociatedRoleARN *string `json:"associatedRoleARN,omitempty"` CertificateS3BucketName *string `json:"certificateS3BucketName,omitempty"` CertificateS3ObjectKey *string `json:"certificateS3ObjectKey,omitempty"` EncryptionKMSKeyID *string `json:"encryptionKMSKeyID,omitempty"` @@ -840,6 +842,7 @@ type CoipCIDR struct { // Describes a customer-owned address pool. type CoipPool struct { + PoolARN *string `json:"poolARN,omitempty"` PoolCIDRs []*string `json:"poolCIDRs,omitempty"` Tags []*Tag `json:"tags,omitempty"` } @@ -1284,16 +1287,17 @@ type EBSStatusDetails struct { // The EC2 Instance Connect Endpoint. type EC2InstanceConnectEndpoint struct { - AvailabilityZone *string `json:"availabilityZone,omitempty"` - CreatedAt *metav1.Time `json:"createdAt,omitempty"` - DNSName *string `json:"dnsName,omitempty"` - FipsDNSName *string `json:"fipsDNSName,omitempty"` - OwnerID *string `json:"ownerID,omitempty"` - PreserveClientIP *bool `json:"preserveClientIP,omitempty"` - StateMessage *string `json:"stateMessage,omitempty"` - SubnetID *string `json:"subnetID,omitempty"` - Tags []*Tag `json:"tags,omitempty"` - VPCID *string `json:"vpcID,omitempty"` + AvailabilityZone *string `json:"availabilityZone,omitempty"` + CreatedAt *metav1.Time `json:"createdAt,omitempty"` + DNSName *string `json:"dnsName,omitempty"` + FipsDNSName *string `json:"fipsDNSName,omitempty"` + InstanceConnectEndpointARN *string `json:"instanceConnectEndpointARN,omitempty"` + OwnerID *string `json:"ownerID,omitempty"` + PreserveClientIP *bool `json:"preserveClientIP,omitempty"` + StateMessage *string `json:"stateMessage,omitempty"` + SubnetID *string `json:"subnetID,omitempty"` + Tags []*Tag `json:"tags,omitempty"` + VPCID *string `json:"vpcID,omitempty"` } // ENA Express uses Amazon Web Services Scalable Reliable Datagram (SRD) technology @@ -1447,6 +1451,7 @@ type Explanation struct { CIDRs []*string `json:"cidrs,omitempty"` Direction *string `json:"direction,omitempty"` ExplanationCode *string `json:"explanationCode,omitempty"` + LoadBalancerARN *string `json:"loadBalancerARN,omitempty"` MissingComponent *string `json:"missingComponent,omitempty"` PacketField *string `json:"packetField,omitempty"` Protocols []*string `json:"protocols,omitempty"` @@ -1590,6 +1595,7 @@ type FirewallStatefulRule struct { Direction *string `json:"direction,omitempty"` Protocol *string `json:"protocol,omitempty"` RuleAction *string `json:"ruleAction,omitempty"` + RuleGroupARN *string `json:"ruleGroupARN,omitempty"` Sources []*string `json:"sources,omitempty"` } @@ -1597,6 +1603,7 @@ type FirewallStatefulRule struct { type FirewallStatelessRule struct { Destinations []*string `json:"destinations,omitempty"` RuleAction *string `json:"ruleAction,omitempty"` + RuleGroupARN *string `json:"ruleGroupARN,omitempty"` Sources []*string `json:"sources,omitempty"` } @@ -1917,6 +1924,7 @@ type IKEVersionsRequestListValue struct { type IPAM struct { Description *string `json:"description,omitempty"` EnablePrivateGua *bool `json:"enablePrivateGua,omitempty"` + IPAMARN *string `json:"ipamARN,omitempty"` IPAMRegion *string `json:"ipamRegion,omitempty"` OwnerID *string `json:"ownerID,omitempty"` ResourceDiscoveryAssociationCount *int64 `json:"resourceDiscoveryAssociationCount,omitempty"` @@ -2001,11 +2009,13 @@ type IPAMDiscoveryFailureReason struct { // can use a verification token to validate that you control a public IP address // range when you bring an IP address range to Amazon Web Services (BYOIP). type IPAMExternalResourceVerificationToken struct { - IPAMRegion *string `json:"ipamRegion,omitempty"` - NotAfter *metav1.Time `json:"notAfter,omitempty"` - Tags []*Tag `json:"tags,omitempty"` - TokenName *string `json:"tokenName,omitempty"` - TokenValue *string `json:"tokenValue,omitempty"` + IPAMARN *string `json:"ipamARN,omitempty"` + IPAMExternalResourceVerificationTokenARN *string `json:"ipamExternalResourceVerificationTokenARN,omitempty"` + IPAMRegion *string `json:"ipamRegion,omitempty"` + NotAfter *metav1.Time `json:"notAfter,omitempty"` + Tags []*Tag `json:"tags,omitempty"` + TokenName *string `json:"tokenName,omitempty"` + TokenValue *string `json:"tokenValue,omitempty"` } // The operating Regions for an IPAM. Operating Regions are Amazon Web Services @@ -2033,8 +2043,11 @@ type IPAMOrganizationalUnitExclusion struct { type IPAMPool struct { AutoImport *bool `json:"autoImport,omitempty"` Description *string `json:"description,omitempty"` + IPAMARN *string `json:"ipamARN,omitempty"` + IPAMPoolARN *string `json:"ipamPoolARN,omitempty"` IPAMPoolID *string `json:"ipamPoolID,omitempty"` IPAMRegion *string `json:"ipamRegion,omitempty"` + IPAMScopeARN *string `json:"ipamScopeARN,omitempty"` Locale *string `json:"locale,omitempty"` OwnerID *string `json:"ownerID,omitempty"` PoolDepth *int64 `json:"poolDepth,omitempty"` @@ -2118,6 +2131,7 @@ type IPAMResourceDiscovery struct { // is a resource discovery that has been associated with an IPAM. IPAM aggregates // the resource CIDRs discovered by the associated resource discovery. type IPAMResourceDiscoveryAssociation struct { + IPAMARN *string `json:"ipamARN,omitempty"` IPAMRegion *string `json:"ipamRegion,omitempty"` IPAMResourceDiscoveryAssociationARN *string `json:"ipamResourceDiscoveryAssociationARN,omitempty"` IsDefault *bool `json:"isDefault,omitempty"` @@ -2144,12 +2158,14 @@ type IPAMResourceTag struct { // For more information, see How IPAM works (https://docs.aws.amazon.com/vpc/latest/ipam/how-it-works-ipam.html) // in the Amazon VPC IPAM User Guide. type IPAMScope struct { - Description *string `json:"description,omitempty"` - IPAMRegion *string `json:"ipamRegion,omitempty"` - IsDefault *bool `json:"isDefault,omitempty"` - OwnerID *string `json:"ownerID,omitempty"` - PoolCount *int64 `json:"poolCount,omitempty"` - Tags []*Tag `json:"tags,omitempty"` + Description *string `json:"description,omitempty"` + IPAMARN *string `json:"ipamARN,omitempty"` + IPAMRegion *string `json:"ipamRegion,omitempty"` + IPAMScopeARN *string `json:"ipamScopeARN,omitempty"` + IsDefault *bool `json:"isDefault,omitempty"` + OwnerID *string `json:"ownerID,omitempty"` + PoolCount *int64 `json:"poolCount,omitempty"` + Tags []*Tag `json:"tags,omitempty"` } // Describes the permissions for a security group rule. @@ -3571,21 +3587,23 @@ type LocalGateway struct { // Describes a route for a local gateway route table. type LocalGatewayRoute struct { - CoipPoolID *string `json:"coipPoolID,omitempty"` - DestinationCIDRBlock *string `json:"destinationCIDRBlock,omitempty"` - DestinationPrefixListID *string `json:"destinationPrefixListID,omitempty"` - NetworkInterfaceID *string `json:"networkInterfaceID,omitempty"` - OwnerID *string `json:"ownerID,omitempty"` - SubnetID *string `json:"subnetID,omitempty"` + CoipPoolID *string `json:"coipPoolID,omitempty"` + DestinationCIDRBlock *string `json:"destinationCIDRBlock,omitempty"` + DestinationPrefixListID *string `json:"destinationPrefixListID,omitempty"` + LocalGatewayRouteTableARN *string `json:"localGatewayRouteTableARN,omitempty"` + NetworkInterfaceID *string `json:"networkInterfaceID,omitempty"` + OwnerID *string `json:"ownerID,omitempty"` + SubnetID *string `json:"subnetID,omitempty"` } // Describes a local gateway route table. type LocalGatewayRouteTable struct { - LocalGatewayID *string `json:"localGatewayID,omitempty"` - LocalGatewayRouteTableID *string `json:"localGatewayRouteTableID,omitempty"` - OutpostARN *string `json:"outpostARN,omitempty"` - OwnerID *string `json:"ownerID,omitempty"` - State *string `json:"state,omitempty"` + LocalGatewayID *string `json:"localGatewayID,omitempty"` + LocalGatewayRouteTableARN *string `json:"localGatewayRouteTableARN,omitempty"` + LocalGatewayRouteTableID *string `json:"localGatewayRouteTableID,omitempty"` + OutpostARN *string `json:"outpostARN,omitempty"` + OwnerID *string `json:"ownerID,omitempty"` + State *string `json:"state,omitempty"` // Describes a state change. StateReason *StateReason `json:"stateReason,omitempty"` Tags []*Tag `json:"tags,omitempty"` @@ -3593,22 +3611,24 @@ type LocalGatewayRouteTable struct { // Describes an association between a local gateway route table and a VPC. type LocalGatewayRouteTableVPCAssociation struct { - LocalGatewayID *string `json:"localGatewayID,omitempty"` - LocalGatewayRouteTableID *string `json:"localGatewayRouteTableID,omitempty"` - OwnerID *string `json:"ownerID,omitempty"` - State *string `json:"state,omitempty"` - Tags []*Tag `json:"tags,omitempty"` - VPCID *string `json:"vpcID,omitempty"` + LocalGatewayID *string `json:"localGatewayID,omitempty"` + LocalGatewayRouteTableARN *string `json:"localGatewayRouteTableARN,omitempty"` + LocalGatewayRouteTableID *string `json:"localGatewayRouteTableID,omitempty"` + OwnerID *string `json:"ownerID,omitempty"` + State *string `json:"state,omitempty"` + Tags []*Tag `json:"tags,omitempty"` + VPCID *string `json:"vpcID,omitempty"` } // Describes an association between a local gateway route table and a virtual // interface group. type LocalGatewayRouteTableVirtualInterfaceGroupAssociation struct { - LocalGatewayID *string `json:"localGatewayID,omitempty"` - LocalGatewayRouteTableID *string `json:"localGatewayRouteTableID,omitempty"` - OwnerID *string `json:"ownerID,omitempty"` - State *string `json:"state,omitempty"` - Tags []*Tag `json:"tags,omitempty"` + LocalGatewayID *string `json:"localGatewayID,omitempty"` + LocalGatewayRouteTableARN *string `json:"localGatewayRouteTableARN,omitempty"` + LocalGatewayRouteTableID *string `json:"localGatewayRouteTableID,omitempty"` + OwnerID *string `json:"ownerID,omitempty"` + State *string `json:"state,omitempty"` + Tags []*Tag `json:"tags,omitempty"` } // Describes a local gateway virtual interface. @@ -3653,12 +3673,14 @@ type MaintenanceDetails struct { } // Describes a managed prefix list. -type ManagedPrefixList struct { +type ManagedPrefixList_SDK struct { AddressFamily *string `json:"addressFamily,omitempty"` MaxEntries *int64 `json:"maxEntries,omitempty"` OwnerID *string `json:"ownerID,omitempty"` + PrefixListARN *string `json:"prefixListARN,omitempty"` PrefixListID *string `json:"prefixListID,omitempty"` PrefixListName *string `json:"prefixListName,omitempty"` + State *string `json:"state,omitempty"` StateMessage *string `json:"stateMessage,omitempty"` Tags []*Tag `json:"tags,omitempty"` Version *int64 `json:"version,omitempty"` @@ -3879,39 +3901,45 @@ type NetworkBandwidthGbpsRequest struct { // Describes a Network Access Scope. type NetworkInsightsAccessScope struct { - CreatedDate *metav1.Time `json:"createdDate,omitempty"` - Tags []*Tag `json:"tags,omitempty"` - UpdatedDate *metav1.Time `json:"updatedDate,omitempty"` + CreatedDate *metav1.Time `json:"createdDate,omitempty"` + NetworkInsightsAccessScopeARN *string `json:"networkInsightsAccessScopeARN,omitempty"` + Tags []*Tag `json:"tags,omitempty"` + UpdatedDate *metav1.Time `json:"updatedDate,omitempty"` } // Describes a Network Access Scope analysis. type NetworkInsightsAccessScopeAnalysis struct { - AnalyzedEniCount *int64 `json:"analyzedEniCount,omitempty"` - EndDate *metav1.Time `json:"endDate,omitempty"` - StartDate *metav1.Time `json:"startDate,omitempty"` - StatusMessage *string `json:"statusMessage,omitempty"` - Tags []*Tag `json:"tags,omitempty"` - WarningMessage *string `json:"warningMessage,omitempty"` + AnalyzedEniCount *int64 `json:"analyzedEniCount,omitempty"` + EndDate *metav1.Time `json:"endDate,omitempty"` + NetworkInsightsAccessScopeAnalysisARN *string `json:"networkInsightsAccessScopeAnalysisARN,omitempty"` + StartDate *metav1.Time `json:"startDate,omitempty"` + StatusMessage *string `json:"statusMessage,omitempty"` + Tags []*Tag `json:"tags,omitempty"` + WarningMessage *string `json:"warningMessage,omitempty"` } // Describes a network insights analysis. type NetworkInsightsAnalysis struct { - AdditionalAccounts []*string `json:"additionalAccounts,omitempty"` - NetworkPathFound *bool `json:"networkPathFound,omitempty"` - StartDate *metav1.Time `json:"startDate,omitempty"` - StatusMessage *string `json:"statusMessage,omitempty"` - SuggestedAccounts []*string `json:"suggestedAccounts,omitempty"` - Tags []*Tag `json:"tags,omitempty"` - WarningMessage *string `json:"warningMessage,omitempty"` + AdditionalAccounts []*string `json:"additionalAccounts,omitempty"` + NetworkInsightsAnalysisARN *string `json:"networkInsightsAnalysisARN,omitempty"` + NetworkPathFound *bool `json:"networkPathFound,omitempty"` + StartDate *metav1.Time `json:"startDate,omitempty"` + StatusMessage *string `json:"statusMessage,omitempty"` + SuggestedAccounts []*string `json:"suggestedAccounts,omitempty"` + Tags []*Tag `json:"tags,omitempty"` + WarningMessage *string `json:"warningMessage,omitempty"` } // Describes a path. type NetworkInsightsPath struct { - CreatedDate *metav1.Time `json:"createdDate,omitempty"` - Destination *string `json:"destination,omitempty"` - DestinationPort *int64 `json:"destinationPort,omitempty"` - Source *string `json:"source,omitempty"` - Tags []*Tag `json:"tags,omitempty"` + CreatedDate *metav1.Time `json:"createdDate,omitempty"` + Destination *string `json:"destination,omitempty"` + DestinationARN *string `json:"destinationARN,omitempty"` + DestinationPort *int64 `json:"destinationPort,omitempty"` + NetworkInsightsPathARN *string `json:"networkInsightsPathARN,omitempty"` + Source *string `json:"source,omitempty"` + SourceARN *string `json:"sourceARN,omitempty"` + Tags []*Tag `json:"tags,omitempty"` } // Describes a network interface. @@ -4838,8 +4866,14 @@ type RouteTable_SDK struct { VPCID *string `json:"vpcID,omitempty"` } +// Describes the rule options for a stateful rule group. +type RuleGroupRuleOptionsPair struct { + RuleGroupARN *string `json:"ruleGroupARN,omitempty"` +} + // Describes the type of a stateful rule group. type RuleGroupTypePair struct { + RuleGroupARN *string `json:"ruleGroupARN,omitempty"` RuleGroupType *string `json:"ruleGroupType,omitempty"` } @@ -6141,6 +6175,7 @@ type VPCBlockPublicAccessExclusion struct { DeletionTimestamp *metav1.Time `json:"deletionTimestamp,omitempty"` LastUpdateTimestamp *metav1.Time `json:"lastUpdateTimestamp,omitempty"` Reason *string `json:"reason,omitempty"` + ResourceARN *string `json:"resourceARN,omitempty"` Tags []*Tag `json:"tags,omitempty"` } diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index d388a7fd..3678dcb4 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -737,6 +737,11 @@ func (in *AnalysisRouteTableRoute) DeepCopyInto(out *AnalysisRouteTableRoute) { *out = new(string) **out = **in } + if in.CoreNetworkARN != nil { + in, out := &in.CoreNetworkARN, &out.CoreNetworkARN + *out = new(string) + **out = **in + } if in.DestinationCIDR != nil { in, out := &in.DestinationCIDR, &out.DestinationCIDR *out = new(string) @@ -877,6 +882,11 @@ func (in *AssignedPrivateIPAddress) DeepCopy() *AssignedPrivateIPAddress { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AssociatedRole) DeepCopyInto(out *AssociatedRole) { *out = *in + if in.AssociatedRoleARN != nil { + in, out := &in.AssociatedRoleARN, &out.AssociatedRoleARN + *out = new(string) + **out = **in + } if in.CertificateS3BucketName != nil { in, out := &in.CertificateS3BucketName, &out.CertificateS3BucketName *out = new(string) @@ -3386,6 +3396,11 @@ func (in *CoipCIDR) DeepCopy() *CoipCIDR { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CoipPool) DeepCopyInto(out *CoipPool) { *out = *in + if in.PoolARN != nil { + in, out := &in.PoolARN, &out.PoolARN + *out = new(string) + **out = **in + } if in.PoolCIDRs != nil { in, out := &in.PoolCIDRs, &out.PoolCIDRs *out = make([]*string, len(*in)) @@ -5394,6 +5409,11 @@ func (in *EC2InstanceConnectEndpoint) DeepCopyInto(out *EC2InstanceConnectEndpoi *out = new(string) **out = **in } + if in.InstanceConnectEndpointARN != nil { + in, out := &in.InstanceConnectEndpointARN, &out.InstanceConnectEndpointARN + *out = new(string) + **out = **in + } if in.OwnerID != nil { in, out := &in.OwnerID, &out.OwnerID *out = new(string) @@ -6110,6 +6130,11 @@ func (in *Explanation) DeepCopyInto(out *Explanation) { *out = new(string) **out = **in } + if in.LoadBalancerARN != nil { + in, out := &in.LoadBalancerARN, &out.LoadBalancerARN + *out = new(string) + **out = **in + } if in.MissingComponent != nil { in, out := &in.MissingComponent, &out.MissingComponent *out = new(string) @@ -6726,6 +6751,11 @@ func (in *FirewallStatefulRule) DeepCopyInto(out *FirewallStatefulRule) { *out = new(string) **out = **in } + if in.RuleGroupARN != nil { + in, out := &in.RuleGroupARN, &out.RuleGroupARN + *out = new(string) + **out = **in + } if in.Sources != nil { in, out := &in.Sources, &out.Sources *out = make([]*string, len(*in)) @@ -6768,6 +6798,11 @@ func (in *FirewallStatelessRule) DeepCopyInto(out *FirewallStatelessRule) { *out = new(string) **out = **in } + if in.RuleGroupARN != nil { + in, out := &in.RuleGroupARN, &out.RuleGroupARN + *out = new(string) + **out = **in + } if in.Sources != nil { in, out := &in.Sources, &out.Sources *out = make([]*string, len(*in)) @@ -7952,6 +7987,11 @@ func (in *IPAM) DeepCopyInto(out *IPAM) { *out = new(bool) **out = **in } + if in.IPAMARN != nil { + in, out := &in.IPAMARN, &out.IPAMARN + *out = new(string) + **out = **in + } if in.IPAMRegion != nil { in, out := &in.IPAMRegion, &out.IPAMRegion *out = new(string) @@ -8272,6 +8312,16 @@ func (in *IPAMDiscoveryFailureReason) DeepCopy() *IPAMDiscoveryFailureReason { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPAMExternalResourceVerificationToken) DeepCopyInto(out *IPAMExternalResourceVerificationToken) { *out = *in + if in.IPAMARN != nil { + in, out := &in.IPAMARN, &out.IPAMARN + *out = new(string) + **out = **in + } + if in.IPAMExternalResourceVerificationTokenARN != nil { + in, out := &in.IPAMExternalResourceVerificationTokenARN, &out.IPAMExternalResourceVerificationTokenARN + *out = new(string) + **out = **in + } if in.IPAMRegion != nil { in, out := &in.IPAMRegion, &out.IPAMRegion *out = new(string) @@ -8367,6 +8417,16 @@ func (in *IPAMPool) DeepCopyInto(out *IPAMPool) { *out = new(string) **out = **in } + if in.IPAMARN != nil { + in, out := &in.IPAMARN, &out.IPAMARN + *out = new(string) + **out = **in + } + if in.IPAMPoolARN != nil { + in, out := &in.IPAMPoolARN, &out.IPAMPoolARN + *out = new(string) + **out = **in + } if in.IPAMPoolID != nil { in, out := &in.IPAMPoolID, &out.IPAMPoolID *out = new(string) @@ -8377,6 +8437,11 @@ func (in *IPAMPool) DeepCopyInto(out *IPAMPool) { *out = new(string) **out = **in } + if in.IPAMScopeARN != nil { + in, out := &in.IPAMScopeARN, &out.IPAMScopeARN + *out = new(string) + **out = **in + } if in.Locale != nil { in, out := &in.Locale, &out.Locale *out = new(string) @@ -8734,6 +8799,11 @@ func (in *IPAMResourceDiscovery) DeepCopy() *IPAMResourceDiscovery { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPAMResourceDiscoveryAssociation) DeepCopyInto(out *IPAMResourceDiscoveryAssociation) { *out = *in + if in.IPAMARN != nil { + in, out := &in.IPAMARN, &out.IPAMARN + *out = new(string) + **out = **in + } if in.IPAMRegion != nil { in, out := &in.IPAMRegion, &out.IPAMRegion *out = new(string) @@ -8810,11 +8880,21 @@ func (in *IPAMScope) DeepCopyInto(out *IPAMScope) { *out = new(string) **out = **in } + if in.IPAMARN != nil { + in, out := &in.IPAMARN, &out.IPAMARN + *out = new(string) + **out = **in + } if in.IPAMRegion != nil { in, out := &in.IPAMRegion, &out.IPAMRegion *out = new(string) **out = **in } + if in.IPAMScopeARN != nil { + in, out := &in.IPAMScopeARN, &out.IPAMScopeARN + *out = new(string) + **out = **in + } if in.IsDefault != nil { in, out := &in.IsDefault, &out.IsDefault *out = new(bool) @@ -14849,6 +14929,11 @@ func (in *LocalGatewayRoute) DeepCopyInto(out *LocalGatewayRoute) { *out = new(string) **out = **in } + if in.LocalGatewayRouteTableARN != nil { + in, out := &in.LocalGatewayRouteTableARN, &out.LocalGatewayRouteTableARN + *out = new(string) + **out = **in + } if in.NetworkInterfaceID != nil { in, out := &in.NetworkInterfaceID, &out.NetworkInterfaceID *out = new(string) @@ -14884,6 +14969,11 @@ func (in *LocalGatewayRouteTable) DeepCopyInto(out *LocalGatewayRouteTable) { *out = new(string) **out = **in } + if in.LocalGatewayRouteTableARN != nil { + in, out := &in.LocalGatewayRouteTableARN, &out.LocalGatewayRouteTableARN + *out = new(string) + **out = **in + } if in.LocalGatewayRouteTableID != nil { in, out := &in.LocalGatewayRouteTableID, &out.LocalGatewayRouteTableID *out = new(string) @@ -14940,6 +15030,11 @@ func (in *LocalGatewayRouteTableVPCAssociation) DeepCopyInto(out *LocalGatewayRo *out = new(string) **out = **in } + if in.LocalGatewayRouteTableARN != nil { + in, out := &in.LocalGatewayRouteTableARN, &out.LocalGatewayRouteTableARN + *out = new(string) + **out = **in + } if in.LocalGatewayRouteTableID != nil { in, out := &in.LocalGatewayRouteTableID, &out.LocalGatewayRouteTableID *out = new(string) @@ -14991,6 +15086,11 @@ func (in *LocalGatewayRouteTableVirtualInterfaceGroupAssociation) DeepCopyInto(o *out = new(string) **out = **in } + if in.LocalGatewayRouteTableARN != nil { + in, out := &in.LocalGatewayRouteTableARN, &out.LocalGatewayRouteTableARN + *out = new(string) + **out = **in + } if in.LocalGatewayRouteTableID != nil { in, out := &in.LocalGatewayRouteTableID, &out.LocalGatewayRouteTableID *out = new(string) @@ -15217,6 +15317,178 @@ func (in *MaintenanceDetails) DeepCopy() *MaintenanceDetails { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ManagedPrefixList) DeepCopyInto(out *ManagedPrefixList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagedPrefixList. +func (in *ManagedPrefixList) DeepCopy() *ManagedPrefixList { + if in == nil { + return nil + } + out := new(ManagedPrefixList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ManagedPrefixList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ManagedPrefixListList) DeepCopyInto(out *ManagedPrefixListList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ManagedPrefixList, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagedPrefixListList. +func (in *ManagedPrefixListList) DeepCopy() *ManagedPrefixListList { + if in == nil { + return nil + } + out := new(ManagedPrefixListList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ManagedPrefixListList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ManagedPrefixListSpec) DeepCopyInto(out *ManagedPrefixListSpec) { + *out = *in + if in.AddressFamily != nil { + in, out := &in.AddressFamily, &out.AddressFamily + *out = new(string) + **out = **in + } + if in.Entries != nil { + in, out := &in.Entries, &out.Entries + *out = make([]*AddPrefixListEntry, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(AddPrefixListEntry) + (*in).DeepCopyInto(*out) + } + } + } + if in.MaxEntries != nil { + in, out := &in.MaxEntries, &out.MaxEntries + *out = new(int64) + **out = **in + } + if in.PrefixListName != nil { + in, out := &in.PrefixListName, &out.PrefixListName + *out = new(string) + **out = **in + } + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]*Tag, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Tag) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagedPrefixListSpec. +func (in *ManagedPrefixListSpec) DeepCopy() *ManagedPrefixListSpec { + if in == nil { + return nil + } + out := new(ManagedPrefixListSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ManagedPrefixListStatus) DeepCopyInto(out *ManagedPrefixListStatus) { + *out = *in + if in.ACKResourceMetadata != nil { + in, out := &in.ACKResourceMetadata, &out.ACKResourceMetadata + *out = new(corev1alpha1.ResourceMetadata) + (*in).DeepCopyInto(*out) + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]*corev1alpha1.Condition, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(corev1alpha1.Condition) + (*in).DeepCopyInto(*out) + } + } + } + if in.OwnerID != nil { + in, out := &in.OwnerID, &out.OwnerID + *out = new(string) + **out = **in + } + if in.PrefixListARN != nil { + in, out := &in.PrefixListARN, &out.PrefixListARN + *out = new(string) + **out = **in + } + if in.PrefixListID != nil { + in, out := &in.PrefixListID, &out.PrefixListID + *out = new(string) + **out = **in + } + if in.State != nil { + in, out := &in.State, &out.State + *out = new(string) + **out = **in + } + if in.StateMessage != nil { + in, out := &in.StateMessage, &out.StateMessage + *out = new(string) + **out = **in + } + if in.Version != nil { + in, out := &in.Version, &out.Version + *out = new(int64) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagedPrefixListStatus. +func (in *ManagedPrefixListStatus) DeepCopy() *ManagedPrefixListStatus { + if in == nil { + return nil + } + out := new(ManagedPrefixListStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ManagedPrefixList_SDK) DeepCopyInto(out *ManagedPrefixList_SDK) { *out = *in if in.AddressFamily != nil { in, out := &in.AddressFamily, &out.AddressFamily @@ -15233,6 +15505,11 @@ func (in *ManagedPrefixList) DeepCopyInto(out *ManagedPrefixList) { *out = new(string) **out = **in } + if in.PrefixListARN != nil { + in, out := &in.PrefixListARN, &out.PrefixListARN + *out = new(string) + **out = **in + } if in.PrefixListID != nil { in, out := &in.PrefixListID, &out.PrefixListID *out = new(string) @@ -15243,6 +15520,11 @@ func (in *ManagedPrefixList) DeepCopyInto(out *ManagedPrefixList) { *out = new(string) **out = **in } + if in.State != nil { + in, out := &in.State, &out.State + *out = new(string) + **out = **in + } if in.StateMessage != nil { in, out := &in.StateMessage, &out.StateMessage *out = new(string) @@ -15266,12 +15548,12 @@ func (in *ManagedPrefixList) DeepCopyInto(out *ManagedPrefixList) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagedPrefixList. -func (in *ManagedPrefixList) DeepCopy() *ManagedPrefixList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagedPrefixList_SDK. +func (in *ManagedPrefixList_SDK) DeepCopy() *ManagedPrefixList_SDK { if in == nil { return nil } - out := new(ManagedPrefixList) + out := new(ManagedPrefixList_SDK) in.DeepCopyInto(out) return out } @@ -16494,6 +16776,11 @@ func (in *NetworkInsightsAccessScope) DeepCopyInto(out *NetworkInsightsAccessSco in, out := &in.CreatedDate, &out.CreatedDate *out = (*in).DeepCopy() } + if in.NetworkInsightsAccessScopeARN != nil { + in, out := &in.NetworkInsightsAccessScopeARN, &out.NetworkInsightsAccessScopeARN + *out = new(string) + **out = **in + } if in.Tags != nil { in, out := &in.Tags, &out.Tags *out = make([]*Tag, len(*in)) @@ -16533,6 +16820,11 @@ func (in *NetworkInsightsAccessScopeAnalysis) DeepCopyInto(out *NetworkInsightsA in, out := &in.EndDate, &out.EndDate *out = (*in).DeepCopy() } + if in.NetworkInsightsAccessScopeAnalysisARN != nil { + in, out := &in.NetworkInsightsAccessScopeAnalysisARN, &out.NetworkInsightsAccessScopeAnalysisARN + *out = new(string) + **out = **in + } if in.StartDate != nil { in, out := &in.StartDate, &out.StartDate *out = (*in).DeepCopy() @@ -16584,6 +16876,11 @@ func (in *NetworkInsightsAnalysis) DeepCopyInto(out *NetworkInsightsAnalysis) { } } } + if in.NetworkInsightsAnalysisARN != nil { + in, out := &in.NetworkInsightsAnalysisARN, &out.NetworkInsightsAnalysisARN + *out = new(string) + **out = **in + } if in.NetworkPathFound != nil { in, out := &in.NetworkPathFound, &out.NetworkPathFound *out = new(bool) @@ -16649,16 +16946,31 @@ func (in *NetworkInsightsPath) DeepCopyInto(out *NetworkInsightsPath) { *out = new(string) **out = **in } + if in.DestinationARN != nil { + in, out := &in.DestinationARN, &out.DestinationARN + *out = new(string) + **out = **in + } if in.DestinationPort != nil { in, out := &in.DestinationPort, &out.DestinationPort *out = new(int64) **out = **in } + if in.NetworkInsightsPathARN != nil { + in, out := &in.NetworkInsightsPathARN, &out.NetworkInsightsPathARN + *out = new(string) + **out = **in + } if in.Source != nil { in, out := &in.Source, &out.Source *out = new(string) **out = **in } + if in.SourceARN != nil { + in, out := &in.SourceARN, &out.SourceARN + *out = new(string) + **out = **in + } if in.Tags != nil { in, out := &in.Tags, &out.Tags *out = make([]*Tag, len(*in)) @@ -20509,9 +20821,34 @@ func (in *RouteTable_SDK) DeepCopy() *RouteTable_SDK { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RuleGroupRuleOptionsPair) DeepCopyInto(out *RuleGroupRuleOptionsPair) { + *out = *in + if in.RuleGroupARN != nil { + in, out := &in.RuleGroupARN, &out.RuleGroupARN + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuleGroupRuleOptionsPair. +func (in *RuleGroupRuleOptionsPair) DeepCopy() *RuleGroupRuleOptionsPair { + if in == nil { + return nil + } + out := new(RuleGroupRuleOptionsPair) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RuleGroupTypePair) DeepCopyInto(out *RuleGroupTypePair) { *out = *in + if in.RuleGroupARN != nil { + in, out := &in.RuleGroupARN, &out.RuleGroupARN + *out = new(string) + **out = **in + } if in.RuleGroupType != nil { in, out := &in.RuleGroupType, &out.RuleGroupType *out = new(string) @@ -26727,6 +27064,11 @@ func (in *VPCBlockPublicAccessExclusion) DeepCopyInto(out *VPCBlockPublicAccessE *out = new(string) **out = **in } + if in.ResourceARN != nil { + in, out := &in.ResourceARN, &out.ResourceARN + *out = new(string) + **out = **in + } if in.Tags != nil { in, out := &in.Tags, &out.Tags *out = make([]*Tag, len(*in)) diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 91696d06..05ff1261 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -46,6 +46,7 @@ import ( _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/instance" _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/internet_gateway" _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/launch_template" + _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/managed_prefix_list" _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/nat_gateway" _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/network_acl" _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/route_table" diff --git a/config/crd/bases/ec2.services.k8s.aws_managedprefixlists.yaml b/config/crd/bases/ec2.services.k8s.aws_managedprefixlists.yaml new file mode 100644 index 00000000..8dbfc9bd --- /dev/null +++ b/config/crd/bases/ec2.services.k8s.aws_managedprefixlists.yaml @@ -0,0 +1,194 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.2 + name: managedprefixlists.ec2.services.k8s.aws +spec: + group: ec2.services.k8s.aws + names: + kind: ManagedPrefixList + listKind: ManagedPrefixListList + plural: managedprefixlists + singular: managedprefixlist + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.prefixListID + name: ID + type: string + - jsonPath: .spec.prefixListName + name: NAME + type: string + - jsonPath: .status.state + name: STATE + type: string + - jsonPath: .status.version + name: VERSION + type: integer + name: v1alpha1 + schema: + openAPIV3Schema: + description: ManagedPrefixList is the Schema for the ManagedPrefixLists API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + ManagedPrefixListSpec defines the desired state of ManagedPrefixList. + + Describes a managed prefix list. + properties: + addressFamily: + description: |- + The IP address type. + + Valid Values: IPv4 | IPv6 + type: string + entries: + items: + description: An entry for a prefix list. + properties: + cidr: + type: string + description: + type: string + type: object + type: array + maxEntries: + description: The maximum number of entries for the prefix list. + format: int64 + type: integer + prefixListName: + description: |- + A name for the prefix list. + + Constraints: Up to 255 characters in length. The name cannot start with com.amazonaws. + type: string + tags: + description: |- + The tags. The value parameter is required, but if you don't want the tag + to have a value, specify the parameter with no value, and we set the value + to an empty string. + items: + description: Describes a tag. + properties: + key: + type: string + value: + type: string + type: object + type: array + required: + - addressFamily + - maxEntries + - prefixListName + type: object + status: + description: ManagedPrefixListStatus defines the observed state of ManagedPrefixList + properties: + ackResourceMetadata: + description: |- + All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: |- + ARN is the Amazon Resource Name for the resource. This is a + globally-unique identifier and is set only by the ACK service controller + once the controller has orchestrated the creation of the resource OR + when it has verified that an "adopted" resource (a resource where the + ARN annotation was set by the Kubernetes user on the CR) exists and + matches the supplied CR's Spec field values. + https://github.com/aws/aws-controllers-k8s/issues/270 + type: string + ownerAccountID: + description: |- + OwnerAccountID is the AWS Account ID of the account that owns the + backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: |- + All CRs managed by ACK have a common `Status.Conditions` member that + contains a collection of `ackv1alpha1.Condition` objects that describe + the various terminal states of the CR and its backend AWS service API + resource + items: + description: |- + Condition is the common struct used by all CRDs managed by ACK service + controllers to indicate terminal states of the CR and its backend AWS + service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + ownerID: + description: The ID of the owner of the prefix list. + type: string + prefixListARN: + description: The Amazon Resource Name (ARN) for the prefix list. + type: string + prefixListID: + description: The ID of the prefix list. + type: string + state: + description: The current state of the prefix list. + type: string + stateMessage: + description: The state message. + type: string + version: + description: The version of the prefix list. + format: int64 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 09c09ff7..e820be33 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -9,6 +9,7 @@ resources: - bases/ec2.services.k8s.aws_instances.yaml - bases/ec2.services.k8s.aws_internetgateways.yaml - bases/ec2.services.k8s.aws_launchtemplates.yaml + - bases/ec2.services.k8s.aws_managedprefixlists.yaml - bases/ec2.services.k8s.aws_natgateways.yaml - bases/ec2.services.k8s.aws_networkacls.yaml - bases/ec2.services.k8s.aws_routetables.yaml diff --git a/config/rbac/cluster-role-controller.yaml b/config/rbac/cluster-role-controller.yaml index 84075926..7b7d585f 100644 --- a/config/rbac/cluster-role-controller.yaml +++ b/config/rbac/cluster-role-controller.yaml @@ -32,6 +32,7 @@ rules: - instances - internetgateways - launchtemplates + - managedprefixlists - natgateways - networkacls - routetables @@ -61,6 +62,7 @@ rules: - instances/status - internetgateways/status - launchtemplates/status + - managedprefixlists/status - natgateways/status - networkacls/status - routetables/status diff --git a/config/rbac/role-reader.yaml b/config/rbac/role-reader.yaml index 54b08bab..6b1b9dde 100644 --- a/config/rbac/role-reader.yaml +++ b/config/rbac/role-reader.yaml @@ -16,6 +16,7 @@ rules: - instances - internetgateways - launchtemplates + - managedprefixlists - natgateways - networkacls - routetables diff --git a/config/rbac/role-writer.yaml b/config/rbac/role-writer.yaml index d3a673b4..91b23947 100644 --- a/config/rbac/role-writer.yaml +++ b/config/rbac/role-writer.yaml @@ -16,6 +16,7 @@ rules: - instances - internetgateways - launchtemplates + - managedprefixlists - natgateways - networkacls - routetables @@ -45,6 +46,7 @@ rules: - instances - internetgateways - launchtemplates + - managedprefixlists - natgateways - networkacls - routetables diff --git a/generator.yaml b/generator.yaml index 12377b8a..dc52b657 100644 --- a/generator.yaml +++ b/generator.yaml @@ -113,6 +113,9 @@ ignore: - CreateLaunchTemplateInput.ClientToken - CreateLaunchTemplateInput.TagSpecifications - CreateLaunchTemplateVersionInput.LaunchTemplateData.TagSpecifications + - CreateManagedPrefixListInput.ClientToken + - CreateManagedPrefixListInput.TagSpecifications + - CreateManagedPrefixListInput.DryRun - CreateLaunchTemplateVersionOutput.LaunchTemplateVersion.DefaultVersion resource_names: - CapacityReservationBySplitting @@ -150,7 +153,7 @@ ignore: - LocalGatewayRouteTableVpcAssociation - LocalGatewayRouteTableVirtualInterfaceGroupAssociation - LocalGatewayRoute - - ManagedPrefixList + #- ManagedPrefixList #- NatGateway - NetworkAclEntry #- NetworkAcl @@ -608,6 +611,56 @@ resources: template_path: hooks/nat_gateway/sdk_file_end.go.tpl update_operation: custom_method_name: customUpdateNATGateway + ManagedPrefixList: + exceptions: + terminal_codes: + - InvalidParameterValue + - PrefixListMaxEntriesExceeded + fields: + PrefixListId: + is_primary_key: true + is_read_only: true + print: + path: Status.prefixListID + name: ID + PrefixListName: + print: + path: Spec.prefixListName + name: NAME + State: + is_read_only: true + print: + path: Status.state + name: STATE + Version: + is_read_only: true + print: + path: Status.version + name: VERSION + Entries: + custom_field: + list_of: AddPrefixListEntry + compare: + is_ignored: false + Tags: + from: + operation: CreateTags + path: Tags + synced: + when: + - path: Status.State + in: + - create-complete + - modify-complete + hooks: + sdk_create_post_build_request: + template_path: hooks/managed_prefix_list/sdk_create_post_build_request.go.tpl + sdk_read_many_post_build_request: + template_path: hooks/managed_prefix_list/sdk_read_many_post_build_request.go.tpl + sdk_read_many_post_set_output: + template_path: hooks/managed_prefix_list/sdk_read_many_post_set_output.go.tpl + update_operation: + custom_method_name: customUpdateManagedPrefixList RouteTable: fields: # RouteStatuses as Route to ensure diff --git a/helm/crds/ec2.services.k8s.aws_managedprefixlists.yaml b/helm/crds/ec2.services.k8s.aws_managedprefixlists.yaml new file mode 100644 index 00000000..ce097b8d --- /dev/null +++ b/helm/crds/ec2.services.k8s.aws_managedprefixlists.yaml @@ -0,0 +1,196 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.2 + name: managedprefixlists.ec2.services.k8s.aws +spec: + group: ec2.services.k8s.aws + names: + kind: ManagedPrefixList + listKind: ManagedPrefixListList + plural: managedprefixlists + singular: managedprefixlist + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.prefixListID + name: ID + type: string + - jsonPath: .spec.prefixListName + name: NAME + type: string + - jsonPath: .status.state + name: STATE + type: string + - jsonPath: .status.version + name: VERSION + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: ManagedPrefixList is the Schema for the ManagedPrefixLists API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + ManagedPrefixListSpec defines the desired state of ManagedPrefixList. + + Describes a managed prefix list. + properties: + addressFamily: + description: |- + The IP address version. + + Valid Values: IPv4 | IPv6 + type: string + entries: + description: One or more entries for the prefix list. + items: + description: An entry for a prefix list. + properties: + cidr: + type: string + description: + type: string + type: object + type: array + maxEntries: + description: The maximum number of entries for the prefix list. + format: int64 + type: integer + prefixListName: + description: |- + A name for the prefix list. + + Constraints: Up to 255 characters in length. The name cannot start with + com.amazonaws. + type: string + tags: + description: |- + The tags. The value parameter is required, but if you don't want the tag + to have a value, specify the parameter with no value, and we set the value + to an empty string. + items: + description: Describes a tag. + properties: + key: + type: string + value: + type: string + type: object + type: array + required: + - addressFamily + - maxEntries + - prefixListName + type: object + status: + description: ManagedPrefixListStatus defines the observed state of ManagedPrefixList + properties: + ackResourceMetadata: + description: |- + All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: |- + ARN is the Amazon Resource Name for the resource. This is a + globally-unique identifier and is set only by the ACK service controller + once the controller has orchestrated the creation of the resource OR + when it has verified that an "adopted" resource (a resource where the + ARN annotation was set by the Kubernetes user on the CR) exists and + matches the supplied CR's Spec field values. + https://github.com/aws/aws-controllers-k8s/issues/270 + type: string + ownerAccountID: + description: |- + OwnerAccountID is the AWS Account ID of the account that owns the + backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: |- + All CRs managed by ACK have a common `Status.Conditions` member that + contains a collection of `ackv1alpha1.Condition` objects that describe + the various terminal states of the CR and its backend AWS service API + resource + items: + description: |- + Condition is the common struct used by all CRDs managed by ACK service + controllers to indicate terminal states of the CR and its backend AWS + service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + ownerID: + description: The ID of the owner of the prefix list. + type: string + prefixListARN: + description: The Amazon Resource Name (ARN) of the prefix list. + type: string + prefixListID: + description: The ID of the prefix list. + type: string + state: + description: The state of the prefix list. + type: string + stateMessage: + description: The state message. + type: string + version: + description: The version of the prefix list. + format: int64 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl index 73b55946..e049c98e 100644 --- a/helm/templates/_helpers.tpl +++ b/helm/templates/_helpers.tpl @@ -79,6 +79,7 @@ rules: - instances - internetgateways - launchtemplates + - managedprefixlists - natgateways - networkacls - routetables @@ -108,6 +109,7 @@ rules: - instances/status - internetgateways/status - launchtemplates/status + - managedprefixlists/status - natgateways/status - networkacls/status - routetables/status diff --git a/pkg/resource/managed_prefix_list/delta.go b/pkg/resource/managed_prefix_list/delta.go new file mode 100644 index 00000000..95aabda3 --- /dev/null +++ b/pkg/resource/managed_prefix_list/delta.go @@ -0,0 +1,81 @@ +// 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. + +// Code generated by ack-generate. DO NOT EDIT. + +package managed_prefix_list + +import ( + "bytes" + "reflect" + + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" +) + +// Hack to avoid import errors during build... +var ( + _ = &bytes.Buffer{} + _ = &reflect.Method{} + _ = &acktags.Tags{} +) + +// newResourceDelta returns a new `ackcompare.Delta` used to compare two +// resources +func newResourceDelta( + a *resource, + b *resource, +) *ackcompare.Delta { + delta := ackcompare.NewDelta() + if (a == nil && b != nil) || + (a != nil && b == nil) { + delta.Add("", a, b) + return delta + } + + if ackcompare.HasNilDifference(a.ko.Spec.AddressFamily, b.ko.Spec.AddressFamily) { + delta.Add("Spec.AddressFamily", a.ko.Spec.AddressFamily, b.ko.Spec.AddressFamily) + } else if a.ko.Spec.AddressFamily != nil && b.ko.Spec.AddressFamily != nil { + if *a.ko.Spec.AddressFamily != *b.ko.Spec.AddressFamily { + delta.Add("Spec.AddressFamily", a.ko.Spec.AddressFamily, b.ko.Spec.AddressFamily) + } + } + if len(a.ko.Spec.Entries) != len(b.ko.Spec.Entries) { + delta.Add("Spec.Entries", a.ko.Spec.Entries, b.ko.Spec.Entries) + } else if len(a.ko.Spec.Entries) > 0 { + if !reflect.DeepEqual(a.ko.Spec.Entries, b.ko.Spec.Entries) { + delta.Add("Spec.Entries", a.ko.Spec.Entries, b.ko.Spec.Entries) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.MaxEntries, b.ko.Spec.MaxEntries) { + delta.Add("Spec.MaxEntries", a.ko.Spec.MaxEntries, b.ko.Spec.MaxEntries) + } else if a.ko.Spec.MaxEntries != nil && b.ko.Spec.MaxEntries != nil { + if *a.ko.Spec.MaxEntries != *b.ko.Spec.MaxEntries { + delta.Add("Spec.MaxEntries", a.ko.Spec.MaxEntries, b.ko.Spec.MaxEntries) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.PrefixListName, b.ko.Spec.PrefixListName) { + delta.Add("Spec.PrefixListName", a.ko.Spec.PrefixListName, b.ko.Spec.PrefixListName) + } else if a.ko.Spec.PrefixListName != nil && b.ko.Spec.PrefixListName != nil { + if *a.ko.Spec.PrefixListName != *b.ko.Spec.PrefixListName { + delta.Add("Spec.PrefixListName", a.ko.Spec.PrefixListName, b.ko.Spec.PrefixListName) + } + } + desiredACKTags, _ := convertToOrderedACKTags(a.ko.Spec.Tags) + latestACKTags, _ := convertToOrderedACKTags(b.ko.Spec.Tags) + if !ackcompare.MapStringStringEqual(desiredACKTags, latestACKTags) { + delta.Add("Spec.Tags", a.ko.Spec.Tags, b.ko.Spec.Tags) + } + + return delta +} diff --git a/pkg/resource/managed_prefix_list/descriptor.go b/pkg/resource/managed_prefix_list/descriptor.go new file mode 100644 index 00000000..d119b93b --- /dev/null +++ b/pkg/resource/managed_prefix_list/descriptor.go @@ -0,0 +1,155 @@ +// 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. + +// Code generated by ack-generate. DO NOT EDIT. + +package managed_prefix_list + +import ( + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + rtclient "sigs.k8s.io/controller-runtime/pkg/client" + k8sctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" +) + +const ( + FinalizerString = "finalizers.ec2.services.k8s.aws/ManagedPrefixList" +) + +var ( + GroupVersionResource = svcapitypes.GroupVersion.WithResource("managedprefixlists") + GroupKind = metav1.GroupKind{ + Group: "ec2.services.k8s.aws", + Kind: "ManagedPrefixList", + } +) + +// resourceDescriptor implements the +// `aws-service-operator-k8s/pkg/types.AWSResourceDescriptor` interface +type resourceDescriptor struct { +} + +// GroupVersionKind returns a Kubernetes schema.GroupVersionKind struct that +// describes the API Group, Version and Kind of CRs described by the descriptor +func (d *resourceDescriptor) GroupVersionKind() schema.GroupVersionKind { + return svcapitypes.GroupVersion.WithKind(GroupKind.Kind) +} + +// EmptyRuntimeObject returns an empty object prototype that may be used in +// apimachinery and k8s client operations +func (d *resourceDescriptor) EmptyRuntimeObject() rtclient.Object { + return &svcapitypes.ManagedPrefixList{} +} + +// ResourceFromRuntimeObject returns an AWSResource that has been initialized +// with the supplied runtime.Object +func (d *resourceDescriptor) ResourceFromRuntimeObject( + obj rtclient.Object, +) acktypes.AWSResource { + return &resource{ + ko: obj.(*svcapitypes.ManagedPrefixList), + } +} + +// Delta returns an `ackcompare.Delta` object containing the difference between +// one `AWSResource` and another. +func (d *resourceDescriptor) Delta(a, b acktypes.AWSResource) *ackcompare.Delta { + return newResourceDelta(a.(*resource), b.(*resource)) +} + +// IsManaged returns true if the supplied AWSResource is under the management +// of an ACK service controller. What this means in practice is that the +// underlying custom resource (CR) in the AWSResource has had a +// resource-specific finalizer associated with it. +func (d *resourceDescriptor) IsManaged( + res acktypes.AWSResource, +) bool { + obj := res.RuntimeObject() + if obj == nil { + // Should not happen. If it does, there is a bug in the code + panic("nil RuntimeMetaObject in AWSResource") + } + // Remove use of custom code once + // https://github.com/kubernetes-sigs/controller-runtime/issues/994 is + // fixed. This should be able to be: + // + // return k8sctrlutil.ContainsFinalizer(obj, FinalizerString) + return containsFinalizer(obj, FinalizerString) +} + +// Remove once https://github.com/kubernetes-sigs/controller-runtime/issues/994 +// is fixed. +func containsFinalizer(obj rtclient.Object, finalizer string) bool { + f := obj.GetFinalizers() + for _, e := range f { + if e == finalizer { + return true + } + } + return false +} + +// MarkManaged places the supplied resource under the management of ACK. What +// this typically means is that the resource manager will decorate the +// underlying custom resource (CR) with a finalizer that indicates ACK is +// managing the resource and the underlying CR may not be deleted until ACK is +// finished cleaning up any backend AWS service resources associated with the +// CR. +func (d *resourceDescriptor) MarkManaged( + res acktypes.AWSResource, +) { + obj := res.RuntimeObject() + if obj == nil { + // Should not happen. If it does, there is a bug in the code + panic("nil RuntimeMetaObject in AWSResource") + } + k8sctrlutil.AddFinalizer(obj, FinalizerString) +} + +// MarkUnmanaged removes the supplied resource from management by ACK. What +// this typically means is that the resource manager will remove a finalizer +// underlying custom resource (CR) that indicates ACK is managing the resource. +// This will allow the Kubernetes API server to delete the underlying CR. +func (d *resourceDescriptor) MarkUnmanaged( + res acktypes.AWSResource, +) { + obj := res.RuntimeObject() + if obj == nil { + // Should not happen. If it does, there is a bug in the code + panic("nil RuntimeMetaObject in AWSResource") + } + k8sctrlutil.RemoveFinalizer(obj, FinalizerString) +} + +// MarkAdopted places descriptors on the custom resource that indicate the +// resource was not created from within ACK. +func (d *resourceDescriptor) MarkAdopted( + res acktypes.AWSResource, +) { + obj := res.RuntimeObject() + if obj == nil { + // Should not happen. If it does, there is a bug in the code + panic("nil RuntimeObject in AWSResource") + } + curr := obj.GetAnnotations() + if curr == nil { + curr = make(map[string]string) + } + curr[ackv1alpha1.AnnotationAdopted] = "true" + obj.SetAnnotations(curr) +} diff --git a/pkg/resource/managed_prefix_list/hooks.go b/pkg/resource/managed_prefix_list/hooks.go new file mode 100644 index 00000000..518af265 --- /dev/null +++ b/pkg/resource/managed_prefix_list/hooks.go @@ -0,0 +1,183 @@ +// 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. + +package managed_prefix_list + +import ( + "context" + + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + "github.com/aws/aws-sdk-go-v2/aws" + svcsdk "github.com/aws/aws-sdk-go-v2/service/ec2" + svcsdktypes "github.com/aws/aws-sdk-go-v2/service/ec2/types" + + "github.com/aws-controllers-k8s/ec2-controller/pkg/tags" +) + +// updateTagSpecificationsInCreateRequest adds +// Tags defined in the Spec to CreateManagedPrefixListInput.TagSpecification +// and ensures the ResourceType is always set to 'prefix-list' +func updateTagSpecificationsInCreateRequest(r *resource, + input *svcsdk.CreateManagedPrefixListInput) { + input.TagSpecifications = nil + desiredTagSpecs := svcsdktypes.TagSpecification{} + if r.ko.Spec.Tags != nil { + requestedTags := []svcsdktypes.Tag{} + for _, desiredTag := range r.ko.Spec.Tags { + // Add in tags defined in the Spec + tag := svcsdktypes.Tag{} + if desiredTag.Key != nil && desiredTag.Value != nil { + tag.Key = desiredTag.Key + tag.Value = desiredTag.Value + } + requestedTags = append(requestedTags, tag) + } + desiredTagSpecs.ResourceType = "prefix-list" + desiredTagSpecs.Tags = requestedTags + input.TagSpecifications = []svcsdktypes.TagSpecification{desiredTagSpecs} + } +} + +// customUpdateManagedPrefixList provides custom logic for updating ManagedPrefixList +func (rm *resourceManager) customUpdateManagedPrefixList( + ctx context.Context, + desired *resource, + latest *resource, + delta *ackcompare.Delta, +) (*resource, error) { + // If there are no changes, return the latest + if delta == nil || len(delta.Differences) == 0 { + return desired, nil + } + + // Handle tag updates first + if delta.DifferentAt("Spec.Tags") { + if err := tags.Sync( + ctx, rm.sdkapi, rm.metrics, *latest.ko.Status.PrefixListID, + desired.ko.Spec.Tags, latest.ko.Spec.Tags, + ); err != nil { + return nil, err + } + } + + // Only continue if something other than Tags has changed in the Spec + if !delta.DifferentExcept("Spec.Tags") { + return desired, nil + } + + // Build the modify input + input := &svcsdk.ModifyManagedPrefixListInput{} + input.PrefixListId = latest.ko.Status.PrefixListID + + // Check if we need to update the prefix list name + if delta.DifferentAt("Spec.PrefixListName") { + input.PrefixListName = desired.ko.Spec.PrefixListName + } + + // Check if we need to update max entries + if delta.DifferentAt("Spec.MaxEntries") { + if desired.ko.Spec.MaxEntries != nil { + maxEntriesCopy := int32(*desired.ko.Spec.MaxEntries) + input.MaxEntries = &maxEntriesCopy + } + } + + // Handle entries changes + if delta.DifferentAt("Spec.Entries") { + // Calculate entries to add and remove + currentEntries := make(map[string]string) + if latest.ko.Spec.Entries != nil { + for _, entry := range latest.ko.Spec.Entries { + if entry.CIDR != nil { + desc := "" + if entry.Description != nil { + desc = *entry.Description + } + currentEntries[*entry.CIDR] = desc + } + } + } + + desiredEntries := make(map[string]string) + if desired.ko.Spec.Entries != nil { + for _, entry := range desired.ko.Spec.Entries { + if entry.CIDR != nil { + desc := "" + if entry.Description != nil { + desc = *entry.Description + } + desiredEntries[*entry.CIDR] = desc + } + } + } + + // Entries to add (in desired but not in current, or descriptions changed) + var addEntries []svcsdktypes.AddPrefixListEntry + for cidr, desc := range desiredEntries { + currentDesc, exists := currentEntries[cidr] + if !exists || currentDesc != desc { + entry := svcsdktypes.AddPrefixListEntry{ + Cidr: aws.String(cidr), + } + if desc != "" { + entry.Description = aws.String(desc) + } + addEntries = append(addEntries, entry) + } + } + + // Entries to remove (in current but not in desired) + var removeEntries []svcsdktypes.RemovePrefixListEntry + for cidr := range currentEntries { + if _, exists := desiredEntries[cidr]; !exists { + removeEntries = append(removeEntries, svcsdktypes.RemovePrefixListEntry{ + Cidr: aws.String(cidr), + }) + } + } + + if len(addEntries) > 0 { + input.AddEntries = addEntries + } + if len(removeEntries) > 0 { + input.RemoveEntries = removeEntries + } + + // Set current version for optimistic locking (required by AWS) + if latest.ko.Status.Version != nil { + input.CurrentVersion = latest.ko.Status.Version + } + } + + // Only call ModifyManagedPrefixList if there are actual changes + if input.PrefixListName != nil || input.MaxEntries != nil || + len(input.AddEntries) > 0 || len(input.RemoveEntries) > 0 { + resp, err := rm.sdkapi.ModifyManagedPrefixList(ctx, input) + rm.metrics.RecordAPICall("UPDATE", "ModifyManagedPrefixList", err) + if err != nil { + return nil, err + } + + // Update the status with the response + if resp.PrefixList != nil { + if resp.PrefixList.State != "" { + desired.ko.Status.State = aws.String(string(resp.PrefixList.State)) + } + if resp.PrefixList.Version != nil { + desired.ko.Status.Version = resp.PrefixList.Version + } + } + } + + return desired, nil +} diff --git a/pkg/resource/managed_prefix_list/identifiers.go b/pkg/resource/managed_prefix_list/identifiers.go new file mode 100644 index 00000000..0cd93b2d --- /dev/null +++ b/pkg/resource/managed_prefix_list/identifiers.go @@ -0,0 +1,55 @@ +// 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. + +// Code generated by ack-generate. DO NOT EDIT. + +package managed_prefix_list + +import ( + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" +) + +// resourceIdentifiers implements the +// `aws-service-operator-k8s/pkg/types.AWSResourceIdentifiers` interface +type resourceIdentifiers struct { + meta *ackv1alpha1.ResourceMetadata +} + +// ARN returns the AWS Resource Name for the backend AWS resource. If nil, +// this means the resource has not yet been created in the backend AWS +// service. +func (ri *resourceIdentifiers) ARN() *ackv1alpha1.AWSResourceName { + if ri.meta != nil { + return ri.meta.ARN + } + return nil +} + +// OwnerAccountID returns the AWS account identifier in which the +// backend AWS resource resides, or nil if this information is not known +// for the resource +func (ri *resourceIdentifiers) OwnerAccountID() *ackv1alpha1.AWSAccountID { + if ri.meta != nil { + return ri.meta.OwnerAccountID + } + return nil +} + +// Region returns the AWS region in which the resource exists, or +// nil if this information is not known. +func (ri *resourceIdentifiers) Region() *ackv1alpha1.AWSRegion { + if ri.meta != nil { + return ri.meta.Region + } + return nil +} diff --git a/pkg/resource/managed_prefix_list/manager.go b/pkg/resource/managed_prefix_list/manager.go new file mode 100644 index 00000000..7b0c1026 --- /dev/null +++ b/pkg/resource/managed_prefix_list/manager.go @@ -0,0 +1,412 @@ +// 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. + +// Code generated by ack-generate. DO NOT EDIT. + +package managed_prefix_list + +import ( + "context" + "fmt" + "time" + + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + ackcondition "github.com/aws-controllers-k8s/runtime/pkg/condition" + ackcfg "github.com/aws-controllers-k8s/runtime/pkg/config" + ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" + ackmetrics "github.com/aws-controllers-k8s/runtime/pkg/metrics" + ackrequeue "github.com/aws-controllers-k8s/runtime/pkg/requeue" + ackrt "github.com/aws-controllers-k8s/runtime/pkg/runtime" + ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + ackutil "github.com/aws-controllers-k8s/runtime/pkg/util" + "github.com/aws/aws-sdk-go-v2/aws" + svcsdk "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + + svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" +) + +var ( + _ = ackutil.InStrings + _ = acktags.NewTags() + _ = ackrt.MissingImageTagValue + _ = svcapitypes.ManagedPrefixList{} +) + +// +kubebuilder:rbac:groups=ec2.services.k8s.aws,resources=managedprefixlists,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=ec2.services.k8s.aws,resources=managedprefixlists/status,verbs=get;update;patch + +var lateInitializeFieldNames = []string{} + +// resourceManager is responsible for providing a consistent way to perform +// CRUD operations in a backend AWS service API for Book custom resources. +type resourceManager struct { + // cfg is a copy of the ackcfg.Config object passed on start of the service + // controller + cfg ackcfg.Config + // clientcfg is a copy of the client configuration passed on start of the + // service controller + clientcfg aws.Config + // log refers to the logr.Logger object handling logging for the service + // controller + log logr.Logger + // metrics contains a collection of Prometheus metric objects that the + // service controller and its reconcilers track + metrics *ackmetrics.Metrics + // rr is the Reconciler which can be used for various utility + // functions such as querying for Secret values given a SecretReference + rr acktypes.Reconciler + // awsAccountID is the AWS account identifier that contains the resources + // managed by this resource manager + awsAccountID ackv1alpha1.AWSAccountID + // The AWS Region that this resource manager targets + awsRegion ackv1alpha1.AWSRegion + // sdk is a pointer to the AWS service API client exposed by the + // aws-sdk-go-v2/services/{alias} package. + sdkapi *svcsdk.Client +} + +// concreteResource returns a pointer to a resource from the supplied +// generic AWSResource interface +func (rm *resourceManager) concreteResource( + res acktypes.AWSResource, +) *resource { + // cast the generic interface into a pointer type specific to the concrete + // implementing resource type managed by this resource manager + return res.(*resource) +} + +// ReadOne returns the currently-observed state of the supplied AWSResource in +// the backend AWS service API. +func (rm *resourceManager) ReadOne( + ctx context.Context, + res acktypes.AWSResource, +) (acktypes.AWSResource, error) { + r := rm.concreteResource(res) + if r.ko == nil { + // Should never happen... if it does, it's buggy code. + panic("resource manager's ReadOne() method received resource with nil CR object") + } + observed, err := rm.sdkFind(ctx, r) + mirrorAWSTags(r, observed) + if err != nil { + if observed != nil { + return rm.onError(observed, err) + } + return rm.onError(r, err) + } + return rm.onSuccess(observed) +} + +// Create attempts to create the supplied AWSResource in the backend AWS +// service API, returning an AWSResource representing the newly-created +// resource +func (rm *resourceManager) Create( + ctx context.Context, + res acktypes.AWSResource, +) (acktypes.AWSResource, error) { + r := rm.concreteResource(res) + if r.ko == nil { + // Should never happen... if it does, it's buggy code. + panic("resource manager's Create() method received resource with nil CR object") + } + created, err := rm.sdkCreate(ctx, r) + if err != nil { + if created != nil { + return rm.onError(created, err) + } + return rm.onError(r, err) + } + return rm.onSuccess(created) +} + +// Update attempts to mutate the supplied desired AWSResource in the backend AWS +// service API, returning an AWSResource representing the newly-mutated +// resource. +// Note for specialized logic implementers can check to see how the latest +// observed resource differs from the supplied desired state. The +// higher-level reonciler determines whether or not the desired differs +// from the latest observed and decides whether to call the resource +// manager's Update method +func (rm *resourceManager) Update( + ctx context.Context, + resDesired acktypes.AWSResource, + resLatest acktypes.AWSResource, + delta *ackcompare.Delta, +) (acktypes.AWSResource, error) { + desired := rm.concreteResource(resDesired) + latest := rm.concreteResource(resLatest) + if desired.ko == nil || latest.ko == nil { + // Should never happen... if it does, it's buggy code. + panic("resource manager's Update() method received resource with nil CR object") + } + updated, err := rm.sdkUpdate(ctx, desired, latest, delta) + if err != nil { + if updated != nil { + return rm.onError(updated, err) + } + return rm.onError(latest, err) + } + return rm.onSuccess(updated) +} + +// Delete attempts to destroy the supplied AWSResource in the backend AWS +// service API, returning an AWSResource representing the +// resource being deleted (if delete is asynchronous and takes time) +func (rm *resourceManager) Delete( + ctx context.Context, + res acktypes.AWSResource, +) (acktypes.AWSResource, error) { + r := rm.concreteResource(res) + if r.ko == nil { + // Should never happen... if it does, it's buggy code. + panic("resource manager's Update() method received resource with nil CR object") + } + observed, err := rm.sdkDelete(ctx, r) + if err != nil { + if observed != nil { + return rm.onError(observed, err) + } + return rm.onError(r, err) + } + + return rm.onSuccess(observed) +} + +// ARNFromName returns an AWS Resource Name from a given string name. This +// is useful for constructing ARNs for APIs that require ARNs in their +// GetAttributes operations but all we have (for new CRs at least) is a +// name for the resource +func (rm *resourceManager) ARNFromName(name string) string { + return fmt.Sprintf( + "arn:aws:ec2:%s:%s:%s", + rm.awsRegion, + rm.awsAccountID, + name, + ) +} + +// LateInitialize returns an acktypes.AWSResource after setting the late initialized +// fields from the readOne call. This method will initialize the optional fields +// which were not provided by the k8s user but were defaulted by the AWS service. +// If there are no such fields to be initialized, the returned object is similar to +// object passed in the parameter. +func (rm *resourceManager) LateInitialize( + ctx context.Context, + latest acktypes.AWSResource, +) (acktypes.AWSResource, error) { + rlog := ackrtlog.FromContext(ctx) + // If there are no fields to late initialize, do nothing + if len(lateInitializeFieldNames) == 0 { + rlog.Debug("no late initialization required.") + return latest, nil + } + latestCopy := latest.DeepCopy() + lateInitConditionReason := "" + lateInitConditionMessage := "" + observed, err := rm.ReadOne(ctx, latestCopy) + if err != nil { + lateInitConditionMessage = "Unable to complete Read operation required for late initialization" + lateInitConditionReason = "Late Initialization Failure" + ackcondition.SetLateInitialized(latestCopy, corev1.ConditionFalse, &lateInitConditionMessage, &lateInitConditionReason) + ackcondition.SetSynced(latestCopy, corev1.ConditionFalse, nil, nil) + return latestCopy, err + } + lateInitializedRes := rm.lateInitializeFromReadOneOutput(observed, latestCopy) + incompleteInitialization := rm.incompleteLateInitialization(lateInitializedRes) + if incompleteInitialization { + // Add the condition with LateInitialized=False + lateInitConditionMessage = "Late initialization did not complete, requeuing with delay of 5 seconds" + lateInitConditionReason = "Delayed Late Initialization" + ackcondition.SetLateInitialized(lateInitializedRes, corev1.ConditionFalse, &lateInitConditionMessage, &lateInitConditionReason) + ackcondition.SetSynced(lateInitializedRes, corev1.ConditionFalse, nil, nil) + return lateInitializedRes, ackrequeue.NeededAfter(nil, time.Duration(5)*time.Second) + } + // Set LateInitialized condition to True + lateInitConditionMessage = "Late initialization successful" + lateInitConditionReason = "Late initialization successful" + ackcondition.SetLateInitialized(lateInitializedRes, corev1.ConditionTrue, &lateInitConditionMessage, &lateInitConditionReason) + return lateInitializedRes, nil +} + +// incompleteLateInitialization return true if there are fields which were supposed to be +// late initialized but are not. If all the fields are late initialized, false is returned +func (rm *resourceManager) incompleteLateInitialization( + res acktypes.AWSResource, +) bool { + return false +} + +// lateInitializeFromReadOneOutput late initializes the 'latest' resource from the 'observed' +// resource and returns 'latest' resource +func (rm *resourceManager) lateInitializeFromReadOneOutput( + observed acktypes.AWSResource, + latest acktypes.AWSResource, +) acktypes.AWSResource { + return latest +} + +// IsSynced returns true if the resource is synced. +func (rm *resourceManager) IsSynced(ctx context.Context, res acktypes.AWSResource) (bool, error) { + r := rm.concreteResource(res) + if r.ko == nil { + // Should never happen... if it does, it's buggy code. + panic("resource manager's IsSynced() method received resource with nil CR object") + } + + if r.ko.Status.State == nil { + return false, nil + } + stateCandidates := []string{"create-complete", "modify-complete"} + if !ackutil.InStrings(*r.ko.Status.State, stateCandidates) { + return false, nil + } + + return true, nil +} + +// EnsureTags ensures that tags are present inside the AWSResource. +// If the AWSResource does not have any existing resource tags, the 'tags' +// field is initialized and the controller tags are added. +// If the AWSResource has existing resource tags, then controller tags are +// added to the existing resource tags without overriding them. +// If the AWSResource does not support tags, only then the controller tags +// will not be added to the AWSResource. +func (rm *resourceManager) EnsureTags( + ctx context.Context, + res acktypes.AWSResource, + md acktypes.ServiceControllerMetadata, +) error { + r := rm.concreteResource(res) + if r.ko == nil { + // Should never happen... if it does, it's buggy code. + panic("resource manager's EnsureTags method received resource with nil CR object") + } + defaultTags := ackrt.GetDefaultTags(&rm.cfg, r.ko, md) + var existingTags []*svcapitypes.Tag + existingTags = r.ko.Spec.Tags + resourceTags, keyOrder := convertToOrderedACKTags(existingTags) + tags := acktags.Merge(resourceTags, defaultTags) + r.ko.Spec.Tags = fromACKTags(tags, keyOrder) + return nil +} + +// FilterAWSTags ignores tags that have keys that start with "aws:" +// is needed to ensure the controller does not attempt to remove +// tags set by AWS. This function needs to be called after each Read +// operation. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func (rm *resourceManager) FilterSystemTags(res acktypes.AWSResource) { + r := rm.concreteResource(res) + if r == nil || r.ko == nil { + return + } + var existingTags []*svcapitypes.Tag + existingTags = r.ko.Spec.Tags + resourceTags, tagKeyOrder := convertToOrderedACKTags(existingTags) + ignoreSystemTags(resourceTags) + r.ko.Spec.Tags = fromACKTags(resourceTags, tagKeyOrder) +} + +// mirrorAWSTags ensures that AWS tags are included in the desired resource +// if they are present in the latest resource. This will ensure that the +// aws tags are not present in a diff. The logic of the controller will +// ensure these tags aren't patched to the resource in the cluster, and +// will only be present to make sure we don't try to remove these tags. +// +// Although there are a lot of similarities between this function and +// EnsureTags, they are very much different. +// While EnsureTags tries to make sure the resource contains the controller +// tags, mirrowAWSTags tries to make sure tags injected by AWS are mirrored +// from the latest resoruce to the desired resource. +func mirrorAWSTags(a *resource, b *resource) { + if a == nil || a.ko == nil || b == nil || b.ko == nil { + return + } + var existingLatestTags []*svcapitypes.Tag + var existingDesiredTags []*svcapitypes.Tag + existingDesiredTags = a.ko.Spec.Tags + existingLatestTags = b.ko.Spec.Tags + desiredTags, desiredTagKeyOrder := convertToOrderedACKTags(existingDesiredTags) + latestTags, _ := convertToOrderedACKTags(existingLatestTags) + syncAWSTags(desiredTags, latestTags) + a.ko.Spec.Tags = fromACKTags(desiredTags, desiredTagKeyOrder) +} + +// newResourceManager returns a new struct implementing +// acktypes.AWSResourceManager +// This is for AWS-SDK-GO-V2 - Created newResourceManager With AWS sdk-Go-ClientV2 +func newResourceManager( + cfg ackcfg.Config, + clientcfg aws.Config, + log logr.Logger, + metrics *ackmetrics.Metrics, + rr acktypes.Reconciler, + id ackv1alpha1.AWSAccountID, + region ackv1alpha1.AWSRegion, +) (*resourceManager, error) { + return &resourceManager{ + cfg: cfg, + clientcfg: clientcfg, + log: log, + metrics: metrics, + rr: rr, + awsAccountID: id, + awsRegion: region, + sdkapi: svcsdk.NewFromConfig(clientcfg), + }, nil +} + +// onError updates resource conditions and returns updated resource +// it returns nil if no condition is updated. +func (rm *resourceManager) onError( + r *resource, + err error, +) (acktypes.AWSResource, error) { + if r == nil { + return nil, err + } + r1, updated := rm.updateConditions(r, false, err) + if !updated { + return r, err + } + for _, condition := range r1.Conditions() { + if condition.Type == ackv1alpha1.ConditionTypeTerminal && + condition.Status == corev1.ConditionTrue { + // resource is in Terminal condition + // return Terminal error + return r1, ackerr.Terminal + } + } + return r1, err +} + +// onSuccess updates resource conditions and returns updated resource +// it returns the supplied resource if no condition is updated. +func (rm *resourceManager) onSuccess( + r *resource, +) (acktypes.AWSResource, error) { + if r == nil { + return nil, nil + } + r1, updated := rm.updateConditions(r, true, nil) + if !updated { + return r, nil + } + return r1, nil +} diff --git a/pkg/resource/managed_prefix_list/manager_factory.go b/pkg/resource/managed_prefix_list/manager_factory.go new file mode 100644 index 00000000..9a918b7e --- /dev/null +++ b/pkg/resource/managed_prefix_list/manager_factory.go @@ -0,0 +1,100 @@ +// 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. + +// Code generated by ack-generate. DO NOT EDIT. + +package managed_prefix_list + +import ( + "fmt" + "sync" + + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcfg "github.com/aws-controllers-k8s/runtime/pkg/config" + ackmetrics "github.com/aws-controllers-k8s/runtime/pkg/metrics" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/go-logr/logr" + + svcresource "github.com/aws-controllers-k8s/ec2-controller/pkg/resource" +) + +// resourceManagerFactory produces resourceManager objects. It implements the +// `types.AWSResourceManagerFactory` interface. +type resourceManagerFactory struct { + sync.RWMutex + // rmCache contains resource managers for a particular AWS account ID + rmCache map[string]*resourceManager +} + +// ResourcePrototype returns an AWSResource that resource managers produced by +// this factory will handle +func (f *resourceManagerFactory) ResourceDescriptor() acktypes.AWSResourceDescriptor { + return &resourceDescriptor{} +} + +// ManagerFor returns a resource manager object that can manage resources for a +// supplied AWS account +func (f *resourceManagerFactory) ManagerFor( + cfg ackcfg.Config, + clientcfg aws.Config, + log logr.Logger, + metrics *ackmetrics.Metrics, + rr acktypes.Reconciler, + id ackv1alpha1.AWSAccountID, + region ackv1alpha1.AWSRegion, + roleARN ackv1alpha1.AWSResourceName, +) (acktypes.AWSResourceManager, error) { + // We use the account ID, region, and role ARN to uniquely identify a + // resource manager. This helps us to avoid creating multiple resource + // managers for the same account/region/roleARN combination. + rmId := fmt.Sprintf("%s/%s/%s", id, region, roleARN) + f.RLock() + rm, found := f.rmCache[rmId] + f.RUnlock() + + if found { + return rm, nil + } + + f.Lock() + defer f.Unlock() + + rm, err := newResourceManager(cfg, clientcfg, log, metrics, rr, id, region) + if err != nil { + return nil, err + } + f.rmCache[rmId] = rm + return rm, nil +} + +// IsAdoptable returns true if the resource is able to be adopted +func (f *resourceManagerFactory) IsAdoptable() bool { + return true +} + +// RequeueOnSuccessSeconds returns true if the resource should be requeued after specified seconds +// Default is false which means resource will not be requeued after success. +func (f *resourceManagerFactory) RequeueOnSuccessSeconds() int { + return 0 +} + +func newResourceManagerFactory() *resourceManagerFactory { + return &resourceManagerFactory{ + rmCache: map[string]*resourceManager{}, + } +} + +func init() { + svcresource.RegisterManagerFactory(newResourceManagerFactory()) +} diff --git a/pkg/resource/managed_prefix_list/references.go b/pkg/resource/managed_prefix_list/references.go new file mode 100644 index 00000000..6a954995 --- /dev/null +++ b/pkg/resource/managed_prefix_list/references.go @@ -0,0 +1,57 @@ +// 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. + +// Code generated by ack-generate. DO NOT EDIT. + +package managed_prefix_list + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/client" + + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + + svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" +) + +// ClearResolvedReferences removes any reference values that were made +// concrete in the spec. It returns a copy of the input AWSResource which +// contains the original *Ref values, but none of their respective concrete +// values. +func (rm *resourceManager) ClearResolvedReferences(res acktypes.AWSResource) acktypes.AWSResource { + ko := rm.concreteResource(res).ko.DeepCopy() + + return &resource{ko} +} + +// ResolveReferences finds if there are any Reference field(s) present +// inside AWSResource passed in the parameter and attempts to resolve those +// reference field(s) into their respective target field(s). It returns a +// copy of the input AWSResource with resolved reference(s), a boolean which +// is set to true if the resource contains any references (regardless of if +// they are resolved successfully) and an error if the passed AWSResource's +// reference field(s) could not be resolved. +func (rm *resourceManager) ResolveReferences( + ctx context.Context, + apiReader client.Reader, + res acktypes.AWSResource, +) (acktypes.AWSResource, bool, error) { + return res, false, nil +} + +// validateReferenceFields validates the reference field and corresponding +// identifier field. +func validateReferenceFields(ko *svcapitypes.ManagedPrefixList) error { + return nil +} diff --git a/pkg/resource/managed_prefix_list/resource.go b/pkg/resource/managed_prefix_list/resource.go new file mode 100644 index 00000000..952856f5 --- /dev/null +++ b/pkg/resource/managed_prefix_list/resource.go @@ -0,0 +1,113 @@ +// 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. + +// Code generated by ack-generate. DO NOT EDIT. + +package managed_prefix_list + +import ( + "fmt" + + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackerrors "github.com/aws-controllers-k8s/runtime/pkg/errors" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + rtclient "sigs.k8s.io/controller-runtime/pkg/client" + + svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" +) + +// Hack to avoid import errors during build... +var ( + _ = &ackerrors.MissingNameIdentifier +) + +// resource implements the `aws-controller-k8s/runtime/pkg/types.AWSResource` +// interface +type resource struct { + // The Kubernetes-native CR representing the resource + ko *svcapitypes.ManagedPrefixList +} + +// Identifiers returns an AWSResourceIdentifiers object containing various +// identifying information, including the AWS account ID that owns the +// resource, the resource's AWS Resource Name (ARN) +func (r *resource) Identifiers() acktypes.AWSResourceIdentifiers { + return &resourceIdentifiers{r.ko.Status.ACKResourceMetadata} +} + +// IsBeingDeleted returns true if the Kubernetes resource has a non-zero +// deletion timestamp +func (r *resource) IsBeingDeleted() bool { + return !r.ko.DeletionTimestamp.IsZero() +} + +// RuntimeObject returns the Kubernetes apimachinery/runtime representation of +// the AWSResource +func (r *resource) RuntimeObject() rtclient.Object { + return r.ko +} + +// MetaObject returns the Kubernetes apimachinery/apis/meta/v1.Object +// representation of the AWSResource +func (r *resource) MetaObject() metav1.Object { + return r.ko.GetObjectMeta() +} + +// Conditions returns the ACK Conditions collection for the AWSResource +func (r *resource) Conditions() []*ackv1alpha1.Condition { + return r.ko.Status.Conditions +} + +// ReplaceConditions sets the Conditions status field for the resource +func (r *resource) ReplaceConditions(conditions []*ackv1alpha1.Condition) { + r.ko.Status.Conditions = conditions +} + +// SetObjectMeta sets the ObjectMeta field for the resource +func (r *resource) SetObjectMeta(meta metav1.ObjectMeta) { + r.ko.ObjectMeta = meta +} + +// SetStatus will set the Status field for the resource +func (r *resource) SetStatus(desired acktypes.AWSResource) { + r.ko.Status = desired.(*resource).ko.Status +} + +// SetIdentifiers sets the Spec or Status field that is referenced as the unique +// resource identifier +func (r *resource) SetIdentifiers(identifier *ackv1alpha1.AWSIdentifiers) error { + if identifier.NameOrID == "" { + return ackerrors.MissingNameIdentifier + } + r.ko.Status.PrefixListID = &identifier.NameOrID + + return nil +} + +// PopulateResourceFromAnnotation populates the fields passed from adoption annotation +func (r *resource) PopulateResourceFromAnnotation(fields map[string]string) error { + primaryKey, ok := fields["prefixListID"] + if !ok { + return ackerrors.NewTerminalError(fmt.Errorf("required field missing: prefixListID")) + } + r.ko.Status.PrefixListID = &primaryKey + + return nil +} + +// DeepCopy will return a copy of the resource +func (r *resource) DeepCopy() acktypes.AWSResource { + koCopy := r.ko.DeepCopy() + return &resource{koCopy} +} diff --git a/pkg/resource/managed_prefix_list/sdk.go b/pkg/resource/managed_prefix_list/sdk.go new file mode 100644 index 00000000..7a37f9f6 --- /dev/null +++ b/pkg/resource/managed_prefix_list/sdk.go @@ -0,0 +1,533 @@ +// 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. + +// Code generated by ack-generate. DO NOT EDIT. + +package managed_prefix_list + +import ( + "context" + "errors" + "fmt" + "math" + "reflect" + "strings" + + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + ackcondition "github.com/aws-controllers-k8s/runtime/pkg/condition" + ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" + ackrequeue "github.com/aws-controllers-k8s/runtime/pkg/requeue" + ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" + "github.com/aws/aws-sdk-go-v2/aws" + svcsdk "github.com/aws/aws-sdk-go-v2/service/ec2" + svcsdktypes "github.com/aws/aws-sdk-go-v2/service/ec2/types" + smithy "github.com/aws/smithy-go" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" +) + +// Hack to avoid import errors during build... +var ( + _ = &metav1.Time{} + _ = strings.ToLower("") + _ = &svcsdk.Client{} + _ = &svcapitypes.ManagedPrefixList{} + _ = ackv1alpha1.AWSAccountID("") + _ = &ackerr.NotFound + _ = &ackcondition.NotManagedMessage + _ = &reflect.Value{} + _ = fmt.Sprintf("") + _ = &ackrequeue.NoRequeue{} + _ = &aws.Config{} +) + +// sdkFind returns SDK-specific information about a supplied resource +func (rm *resourceManager) sdkFind( + ctx context.Context, + r *resource, +) (latest *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkFind") + defer func() { + exit(err) + }() + // If any required fields in the input shape are missing, AWS resource is + // not created yet. Return NotFound here to indicate to callers that the + // resource isn't yet created. + if rm.requiredFieldsMissingFromReadManyInput(r) { + return nil, ackerr.NotFound + } + + input, err := rm.newListRequestPayload(r) + if err != nil { + return nil, err + } + // If we have a PrefixListID, use it to filter the describe call + if r.ko.Status.PrefixListID != nil { + input.PrefixListIds = []string{*r.ko.Status.PrefixListID} + } else if r.ko.Spec.PrefixListName != nil { + // If we don't have an ID yet, filter by both owner ID and prefix list name + // This prevents matching against AWS-managed prefix lists + // and other user-owned prefix lists with different names + filters := []svcsdktypes.Filter{ + { + Name: aws.String("owner-id"), + Values: []string{string(rm.awsAccountID)}, + }, + { + Name: aws.String("prefix-list-name"), + Values: []string{*r.ko.Spec.PrefixListName}, + }, + } + input.Filters = filters + } + + var resp *svcsdk.DescribeManagedPrefixListsOutput + resp, err = rm.sdkapi.DescribeManagedPrefixLists(ctx, input) + rm.metrics.RecordAPICall("READ_MANY", "DescribeManagedPrefixLists", err) + if err != nil { + var awsErr smithy.APIError + if errors.As(err, &awsErr) && awsErr.ErrorCode() == "UNKNOWN" { + return nil, ackerr.NotFound + } + return nil, err + } + + // Merge in the information we read from the API call above to the copy of + // the original Kubernetes object we passed to the function + ko := r.ko.DeepCopy() + + found := false + for _, elem := range resp.PrefixLists { + if elem.AddressFamily != nil { + ko.Spec.AddressFamily = elem.AddressFamily + } else { + ko.Spec.AddressFamily = nil + } + if elem.MaxEntries != nil { + maxEntriesCopy := int64(*elem.MaxEntries) + ko.Spec.MaxEntries = &maxEntriesCopy + } else { + ko.Spec.MaxEntries = nil + } + if elem.OwnerId != nil { + ko.Status.OwnerID = elem.OwnerId + } else { + ko.Status.OwnerID = nil + } + if elem.PrefixListArn != nil { + ko.Status.PrefixListARN = elem.PrefixListArn + } else { + ko.Status.PrefixListARN = nil + } + if elem.PrefixListId != nil { + ko.Status.PrefixListID = elem.PrefixListId + } else { + ko.Status.PrefixListID = nil + } + if elem.PrefixListName != nil { + ko.Spec.PrefixListName = elem.PrefixListName + } else { + ko.Spec.PrefixListName = nil + } + if elem.State != "" { + ko.Status.State = aws.String(string(elem.State)) + } else { + ko.Status.State = nil + } + if elem.StateMessage != nil { + ko.Status.StateMessage = elem.StateMessage + } else { + ko.Status.StateMessage = nil + } + if elem.Tags != nil { + f8 := []*svcapitypes.Tag{} + for _, f8iter := range elem.Tags { + f8elem := &svcapitypes.Tag{} + if f8iter.Key != nil { + f8elem.Key = f8iter.Key + } + if f8iter.Value != nil { + f8elem.Value = f8iter.Value + } + f8 = append(f8, f8elem) + } + ko.Spec.Tags = f8 + } else { + ko.Spec.Tags = nil + } + if elem.Version != nil { + ko.Status.Version = elem.Version + } else { + ko.Status.Version = nil + } + found = true + break + } + if !found { + return nil, ackerr.NotFound + } + + rm.setStatusDefaults(ko) + // Get the entries separately + if ko.Status.PrefixListID != nil { + entriesResp, err := rm.sdkapi.GetManagedPrefixListEntries( + ctx, + &svcsdk.GetManagedPrefixListEntriesInput{ + PrefixListId: ko.Status.PrefixListID, + }, + ) + rm.metrics.RecordAPICall("GET", "GetManagedPrefixListEntries", err) + if err != nil { + return nil, err + } + if entriesResp.Entries != nil { + f0 := []*svcapitypes.AddPrefixListEntry{} + for _, f0iter := range entriesResp.Entries { + f0elem := &svcapitypes.AddPrefixListEntry{} + if f0iter.Cidr != nil { + f0elem.CIDR = f0iter.Cidr + } + if f0iter.Description != nil { + f0elem.Description = f0iter.Description + } + f0 = append(f0, f0elem) + } + ko.Spec.Entries = f0 + } else { + ko.Spec.Entries = nil + } + } + + return &resource{ko}, nil +} + +// requiredFieldsMissingFromReadManyInput returns true if there are any fields +// for the ReadMany Input shape that are required but not present in the +// resource's Spec or Status +func (rm *resourceManager) requiredFieldsMissingFromReadManyInput( + r *resource, +) bool { + return false +} + +// newListRequestPayload returns SDK-specific struct for the HTTP request +// payload of the List API call for the resource +func (rm *resourceManager) newListRequestPayload( + r *resource, +) (*svcsdk.DescribeManagedPrefixListsInput, error) { + res := &svcsdk.DescribeManagedPrefixListsInput{} + + return res, nil +} + +// sdkCreate creates the supplied resource in the backend AWS service API and +// returns a copy of the resource with resource fields (in both Spec and +// Status) filled in with values from the CREATE API operation's Output shape. +func (rm *resourceManager) sdkCreate( + ctx context.Context, + desired *resource, +) (created *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkCreate") + defer func() { + exit(err) + }() + input, err := rm.newCreateRequestPayload(ctx, desired) + if err != nil { + return nil, err + } + updateTagSpecificationsInCreateRequest(desired, input) + + var resp *svcsdk.CreateManagedPrefixListOutput + _ = resp + resp, err = rm.sdkapi.CreateManagedPrefixList(ctx, input) + rm.metrics.RecordAPICall("CREATE", "CreateManagedPrefixList", err) + if err != nil { + return nil, err + } + // Merge in the information we read from the API call above to the copy of + // the original Kubernetes object we passed to the function + ko := desired.ko.DeepCopy() + + if resp.PrefixList.AddressFamily != nil { + ko.Spec.AddressFamily = resp.PrefixList.AddressFamily + } else { + ko.Spec.AddressFamily = nil + } + if resp.PrefixList.MaxEntries != nil { + maxEntriesCopy := int64(*resp.PrefixList.MaxEntries) + ko.Spec.MaxEntries = &maxEntriesCopy + } else { + ko.Spec.MaxEntries = nil + } + if resp.PrefixList.OwnerId != nil { + ko.Status.OwnerID = resp.PrefixList.OwnerId + } else { + ko.Status.OwnerID = nil + } + if resp.PrefixList.PrefixListArn != nil { + ko.Status.PrefixListARN = resp.PrefixList.PrefixListArn + } else { + ko.Status.PrefixListARN = nil + } + if resp.PrefixList.PrefixListId != nil { + ko.Status.PrefixListID = resp.PrefixList.PrefixListId + } else { + ko.Status.PrefixListID = nil + } + if resp.PrefixList.PrefixListName != nil { + ko.Spec.PrefixListName = resp.PrefixList.PrefixListName + } else { + ko.Spec.PrefixListName = nil + } + if resp.PrefixList.State != "" { + ko.Status.State = aws.String(string(resp.PrefixList.State)) + } else { + ko.Status.State = nil + } + if resp.PrefixList.StateMessage != nil { + ko.Status.StateMessage = resp.PrefixList.StateMessage + } else { + ko.Status.StateMessage = nil + } + if resp.PrefixList.Tags != nil { + f8 := []*svcapitypes.Tag{} + for _, f8iter := range resp.PrefixList.Tags { + f8elem := &svcapitypes.Tag{} + if f8iter.Key != nil { + f8elem.Key = f8iter.Key + } + if f8iter.Value != nil { + f8elem.Value = f8iter.Value + } + f8 = append(f8, f8elem) + } + ko.Spec.Tags = f8 + } else { + ko.Spec.Tags = nil + } + if resp.PrefixList.Version != nil { + ko.Status.Version = resp.PrefixList.Version + } else { + ko.Status.Version = nil + } + + rm.setStatusDefaults(ko) + return &resource{ko}, nil +} + +// newCreateRequestPayload returns an SDK-specific struct for the HTTP request +// payload of the Create API call for the resource +func (rm *resourceManager) newCreateRequestPayload( + ctx context.Context, + r *resource, +) (*svcsdk.CreateManagedPrefixListInput, error) { + res := &svcsdk.CreateManagedPrefixListInput{} + + if r.ko.Spec.AddressFamily != nil { + res.AddressFamily = r.ko.Spec.AddressFamily + } + if r.ko.Spec.Entries != nil { + f1 := []svcsdktypes.AddPrefixListEntry{} + for _, f1iter := range r.ko.Spec.Entries { + f1elem := &svcsdktypes.AddPrefixListEntry{} + if f1iter.CIDR != nil { + f1elem.Cidr = f1iter.CIDR + } + if f1iter.Description != nil { + f1elem.Description = f1iter.Description + } + f1 = append(f1, *f1elem) + } + res.Entries = f1 + } + if r.ko.Spec.MaxEntries != nil { + maxEntriesCopy0 := *r.ko.Spec.MaxEntries + if maxEntriesCopy0 > math.MaxInt32 || maxEntriesCopy0 < math.MinInt32 { + return nil, fmt.Errorf("error: field MaxEntries is of type int32") + } + maxEntriesCopy := int32(maxEntriesCopy0) + res.MaxEntries = &maxEntriesCopy + } + if r.ko.Spec.PrefixListName != nil { + res.PrefixListName = r.ko.Spec.PrefixListName + } + + return res, nil +} + +// sdkUpdate patches the supplied resource in the backend AWS service API and +// returns a new resource with updated fields. +func (rm *resourceManager) sdkUpdate( + ctx context.Context, + desired *resource, + latest *resource, + delta *ackcompare.Delta, +) (*resource, error) { + return rm.customUpdateManagedPrefixList(ctx, desired, latest, delta) +} + +// sdkDelete deletes the supplied resource in the backend AWS service API +func (rm *resourceManager) sdkDelete( + ctx context.Context, + r *resource, +) (latest *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkDelete") + defer func() { + exit(err) + }() + input, err := rm.newDeleteRequestPayload(r) + if err != nil { + return nil, err + } + var resp *svcsdk.DeleteManagedPrefixListOutput + _ = resp + resp, err = rm.sdkapi.DeleteManagedPrefixList(ctx, input) + rm.metrics.RecordAPICall("DELETE", "DeleteManagedPrefixList", err) + return nil, err +} + +// newDeleteRequestPayload returns an SDK-specific struct for the HTTP request +// payload of the Delete API call for the resource +func (rm *resourceManager) newDeleteRequestPayload( + r *resource, +) (*svcsdk.DeleteManagedPrefixListInput, error) { + res := &svcsdk.DeleteManagedPrefixListInput{} + + if r.ko.Status.PrefixListID != nil { + res.PrefixListId = r.ko.Status.PrefixListID + } + + return res, nil +} + +// setStatusDefaults sets default properties into supplied custom resource +func (rm *resourceManager) setStatusDefaults( + ko *svcapitypes.ManagedPrefixList, +) { + if ko.Status.ACKResourceMetadata == nil { + ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{} + } + if ko.Status.ACKResourceMetadata.Region == nil { + ko.Status.ACKResourceMetadata.Region = &rm.awsRegion + } + if ko.Status.ACKResourceMetadata.OwnerAccountID == nil { + ko.Status.ACKResourceMetadata.OwnerAccountID = &rm.awsAccountID + } + if ko.Status.Conditions == nil { + ko.Status.Conditions = []*ackv1alpha1.Condition{} + } +} + +// updateConditions returns updated resource, true; if conditions were updated +// else it returns nil, false +func (rm *resourceManager) updateConditions( + r *resource, + onSuccess bool, + err error, +) (*resource, bool) { + ko := r.ko.DeepCopy() + rm.setStatusDefaults(ko) + + // Terminal condition + var terminalCondition *ackv1alpha1.Condition = nil + var recoverableCondition *ackv1alpha1.Condition = nil + var syncCondition *ackv1alpha1.Condition = nil + for _, condition := range ko.Status.Conditions { + if condition.Type == ackv1alpha1.ConditionTypeTerminal { + terminalCondition = condition + } + if condition.Type == ackv1alpha1.ConditionTypeRecoverable { + recoverableCondition = condition + } + if condition.Type == ackv1alpha1.ConditionTypeResourceSynced { + syncCondition = condition + } + } + var termError *ackerr.TerminalError + if rm.terminalAWSError(err) || err == ackerr.SecretTypeNotSupported || err == ackerr.SecretNotFound || errors.As(err, &termError) { + if terminalCondition == nil { + terminalCondition = &ackv1alpha1.Condition{ + Type: ackv1alpha1.ConditionTypeTerminal, + } + ko.Status.Conditions = append(ko.Status.Conditions, terminalCondition) + } + var errorMessage = "" + if err == ackerr.SecretTypeNotSupported || err == ackerr.SecretNotFound || errors.As(err, &termError) { + errorMessage = err.Error() + } else { + awsErr, _ := ackerr.AWSError(err) + errorMessage = awsErr.Error() + } + terminalCondition.Status = corev1.ConditionTrue + terminalCondition.Message = &errorMessage + } else { + // Clear the terminal condition if no longer present + if terminalCondition != nil { + terminalCondition.Status = corev1.ConditionFalse + terminalCondition.Message = nil + } + // Handling Recoverable Conditions + if err != nil { + if recoverableCondition == nil { + // Add a new Condition containing a non-terminal error + recoverableCondition = &ackv1alpha1.Condition{ + Type: ackv1alpha1.ConditionTypeRecoverable, + } + ko.Status.Conditions = append(ko.Status.Conditions, recoverableCondition) + } + recoverableCondition.Status = corev1.ConditionTrue + awsErr, _ := ackerr.AWSError(err) + errorMessage := err.Error() + if awsErr != nil { + errorMessage = awsErr.Error() + } + recoverableCondition.Message = &errorMessage + } else if recoverableCondition != nil { + recoverableCondition.Status = corev1.ConditionFalse + recoverableCondition.Message = nil + } + } + // Required to avoid the "declared but not used" error in the default case + _ = syncCondition + if terminalCondition != nil || recoverableCondition != nil || syncCondition != nil { + return &resource{ko}, true // updated + } + return nil, false // not updated +} + +// terminalAWSError returns awserr, true; if the supplied error is an aws Error type +// and if the exception indicates that it is a Terminal exception +// 'Terminal' exception are specified in generator configuration +func (rm *resourceManager) terminalAWSError(err error) bool { + if err == nil { + return false + } + + var terminalErr smithy.APIError + if !errors.As(err, &terminalErr) { + return false + } + switch terminalErr.ErrorCode() { + case "InvalidParameterValue", + "PrefixListMaxEntriesExceeded": + return true + default: + return false + } +} diff --git a/pkg/resource/managed_prefix_list/tags.go b/pkg/resource/managed_prefix_list/tags.go new file mode 100644 index 00000000..21faa8aa --- /dev/null +++ b/pkg/resource/managed_prefix_list/tags.go @@ -0,0 +1,119 @@ +// 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. + +// Code generated by ack-generate. DO NOT EDIT. + +package managed_prefix_list + +import ( + "slices" + "strings" + + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" + + svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" +) + +var ( + _ = svcapitypes.ManagedPrefixList{} + _ = acktags.NewTags() + ACKSystemTags = []string{"services.k8s.aws/namespace", "services.k8s.aws/controller-version"} +) + +// convertToOrderedACKTags converts the tags parameter into 'acktags.Tags' shape. +// This method helps in creating the hub(acktags.Tags) for merging +// default controller tags with existing resource tags. It also returns a slice +// of keys maintaining the original key Order when the tags are a list +func convertToOrderedACKTags(tags []*svcapitypes.Tag) (acktags.Tags, []string) { + result := acktags.NewTags() + keyOrder := []string{} + + if len(tags) == 0 { + return result, keyOrder + } + for _, t := range tags { + if t.Key != nil { + keyOrder = append(keyOrder, *t.Key) + if t.Value != nil { + result[*t.Key] = *t.Value + } else { + result[*t.Key] = "" + } + } + } + + return result, keyOrder +} + +// fromACKTags converts the tags parameter into []*svcapitypes.Tag shape. +// This method helps in setting the tags back inside AWSResource after merging +// default controller tags with existing resource tags. When a list, +// it maintains the order from original +func fromACKTags(tags acktags.Tags, keyOrder []string) []*svcapitypes.Tag { + result := []*svcapitypes.Tag{} + + for _, k := range keyOrder { + v, ok := tags[k] + if ok { + tag := svcapitypes.Tag{Key: &k, Value: &v} + result = append(result, &tag) + delete(tags, k) + } + } + for k, v := range tags { + tag := svcapitypes.Tag{Key: &k, Value: &v} + result = append(result, &tag) + } + + return result +} + +// ignoreSystemTags ignores tags that have keys that start with "aws:" +// and ACKSystemTags, to avoid patching them to the resourceSpec. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func ignoreSystemTags(tags acktags.Tags) { + for k := range tags { + if strings.HasPrefix(k, "aws:") || + slices.Contains(ACKSystemTags, k) { + delete(tags, k) + } + } +} + +// syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state +// are preserved in the desired state. This prevents the controller from attempting to +// modify AWS-managed tags, which would result in an error. +// +// AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) +// and cannot be modified or deleted through normal tag operations. Common examples include: +// - aws:cloudformation:stack-name +// - aws:servicecatalog:productArn +// +// Parameters: +// - a: The target Tags map to be updated (typically desired state) +// - b: The source Tags map containing AWS-managed tags (typically latest state) +// +// Example: +// +// latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} +// desired := Tags{"environment": "dev"} +// SyncAWSTags(desired, latest) +// desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} +func syncAWSTags(a acktags.Tags, b acktags.Tags) { + for k := range b { + if strings.HasPrefix(k, "aws:") { + a[k] = b[k] + } + } +} diff --git a/templates/hooks/managed_prefix_list/sdk_create_post_build_request.go.tpl b/templates/hooks/managed_prefix_list/sdk_create_post_build_request.go.tpl new file mode 100644 index 00000000..31ef8e89 --- /dev/null +++ b/templates/hooks/managed_prefix_list/sdk_create_post_build_request.go.tpl @@ -0,0 +1,3 @@ + updateTagSpecificationsInCreateRequest(desired, input) + + diff --git a/templates/hooks/managed_prefix_list/sdk_read_many_post_build_request.go.tpl b/templates/hooks/managed_prefix_list/sdk_read_many_post_build_request.go.tpl new file mode 100644 index 00000000..0bd2571b --- /dev/null +++ b/templates/hooks/managed_prefix_list/sdk_read_many_post_build_request.go.tpl @@ -0,0 +1,20 @@ + // If we have a PrefixListID, use it to filter the describe call + if r.ko.Status.PrefixListID != nil { + input.PrefixListIds = []string{*r.ko.Status.PrefixListID} + } else if r.ko.Spec.PrefixListName != nil { + // If we don't have an ID yet, filter by both owner ID and prefix list name + // This prevents matching against AWS-managed prefix lists + // and other user-owned prefix lists with different names + filters := []svcsdktypes.Filter{ + { + Name: aws.String("owner-id"), + Values: []string{string(rm.awsAccountID)}, + }, + { + Name: aws.String("prefix-list-name"), + Values: []string{*r.ko.Spec.PrefixListName}, + }, + } + input.Filters = filters + } + diff --git a/templates/hooks/managed_prefix_list/sdk_read_many_post_set_output.go.tpl b/templates/hooks/managed_prefix_list/sdk_read_many_post_set_output.go.tpl new file mode 100644 index 00000000..46653346 --- /dev/null +++ b/templates/hooks/managed_prefix_list/sdk_read_many_post_set_output.go.tpl @@ -0,0 +1,31 @@ + // Get the entries separately + if ko.Status.PrefixListID != nil { + entriesResp, err := rm.sdkapi.GetManagedPrefixListEntries( + ctx, + &svcsdk.GetManagedPrefixListEntriesInput{ + PrefixListId: ko.Status.PrefixListID, + }, + ) + rm.metrics.RecordAPICall("GET", "GetManagedPrefixListEntries", err) + if err != nil { + return nil, err + } + if entriesResp.Entries != nil { + f0 := []*svcapitypes.AddPrefixListEntry{} + for _, f0iter := range entriesResp.Entries { + f0elem := &svcapitypes.AddPrefixListEntry{} + if f0iter.Cidr != nil { + f0elem.CIDR = f0iter.Cidr + } + if f0iter.Description != nil { + f0elem.Description = f0iter.Description + } + f0 = append(f0, f0elem) + } + ko.Spec.Entries = f0 + } else { + ko.Spec.Entries = nil + } + } + + diff --git a/test/e2e/resources/managed_prefix_list.yaml b/test/e2e/resources/managed_prefix_list.yaml new file mode 100644 index 00000000..6fd7ae87 --- /dev/null +++ b/test/e2e/resources/managed_prefix_list.yaml @@ -0,0 +1,20 @@ +apiVersion: ec2.services.k8s.aws/v1alpha1 +kind: ManagedPrefixList +metadata: + name: $PREFIX_LIST_NAME +spec: + prefixListName: $PREFIX_LIST_NAME + addressFamily: IPv4 + maxEntries: 10 + entries: + - cidr: 10.0.0.0/24 + description: "Internal network A" + - cidr: 10.0.1.0/24 + description: "Internal network B" + - cidr: 192.168.1.0/24 + description: "Office network" + tags: + - key: $TAG_KEY + value: $TAG_VALUE + + diff --git a/test/e2e/resources/managed_prefix_list_ipv6.yaml b/test/e2e/resources/managed_prefix_list_ipv6.yaml new file mode 100644 index 00000000..e75bdd01 --- /dev/null +++ b/test/e2e/resources/managed_prefix_list_ipv6.yaml @@ -0,0 +1,18 @@ +apiVersion: ec2.services.k8s.aws/v1alpha1 +kind: ManagedPrefixList +metadata: + name: $PREFIX_LIST_NAME_IPV6 +spec: + prefixListName: $PREFIX_LIST_NAME_IPV6 + addressFamily: IPv6 + maxEntries: 5 + entries: + - cidr: 2001:db8:1234::/48 + description: "IPv6 network 1" + - cidr: 2001:db8:5678::/48 + description: "IPv6 network 2" + tags: + - key: $TAG_KEY + value: $TAG_VALUE + + diff --git a/test/e2e/tests/helper.py b/test/e2e/tests/helper.py index b4d40fea..54865651 100644 --- a/test/e2e/tests/helper.py +++ b/test/e2e/tests/helper.py @@ -384,3 +384,60 @@ def get_launch_template_version(self, launch_template_id: str, version: str) -> return None except self.ec2_client.exceptions.ClientError: return None + + def get_managed_prefix_list(self, prefix_list_id: str) -> Union[None, Dict]: + """Get a managed prefix list by ID, including its entries.""" + try: + aws_res = self.ec2_client.describe_managed_prefix_lists(PrefixListIds=[prefix_list_id]) + if len(aws_res["PrefixLists"]) > 0: + prefix_list = aws_res["PrefixLists"][0] + + # Also fetch the entries + entries_res = self.ec2_client.get_managed_prefix_list_entries(PrefixListId=prefix_list_id) + prefix_list["Entries"] = entries_res.get("Entries", []) + + return prefix_list + return None + except self.ec2_client.exceptions.ClientError: + return None + + def wait_managed_prefix_list_state(self, prefix_list_id: str, expected_state: str, max_wait_seconds: int = 120) -> bool: + """Wait for a managed prefix list to reach the expected state. + + Args: + prefix_list_id: The ID of the prefix list to wait for + expected_state: The expected state (e.g., 'create-complete', 'modify-complete') + max_wait_seconds: Maximum time to wait in seconds + + Returns: + True if the expected state was reached, False otherwise + """ + interval = 5 + max_tries = max_wait_seconds // interval + + for tries in range(max_tries): + prefix_list = self.get_managed_prefix_list(prefix_list_id) + if prefix_list is None: + return False + + current_state = prefix_list.get('State', '') + if current_state == expected_state: + return True + + # Check if we're in an error state + if 'failed' in current_state.lower(): + return False + + time.sleep(interval) + + return False + + def assert_managed_prefix_list(self, prefix_list_id: str, exists=True): + """Assert that a managed prefix list exists or doesn't exist.""" + res_found = False + try: + aws_res = self.ec2_client.describe_managed_prefix_lists(PrefixListIds=[prefix_list_id]) + res_found = len(aws_res["PrefixLists"]) > 0 + except self.ec2_client.exceptions.ClientError: + pass + assert res_found is exists diff --git a/test/e2e/tests/test_managed_prefix_list.py b/test/e2e/tests/test_managed_prefix_list.py new file mode 100644 index 00000000..635bbac9 --- /dev/null +++ b/test/e2e/tests/test_managed_prefix_list.py @@ -0,0 +1,406 @@ +# 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 Managed Prefix List API. +""" + +import pytest +import time +import logging +import boto3 + +from acktest.resources import random_suffix_name +from acktest.k8s import resource as k8s +from e2e import service_marker, CRD_GROUP, CRD_VERSION, load_ec2_resource +from e2e.replacement_values import REPLACEMENT_VALUES +from e2e.bootstrap_resources import get_bootstrap_resources +from e2e.tests.helper import EC2Validator + +RESOURCE_PLURAL = "managedprefixlists" + +CREATE_WAIT_AFTER_SECONDS = 10 +UPDATE_WAIT_AFTER_SECONDS = 10 +DELETE_WAIT_AFTER_SECONDS = 10 + +@pytest.fixture(scope="module") +def ec2_validator(): + """Fixture to provide EC2 validator for AWS API calls.""" + ec2_client = boto3.client("ec2") + return EC2Validator(ec2_client) + +@pytest.fixture(scope="module") +def prefix_list_ipv4(): + resource_name = random_suffix_name("managed-prefix-list-ipv4", 32) + + replacements = REPLACEMENT_VALUES.copy() + replacements["PREFIX_LIST_NAME"] = resource_name + replacements["TAG_KEY"] = "test-key" + replacements["TAG_VALUE"] = "test-value" + + # Load the resource + resource_data = load_ec2_resource( + "managed_prefix_list", + additional_replacements=replacements, + ) + logging.debug(resource_data) + + # Create k8s resource + 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) + + # Teardown + try: + _, deleted = k8s.delete_custom_resource(ref, 3, 10) + assert deleted + except: + pass + + +@pytest.fixture(scope="module") +def prefix_list_ipv6(): + resource_name = random_suffix_name("managed-prefix-list-ipv6", 32) + + replacements = REPLACEMENT_VALUES.copy() + replacements["PREFIX_LIST_NAME_IPV6"] = resource_name + replacements["TAG_KEY"] = "test-key" + replacements["TAG_VALUE"] = "test-value" + + # Load the resource + resource_data = load_ec2_resource( + "managed_prefix_list_ipv6", + additional_replacements=replacements, + ) + logging.debug(resource_data) + + # Create k8s resource + 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) + + # Teardown + try: + _, deleted = k8s.delete_custom_resource(ref, 3, 10) + assert deleted + except: + pass + + +@service_marker +@pytest.mark.canary +class TestManagedPrefixList: + def test_create_delete_ipv4(self, prefix_list_ipv4, ec2_validator): + """Test creation and deletion of an IPv4 managed prefix list.""" + (ref, cr) = prefix_list_ipv4 + + # Check that the resource was created + assert cr is not None + assert 'status' in cr + assert 'prefixListID' in cr['status'] + + prefix_list_id = cr['status']['prefixListID'] + assert prefix_list_id is not None + assert prefix_list_id.startswith('pl-') + + # Check state + assert 'state' in cr['status'] + state = cr['status']['state'] + assert state in ['create-in-progress', 'create-complete'] + + # Wait for AWS to complete creation + state_reached = ec2_validator.wait_managed_prefix_list_state( + prefix_list_id, + 'create-complete', + max_wait_seconds=180 + ) + assert state_reached, f"Prefix list {prefix_list_id} did not reach create-complete state within timeout" + + # Wait for K8s controller to sync the state from AWS + assert k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=30), \ + "Resource did not sync within timeout" + + # Verify final state + cr = k8s.get_resource(ref) + assert cr['status'].get('state') == 'create-complete', \ + f"Expected state create-complete, got {cr['status'].get('state')}" + + # Check that version was set + assert 'version' in cr['status'] + assert cr['status']['version'] is not None + + def test_create_delete_ipv6(self, prefix_list_ipv6, ec2_validator): + """Test creation and deletion of an IPv6 managed prefix list.""" + (ref, cr) = prefix_list_ipv6 + + # Check that the resource was created + assert cr is not None + assert 'status' in cr + assert 'prefixListID' in cr['status'] + + prefix_list_id = cr['status']['prefixListID'] + assert prefix_list_id is not None + assert prefix_list_id.startswith('pl-') + + # Verify address family in spec + assert cr['spec']['addressFamily'] == 'IPv6' + + # Wait for AWS to complete creation + state_reached = ec2_validator.wait_managed_prefix_list_state( + prefix_list_id, + 'create-complete', + max_wait_seconds=180 + ) + assert state_reached, f"Prefix list {prefix_list_id} did not reach create-complete state within timeout" + + # Wait for K8s controller to sync the state from AWS + assert k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=30), \ + "Resource did not sync within timeout" + + # Verify final state + cr = k8s.get_resource(ref) + assert cr['status'].get('state') == 'create-complete', \ + f"Expected state create-complete, got {cr['status'].get('state')}" + + def test_update_entries(self, prefix_list_ipv4, ec2_validator): + """Test adding and removing prefix list entries.""" + (ref, cr) = prefix_list_ipv4 + + # Get the prefix list ID + assert 'prefixListID' in cr['status'], "PrefixListID should be present in status" + prefix_list_id = cr['status']['prefixListID'] + + # Wait for the controller to process and sync the change + assert k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=40), \ + "Resource did not sync after creation" + + # Wait for initial creation to complete in AWS + state_reached = ec2_validator.wait_managed_prefix_list_state( + prefix_list_id, + 'create-complete', + max_wait_seconds=180 + ) + assert state_reached, f"Prefix list {prefix_list_id} did not reach create-complete state" + + # Verify initial state - should have 3 entries + aws_prefix_list = ec2_validator.get_managed_prefix_list(prefix_list_id) + initial_count = len(aws_prefix_list.get('Entries', [])) + assert initial_count == 3, f"Expected 3 initial entries, got {initial_count}" + + # Get the latest resource + cr = k8s.get_resource(ref) + assert cr['status']['state'] == 'create-complete', f"K8s state is {cr['status']['state']}, expected create-complete" + + # ===== TEST 1: Add an entry (3 → 4) ===== + cr['spec']['entries'].append({ + 'cidr': '10.0.2.0/24', + 'description': 'New network C' + }) + + # Apply the update + k8s.patch_custom_resource(ref, cr) + + # Give AWS time to process the async modification + time.sleep(UPDATE_WAIT_AFTER_SECONDS) + + # Wait for the controller to process and sync the change + assert k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=40), \ + "Resource did not sync after add" + + # Now wait for AWS to complete the modification + state_reached = ec2_validator.wait_managed_prefix_list_state( + prefix_list_id, + 'modify-complete', + max_wait_seconds=180 + ) + assert state_reached, f"Prefix list {prefix_list_id} did not reach modify-complete state after add" + + # Verify in AWS + aws_prefix_list = ec2_validator.get_managed_prefix_list(prefix_list_id) + after_add_count = len(aws_prefix_list.get('Entries', [])) + assert after_add_count == 4, f"Expected 4 entries after add, got {after_add_count}" + + # ===== TEST 2: Remove an entry (4 → 3) ===== + cr = k8s.get_resource(ref) + original_entries = cr['spec']['entries'][:] + + # Remove the entry we just added + entry_to_remove = '10.0.2.0/24' + cr['spec']['entries'] = [e for e in original_entries if e['cidr'] != entry_to_remove] + + # Apply the update + k8s.patch_custom_resource(ref, cr) + + # Give AWS time to process the async modification + time.sleep(UPDATE_WAIT_AFTER_SECONDS) + + # Wait for the controller to process and sync the change + assert k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=40), \ + "Resource did not sync after removal" + + # Now wait for AWS to complete the modification + state_reached = ec2_validator.wait_managed_prefix_list_state( + prefix_list_id, + 'modify-complete', + max_wait_seconds=180 + ) + assert state_reached, f"Prefix list {prefix_list_id} did not reach modify-complete state after removal" + + # Verify in AWS + aws_prefix_list = ec2_validator.get_managed_prefix_list(prefix_list_id) + after_remove_entries = aws_prefix_list.get('Entries', []) + after_remove_count = len(after_remove_entries) + aws_cidrs = [e['Cidr'] for e in after_remove_entries] + + # Verify deletion happened + assert after_remove_count == 3, f"Expected 3 entries after removal, got {after_remove_count}" + assert entry_to_remove not in aws_cidrs, \ + f"Entry {entry_to_remove} should have been removed but is still in AWS: {aws_cidrs}" + + # Final verification - check K8s matches AWS + cr = k8s.get_resource(ref) + k8s_cidrs = [e['cidr'] for e in cr['spec'].get('entries', [])] + assert len(k8s_cidrs) == 3, f"Expected 3 entries in K8s, got {len(k8s_cidrs)}" + assert entry_to_remove not in k8s_cidrs, \ + f"Entry {entry_to_remove} should not be in K8s: {k8s_cidrs}" + + def test_update_tags(self, prefix_list_ipv4, ec2_validator): + """Test adding, updating, and removing prefix list tags.""" + (ref, cr) = prefix_list_ipv4 + + # Get the prefix list ID + prefix_list_id = cr['status']['prefixListID'] + + # Get the latest version of the resource to avoid conflicts + cr = k8s.get_resource(ref) + + # ===== TEST 1: Add a new tag ===== + new_tag = { + 'key': 'Environment', + 'value': 'Test' + } + if 'tags' not in cr['spec']: + cr['spec']['tags'] = [] + cr['spec']['tags'].append(new_tag) + + # Apply the update + k8s.patch_custom_resource(ref, cr) + time.sleep(UPDATE_WAIT_AFTER_SECONDS) + + # Wait for the resource to be synced + assert k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=5), \ + "Resource did not sync after adding tag" + + # Get the updated resource + cr = k8s.get_resource(ref) + + # Verify tag was added in K8s + tags = cr['spec'].get('tags', []) + assert any(tag['key'] == 'Environment' and tag['value'] == 'Test' for tag in tags), \ + "Environment tag should be added in K8s" + + # Verify tag was added in AWS + aws_prefix_list = ec2_validator.get_managed_prefix_list(prefix_list_id) + aws_tags = aws_prefix_list.get('Tags', []) + assert any(tag['Key'] == 'Environment' and tag['Value'] == 'Test' for tag in aws_tags), \ + "Environment tag should be added in AWS" + + # ===== TEST 2: Update an existing tag ===== + for tag in cr['spec']['tags']: + if tag['key'] == 'Environment': + tag['value'] = 'Development' + + # Apply the update + k8s.patch_custom_resource(ref, cr) + time.sleep(UPDATE_WAIT_AFTER_SECONDS) + + # Wait for the resource to be synced + assert k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=5), \ + "Resource did not sync after updating tag" + + # Get the updated resource + cr = k8s.get_resource(ref) + + # Verify tag was updated in K8s + tags = cr['spec'].get('tags', []) + assert any(tag['key'] == 'Environment' and tag['value'] == 'Development' for tag in tags), \ + "Environment tag should be updated to Development in K8s" + + # Verify tag was updated in AWS + aws_prefix_list = ec2_validator.get_managed_prefix_list(prefix_list_id) + aws_tags = aws_prefix_list.get('Tags', []) + assert any(tag['Key'] == 'Environment' and tag['Value'] == 'Development' for tag in aws_tags), \ + "Environment tag should be updated to Development in AWS" + + # ===== TEST 3: Remove a tag ===== + cr['spec']['tags'] = [tag for tag in cr['spec']['tags'] if tag['key'] != 'Environment'] + + # Apply the update + k8s.patch_custom_resource(ref, cr) + time.sleep(UPDATE_WAIT_AFTER_SECONDS) + + # Wait for the resource to be synced + assert k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=5), \ + "Resource did not sync after removing tag" + + # Get the updated resource + cr = k8s.get_resource(ref) + + # Verify tag was removed in K8s + tags = cr['spec'].get('tags', []) + assert not any(tag['key'] == 'Environment' for tag in tags), \ + "Environment tag should be removed from K8s" + + # Verify tag was removed in AWS + aws_prefix_list = ec2_validator.get_managed_prefix_list(prefix_list_id) + aws_tags = aws_prefix_list.get('Tags', []) + assert not any(tag['Key'] == 'Environment' for tag in aws_tags), \ + "Environment tag should be removed from AWS" + + def test_prefix_list_fields(self, prefix_list_ipv4): + """Test that all expected fields are present.""" + (ref, cr) = prefix_list_ipv4 + + # Check spec fields + assert 'prefixListName' in cr['spec'] + assert 'addressFamily' in cr['spec'] + assert 'maxEntries' in cr['spec'] + assert 'entries' in cr['spec'] + + # Check status fields + assert 'prefixListID' in cr['status'] + assert 'state' in cr['status'] + assert 'version' in cr['status'] + assert 'ownerID' in cr['status'] + + # Validate entry structure + for entry in cr['spec']['entries']: + assert 'cidr' in entry + # Description is optional