diff --git a/apis/v1alpha1/ack-generate-metadata.yaml b/apis/v1alpha1/ack-generate-metadata.yaml index b51f02e8..7460d977 100755 --- a/apis/v1alpha1/ack-generate-metadata.yaml +++ b/apis/v1alpha1/ack-generate-metadata.yaml @@ -1,13 +1,13 @@ ack_generate_info: - build_date: "2022-03-22T20:05:53Z" + build_date: "2022-03-23T01:22:49Z" build_hash: 6f659f796434e8fd6443c0b3a5b495daae910035 - go_version: go1.17.5 + go_version: go1.17.8 version: v0.18.0 -api_directory_checksum: c9ac09820420d1c7cbe615b8baa14da9f72aab08 +api_directory_checksum: cc3671a6c2f26f433ffe32815ccfaa24d12e42d1 api_version: v1alpha1 aws_sdk_go_version: v1.42.0 generator_config_info: - file_checksum: 8cdcf3f86b878ab3f30b3e102a5a6b1df8dfafb3 + file_checksum: 721b58308fb63699f1ac3c233e529189e7d59c4c original_file_name: generator.yaml last_modification: reason: API generation diff --git a/apis/v1alpha1/generator.yaml b/apis/v1alpha1/generator.yaml index cda12ee5..a9af4995 100644 --- a/apis/v1alpha1/generator.yaml +++ b/apis/v1alpha1/generator.yaml @@ -6,6 +6,8 @@ ignore: - AllocateAddressInput.DryRun - CreateDhcpOptionsInput.DryRun - CreateInternetGatewayInput.DryRun + - CreateNatGatewayInput.ClientToken + - CreateNatGatewayInput.DryRun - CreateRouteInput.DryRun - CreateRouteInput.RouteTableId - CreateRouteTableInput.DryRun @@ -46,7 +48,7 @@ ignore: - LocalGatewayRouteTableVpcAssociation - LocalGatewayRoute - ManagedPrefixList - - NatGateway + #- NatGateway - NetworkAclEntry - NetworkAcl - NetworkInsightsPath @@ -101,6 +103,8 @@ operations: operation_type: - Delete resource_name: ElasticIPAddress + CreateNatGateway: + output_wrapper_field_path: NatGateway CreateVpcEndpoint: output_wrapper_field_path: VpcEndpoint DeleteVpcEndpoints: @@ -187,6 +191,21 @@ resources: template_path: hooks/elastic_ip_address/sdk_read_many_pre_build_request.go.tpl sdk_read_many_post_build_request: template_path: hooks/elastic_ip_address/sdk_read_many_post_build_request.go.tpl + NatGateway: + fields: + AllocationId: + references: + resource: ElasticIPAddress + path: Status.AllocationID + SubnetId: + references: + resource: Subnet + path: Status.SubnetID + synced: + when: + - path: Status.State + in: + - available SecurityGroup: fields: # support EC2-VPC only diff --git a/apis/v1alpha1/nat_gateway.go b/apis/v1alpha1/nat_gateway.go new file mode 100644 index 00000000..b10fb9fd --- /dev/null +++ b/apis/v1alpha1/nat_gateway.go @@ -0,0 +1,148 @@ +// 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" +) + +// NatGatewaySpec defines the desired state of NatGateway. +// +// Describes a NAT gateway. +type NATGatewaySpec struct { + // [Public NAT gateways only] The allocation ID of an Elastic IP address to + // associate with the NAT gateway. You cannot specify an Elastic IP address + // with a private NAT gateway. If the Elastic IP address is associated with + // another resource, you must first disassociate it. + AllocationID *string `json:"allocationID,omitempty"` + AllocationRef *ackv1alpha1.AWSResourceReferenceWrapper `json:"allocationRef,omitempty"` + // Indicates whether the NAT gateway supports public or private connectivity. + // The default is public connectivity. + ConnectivityType *string `json:"connectivityType,omitempty"` + // The subnet in which to create the NAT gateway. + SubnetID *string `json:"subnetID,omitempty"` + SubnetRef *ackv1alpha1.AWSResourceReferenceWrapper `json:"subnetRef,omitempty"` + // The tags to assign to the NAT gateway. + TagSpecifications []*TagSpecification `json:"tagSpecifications,omitempty"` +} + +// NATGatewayStatus defines the observed state of NATGateway +type NATGatewayStatus 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 date and time the NAT gateway was created. + // +kubebuilder:validation:Optional + CreateTime *metav1.Time `json:"createTime,omitempty"` + // The date and time the NAT gateway was deleted, if applicable. + // +kubebuilder:validation:Optional + DeleteTime *metav1.Time `json:"deleteTime,omitempty"` + // If the NAT gateway could not be created, specifies the error code for the + // failure. (InsufficientFreeAddressesInSubnet | Gateway.NotAttached | InvalidAllocationID.NotFound + // | Resource.AlreadyAssociated | InternalError | InvalidSubnetID.NotFound) + // +kubebuilder:validation:Optional + FailureCode *string `json:"failureCode,omitempty"` + // If the NAT gateway could not be created, specifies the error message for + // the failure, that corresponds to the error code. + // + // * For InsufficientFreeAddressesInSubnet: "Subnet has insufficient free + // addresses to create this NAT gateway" + // + // * For Gateway.NotAttached: "Network vpc-xxxxxxxx has no Internet gateway + // attached" + // + // * For InvalidAllocationID.NotFound: "Elastic IP address eipalloc-xxxxxxxx + // could not be associated with this NAT gateway" + // + // * For Resource.AlreadyAssociated: "Elastic IP address eipalloc-xxxxxxxx + // is already associated" + // + // * For InternalError: "Network interface eni-xxxxxxxx, created and used + // internally by this NAT gateway is in an invalid state. Please try again." + // + // * For InvalidSubnetID.NotFound: "The specified subnet subnet-xxxxxxxx + // does not exist or could not be found." + // +kubebuilder:validation:Optional + FailureMessage *string `json:"failureMessage,omitempty"` + // Information about the IP addresses and network interface associated with + // the NAT gateway. + // +kubebuilder:validation:Optional + NATGatewayAddresses []*NATGatewayAddress `json:"natGatewayAddresses,omitempty"` + // The ID of the NAT gateway. + // +kubebuilder:validation:Optional + NATGatewayID *string `json:"natGatewayID,omitempty"` + // Reserved. If you need to sustain traffic greater than the documented limits + // (https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html), + // contact us through the Support Center (https://console.aws.amazon.com/support/home?). + // +kubebuilder:validation:Optional + ProvisionedBandwidth *ProvisionedBandwidth `json:"provisionedBandwidth,omitempty"` + // The state of the NAT gateway. + // + // * pending: The NAT gateway is being created and is not ready to process + // traffic. + // + // * failed: The NAT gateway could not be created. Check the failureCode + // and failureMessage fields for the reason. + // + // * available: The NAT gateway is able to process traffic. This status remains + // until you delete the NAT gateway, and does not indicate the health of + // the NAT gateway. + // + // * deleting: The NAT gateway is in the process of being terminated and + // may still be processing traffic. + // + // * deleted: The NAT gateway has been terminated and is no longer processing + // traffic. + // +kubebuilder:validation:Optional + State *string `json:"state,omitempty"` + // The tags for the NAT gateway. + // +kubebuilder:validation:Optional + Tags []*Tag `json:"tags,omitempty"` + // The ID of the VPC in which the NAT gateway is located. + // +kubebuilder:validation:Optional + VPCID *string `json:"vpcID,omitempty"` +} + +// NATGateway is the Schema for the NATGateways API +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type NATGateway struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec NATGatewaySpec `json:"spec,omitempty"` + Status NATGatewayStatus `json:"status,omitempty"` +} + +// NATGatewayList contains a list of NATGateway +// +kubebuilder:object:root=true +type NATGatewayList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NATGateway `json:"items"` +} + +func init() { + SchemeBuilder.Register(&NATGateway{}, &NATGatewayList{}) +} diff --git a/apis/v1alpha1/types.go b/apis/v1alpha1/types.go index ca2f5d1c..3caed5d3 100644 --- a/apis/v1alpha1/types.go +++ b/apis/v1alpha1/types.go @@ -2097,18 +2097,6 @@ type MovingAddressStatus struct { PublicIP *string `json:"publicIP,omitempty"` } -// Describes a NAT gateway. -type NATGateway struct { - CreateTime *metav1.Time `json:"createTime,omitempty"` - DeleteTime *metav1.Time `json:"deleteTime,omitempty"` - FailureCode *string `json:"failureCode,omitempty"` - FailureMessage *string `json:"failureMessage,omitempty"` - NATGatewayID *string `json:"natGatewayID,omitempty"` - SubnetID *string `json:"subnetID,omitempty"` - Tags []*Tag `json:"tags,omitempty"` - VPCID *string `json:"vpcID,omitempty"` -} - // Describes the IP addresses and network interface associated with a NAT gateway. type NATGatewayAddress struct { AllocationID *string `json:"allocationID,omitempty"` @@ -2117,6 +2105,25 @@ type NATGatewayAddress struct { PublicIP *string `json:"publicIP,omitempty"` } +// Describes a NAT gateway. +type NATGateway_SDK struct { + ConnectivityType *string `json:"connectivityType,omitempty"` + CreateTime *metav1.Time `json:"createTime,omitempty"` + DeleteTime *metav1.Time `json:"deleteTime,omitempty"` + FailureCode *string `json:"failureCode,omitempty"` + FailureMessage *string `json:"failureMessage,omitempty"` + NATGatewayAddresses []*NATGatewayAddress `json:"natGatewayAddresses,omitempty"` + NATGatewayID *string `json:"natGatewayID,omitempty"` + // Reserved. If you need to sustain traffic greater than the documented limits + // (https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html), + // contact us through the Support Center (https://console.aws.amazon.com/support/home?). + ProvisionedBandwidth *ProvisionedBandwidth `json:"provisionedBandwidth,omitempty"` + State *string `json:"state,omitempty"` + SubnetID *string `json:"subnetID,omitempty"` + Tags []*Tag `json:"tags,omitempty"` + VPCID *string `json:"vpcID,omitempty"` +} + // Describes a network ACL. type NetworkACL struct { IsDefault *bool `json:"isDefault,omitempty"` diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index 91aed907..02361e3a 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -9285,6 +9285,167 @@ func (in *MovingAddressStatus) DeepCopy() *MovingAddressStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NATGateway) DeepCopyInto(out *NATGateway) { *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 NATGateway. +func (in *NATGateway) DeepCopy() *NATGateway { + if in == nil { + return nil + } + out := new(NATGateway) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NATGateway) 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 *NATGatewayAddress) DeepCopyInto(out *NATGatewayAddress) { + *out = *in + if in.AllocationID != nil { + in, out := &in.AllocationID, &out.AllocationID + *out = new(string) + **out = **in + } + if in.NetworkInterfaceID != nil { + in, out := &in.NetworkInterfaceID, &out.NetworkInterfaceID + *out = new(string) + **out = **in + } + if in.PrivateIP != nil { + in, out := &in.PrivateIP, &out.PrivateIP + *out = new(string) + **out = **in + } + if in.PublicIP != nil { + in, out := &in.PublicIP, &out.PublicIP + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NATGatewayAddress. +func (in *NATGatewayAddress) DeepCopy() *NATGatewayAddress { + if in == nil { + return nil + } + out := new(NATGatewayAddress) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NATGatewayList) DeepCopyInto(out *NATGatewayList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NATGateway, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NATGatewayList. +func (in *NATGatewayList) DeepCopy() *NATGatewayList { + if in == nil { + return nil + } + out := new(NATGatewayList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NATGatewayList) 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 *NATGatewaySpec) DeepCopyInto(out *NATGatewaySpec) { + *out = *in + if in.AllocationID != nil { + in, out := &in.AllocationID, &out.AllocationID + *out = new(string) + **out = **in + } + if in.AllocationRef != nil { + in, out := &in.AllocationRef, &out.AllocationRef + *out = new(corev1alpha1.AWSResourceReferenceWrapper) + (*in).DeepCopyInto(*out) + } + if in.ConnectivityType != nil { + in, out := &in.ConnectivityType, &out.ConnectivityType + *out = new(string) + **out = **in + } + if in.SubnetID != nil { + in, out := &in.SubnetID, &out.SubnetID + *out = new(string) + **out = **in + } + if in.SubnetRef != nil { + in, out := &in.SubnetRef, &out.SubnetRef + *out = new(corev1alpha1.AWSResourceReferenceWrapper) + (*in).DeepCopyInto(*out) + } + if in.TagSpecifications != nil { + in, out := &in.TagSpecifications, &out.TagSpecifications + *out = make([]*TagSpecification, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(TagSpecification) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NATGatewaySpec. +func (in *NATGatewaySpec) DeepCopy() *NATGatewaySpec { + if in == nil { + return nil + } + out := new(NATGatewaySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NATGatewayStatus) DeepCopyInto(out *NATGatewayStatus) { + *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.CreateTime != nil { in, out := &in.CreateTime, &out.CreateTime *out = (*in).DeepCopy() @@ -9303,13 +9464,29 @@ func (in *NATGateway) DeepCopyInto(out *NATGateway) { *out = new(string) **out = **in } + if in.NATGatewayAddresses != nil { + in, out := &in.NATGatewayAddresses, &out.NATGatewayAddresses + *out = make([]*NATGatewayAddress, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(NATGatewayAddress) + (*in).DeepCopyInto(*out) + } + } + } if in.NATGatewayID != nil { in, out := &in.NATGatewayID, &out.NATGatewayID *out = new(string) **out = **in } - if in.SubnetID != nil { - in, out := &in.SubnetID, &out.SubnetID + if in.ProvisionedBandwidth != nil { + in, out := &in.ProvisionedBandwidth, &out.ProvisionedBandwidth + *out = new(ProvisionedBandwidth) + (*in).DeepCopyInto(*out) + } + if in.State != nil { + in, out := &in.State, &out.State *out = new(string) **out = **in } @@ -9331,47 +9508,97 @@ func (in *NATGateway) DeepCopyInto(out *NATGateway) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NATGateway. -func (in *NATGateway) DeepCopy() *NATGateway { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NATGatewayStatus. +func (in *NATGatewayStatus) DeepCopy() *NATGatewayStatus { if in == nil { return nil } - out := new(NATGateway) + out := new(NATGatewayStatus) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NATGatewayAddress) DeepCopyInto(out *NATGatewayAddress) { +func (in *NATGateway_SDK) DeepCopyInto(out *NATGateway_SDK) { *out = *in - if in.AllocationID != nil { - in, out := &in.AllocationID, &out.AllocationID + if in.ConnectivityType != nil { + in, out := &in.ConnectivityType, &out.ConnectivityType *out = new(string) **out = **in } - if in.NetworkInterfaceID != nil { - in, out := &in.NetworkInterfaceID, &out.NetworkInterfaceID + if in.CreateTime != nil { + in, out := &in.CreateTime, &out.CreateTime + *out = (*in).DeepCopy() + } + if in.DeleteTime != nil { + in, out := &in.DeleteTime, &out.DeleteTime + *out = (*in).DeepCopy() + } + if in.FailureCode != nil { + in, out := &in.FailureCode, &out.FailureCode *out = new(string) **out = **in } - if in.PrivateIP != nil { - in, out := &in.PrivateIP, &out.PrivateIP + if in.FailureMessage != nil { + in, out := &in.FailureMessage, &out.FailureMessage *out = new(string) **out = **in } - if in.PublicIP != nil { - in, out := &in.PublicIP, &out.PublicIP + if in.NATGatewayAddresses != nil { + in, out := &in.NATGatewayAddresses, &out.NATGatewayAddresses + *out = make([]*NATGatewayAddress, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(NATGatewayAddress) + (*in).DeepCopyInto(*out) + } + } + } + if in.NATGatewayID != nil { + in, out := &in.NATGatewayID, &out.NATGatewayID + *out = new(string) + **out = **in + } + if in.ProvisionedBandwidth != nil { + in, out := &in.ProvisionedBandwidth, &out.ProvisionedBandwidth + *out = new(ProvisionedBandwidth) + (*in).DeepCopyInto(*out) + } + if in.State != nil { + in, out := &in.State, &out.State + *out = new(string) + **out = **in + } + if in.SubnetID != nil { + in, out := &in.SubnetID, &out.SubnetID + *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) + } + } + } + if in.VPCID != nil { + in, out := &in.VPCID, &out.VPCID *out = new(string) **out = **in } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NATGatewayAddress. -func (in *NATGatewayAddress) DeepCopy() *NATGatewayAddress { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NATGateway_SDK. +func (in *NATGateway_SDK) DeepCopy() *NATGateway_SDK { if in == nil { return nil } - out := new(NATGatewayAddress) + out := new(NATGateway_SDK) in.DeepCopyInto(out) return out } diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 4a9dbf86..ade41588 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -36,6 +36,7 @@ import ( _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/dhcp_options" _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/elastic_ip_address" _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/internet_gateway" + _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/nat_gateway" _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/route_table" _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/security_group" _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/subnet" diff --git a/config/crd/bases/ec2.services.k8s.aws_natgateways.yaml b/config/crd/bases/ec2.services.k8s.aws_natgateways.yaml new file mode 100644 index 00000000..3206639e --- /dev/null +++ b/config/crd/bases/ec2.services.k8s.aws_natgateways.yaml @@ -0,0 +1,271 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: natgateways.ec2.services.k8s.aws +spec: + group: ec2.services.k8s.aws + names: + kind: NATGateway + listKind: NATGatewayList + plural: natgateways + singular: natgateway + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: NATGateway is the Schema for the NATGateways 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: "NatGatewaySpec defines the desired state of NatGateway. + \n Describes a NAT gateway." + properties: + allocationID: + description: '[Public NAT gateways only] The allocation ID of an Elastic + IP address to associate with the NAT gateway. You cannot specify + an Elastic IP address with a private NAT gateway. If the Elastic + IP address is associated with another resource, you must first disassociate + it.' + type: string + allocationRef: + description: 'AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference type to provide more user friendly syntax + for references using ''from'' field Ex: APIIDRef: from: name: + my-api' + properties: + from: + description: AWSResourceReference provides all the values necessary + to reference another k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + type: object + type: object + connectivityType: + description: Indicates whether the NAT gateway supports public or + private connectivity. The default is public connectivity. + type: string + subnetID: + description: The subnet in which to create the NAT gateway. + type: string + subnetRef: + description: 'AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference type to provide more user friendly syntax + for references using ''from'' field Ex: APIIDRef: from: name: + my-api' + properties: + from: + description: AWSResourceReference provides all the values necessary + to reference another k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + type: object + type: object + tagSpecifications: + description: The tags to assign to the NAT gateway. + items: + description: The tags to apply to a resource when the resource is + being created. + properties: + resourceType: + type: string + tags: + items: + description: Describes a tag. + properties: + key: + type: string + value: + type: string + type: object + type: array + type: object + type: array + type: object + status: + description: NATGatewayStatus defines the observed state of NATGateway + 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. TODO(vijat@): Find a better + strategy for resources that do not have ARN in CreateOutputResponse + 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 + createTime: + description: The date and time the NAT gateway was created. + format: date-time + type: string + deleteTime: + description: The date and time the NAT gateway was deleted, if applicable. + format: date-time + type: string + failureCode: + description: If the NAT gateway could not be created, specifies the + error code for the failure. (InsufficientFreeAddressesInSubnet | + Gateway.NotAttached | InvalidAllocationID.NotFound | Resource.AlreadyAssociated + | InternalError | InvalidSubnetID.NotFound) + type: string + failureMessage: + description: "If the NAT gateway could not be created, specifies the + error message for the failure, that corresponds to the error code. + \n * For InsufficientFreeAddressesInSubnet: \"Subnet has insufficient + free addresses to create this NAT gateway\" \n * For Gateway.NotAttached: + \"Network vpc-xxxxxxxx has no Internet gateway attached\" \n + \ * For InvalidAllocationID.NotFound: \"Elastic IP address eipalloc-xxxxxxxx + \ could not be associated with this NAT gateway\" \n * For + Resource.AlreadyAssociated: \"Elastic IP address eipalloc-xxxxxxxx + \ is already associated\" \n * For InternalError: \"Network + interface eni-xxxxxxxx, created and used internally by this NAT + gateway is in an invalid state. Please try again.\" \n * For + InvalidSubnetID.NotFound: \"The specified subnet subnet-xxxxxxxx + \ does not exist or could not be found.\"" + type: string + natGatewayAddresses: + description: Information about the IP addresses and network interface + associated with the NAT gateway. + items: + description: Describes the IP addresses and network interface associated + with a NAT gateway. + properties: + allocationID: + type: string + networkInterfaceID: + type: string + privateIP: + type: string + publicIP: + type: string + type: object + type: array + natGatewayID: + description: The ID of the NAT gateway. + type: string + provisionedBandwidth: + description: Reserved. If you need to sustain traffic greater than + the documented limits (https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html), + contact us through the Support Center (https://console.aws.amazon.com/support/home?). + properties: + provisionTime: + format: date-time + type: string + provisioned: + type: string + requestTime: + format: date-time + type: string + requested: + type: string + status: + type: string + type: object + state: + description: "The state of the NAT gateway. \n * pending: The NAT + gateway is being created and is not ready to process traffic. + \n * failed: The NAT gateway could not be created. Check the + failureCode and failureMessage fields for the reason. \n * + available: The NAT gateway is able to process traffic. This status + remains until you delete the NAT gateway, and does not indicate + the health of the NAT gateway. \n * deleting: The NAT gateway + is in the process of being terminated and may still be processing + traffic. \n * deleted: The NAT gateway has been terminated and + is no longer processing traffic." + type: string + tags: + description: The tags for the NAT gateway. + items: + description: Describes a tag. + properties: + key: + type: string + value: + type: string + type: object + type: array + vpcID: + description: The ID of the VPC in which the NAT gateway is located. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index f78bc370..d2265c7d 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -6,6 +6,7 @@ resources: - bases/ec2.services.k8s.aws_dhcpoptions.yaml - bases/ec2.services.k8s.aws_elasticipaddresses.yaml - bases/ec2.services.k8s.aws_internetgateways.yaml + - bases/ec2.services.k8s.aws_natgateways.yaml - bases/ec2.services.k8s.aws_routetables.yaml - bases/ec2.services.k8s.aws_securitygroups.yaml - bases/ec2.services.k8s.aws_subnets.yaml diff --git a/config/rbac/cluster-role-controller.yaml b/config/rbac/cluster-role-controller.yaml index a7cb2109..4dede92d 100644 --- a/config/rbac/cluster-role-controller.yaml +++ b/config/rbac/cluster-role-controller.yaml @@ -92,6 +92,26 @@ rules: - get - patch - update +- apiGroups: + - ec2.services.k8s.aws + resources: + - natgateways + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - ec2.services.k8s.aws + resources: + - natgateways/status + verbs: + - get + - patch + - update - apiGroups: - ec2.services.k8s.aws resources: diff --git a/config/rbac/role-reader.yaml b/config/rbac/role-reader.yaml index ea732e32..244de4ce 100644 --- a/config/rbac/role-reader.yaml +++ b/config/rbac/role-reader.yaml @@ -12,6 +12,7 @@ rules: - dhcpoptions - elasticipaddresses - internetgateways + - natgateways - routetables - securitygroups - subnets diff --git a/config/rbac/role-writer.yaml b/config/rbac/role-writer.yaml index b7fb3a3e..992a763a 100644 --- a/config/rbac/role-writer.yaml +++ b/config/rbac/role-writer.yaml @@ -12,6 +12,7 @@ rules: - dhcpoptions - elasticipaddresses - internetgateways + - natgateways - routetables - securitygroups - subnets @@ -32,6 +33,7 @@ rules: - dhcpoptions - elasticipaddresses - internetgateways + - natgateways - routetables - securitygroups - subnets diff --git a/generator.yaml b/generator.yaml index cda12ee5..a9af4995 100644 --- a/generator.yaml +++ b/generator.yaml @@ -6,6 +6,8 @@ ignore: - AllocateAddressInput.DryRun - CreateDhcpOptionsInput.DryRun - CreateInternetGatewayInput.DryRun + - CreateNatGatewayInput.ClientToken + - CreateNatGatewayInput.DryRun - CreateRouteInput.DryRun - CreateRouteInput.RouteTableId - CreateRouteTableInput.DryRun @@ -46,7 +48,7 @@ ignore: - LocalGatewayRouteTableVpcAssociation - LocalGatewayRoute - ManagedPrefixList - - NatGateway + #- NatGateway - NetworkAclEntry - NetworkAcl - NetworkInsightsPath @@ -101,6 +103,8 @@ operations: operation_type: - Delete resource_name: ElasticIPAddress + CreateNatGateway: + output_wrapper_field_path: NatGateway CreateVpcEndpoint: output_wrapper_field_path: VpcEndpoint DeleteVpcEndpoints: @@ -187,6 +191,21 @@ resources: template_path: hooks/elastic_ip_address/sdk_read_many_pre_build_request.go.tpl sdk_read_many_post_build_request: template_path: hooks/elastic_ip_address/sdk_read_many_post_build_request.go.tpl + NatGateway: + fields: + AllocationId: + references: + resource: ElasticIPAddress + path: Status.AllocationID + SubnetId: + references: + resource: Subnet + path: Status.SubnetID + synced: + when: + - path: Status.State + in: + - available SecurityGroup: fields: # support EC2-VPC only diff --git a/helm/crds/ec2.services.k8s.aws_natgateways.yaml b/helm/crds/ec2.services.k8s.aws_natgateways.yaml new file mode 100644 index 00000000..3206639e --- /dev/null +++ b/helm/crds/ec2.services.k8s.aws_natgateways.yaml @@ -0,0 +1,271 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: natgateways.ec2.services.k8s.aws +spec: + group: ec2.services.k8s.aws + names: + kind: NATGateway + listKind: NATGatewayList + plural: natgateways + singular: natgateway + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: NATGateway is the Schema for the NATGateways 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: "NatGatewaySpec defines the desired state of NatGateway. + \n Describes a NAT gateway." + properties: + allocationID: + description: '[Public NAT gateways only] The allocation ID of an Elastic + IP address to associate with the NAT gateway. You cannot specify + an Elastic IP address with a private NAT gateway. If the Elastic + IP address is associated with another resource, you must first disassociate + it.' + type: string + allocationRef: + description: 'AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference type to provide more user friendly syntax + for references using ''from'' field Ex: APIIDRef: from: name: + my-api' + properties: + from: + description: AWSResourceReference provides all the values necessary + to reference another k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + type: object + type: object + connectivityType: + description: Indicates whether the NAT gateway supports public or + private connectivity. The default is public connectivity. + type: string + subnetID: + description: The subnet in which to create the NAT gateway. + type: string + subnetRef: + description: 'AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference type to provide more user friendly syntax + for references using ''from'' field Ex: APIIDRef: from: name: + my-api' + properties: + from: + description: AWSResourceReference provides all the values necessary + to reference another k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + type: object + type: object + tagSpecifications: + description: The tags to assign to the NAT gateway. + items: + description: The tags to apply to a resource when the resource is + being created. + properties: + resourceType: + type: string + tags: + items: + description: Describes a tag. + properties: + key: + type: string + value: + type: string + type: object + type: array + type: object + type: array + type: object + status: + description: NATGatewayStatus defines the observed state of NATGateway + 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. TODO(vijat@): Find a better + strategy for resources that do not have ARN in CreateOutputResponse + 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 + createTime: + description: The date and time the NAT gateway was created. + format: date-time + type: string + deleteTime: + description: The date and time the NAT gateway was deleted, if applicable. + format: date-time + type: string + failureCode: + description: If the NAT gateway could not be created, specifies the + error code for the failure. (InsufficientFreeAddressesInSubnet | + Gateway.NotAttached | InvalidAllocationID.NotFound | Resource.AlreadyAssociated + | InternalError | InvalidSubnetID.NotFound) + type: string + failureMessage: + description: "If the NAT gateway could not be created, specifies the + error message for the failure, that corresponds to the error code. + \n * For InsufficientFreeAddressesInSubnet: \"Subnet has insufficient + free addresses to create this NAT gateway\" \n * For Gateway.NotAttached: + \"Network vpc-xxxxxxxx has no Internet gateway attached\" \n + \ * For InvalidAllocationID.NotFound: \"Elastic IP address eipalloc-xxxxxxxx + \ could not be associated with this NAT gateway\" \n * For + Resource.AlreadyAssociated: \"Elastic IP address eipalloc-xxxxxxxx + \ is already associated\" \n * For InternalError: \"Network + interface eni-xxxxxxxx, created and used internally by this NAT + gateway is in an invalid state. Please try again.\" \n * For + InvalidSubnetID.NotFound: \"The specified subnet subnet-xxxxxxxx + \ does not exist or could not be found.\"" + type: string + natGatewayAddresses: + description: Information about the IP addresses and network interface + associated with the NAT gateway. + items: + description: Describes the IP addresses and network interface associated + with a NAT gateway. + properties: + allocationID: + type: string + networkInterfaceID: + type: string + privateIP: + type: string + publicIP: + type: string + type: object + type: array + natGatewayID: + description: The ID of the NAT gateway. + type: string + provisionedBandwidth: + description: Reserved. If you need to sustain traffic greater than + the documented limits (https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html), + contact us through the Support Center (https://console.aws.amazon.com/support/home?). + properties: + provisionTime: + format: date-time + type: string + provisioned: + type: string + requestTime: + format: date-time + type: string + requested: + type: string + status: + type: string + type: object + state: + description: "The state of the NAT gateway. \n * pending: The NAT + gateway is being created and is not ready to process traffic. + \n * failed: The NAT gateway could not be created. Check the + failureCode and failureMessage fields for the reason. \n * + available: The NAT gateway is able to process traffic. This status + remains until you delete the NAT gateway, and does not indicate + the health of the NAT gateway. \n * deleting: The NAT gateway + is in the process of being terminated and may still be processing + traffic. \n * deleted: The NAT gateway has been terminated and + is no longer processing traffic." + type: string + tags: + description: The tags for the NAT gateway. + items: + description: Describes a tag. + properties: + key: + type: string + value: + type: string + type: object + type: array + vpcID: + description: The ID of the VPC in which the NAT gateway is located. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/helm/templates/cluster-role-controller.yaml b/helm/templates/cluster-role-controller.yaml index b4dec964..2ef323ce 100644 --- a/helm/templates/cluster-role-controller.yaml +++ b/helm/templates/cluster-role-controller.yaml @@ -98,6 +98,26 @@ rules: - get - patch - update +- apiGroups: + - ec2.services.k8s.aws + resources: + - natgateways + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - ec2.services.k8s.aws + resources: + - natgateways/status + verbs: + - get + - patch + - update - apiGroups: - ec2.services.k8s.aws resources: diff --git a/helm/templates/role-reader.yaml b/helm/templates/role-reader.yaml index 731f33c1..2c7c0c7e 100644 --- a/helm/templates/role-reader.yaml +++ b/helm/templates/role-reader.yaml @@ -12,6 +12,7 @@ rules: - dhcpoptions - elasticipaddresses - internetgateways + - natgateways - routetables - securitygroups - subnets diff --git a/helm/templates/role-writer.yaml b/helm/templates/role-writer.yaml index 869c344a..e48fbedf 100644 --- a/helm/templates/role-writer.yaml +++ b/helm/templates/role-writer.yaml @@ -15,6 +15,8 @@ rules: - internetgateways + - natgateways + - routetables - securitygroups @@ -41,6 +43,7 @@ rules: - dhcpoptions - elasticipaddresses - internetgateways + - natgateways - routetables - securitygroups - subnets diff --git a/pkg/resource/nat_gateway/delta.go b/pkg/resource/nat_gateway/delta.go new file mode 100644 index 00000000..cd3e67e4 --- /dev/null +++ b/pkg/resource/nat_gateway/delta.go @@ -0,0 +1,76 @@ +// 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 nat_gateway + +import ( + "bytes" + "reflect" + + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" +) + +// Hack to avoid import errors during build... +var ( + _ = &bytes.Buffer{} + _ = &reflect.Method{} +) + +// 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.AllocationID, b.ko.Spec.AllocationID) { + delta.Add("Spec.AllocationID", a.ko.Spec.AllocationID, b.ko.Spec.AllocationID) + } else if a.ko.Spec.AllocationID != nil && b.ko.Spec.AllocationID != nil { + if *a.ko.Spec.AllocationID != *b.ko.Spec.AllocationID { + delta.Add("Spec.AllocationID", a.ko.Spec.AllocationID, b.ko.Spec.AllocationID) + } + } + if !reflect.DeepEqual(a.ko.Spec.AllocationRef, b.ko.Spec.AllocationRef) { + delta.Add("Spec.AllocationRef", a.ko.Spec.AllocationRef, b.ko.Spec.AllocationRef) + } + if ackcompare.HasNilDifference(a.ko.Spec.ConnectivityType, b.ko.Spec.ConnectivityType) { + delta.Add("Spec.ConnectivityType", a.ko.Spec.ConnectivityType, b.ko.Spec.ConnectivityType) + } else if a.ko.Spec.ConnectivityType != nil && b.ko.Spec.ConnectivityType != nil { + if *a.ko.Spec.ConnectivityType != *b.ko.Spec.ConnectivityType { + delta.Add("Spec.ConnectivityType", a.ko.Spec.ConnectivityType, b.ko.Spec.ConnectivityType) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.SubnetID, b.ko.Spec.SubnetID) { + delta.Add("Spec.SubnetID", a.ko.Spec.SubnetID, b.ko.Spec.SubnetID) + } else if a.ko.Spec.SubnetID != nil && b.ko.Spec.SubnetID != nil { + if *a.ko.Spec.SubnetID != *b.ko.Spec.SubnetID { + delta.Add("Spec.SubnetID", a.ko.Spec.SubnetID, b.ko.Spec.SubnetID) + } + } + if !reflect.DeepEqual(a.ko.Spec.SubnetRef, b.ko.Spec.SubnetRef) { + delta.Add("Spec.SubnetRef", a.ko.Spec.SubnetRef, b.ko.Spec.SubnetRef) + } + if !reflect.DeepEqual(a.ko.Spec.TagSpecifications, b.ko.Spec.TagSpecifications) { + delta.Add("Spec.TagSpecifications", a.ko.Spec.TagSpecifications, b.ko.Spec.TagSpecifications) + } + + return delta +} diff --git a/pkg/resource/nat_gateway/descriptor.go b/pkg/resource/nat_gateway/descriptor.go new file mode 100644 index 00000000..b7c76f84 --- /dev/null +++ b/pkg/resource/nat_gateway/descriptor.go @@ -0,0 +1,154 @@ +// 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 nat_gateway + +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" + 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/NATGateway" +) + +var ( + GroupVersionResource = svcapitypes.GroupVersion.WithResource("natgateways") + GroupKind = metav1.GroupKind{ + Group: "ec2.services.k8s.aws", + Kind: "NATGateway", + } +) + +// resourceDescriptor implements the +// `aws-service-operator-k8s/pkg/types.AWSResourceDescriptor` interface +type resourceDescriptor struct { +} + +// GroupKind returns a Kubernetes metav1.GroupKind struct that describes the +// API Group and Kind of CRs described by the descriptor +func (d *resourceDescriptor) GroupKind() *metav1.GroupKind { + return &GroupKind +} + +// EmptyRuntimeObject returns an empty object prototype that may be used in +// apimachinery and k8s client operations +func (d *resourceDescriptor) EmptyRuntimeObject() rtclient.Object { + return &svcapitypes.NATGateway{} +} + +// 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.NATGateway), + } +} + +// 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/nat_gateway/identifiers.go b/pkg/resource/nat_gateway/identifiers.go new file mode 100644 index 00000000..e8c2ea00 --- /dev/null +++ b/pkg/resource/nat_gateway/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 nat_gateway + +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/nat_gateway/manager.go b/pkg/resource/nat_gateway/manager.go new file mode 100644 index 00000000..54e0acf4 --- /dev/null +++ b/pkg/resource/nat_gateway/manager.go @@ -0,0 +1,330 @@ +// 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 nat_gateway + +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" + ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + ackutil "github.com/aws-controllers-k8s/runtime/pkg/util" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + + svcsdk "github.com/aws/aws-sdk-go/service/ec2" + svcsdkapi "github.com/aws/aws-sdk-go/service/ec2/ec2iface" +) + +var ( + _ = ackutil.InStrings +) + +// +kubebuilder:rbac:groups=ec2.services.k8s.aws,resources=natgateways,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=ec2.services.k8s.aws,resources=natgateways/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 + // 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 + // sess is the AWS SDK Session object used to communicate with the backend + // AWS service API + sess *session.Session + // sdk is a pointer to the AWS service API interface exposed by the + // aws-sdk-go/services/{alias}/{alias}iface package. + sdkapi svcsdkapi.EC2API +} + +// 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) + 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 { + 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 { + 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{"available"} + if !ackutil.InStrings(*r.ko.Status.State, stateCandidates) { + return false, nil + } + + return true, nil +} + +// newResourceManager returns a new struct implementing +// acktypes.AWSResourceManager +func newResourceManager( + cfg ackcfg.Config, + log logr.Logger, + metrics *ackmetrics.Metrics, + rr acktypes.Reconciler, + sess *session.Session, + id ackv1alpha1.AWSAccountID, + region ackv1alpha1.AWSRegion, +) (*resourceManager, error) { + return &resourceManager{ + cfg: cfg, + log: log, + metrics: metrics, + rr: rr, + awsAccountID: id, + awsRegion: region, + sess: sess, + sdkapi: svcsdk.New(sess), + }, 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/nat_gateway/manager_factory.go b/pkg/resource/nat_gateway/manager_factory.go new file mode 100644 index 00000000..e81c1d12 --- /dev/null +++ b/pkg/resource/nat_gateway/manager_factory.go @@ -0,0 +1,96 @@ +// 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 nat_gateway + +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/aws/session" + "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, + log logr.Logger, + metrics *ackmetrics.Metrics, + rr acktypes.Reconciler, + sess *session.Session, + id ackv1alpha1.AWSAccountID, + region ackv1alpha1.AWSRegion, +) (acktypes.AWSResourceManager, error) { + rmId := fmt.Sprintf("%s/%s", id, region) + f.RLock() + rm, found := f.rmCache[rmId] + f.RUnlock() + + if found { + return rm, nil + } + + f.Lock() + defer f.Unlock() + + rm, err := newResourceManager(cfg, log, metrics, rr, sess, 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/nat_gateway/references.go b/pkg/resource/nat_gateway/references.go new file mode 100644 index 00000000..27ce414d --- /dev/null +++ b/pkg/resource/nat_gateway/references.go @@ -0,0 +1,193 @@ +// 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 nat_gateway + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcondition "github.com/aws-controllers-k8s/runtime/pkg/condition" + ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + + svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" +) + +// 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 target field(s). +// It returns an AWSResource with resolved reference(s), and an error if the +// passed AWSResource's reference field(s) cannot be resolved. +// This method also adds/updates the ConditionTypeReferencesResolved for the +// AWSResource. +func (rm *resourceManager) ResolveReferences( + ctx context.Context, + apiReader client.Reader, + res acktypes.AWSResource, +) (acktypes.AWSResource, error) { + namespace := res.MetaObject().GetNamespace() + ko := rm.concreteResource(res).ko.DeepCopy() + err := validateReferenceFields(ko) + if err == nil { + err = resolveReferenceForAllocationID(ctx, apiReader, namespace, ko) + } + if err == nil { + err = resolveReferenceForSubnetID(ctx, apiReader, namespace, ko) + } + + if hasNonNilReferences(ko) { + return ackcondition.WithReferencesResolvedCondition(&resource{ko}, err) + } + return &resource{ko}, err +} + +// validateReferenceFields validates the reference field and corresponding +// identifier field. +func validateReferenceFields(ko *svcapitypes.NATGateway) error { + if ko.Spec.AllocationRef != nil && ko.Spec.AllocationID != nil { + return ackerr.ResourceReferenceAndIDNotSupportedFor("AllocationID", "AllocationRef") + } + if ko.Spec.SubnetRef != nil && ko.Spec.SubnetID != nil { + return ackerr.ResourceReferenceAndIDNotSupportedFor("SubnetID", "SubnetRef") + } + if ko.Spec.SubnetRef == nil && ko.Spec.SubnetID == nil { + return ackerr.ResourceReferenceOrIDRequiredFor("SubnetID", "SubnetRef") + } + return nil +} + +// hasNonNilReferences returns true if resource contains a reference to another +// resource +func hasNonNilReferences(ko *svcapitypes.NATGateway) bool { + return false || ko.Spec.AllocationRef != nil || ko.Spec.SubnetRef != nil +} + +// resolveReferenceForAllocationID reads the resource referenced +// from AllocationRef field and sets the AllocationID +// from referenced resource +func resolveReferenceForAllocationID( + ctx context.Context, + apiReader client.Reader, + namespace string, + ko *svcapitypes.NATGateway, +) error { + if ko.Spec.AllocationRef != nil && + ko.Spec.AllocationRef.From != nil { + arr := ko.Spec.AllocationRef.From + if arr == nil || arr.Name == nil || *arr.Name == "" { + return fmt.Errorf("provided resource reference is nil or empty") + } + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: *arr.Name, + } + obj := svcapitypes.ElasticIPAddress{} + err := apiReader.Get(ctx, namespacedName, &obj) + if err != nil { + return err + } + var refResourceSynced, refResourceTerminal bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeResourceSynced && + cond.Status == corev1.ConditionTrue { + refResourceSynced = true + } + if cond.Type == ackv1alpha1.ConditionTypeTerminal && + cond.Status == corev1.ConditionTrue { + refResourceTerminal = true + } + } + if refResourceTerminal { + return ackerr.ResourceReferenceTerminalFor( + "ElasticIPAddress", + namespace, *arr.Name) + } + if !refResourceSynced { + return ackerr.ResourceReferenceNotSyncedFor( + "ElasticIPAddress", + namespace, *arr.Name) + } + if obj.Status.AllocationID == nil { + return ackerr.ResourceReferenceMissingTargetFieldFor( + "ElasticIPAddress", + namespace, *arr.Name, + "Status.AllocationID") + } + ko.Spec.AllocationID = obj.Status.AllocationID + } + return nil +} + +// resolveReferenceForSubnetID reads the resource referenced +// from SubnetRef field and sets the SubnetID +// from referenced resource +func resolveReferenceForSubnetID( + ctx context.Context, + apiReader client.Reader, + namespace string, + ko *svcapitypes.NATGateway, +) error { + if ko.Spec.SubnetRef != nil && + ko.Spec.SubnetRef.From != nil { + arr := ko.Spec.SubnetRef.From + if arr == nil || arr.Name == nil || *arr.Name == "" { + return fmt.Errorf("provided resource reference is nil or empty") + } + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: *arr.Name, + } + obj := svcapitypes.Subnet{} + err := apiReader.Get(ctx, namespacedName, &obj) + if err != nil { + return err + } + var refResourceSynced, refResourceTerminal bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeResourceSynced && + cond.Status == corev1.ConditionTrue { + refResourceSynced = true + } + if cond.Type == ackv1alpha1.ConditionTypeTerminal && + cond.Status == corev1.ConditionTrue { + refResourceTerminal = true + } + } + if refResourceTerminal { + return ackerr.ResourceReferenceTerminalFor( + "Subnet", + namespace, *arr.Name) + } + if !refResourceSynced { + return ackerr.ResourceReferenceNotSyncedFor( + "Subnet", + namespace, *arr.Name) + } + if obj.Status.SubnetID == nil { + return ackerr.ResourceReferenceMissingTargetFieldFor( + "Subnet", + namespace, *arr.Name, + "Status.SubnetID") + } + ko.Spec.SubnetID = obj.Status.SubnetID + } + return nil +} diff --git a/pkg/resource/nat_gateway/resource.go b/pkg/resource/nat_gateway/resource.go new file mode 100644 index 00000000..344dd3fe --- /dev/null +++ b/pkg/resource/nat_gateway/resource.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 nat_gateway + +import ( + 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.NATGateway +} + +// 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 timestemp +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.NATGatewayID = &identifier.NameOrID + + 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/nat_gateway/sdk.go b/pkg/resource/nat_gateway/sdk.go new file mode 100644 index 00000000..d1903672 --- /dev/null +++ b/pkg/resource/nat_gateway/sdk.go @@ -0,0 +1,552 @@ +// 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 nat_gateway + +import ( + "context" + "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" + ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" + "github.com/aws/aws-sdk-go/aws" + svcsdk "github.com/aws/aws-sdk-go/service/ec2" + 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("") + _ = &aws.JSONValue{} + _ = &svcsdk.EC2{} + _ = &svcapitypes.NATGateway{} + _ = ackv1alpha1.AWSAccountID("") + _ = &ackerr.NotFound + _ = &ackcondition.NotManagedMessage + _ = &reflect.Value{} +) + +// 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 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 + } + var resp *svcsdk.DescribeNatGatewaysOutput + resp, err = rm.sdkapi.DescribeNatGatewaysWithContext(ctx, input) + rm.metrics.RecordAPICall("READ_MANY", "DescribeNatGateways", err) + if err != nil { + if awsErr, ok := ackerr.AWSError(err); ok && awsErr.Code() == "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.NatGateways { + if elem.ConnectivityType != nil { + ko.Spec.ConnectivityType = elem.ConnectivityType + } else { + ko.Spec.ConnectivityType = nil + } + if elem.CreateTime != nil { + ko.Status.CreateTime = &metav1.Time{*elem.CreateTime} + } else { + ko.Status.CreateTime = nil + } + if elem.DeleteTime != nil { + ko.Status.DeleteTime = &metav1.Time{*elem.DeleteTime} + } else { + ko.Status.DeleteTime = nil + } + if elem.FailureCode != nil { + ko.Status.FailureCode = elem.FailureCode + } else { + ko.Status.FailureCode = nil + } + if elem.FailureMessage != nil { + ko.Status.FailureMessage = elem.FailureMessage + } else { + ko.Status.FailureMessage = nil + } + if elem.NatGatewayAddresses != nil { + f5 := []*svcapitypes.NATGatewayAddress{} + for _, f5iter := range elem.NatGatewayAddresses { + f5elem := &svcapitypes.NATGatewayAddress{} + if f5iter.AllocationId != nil { + f5elem.AllocationID = f5iter.AllocationId + } + if f5iter.NetworkInterfaceId != nil { + f5elem.NetworkInterfaceID = f5iter.NetworkInterfaceId + } + if f5iter.PrivateIp != nil { + f5elem.PrivateIP = f5iter.PrivateIp + } + if f5iter.PublicIp != nil { + f5elem.PublicIP = f5iter.PublicIp + } + f5 = append(f5, f5elem) + } + ko.Status.NATGatewayAddresses = f5 + } else { + ko.Status.NATGatewayAddresses = nil + } + if elem.NatGatewayId != nil { + ko.Status.NATGatewayID = elem.NatGatewayId + } else { + ko.Status.NATGatewayID = nil + } + if elem.ProvisionedBandwidth != nil { + f7 := &svcapitypes.ProvisionedBandwidth{} + if elem.ProvisionedBandwidth.ProvisionTime != nil { + f7.ProvisionTime = &metav1.Time{*elem.ProvisionedBandwidth.ProvisionTime} + } + if elem.ProvisionedBandwidth.Provisioned != nil { + f7.Provisioned = elem.ProvisionedBandwidth.Provisioned + } + if elem.ProvisionedBandwidth.RequestTime != nil { + f7.RequestTime = &metav1.Time{*elem.ProvisionedBandwidth.RequestTime} + } + if elem.ProvisionedBandwidth.Requested != nil { + f7.Requested = elem.ProvisionedBandwidth.Requested + } + if elem.ProvisionedBandwidth.Status != nil { + f7.Status = elem.ProvisionedBandwidth.Status + } + ko.Status.ProvisionedBandwidth = f7 + } else { + ko.Status.ProvisionedBandwidth = nil + } + if elem.State != nil { + ko.Status.State = elem.State + } else { + ko.Status.State = nil + } + if elem.SubnetId != nil { + ko.Spec.SubnetID = elem.SubnetId + } else { + ko.Spec.SubnetID = nil + } + if elem.Tags != nil { + f10 := []*svcapitypes.Tag{} + for _, f10iter := range elem.Tags { + f10elem := &svcapitypes.Tag{} + if f10iter.Key != nil { + f10elem.Key = f10iter.Key + } + if f10iter.Value != nil { + f10elem.Value = f10iter.Value + } + f10 = append(f10, f10elem) + } + ko.Status.Tags = f10 + } else { + ko.Status.Tags = nil + } + if elem.VpcId != nil { + ko.Status.VPCID = elem.VpcId + } else { + ko.Status.VPCID = nil + } + found = true + break + } + if !found { + return nil, ackerr.NotFound + } + + rm.setStatusDefaults(ko) + 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 r.ko.Status.NATGatewayID == nil + +} + +// 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.DescribeNatGatewaysInput, error) { + res := &svcsdk.DescribeNatGatewaysInput{} + + if r.ko.Status.NATGatewayID != nil { + f3 := []*string{} + f3 = append(f3, r.ko.Status.NATGatewayID) + res.SetNatGatewayIds(f3) + } + + 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 exit(err) + input, err := rm.newCreateRequestPayload(ctx, desired) + if err != nil { + return nil, err + } + + var resp *svcsdk.CreateNatGatewayOutput + _ = resp + resp, err = rm.sdkapi.CreateNatGatewayWithContext(ctx, input) + rm.metrics.RecordAPICall("CREATE", "CreateNatGateway", 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.NatGateway.ConnectivityType != nil { + ko.Spec.ConnectivityType = resp.NatGateway.ConnectivityType + } else { + ko.Spec.ConnectivityType = nil + } + if resp.NatGateway.CreateTime != nil { + ko.Status.CreateTime = &metav1.Time{*resp.NatGateway.CreateTime} + } else { + ko.Status.CreateTime = nil + } + if resp.NatGateway.DeleteTime != nil { + ko.Status.DeleteTime = &metav1.Time{*resp.NatGateway.DeleteTime} + } else { + ko.Status.DeleteTime = nil + } + if resp.NatGateway.FailureCode != nil { + ko.Status.FailureCode = resp.NatGateway.FailureCode + } else { + ko.Status.FailureCode = nil + } + if resp.NatGateway.FailureMessage != nil { + ko.Status.FailureMessage = resp.NatGateway.FailureMessage + } else { + ko.Status.FailureMessage = nil + } + if resp.NatGateway.NatGatewayAddresses != nil { + f5 := []*svcapitypes.NATGatewayAddress{} + for _, f5iter := range resp.NatGateway.NatGatewayAddresses { + f5elem := &svcapitypes.NATGatewayAddress{} + if f5iter.AllocationId != nil { + f5elem.AllocationID = f5iter.AllocationId + } + if f5iter.NetworkInterfaceId != nil { + f5elem.NetworkInterfaceID = f5iter.NetworkInterfaceId + } + if f5iter.PrivateIp != nil { + f5elem.PrivateIP = f5iter.PrivateIp + } + if f5iter.PublicIp != nil { + f5elem.PublicIP = f5iter.PublicIp + } + f5 = append(f5, f5elem) + } + ko.Status.NATGatewayAddresses = f5 + } else { + ko.Status.NATGatewayAddresses = nil + } + if resp.NatGateway.NatGatewayId != nil { + ko.Status.NATGatewayID = resp.NatGateway.NatGatewayId + } else { + ko.Status.NATGatewayID = nil + } + if resp.NatGateway.ProvisionedBandwidth != nil { + f7 := &svcapitypes.ProvisionedBandwidth{} + if resp.NatGateway.ProvisionedBandwidth.ProvisionTime != nil { + f7.ProvisionTime = &metav1.Time{*resp.NatGateway.ProvisionedBandwidth.ProvisionTime} + } + if resp.NatGateway.ProvisionedBandwidth.Provisioned != nil { + f7.Provisioned = resp.NatGateway.ProvisionedBandwidth.Provisioned + } + if resp.NatGateway.ProvisionedBandwidth.RequestTime != nil { + f7.RequestTime = &metav1.Time{*resp.NatGateway.ProvisionedBandwidth.RequestTime} + } + if resp.NatGateway.ProvisionedBandwidth.Requested != nil { + f7.Requested = resp.NatGateway.ProvisionedBandwidth.Requested + } + if resp.NatGateway.ProvisionedBandwidth.Status != nil { + f7.Status = resp.NatGateway.ProvisionedBandwidth.Status + } + ko.Status.ProvisionedBandwidth = f7 + } else { + ko.Status.ProvisionedBandwidth = nil + } + if resp.NatGateway.State != nil { + ko.Status.State = resp.NatGateway.State + } else { + ko.Status.State = nil + } + if resp.NatGateway.SubnetId != nil { + ko.Spec.SubnetID = resp.NatGateway.SubnetId + } else { + ko.Spec.SubnetID = nil + } + if resp.NatGateway.Tags != nil { + f10 := []*svcapitypes.Tag{} + for _, f10iter := range resp.NatGateway.Tags { + f10elem := &svcapitypes.Tag{} + if f10iter.Key != nil { + f10elem.Key = f10iter.Key + } + if f10iter.Value != nil { + f10elem.Value = f10iter.Value + } + f10 = append(f10, f10elem) + } + ko.Status.Tags = f10 + } else { + ko.Status.Tags = nil + } + if resp.NatGateway.VpcId != nil { + ko.Status.VPCID = resp.NatGateway.VpcId + } else { + ko.Status.VPCID = 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.CreateNatGatewayInput, error) { + res := &svcsdk.CreateNatGatewayInput{} + + if r.ko.Spec.AllocationID != nil { + res.SetAllocationId(*r.ko.Spec.AllocationID) + } + if r.ko.Spec.ConnectivityType != nil { + res.SetConnectivityType(*r.ko.Spec.ConnectivityType) + } + if r.ko.Spec.SubnetID != nil { + res.SetSubnetId(*r.ko.Spec.SubnetID) + } + if r.ko.Spec.TagSpecifications != nil { + f3 := []*svcsdk.TagSpecification{} + for _, f3iter := range r.ko.Spec.TagSpecifications { + f3elem := &svcsdk.TagSpecification{} + if f3iter.ResourceType != nil { + f3elem.SetResourceType(*f3iter.ResourceType) + } + if f3iter.Tags != nil { + f3elemf1 := []*svcsdk.Tag{} + for _, f3elemf1iter := range f3iter.Tags { + f3elemf1elem := &svcsdk.Tag{} + if f3elemf1iter.Key != nil { + f3elemf1elem.SetKey(*f3elemf1iter.Key) + } + if f3elemf1iter.Value != nil { + f3elemf1elem.SetValue(*f3elemf1iter.Value) + } + f3elemf1 = append(f3elemf1, f3elemf1elem) + } + f3elem.SetTags(f3elemf1) + } + f3 = append(f3, f3elem) + } + res.SetTagSpecifications(f3) + } + + 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) { + // TODO(jaypipes): Figure this out... + return nil, ackerr.NotImplemented +} + +// 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 exit(err) + input, err := rm.newDeleteRequestPayload(r) + if err != nil { + return nil, err + } + var resp *svcsdk.DeleteNatGatewayOutput + _ = resp + resp, err = rm.sdkapi.DeleteNatGatewayWithContext(ctx, input) + rm.metrics.RecordAPICall("DELETE", "DeleteNatGateway", 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.DeleteNatGatewayInput, error) { + res := &svcsdk.DeleteNatGatewayInput{} + + if r.ko.Status.NATGatewayID != nil { + res.SetNatGatewayId(*r.ko.Status.NATGatewayID) + } + + return res, nil +} + +// setStatusDefaults sets default properties into supplied custom resource +func (rm *resourceManager) setStatusDefaults( + ko *svcapitypes.NATGateway, +) { + 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 + } + } + + if rm.terminalAWSError(err) || err == ackerr.SecretTypeNotSupported || err == ackerr.SecretNotFound { + 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 { + 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 { + // No terminal_errors specified for this resource in generator config + return false +} diff --git a/test/e2e/resources/nat_gateway.yaml b/test/e2e/resources/nat_gateway.yaml new file mode 100644 index 00000000..94a5fe33 --- /dev/null +++ b/test/e2e/resources/nat_gateway.yaml @@ -0,0 +1,12 @@ +apiVersion: ec2.services.k8s.aws/v1alpha1 +kind: NATGateway +metadata: + name: $NAT_GATEWAY_NAME +spec: + allocationID: $ALLOCATION_ID + subnetID: $SUBNET_ID + tagSpecifications: + - resourceType: "natgateway" + tags: + - key: Name + value: $NAT_GATEWAY_NAME diff --git a/test/e2e/tests/__init__.py b/test/e2e/tests/__init__.py new file mode 100644 index 00000000..97b6b431 --- /dev/null +++ b/test/e2e/tests/__init__.py @@ -0,0 +1,12 @@ +# 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. diff --git a/test/e2e/tests/helper.py b/test/e2e/tests/helper.py index 61bb700d..02c2b859 100644 --- a/test/e2e/tests/helper.py +++ b/test/e2e/tests/helper.py @@ -36,6 +36,19 @@ def assert_internet_gateway(self, ig_id: str, exists=True): pass assert res_found is exists + def assert_nat_gateway(self, ngw_id: str, exists=True): + res_found = False + try: + aws_res = self.ec2_client.describe_nat_gateways(NatGatewayIds=[ngw_id]) + assert len(aws_res["NatGateways"]) > 0 + ngw = aws_res["NatGateways"][0] + # NATGateway may take awhile to be removed server-side, so + # treat 'deleting' and 'deleted' states as resource no longer existing + res_found = ngw is not None and ngw['State'] != "deleting" and ngw['State'] != "deleted" + except self.ec2_client.exceptions.ClientError: + pass + assert res_found is exists + def assert_route(self, route_table_id: str, gateway_id: str, origin: str, exists=True): res_found = False try: diff --git a/test/e2e/tests/test_nat_gateway.py b/test/e2e/tests/test_nat_gateway.py new file mode 100644 index 00000000..530173ff --- /dev/null +++ b/test/e2e/tests/test_nat_gateway.py @@ -0,0 +1,120 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You may +# not use this file except in compliance with the License. A copy of the +# License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is distributed +# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +# express or implied. See the License for the specific language governing +# permissions and limitations under the License. + +"""Integration tests for the NATGateway API. +""" + +import pytest +import time +import logging + +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 +from .test_elastic_ip_address import RESOURCE_PLURAL as ELASTIC_IP_PLURAL + +RESOURCE_PLURAL = "natgateways" + +CREATE_WAIT_AFTER_SECONDS = 10 +DELETE_WAIT_AFTER_SECONDS = 10 + +@pytest.fixture +def standard_elastic_address(): + cluster_name = random_suffix_name("nat-gateway-eip", 32) + + replacements = REPLACEMENT_VALUES.copy() + replacements["ADDRESS_NAME"] = cluster_name + + resource_data = load_ec2_resource( + "elastic_ip_address", + additional_replacements=replacements, + ) + logging.debug(resource_data) + + # Create the k8s resource + ref = k8s.CustomResourceReference( + CRD_GROUP, CRD_VERSION, ELASTIC_IP_PLURAL, + cluster_name, namespace="default", + ) + k8s.create_custom_resource(ref, resource_data) + cr = k8s.wait_resource_consumed_by_controller(ref) + + # ElasticIP are not usable immediately after they are created, so this will + # buy us some time in case we try to mount it too early. + time.sleep(CREATE_WAIT_AFTER_SECONDS) + + assert cr is not None + assert k8s.get_resource_exists(ref) + + yield (ref, cr) + + # Try to delete, if doesn't already exist + try: + _, deleted = k8s.delete_custom_resource(ref, 3, 10) + assert deleted + except: + pass + +@service_marker +@pytest.mark.canary +class TestNATGateway: + def test_create_delete(self, standard_elastic_address, ec2_client): + test_resource_values = REPLACEMENT_VALUES.copy() + resource_name = random_suffix_name("nat-gateway-test", 24) + test_vpc = get_bootstrap_resources().SharedTestVPC + subnet_id = test_vpc.public_subnets.subnet_ids[0] + + (_, eip) = standard_elastic_address + + test_resource_values["NAT_GATEWAY_NAME"] = resource_name + test_resource_values["SUBNET_ID"] = subnet_id + test_resource_values["ALLOCATION_ID"] = eip["status"]["allocationID"] + + # Load NAT Gateway CR + resource_data = load_ec2_resource( + "nat_gateway", + additional_replacements=test_resource_values, + ) + 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) + cr = k8s.wait_resource_consumed_by_controller(ref) + + assert cr is not None + assert k8s.get_resource_exists(ref) + + resource = k8s.get_resource(ref) + resource_id = resource["status"]["natGatewayID"] + + time.sleep(CREATE_WAIT_AFTER_SECONDS) + + # Check NAT Gateway exists in AWS + ec2_validator = EC2Validator(ec2_client) + ec2_validator.assert_nat_gateway(resource_id) + + # Delete k8s resource + _, deleted = k8s.delete_custom_resource(ref) + assert deleted is True + + time.sleep(DELETE_WAIT_AFTER_SECONDS) + + # Check NAT Gateway no longer exists in AWS + ec2_validator.assert_nat_gateway(resource_id, exists=False) \ No newline at end of file