diff --git a/apis/v1alpha1/ack-generate-metadata.yaml b/apis/v1alpha1/ack-generate-metadata.yaml index 3da185b4..fa77ce28 100755 --- a/apis/v1alpha1/ack-generate-metadata.yaml +++ b/apis/v1alpha1/ack-generate-metadata.yaml @@ -1,14 +1,14 @@ ack_generate_info: - build_date: "2021-09-01T18:32:41Z" - build_hash: 6f22b7b568e25b4ee007bb1ab5f9338c16daf172 + build_date: "2021-09-02T21:26:56Z" + build_hash: d20cb4f8170e3bae0af2783f1cccbcd98c868b52 go_version: go1.17 darwin/amd64 version: v0.13.0 -api_directory_checksum: ae8b61e0359ce7d3f8795b243537b07c43e2609e +api_directory_checksum: 5c310ddd87ebed17d437af5c8a17cad97d2fd083 api_version: v1alpha1 aws_sdk_go_version: v1.37.10 generator_config_info: - file_checksum: d37d8e26f07ea82e6ce896adffc138034078c1e1 + file_checksum: 61ae549a230a8641ecc9edec1eec8c0bcde87b83 original_file_name: generator.yaml last_modification: reason: API generation - timestamp: 2021-09-01 18:32:53.713542 +0000 UTC + timestamp: 2021-09-02 21:27:04.884645 +0000 UTC diff --git a/apis/v1alpha1/generator.yaml b/apis/v1alpha1/generator.yaml index 81511ea9..8948a194 100644 --- a/apis/v1alpha1/generator.yaml +++ b/apis/v1alpha1/generator.yaml @@ -2,6 +2,7 @@ ignore: field_paths: - CreateVpcInput.DryRun - CreateSubnetInput.DryRun + - CreateRouteTableInput.DryRun resource_names: - AccountAttribute - CapacityReservation @@ -32,7 +33,7 @@ ignore: - NetworkInterface - PlacementGroup - ReservedInstancesListing - - RouteTable + #- RouteTable - Route - SecurityGroup - Snapshot @@ -64,6 +65,11 @@ ignore: resources: Subnet: + exceptions: + terminal_codes: + - InvalidVpcID.Malformed + - InvalidVpcID.NotFound + RouteTable: exceptions: terminal_codes: - InvalidVpcID.Malformed diff --git a/apis/v1alpha1/route_table.go b/apis/v1alpha1/route_table.go new file mode 100644 index 00000000..6ecf6e35 --- /dev/null +++ b/apis/v1alpha1/route_table.go @@ -0,0 +1,87 @@ +// 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" +) + +// RouteTableSpec defines the desired state of RouteTable. +// +// Describes a route table. +type RouteTableSpec struct { + // The tags to assign to the route table. + TagSpecifications []*TagSpecification `json:"tagSpecifications,omitempty"` + // The ID of the VPC. + // +kubebuilder:validation:Required + VPCID *string `json:"vpcID"` +} + +// RouteTableStatus defines the observed state of RouteTable +type RouteTableStatus 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 associations between the route table and one or more subnets or a gateway. + // +kubebuilder:validation:Optional + Associations []*RouteTableAssociation `json:"associations,omitempty"` + // The ID of the AWS account that owns the route table. + // +kubebuilder:validation:Optional + OwnerID *string `json:"ownerID,omitempty"` + // Any virtual private gateway (VGW) propagating routes. + // +kubebuilder:validation:Optional + PropagatingVGWs []*PropagatingVGW `json:"propagatingVGWs,omitempty"` + // The ID of the route table. + // +kubebuilder:validation:Optional + RouteTableID *string `json:"routeTableID,omitempty"` + // The routes in the route table. + // +kubebuilder:validation:Optional + Routes []*Route `json:"routes,omitempty"` + // Any tags assigned to the route table. + // +kubebuilder:validation:Optional + Tags []*Tag `json:"tags,omitempty"` +} + +// RouteTable is the Schema for the RouteTables API +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type RouteTable struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec RouteTableSpec `json:"spec,omitempty"` + Status RouteTableStatus `json:"status,omitempty"` +} + +// RouteTableList contains a list of RouteTable +// +kubebuilder:object:root=true +type RouteTableList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []RouteTable `json:"items"` +} + +func init() { + SchemeBuilder.Register(&RouteTable{}, &RouteTableList{}) +} diff --git a/apis/v1alpha1/types.go b/apis/v1alpha1/types.go index 92df0cc8..a8677333 100644 --- a/apis/v1alpha1/types.go +++ b/apis/v1alpha1/types.go @@ -310,9 +310,10 @@ type CapacityReservationTargetResponse struct { // Describes a carrier gateway. type CarrierGateway struct { - OwnerID *string `json:"ownerID,omitempty"` - Tags []*Tag `json:"tags,omitempty"` - VPCID *string `json:"vpcID,omitempty"` + CarrierGatewayID *string `json:"carrierGatewayID,omitempty"` + OwnerID *string `json:"ownerID,omitempty"` + Tags []*Tag `json:"tags,omitempty"` + VPCID *string `json:"vpcID,omitempty"` } // Information about the client certificate used for authentication. @@ -2282,6 +2283,7 @@ type ResponseLaunchTemplateData struct { // Describes a route in a route table. type Route struct { + CarrierGatewayID *string `json:"carrierGatewayID,omitempty"` DestinationCIDRBlock *string `json:"destinationCIDRBlock,omitempty"` DestinationIPv6CIDRBlock *string `json:"destinationIPv6CIDRBlock,omitempty"` DestinationPrefixListID *string `json:"destinationPrefixListID,omitempty"` @@ -2292,33 +2294,42 @@ type Route struct { LocalGatewayID *string `json:"localGatewayID,omitempty"` NatGatewayID *string `json:"natGatewayID,omitempty"` NetworkInterfaceID *string `json:"networkInterfaceID,omitempty"` + Origin *string `json:"origin,omitempty"` + State *string `json:"state,omitempty"` TransitGatewayID *string `json:"transitGatewayID,omitempty"` VPCPeeringConnectionID *string `json:"vpcPeeringConnectionID,omitempty"` } -// Describes a route table. -type RouteTable struct { - OwnerID *string `json:"ownerID,omitempty"` - RouteTableID *string `json:"routeTableID,omitempty"` - Tags []*Tag `json:"tags,omitempty"` - VPCID *string `json:"vpcID,omitempty"` -} - // Describes an association between a route table and a subnet or gateway. type RouteTableAssociation struct { - GatewayID *string `json:"gatewayID,omitempty"` - Main *bool `json:"main,omitempty"` - RouteTableAssociationID *string `json:"routeTableAssociationID,omitempty"` - RouteTableID *string `json:"routeTableID,omitempty"` - SubnetID *string `json:"subnetID,omitempty"` + // Describes the state of an association between a route table and a subnet + // or gateway. + AssociationState *RouteTableAssociationState `json:"associationState,omitempty"` + GatewayID *string `json:"gatewayID,omitempty"` + Main *bool `json:"main,omitempty"` + RouteTableAssociationID *string `json:"routeTableAssociationID,omitempty"` + RouteTableID *string `json:"routeTableID,omitempty"` + SubnetID *string `json:"subnetID,omitempty"` } // Describes the state of an association between a route table and a subnet // or gateway. type RouteTableAssociationState struct { + State *string `json:"state,omitempty"` StatusMessage *string `json:"statusMessage,omitempty"` } +// Describes a route table. +type RouteTable_SDK struct { + Associations []*RouteTableAssociation `json:"associations,omitempty"` + OwnerID *string `json:"ownerID,omitempty"` + PropagatingVGWs []*PropagatingVGW `json:"propagatingVGWs,omitempty"` + RouteTableID *string `json:"routeTableID,omitempty"` + Routes []*Route `json:"routes,omitempty"` + Tags []*Tag `json:"tags,omitempty"` + VPCID *string `json:"vpcID,omitempty"` +} + // Describes the monitoring of an instance. type RunInstancesMonitoringEnabled struct { Enabled *bool `json:"enabled,omitempty"` diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index 3eeafe78..647e9062 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -1185,6 +1185,11 @@ func (in *CapacityReservationTargetResponse) DeepCopy() *CapacityReservationTarg // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CarrierGateway) DeepCopyInto(out *CarrierGateway) { *out = *in + if in.CarrierGatewayID != nil { + in, out := &in.CarrierGatewayID, &out.CarrierGatewayID + *out = new(string) + **out = **in + } if in.OwnerID != nil { in, out := &in.OwnerID, &out.OwnerID *out = new(string) @@ -9619,6 +9624,11 @@ func (in *ResponseLaunchTemplateData) DeepCopy() *ResponseLaunchTemplateData { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Route) DeepCopyInto(out *Route) { *out = *in + if in.CarrierGatewayID != nil { + in, out := &in.CarrierGatewayID, &out.CarrierGatewayID + *out = new(string) + **out = **in + } if in.DestinationCIDRBlock != nil { in, out := &in.DestinationCIDRBlock, &out.DestinationCIDRBlock *out = new(string) @@ -9669,6 +9679,16 @@ func (in *Route) DeepCopyInto(out *Route) { *out = new(string) **out = **in } + if in.Origin != nil { + in, out := &in.Origin, &out.Origin + *out = new(string) + **out = **in + } + if in.State != nil { + in, out := &in.State, &out.State + *out = new(string) + **out = **in + } if in.TransitGatewayID != nil { in, out := &in.TransitGatewayID, &out.TransitGatewayID *out = new(string) @@ -9694,32 +9714,10 @@ func (in *Route) DeepCopy() *Route { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RouteTable) DeepCopyInto(out *RouteTable) { *out = *in - if in.OwnerID != nil { - in, out := &in.OwnerID, &out.OwnerID - *out = new(string) - **out = **in - } - if in.RouteTableID != nil { - in, out := &in.RouteTableID, &out.RouteTableID - *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 - } + 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 RouteTable. @@ -9732,9 +9730,22 @@ func (in *RouteTable) DeepCopy() *RouteTable { return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RouteTable) 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 *RouteTableAssociation) DeepCopyInto(out *RouteTableAssociation) { *out = *in + if in.AssociationState != nil { + in, out := &in.AssociationState, &out.AssociationState + *out = new(RouteTableAssociationState) + (*in).DeepCopyInto(*out) + } if in.GatewayID != nil { in, out := &in.GatewayID, &out.GatewayID *out = new(string) @@ -9775,6 +9786,11 @@ func (in *RouteTableAssociation) DeepCopy() *RouteTableAssociation { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RouteTableAssociationState) DeepCopyInto(out *RouteTableAssociationState) { *out = *in + if in.State != nil { + in, out := &in.State, &out.State + *out = new(string) + **out = **in + } if in.StatusMessage != nil { in, out := &in.StatusMessage, &out.StatusMessage *out = new(string) @@ -9792,6 +9808,228 @@ func (in *RouteTableAssociationState) DeepCopy() *RouteTableAssociationState { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RouteTableList) DeepCopyInto(out *RouteTableList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]RouteTable, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteTableList. +func (in *RouteTableList) DeepCopy() *RouteTableList { + if in == nil { + return nil + } + out := new(RouteTableList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RouteTableList) 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 *RouteTableSpec) DeepCopyInto(out *RouteTableSpec) { + *out = *in + 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) + } + } + } + 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 RouteTableSpec. +func (in *RouteTableSpec) DeepCopy() *RouteTableSpec { + if in == nil { + return nil + } + out := new(RouteTableSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RouteTableStatus) DeepCopyInto(out *RouteTableStatus) { + *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.Associations != nil { + in, out := &in.Associations, &out.Associations + *out = make([]*RouteTableAssociation, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(RouteTableAssociation) + (*in).DeepCopyInto(*out) + } + } + } + if in.OwnerID != nil { + in, out := &in.OwnerID, &out.OwnerID + *out = new(string) + **out = **in + } + if in.PropagatingVGWs != nil { + in, out := &in.PropagatingVGWs, &out.PropagatingVGWs + *out = make([]*PropagatingVGW, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(PropagatingVGW) + (*in).DeepCopyInto(*out) + } + } + } + if in.RouteTableID != nil { + in, out := &in.RouteTableID, &out.RouteTableID + *out = new(string) + **out = **in + } + if in.Routes != nil { + in, out := &in.Routes, &out.Routes + *out = make([]*Route, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Route) + (*in).DeepCopyInto(*out) + } + } + } + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]*Tag, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Tag) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteTableStatus. +func (in *RouteTableStatus) DeepCopy() *RouteTableStatus { + if in == nil { + return nil + } + out := new(RouteTableStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RouteTable_SDK) DeepCopyInto(out *RouteTable_SDK) { + *out = *in + if in.Associations != nil { + in, out := &in.Associations, &out.Associations + *out = make([]*RouteTableAssociation, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(RouteTableAssociation) + (*in).DeepCopyInto(*out) + } + } + } + if in.OwnerID != nil { + in, out := &in.OwnerID, &out.OwnerID + *out = new(string) + **out = **in + } + if in.PropagatingVGWs != nil { + in, out := &in.PropagatingVGWs, &out.PropagatingVGWs + *out = make([]*PropagatingVGW, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(PropagatingVGW) + (*in).DeepCopyInto(*out) + } + } + } + if in.RouteTableID != nil { + in, out := &in.RouteTableID, &out.RouteTableID + *out = new(string) + **out = **in + } + if in.Routes != nil { + in, out := &in.Routes, &out.Routes + *out = make([]*Route, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Route) + (*in).DeepCopyInto(*out) + } + } + } + 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 RouteTable_SDK. +func (in *RouteTable_SDK) DeepCopy() *RouteTable_SDK { + if in == nil { + return nil + } + out := new(RouteTable_SDK) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RunInstancesMonitoringEnabled) DeepCopyInto(out *RunInstancesMonitoringEnabled) { *out = *in diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 78fe0b5d..d5d466d4 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -33,6 +33,7 @@ import ( svcresource "github.com/aws-controllers-k8s/ec2-controller/pkg/resource" ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/route_table" _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/subnet" _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/vpc" ) diff --git a/config/crd/bases/ec2.services.k8s.aws_routetables.yaml b/config/crd/bases/ec2.services.k8s.aws_routetables.yaml new file mode 100644 index 00000000..0aa50037 --- /dev/null +++ b/config/crd/bases/ec2.services.k8s.aws_routetables.yaml @@ -0,0 +1,227 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.1 + creationTimestamp: null + name: routetables.ec2.services.k8s.aws +spec: + group: ec2.services.k8s.aws + names: + kind: RouteTable + listKind: RouteTableList + plural: routetables + singular: routetable + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: RouteTable is the Schema for the RouteTables 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: "RouteTableSpec defines the desired state of RouteTable. + \n Describes a route table." + properties: + tagSpecifications: + description: The tags to assign to the route table. + 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 + vpcID: + description: The ID of the VPC. + type: string + required: + - vpcID + type: object + status: + description: RouteTableStatus defines the observed state of RouteTable + 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 + required: + - ownerAccountID + type: object + associations: + description: The associations between the route table and one or more + subnets or a gateway. + items: + description: Describes an association between a route table and + a subnet or gateway. + properties: + associationState: + description: Describes the state of an association between a + route table and a subnet or gateway. + properties: + state: + type: string + statusMessage: + type: string + type: object + gatewayID: + type: string + main: + type: boolean + routeTableAssociationID: + type: string + routeTableID: + type: string + subnetID: + type: string + type: object + type: array + conditions: + description: All CRS managed by ACK have a common `Status.Conditions` + member that contains a collection of `ackv1alpha1.Condition` objects + that describe the various terminal states of the CR and its backend + AWS service API resource + items: + description: Condition is the common struct used by all CRDs managed + by ACK service controllers to indicate terminal states of the + CR and its backend AWS service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + ownerID: + description: The ID of the AWS account that owns the route table. + type: string + propagatingVGWs: + description: Any virtual private gateway (VGW) propagating routes. + items: + description: Describes a virtual private gateway propagating route. + properties: + gatewayID: + type: string + type: object + type: array + routeTableID: + description: The ID of the route table. + type: string + routes: + description: The routes in the route table. + items: + description: Describes a route in a route table. + properties: + carrierGatewayID: + type: string + destinationCIDRBlock: + type: string + destinationIPv6CIDRBlock: + type: string + destinationPrefixListID: + type: string + egressOnlyInternetGatewayID: + type: string + gatewayID: + type: string + instanceID: + type: string + instanceOwnerID: + type: string + localGatewayID: + type: string + natGatewayID: + type: string + networkInterfaceID: + type: string + origin: + type: string + state: + type: string + transitGatewayID: + type: string + vpcPeeringConnectionID: + type: string + type: object + type: array + tags: + description: Any tags assigned to the route table. + items: + description: Describes a tag. + properties: + key: + type: string + value: + type: string + type: object + type: array + 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 f60a0e39..44c643bb 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -3,5 +3,6 @@ kind: Kustomization bases: - common resources: + - bases/ec2.services.k8s.aws_routetables.yaml - bases/ec2.services.k8s.aws_subnets.yaml - bases/ec2.services.k8s.aws_vpcs.yaml diff --git a/config/rbac/cluster-role-controller.yaml b/config/rbac/cluster-role-controller.yaml index bd6faab3..fb25139e 100644 --- a/config/rbac/cluster-role-controller.yaml +++ b/config/rbac/cluster-role-controller.yaml @@ -22,6 +22,26 @@ rules: - get - list - watch +- apiGroups: + - ec2.services.k8s.aws + resources: + - routetables + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - ec2.services.k8s.aws + resources: + - routetables/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 2b69d5b4..39580381 100644 --- a/config/rbac/role-reader.yaml +++ b/config/rbac/role-reader.yaml @@ -9,6 +9,7 @@ rules: - apiGroups: - ec2.services.k8s.aws resources: + - routetables - subnets - vpcs verbs: diff --git a/config/rbac/role-writer.yaml b/config/rbac/role-writer.yaml index 425bb80a..fc6f0073 100644 --- a/config/rbac/role-writer.yaml +++ b/config/rbac/role-writer.yaml @@ -9,6 +9,7 @@ rules: - apiGroups: - ec2.services.k8s.aws resources: + - routetables - subnets - vpcs verbs: @@ -22,6 +23,7 @@ rules: - apiGroups: - ec2.services.k8s.aws resources: + - routetables - subnets - vpcs verbs: diff --git a/generator.yaml b/generator.yaml index 81511ea9..8948a194 100644 --- a/generator.yaml +++ b/generator.yaml @@ -2,6 +2,7 @@ ignore: field_paths: - CreateVpcInput.DryRun - CreateSubnetInput.DryRun + - CreateRouteTableInput.DryRun resource_names: - AccountAttribute - CapacityReservation @@ -32,7 +33,7 @@ ignore: - NetworkInterface - PlacementGroup - ReservedInstancesListing - - RouteTable + #- RouteTable - Route - SecurityGroup - Snapshot @@ -64,6 +65,11 @@ ignore: resources: Subnet: + exceptions: + terminal_codes: + - InvalidVpcID.Malformed + - InvalidVpcID.NotFound + RouteTable: exceptions: terminal_codes: - InvalidVpcID.Malformed diff --git a/helm/crds/ec2.services.k8s.aws_routetables.yaml b/helm/crds/ec2.services.k8s.aws_routetables.yaml new file mode 100644 index 00000000..0aa50037 --- /dev/null +++ b/helm/crds/ec2.services.k8s.aws_routetables.yaml @@ -0,0 +1,227 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.1 + creationTimestamp: null + name: routetables.ec2.services.k8s.aws +spec: + group: ec2.services.k8s.aws + names: + kind: RouteTable + listKind: RouteTableList + plural: routetables + singular: routetable + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: RouteTable is the Schema for the RouteTables 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: "RouteTableSpec defines the desired state of RouteTable. + \n Describes a route table." + properties: + tagSpecifications: + description: The tags to assign to the route table. + 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 + vpcID: + description: The ID of the VPC. + type: string + required: + - vpcID + type: object + status: + description: RouteTableStatus defines the observed state of RouteTable + 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 + required: + - ownerAccountID + type: object + associations: + description: The associations between the route table and one or more + subnets or a gateway. + items: + description: Describes an association between a route table and + a subnet or gateway. + properties: + associationState: + description: Describes the state of an association between a + route table and a subnet or gateway. + properties: + state: + type: string + statusMessage: + type: string + type: object + gatewayID: + type: string + main: + type: boolean + routeTableAssociationID: + type: string + routeTableID: + type: string + subnetID: + type: string + type: object + type: array + conditions: + description: All CRS managed by ACK have a common `Status.Conditions` + member that contains a collection of `ackv1alpha1.Condition` objects + that describe the various terminal states of the CR and its backend + AWS service API resource + items: + description: Condition is the common struct used by all CRDs managed + by ACK service controllers to indicate terminal states of the + CR and its backend AWS service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + ownerID: + description: The ID of the AWS account that owns the route table. + type: string + propagatingVGWs: + description: Any virtual private gateway (VGW) propagating routes. + items: + description: Describes a virtual private gateway propagating route. + properties: + gatewayID: + type: string + type: object + type: array + routeTableID: + description: The ID of the route table. + type: string + routes: + description: The routes in the route table. + items: + description: Describes a route in a route table. + properties: + carrierGatewayID: + type: string + destinationCIDRBlock: + type: string + destinationIPv6CIDRBlock: + type: string + destinationPrefixListID: + type: string + egressOnlyInternetGatewayID: + type: string + gatewayID: + type: string + instanceID: + type: string + instanceOwnerID: + type: string + localGatewayID: + type: string + natGatewayID: + type: string + networkInterfaceID: + type: string + origin: + type: string + state: + type: string + transitGatewayID: + type: string + vpcPeeringConnectionID: + type: string + type: object + type: array + tags: + description: Any tags assigned to the route table. + items: + description: Describes a tag. + properties: + key: + type: string + value: + type: string + type: object + type: array + 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 cc037e56..095ff180 100644 --- a/helm/templates/cluster-role-controller.yaml +++ b/helm/templates/cluster-role-controller.yaml @@ -28,6 +28,26 @@ rules: - get - list - watch +- apiGroups: + - ec2.services.k8s.aws + resources: + - routetables + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - ec2.services.k8s.aws + resources: + - routetables/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 e2c9264a..03a2d95e 100644 --- a/helm/templates/role-reader.yaml +++ b/helm/templates/role-reader.yaml @@ -9,6 +9,7 @@ rules: - apiGroups: - ec2.services.k8s.aws resources: + - routetables - subnets - vpcs verbs: diff --git a/helm/templates/role-writer.yaml b/helm/templates/role-writer.yaml index 99ca51dd..aca128f1 100644 --- a/helm/templates/role-writer.yaml +++ b/helm/templates/role-writer.yaml @@ -9,6 +9,8 @@ rules: - apiGroups: - ec2.services.k8s.aws resources: + - routetables + - subnets - vpcs @@ -24,6 +26,7 @@ rules: - apiGroups: - ec2.services.k8s.aws resources: + - routetables - subnets - vpcs verbs: diff --git a/pkg/resource/route_table/delta.go b/pkg/resource/route_table/delta.go new file mode 100644 index 00000000..0cfce948 --- /dev/null +++ b/pkg/resource/route_table/delta.go @@ -0,0 +1,56 @@ +// 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 route_table + +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 !reflect.DeepEqual(a.ko.Spec.TagSpecifications, b.ko.Spec.TagSpecifications) { + delta.Add("Spec.TagSpecifications", a.ko.Spec.TagSpecifications, b.ko.Spec.TagSpecifications) + } + if ackcompare.HasNilDifference(a.ko.Spec.VPCID, b.ko.Spec.VPCID) { + delta.Add("Spec.VPCID", a.ko.Spec.VPCID, b.ko.Spec.VPCID) + } else if a.ko.Spec.VPCID != nil && b.ko.Spec.VPCID != nil { + if *a.ko.Spec.VPCID != *b.ko.Spec.VPCID { + delta.Add("Spec.VPCID", a.ko.Spec.VPCID, b.ko.Spec.VPCID) + } + } + + return delta +} diff --git a/pkg/resource/route_table/descriptor.go b/pkg/resource/route_table/descriptor.go new file mode 100644 index 00000000..1dcb2039 --- /dev/null +++ b/pkg/resource/route_table/descriptor.go @@ -0,0 +1,153 @@ +// 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 route_table + +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" + k8sapirt "k8s.io/apimachinery/pkg/runtime" + 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/RouteTable" +) + +var ( + resourceGK = metav1.GroupKind{ + Group: "ec2.services.k8s.aws", + Kind: "RouteTable", + } +) + +// 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 &resourceGK +} + +// EmptyRuntimeObject returns an empty object prototype that may be used in +// apimachinery and k8s client operations +func (d *resourceDescriptor) EmptyRuntimeObject() k8sapirt.Object { + return &svcapitypes.RouteTable{} +} + +// ResourceFromRuntimeObject returns an AWSResource that has been initialized +// with the supplied runtime.Object +func (d *resourceDescriptor) ResourceFromRuntimeObject( + obj k8sapirt.Object, +) acktypes.AWSResource { + return &resource{ + ko: obj.(*svcapitypes.RouteTable), + } +} + +// 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.RuntimeMetaObject() + 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 acktypes.RuntimeMetaObject, 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.RuntimeMetaObject() + 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.RuntimeMetaObject() + 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.RuntimeMetaObject() + if obj == nil { + // Should not happen. If it does, there is a bug in the code + panic("nil RuntimeMetaObject 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/route_table/identifiers.go b/pkg/resource/route_table/identifiers.go new file mode 100644 index 00000000..f5802884 --- /dev/null +++ b/pkg/resource/route_table/identifiers.go @@ -0,0 +1,46 @@ +// 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 route_table + +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 +} diff --git a/pkg/resource/route_table/manager.go b/pkg/resource/route_table/manager.go new file mode 100644 index 00000000..5e01a872 --- /dev/null +++ b/pkg/resource/route_table/manager.go @@ -0,0 +1,301 @@ +// 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 route_table + +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" + "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" +) + +// +kubebuilder:rbac:groups=ec2.services.k8s.aws,resources=routetables,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=ec2.services.k8s.aws,resources=routetables/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) + } + + if observed != nil { + return rm.onSuccess(observed) + } + return rm.onSuccess(r) +} + +// 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) + 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) + 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 +} + +// 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) { + 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) { + r1, updated := rm.updateConditions(r, true, nil) + if !updated { + return r, nil + } + return r1, nil +} diff --git a/pkg/resource/route_table/manager_factory.go b/pkg/resource/route_table/manager_factory.go new file mode 100644 index 00000000..39935497 --- /dev/null +++ b/pkg/resource/route_table/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 route_table + +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/route_table/resource.go b/pkg/resource/route_table/resource.go new file mode 100644 index 00000000..7b8eb074 --- /dev/null +++ b/pkg/resource/route_table/resource.go @@ -0,0 +1,107 @@ +// 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 route_table + +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" + k8srt "k8s.io/apimachinery/pkg/runtime" + + 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.RouteTable +} + +// 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() k8srt.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 +} + +// RuntimeMetaObject returns an object that implements both the Kubernetes +// apimachinery/runtime.Object and the Kubernetes +// apimachinery/apis/meta/v1.Object interfaces +func (r *resource) RuntimeMetaObject() acktypes.RuntimeMetaObject { + return r.ko +} + +// 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.RouteTableID = &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/route_table/sdk.go b/pkg/resource/route_table/sdk.go new file mode 100644 index 00000000..2051a6db --- /dev/null +++ b/pkg/resource/route_table/sdk.go @@ -0,0 +1,611 @@ +// 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 route_table + +import ( + "context" + "strings" + + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + 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.RouteTable{} + _ = ackv1alpha1.AWSAccountID("") + _ = &ackerr.NotFound +) + +// 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.DescribeRouteTablesOutput + resp, err = rm.sdkapi.DescribeRouteTablesWithContext(ctx, input) + rm.metrics.RecordAPICall("READ_MANY", "DescribeRouteTables", 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.RouteTables { + if elem.Associations != nil { + f0 := []*svcapitypes.RouteTableAssociation{} + for _, f0iter := range elem.Associations { + f0elem := &svcapitypes.RouteTableAssociation{} + if f0iter.AssociationState != nil { + f0elemf0 := &svcapitypes.RouteTableAssociationState{} + if f0iter.AssociationState.State != nil { + f0elemf0.State = f0iter.AssociationState.State + } + if f0iter.AssociationState.StatusMessage != nil { + f0elemf0.StatusMessage = f0iter.AssociationState.StatusMessage + } + f0elem.AssociationState = f0elemf0 + } + if f0iter.GatewayId != nil { + f0elem.GatewayID = f0iter.GatewayId + } + if f0iter.Main != nil { + f0elem.Main = f0iter.Main + } + if f0iter.RouteTableAssociationId != nil { + f0elem.RouteTableAssociationID = f0iter.RouteTableAssociationId + } + if f0iter.RouteTableId != nil { + f0elem.RouteTableID = f0iter.RouteTableId + } + if f0iter.SubnetId != nil { + f0elem.SubnetID = f0iter.SubnetId + } + f0 = append(f0, f0elem) + } + ko.Status.Associations = f0 + } else { + ko.Status.Associations = nil + } + if elem.OwnerId != nil { + ko.Status.OwnerID = elem.OwnerId + } else { + ko.Status.OwnerID = nil + } + if elem.PropagatingVgws != nil { + f2 := []*svcapitypes.PropagatingVGW{} + for _, f2iter := range elem.PropagatingVgws { + f2elem := &svcapitypes.PropagatingVGW{} + if f2iter.GatewayId != nil { + f2elem.GatewayID = f2iter.GatewayId + } + f2 = append(f2, f2elem) + } + ko.Status.PropagatingVGWs = f2 + } else { + ko.Status.PropagatingVGWs = nil + } + if elem.RouteTableId != nil { + ko.Status.RouteTableID = elem.RouteTableId + } else { + ko.Status.RouteTableID = nil + } + if elem.Routes != nil { + f4 := []*svcapitypes.Route{} + for _, f4iter := range elem.Routes { + f4elem := &svcapitypes.Route{} + if f4iter.CarrierGatewayId != nil { + f4elem.CarrierGatewayID = f4iter.CarrierGatewayId + } + if f4iter.DestinationCidrBlock != nil { + f4elem.DestinationCIDRBlock = f4iter.DestinationCidrBlock + } + if f4iter.DestinationIpv6CidrBlock != nil { + f4elem.DestinationIPv6CIDRBlock = f4iter.DestinationIpv6CidrBlock + } + if f4iter.DestinationPrefixListId != nil { + f4elem.DestinationPrefixListID = f4iter.DestinationPrefixListId + } + if f4iter.EgressOnlyInternetGatewayId != nil { + f4elem.EgressOnlyInternetGatewayID = f4iter.EgressOnlyInternetGatewayId + } + if f4iter.GatewayId != nil { + f4elem.GatewayID = f4iter.GatewayId + } + if f4iter.InstanceId != nil { + f4elem.InstanceID = f4iter.InstanceId + } + if f4iter.InstanceOwnerId != nil { + f4elem.InstanceOwnerID = f4iter.InstanceOwnerId + } + if f4iter.LocalGatewayId != nil { + f4elem.LocalGatewayID = f4iter.LocalGatewayId + } + if f4iter.NatGatewayId != nil { + f4elem.NatGatewayID = f4iter.NatGatewayId + } + if f4iter.NetworkInterfaceId != nil { + f4elem.NetworkInterfaceID = f4iter.NetworkInterfaceId + } + if f4iter.Origin != nil { + f4elem.Origin = f4iter.Origin + } + if f4iter.State != nil { + f4elem.State = f4iter.State + } + if f4iter.TransitGatewayId != nil { + f4elem.TransitGatewayID = f4iter.TransitGatewayId + } + if f4iter.VpcPeeringConnectionId != nil { + f4elem.VPCPeeringConnectionID = f4iter.VpcPeeringConnectionId + } + f4 = append(f4, f4elem) + } + ko.Status.Routes = f4 + } else { + ko.Status.Routes = nil + } + if elem.Tags != nil { + f5 := []*svcapitypes.Tag{} + for _, f5iter := range elem.Tags { + f5elem := &svcapitypes.Tag{} + if f5iter.Key != nil { + f5elem.Key = f5iter.Key + } + if f5iter.Value != nil { + f5elem.Value = f5iter.Value + } + f5 = append(f5, f5elem) + } + ko.Status.Tags = f5 + } else { + ko.Status.Tags = nil + } + if elem.VpcId != nil { + ko.Spec.VPCID = elem.VpcId + } else { + ko.Spec.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.RouteTableID == 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.DescribeRouteTablesInput, error) { + res := &svcsdk.DescribeRouteTablesInput{} + + if r.ko.Status.RouteTableID != nil { + f4 := []*string{} + f4 = append(f4, r.ko.Status.RouteTableID) + res.SetRouteTableIds(f4) + } + + 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.CreateRouteTableOutput + _ = resp + resp, err = rm.sdkapi.CreateRouteTableWithContext(ctx, input) + rm.metrics.RecordAPICall("CREATE", "CreateRouteTable", 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.RouteTable.Associations != nil { + f0 := []*svcapitypes.RouteTableAssociation{} + for _, f0iter := range resp.RouteTable.Associations { + f0elem := &svcapitypes.RouteTableAssociation{} + if f0iter.AssociationState != nil { + f0elemf0 := &svcapitypes.RouteTableAssociationState{} + if f0iter.AssociationState.State != nil { + f0elemf0.State = f0iter.AssociationState.State + } + if f0iter.AssociationState.StatusMessage != nil { + f0elemf0.StatusMessage = f0iter.AssociationState.StatusMessage + } + f0elem.AssociationState = f0elemf0 + } + if f0iter.GatewayId != nil { + f0elem.GatewayID = f0iter.GatewayId + } + if f0iter.Main != nil { + f0elem.Main = f0iter.Main + } + if f0iter.RouteTableAssociationId != nil { + f0elem.RouteTableAssociationID = f0iter.RouteTableAssociationId + } + if f0iter.RouteTableId != nil { + f0elem.RouteTableID = f0iter.RouteTableId + } + if f0iter.SubnetId != nil { + f0elem.SubnetID = f0iter.SubnetId + } + f0 = append(f0, f0elem) + } + ko.Status.Associations = f0 + } else { + ko.Status.Associations = nil + } + if resp.RouteTable.OwnerId != nil { + ko.Status.OwnerID = resp.RouteTable.OwnerId + } else { + ko.Status.OwnerID = nil + } + if resp.RouteTable.PropagatingVgws != nil { + f2 := []*svcapitypes.PropagatingVGW{} + for _, f2iter := range resp.RouteTable.PropagatingVgws { + f2elem := &svcapitypes.PropagatingVGW{} + if f2iter.GatewayId != nil { + f2elem.GatewayID = f2iter.GatewayId + } + f2 = append(f2, f2elem) + } + ko.Status.PropagatingVGWs = f2 + } else { + ko.Status.PropagatingVGWs = nil + } + if resp.RouteTable.RouteTableId != nil { + ko.Status.RouteTableID = resp.RouteTable.RouteTableId + } else { + ko.Status.RouteTableID = nil + } + if resp.RouteTable.Routes != nil { + f4 := []*svcapitypes.Route{} + for _, f4iter := range resp.RouteTable.Routes { + f4elem := &svcapitypes.Route{} + if f4iter.CarrierGatewayId != nil { + f4elem.CarrierGatewayID = f4iter.CarrierGatewayId + } + if f4iter.DestinationCidrBlock != nil { + f4elem.DestinationCIDRBlock = f4iter.DestinationCidrBlock + } + if f4iter.DestinationIpv6CidrBlock != nil { + f4elem.DestinationIPv6CIDRBlock = f4iter.DestinationIpv6CidrBlock + } + if f4iter.DestinationPrefixListId != nil { + f4elem.DestinationPrefixListID = f4iter.DestinationPrefixListId + } + if f4iter.EgressOnlyInternetGatewayId != nil { + f4elem.EgressOnlyInternetGatewayID = f4iter.EgressOnlyInternetGatewayId + } + if f4iter.GatewayId != nil { + f4elem.GatewayID = f4iter.GatewayId + } + if f4iter.InstanceId != nil { + f4elem.InstanceID = f4iter.InstanceId + } + if f4iter.InstanceOwnerId != nil { + f4elem.InstanceOwnerID = f4iter.InstanceOwnerId + } + if f4iter.LocalGatewayId != nil { + f4elem.LocalGatewayID = f4iter.LocalGatewayId + } + if f4iter.NatGatewayId != nil { + f4elem.NatGatewayID = f4iter.NatGatewayId + } + if f4iter.NetworkInterfaceId != nil { + f4elem.NetworkInterfaceID = f4iter.NetworkInterfaceId + } + if f4iter.Origin != nil { + f4elem.Origin = f4iter.Origin + } + if f4iter.State != nil { + f4elem.State = f4iter.State + } + if f4iter.TransitGatewayId != nil { + f4elem.TransitGatewayID = f4iter.TransitGatewayId + } + if f4iter.VpcPeeringConnectionId != nil { + f4elem.VPCPeeringConnectionID = f4iter.VpcPeeringConnectionId + } + f4 = append(f4, f4elem) + } + ko.Status.Routes = f4 + } else { + ko.Status.Routes = nil + } + if resp.RouteTable.Tags != nil { + f5 := []*svcapitypes.Tag{} + for _, f5iter := range resp.RouteTable.Tags { + f5elem := &svcapitypes.Tag{} + if f5iter.Key != nil { + f5elem.Key = f5iter.Key + } + if f5iter.Value != nil { + f5elem.Value = f5iter.Value + } + f5 = append(f5, f5elem) + } + ko.Status.Tags = f5 + } else { + ko.Status.Tags = nil + } + if resp.RouteTable.VpcId != nil { + ko.Spec.VPCID = resp.RouteTable.VpcId + } else { + ko.Spec.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.CreateRouteTableInput, error) { + res := &svcsdk.CreateRouteTableInput{} + + if r.ko.Spec.TagSpecifications != nil { + f0 := []*svcsdk.TagSpecification{} + for _, f0iter := range r.ko.Spec.TagSpecifications { + f0elem := &svcsdk.TagSpecification{} + if f0iter.ResourceType != nil { + f0elem.SetResourceType(*f0iter.ResourceType) + } + if f0iter.Tags != nil { + f0elemf1 := []*svcsdk.Tag{} + for _, f0elemf1iter := range f0iter.Tags { + f0elemf1elem := &svcsdk.Tag{} + if f0elemf1iter.Key != nil { + f0elemf1elem.SetKey(*f0elemf1iter.Key) + } + if f0elemf1iter.Value != nil { + f0elemf1elem.SetValue(*f0elemf1iter.Value) + } + f0elemf1 = append(f0elemf1, f0elemf1elem) + } + f0elem.SetTags(f0elemf1) + } + f0 = append(f0, f0elem) + } + res.SetTagSpecifications(f0) + } + if r.ko.Spec.VPCID != nil { + res.SetVpcId(*r.ko.Spec.VPCID) + } + + 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.DeleteRouteTableOutput + _ = resp + resp, err = rm.sdkapi.DeleteRouteTableWithContext(ctx, input) + rm.metrics.RecordAPICall("DELETE", "DeleteRouteTable", 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.DeleteRouteTableInput, error) { + res := &svcsdk.DeleteRouteTableInput{} + + if r.ko.Status.RouteTableID != nil { + res.SetRouteTableId(*r.ko.Status.RouteTableID) + } + + return res, nil +} + +// setStatusDefaults sets default properties into supplied custom resource +func (rm *resourceManager) setStatusDefaults( + ko *svcapitypes.RouteTable, +) { + if ko.Status.ACKResourceMetadata == nil { + ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{} + } + 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 { + if err == nil { + return false + } + awsErr, ok := ackerr.AWSError(err) + if !ok { + return false + } + switch awsErr.Code() { + case "InvalidVpcID.Malformed", + "InvalidVpcID.NotFound": + return true + default: + return false + } +} diff --git a/test/e2e/bootstrap_resources.py b/test/e2e/bootstrap_resources.py index 1042a816..15263c69 100644 --- a/test/e2e/bootstrap_resources.py +++ b/test/e2e/bootstrap_resources.py @@ -14,20 +14,18 @@ for them. """ from dataclasses import dataclass - +from acktest.bootstrapping import Resources +from acktest.bootstrapping.vpc import VPC from e2e import bootstrap_directory -from acktest.resources import read_bootstrap_config @dataclass -class TestBootstrapResources: - pass +class BootstrapResources(Resources): + SharedTestVPC: VPC _bootstrap_resources = None -def get_bootstrap_resources(bootstrap_file_name: str = "bootstrap.yaml"): +def get_bootstrap_resources(bootstrap_file_name: str = "bootstrap.pkl") -> BootstrapResources: global _bootstrap_resources if _bootstrap_resources is None: - _bootstrap_resources = TestBootstrapResources( - **read_bootstrap_config(bootstrap_directory, bootstrap_file_name=bootstrap_file_name), - ) + _bootstrap_resources = BootstrapResources.deserialize(bootstrap_directory, bootstrap_file_name=bootstrap_file_name) return _bootstrap_resources \ No newline at end of file diff --git a/test/e2e/conftest.py b/test/e2e/conftest.py index f1925ed9..1774515e 100644 --- a/test/e2e/conftest.py +++ b/test/e2e/conftest.py @@ -11,12 +11,8 @@ # express or implied. See the License for the specific language governing # permissions and limitations under the License. -import os import pytest -from acktest import k8s - - def pytest_addoption(parser): parser.addoption("--runslow", action="store_true", default=False, help="run slow tests") @@ -39,8 +35,3 @@ def pytest_collection_modifyitems(config, items): for item in items: if "slow" in item.keywords: item.add_marker(skip_slow) - -# Provide a k8s client to interact with the integration test cluster -@pytest.fixture(scope='class') -def k8s_client(): - return k8s._get_k8s_api_client() \ No newline at end of file diff --git a/test/e2e/requirements.txt b/test/e2e/requirements.txt index 7adaa07b..9b734ba5 100644 --- a/test/e2e/requirements.txt +++ b/test/e2e/requirements.txt @@ -1 +1 @@ -acktest @ git+https://github.com/aws-controllers-k8s/test-infra.git@d4939dce5c067c9ae300940322046f5e2af4e7bc \ No newline at end of file +acktest @ git+https://github.com/aws-controllers-k8s/test-infra.git@5220331d003e3a23de4470e68d02c99b16c81989 \ No newline at end of file diff --git a/test/e2e/resources/route_table.yaml b/test/e2e/resources/route_table.yaml new file mode 100644 index 00000000..2e34dfea --- /dev/null +++ b/test/e2e/resources/route_table.yaml @@ -0,0 +1,6 @@ +apiVersion: ec2.services.k8s.aws/v1alpha1 +kind: RouteTable +metadata: + name: $ROUTE_TABLE_NAME +spec: + vpcID: $VPC_ID \ No newline at end of file diff --git a/test/e2e/service_bootstrap.py b/test/e2e/service_bootstrap.py index 641f99d5..12009de1 100644 --- a/test/e2e/service_bootstrap.py +++ b/test/e2e/service_bootstrap.py @@ -10,23 +10,31 @@ # 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. -"""Bootstraps the resources required to run the ec2 integration tests. +"""Bootstraps the resources required to run EC2 integration tests. """ import logging -from pathlib import Path -from acktest import resources +from acktest.bootstrapping import Resources, BootstrapFailureException +from acktest.bootstrapping.vpc import VPC from e2e import bootstrap_directory -from e2e.bootstrap_resources import TestBootstrapResources +from e2e.bootstrap_resources import BootstrapResources - -def service_bootstrap() -> dict: +def service_bootstrap() -> Resources: logging.getLogger().setLevel(logging.INFO) - return TestBootstrapResources( - ).__dict__ + resources = BootstrapResources( + SharedTestVPC=VPC(name_prefix="e2e-test-vpc", num_public_subnet=0, num_private_subnet=0) + ) + + try: + resources.bootstrap() + except BootstrapFailureException: + exit(254) + + return resources if __name__ == "__main__": config = service_bootstrap() - resources.write_bootstrap_config(config, bootstrap_directory) \ No newline at end of file + # Write config to current directory by default + config.serialize(bootstrap_directory) \ No newline at end of file diff --git a/test/e2e/service_cleanup.py b/test/e2e/service_cleanup.py index e5343dc5..8742e5ab 100644 --- a/test/e2e/service_cleanup.py +++ b/test/e2e/service_cleanup.py @@ -10,23 +10,19 @@ # 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. -"""Cleans up the resources created by the ec2 bootstrapping process. +"""Cleans up the resources created by the EC2 bootstrapping process. """ import logging -from pathlib import Path -from acktest import resources +from acktest.bootstrapping import Resources from e2e import bootstrap_directory -from e2e.bootstrap_resources import TestBootstrapResources -def service_cleanup(config: dict): +def service_cleanup(): logging.getLogger().setLevel(logging.INFO) - resources = TestBootstrapResources( - **config - ) + resources = Resources.deserialize(bootstrap_directory) + resources.cleanup() if __name__ == "__main__": - bootstrap_config = resources.read_bootstrap_config(bootstrap_directory) - service_cleanup(bootstrap_config) \ No newline at end of file + service_cleanup() \ No newline at end of file diff --git a/test/e2e/tests/test_route_table.py b/test/e2e/tests/test_route_table.py new file mode 100644 index 00000000..85e7b8ed --- /dev/null +++ b/test/e2e/tests/test_route_table.py @@ -0,0 +1,134 @@ +# 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 RouteTable API. +""" + +import boto3 +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 + +RESOURCE_PLURAL = "routetables" + +CREATE_WAIT_AFTER_SECONDS = 10 +DELETE_WAIT_AFTER_SECONDS = 10 + +@pytest.fixture(scope="module") +def ec2_client(): + return boto3.client("ec2") + + +def get_route_table(ec2_client, route_table_id: str) -> dict: + try: + resp = ec2_client.describe_route_tables( + Filters=[{"Name": "route-table-id", "Values": [route_table_id]}] + ) + except Exception as e: + logging.debug(e) + return None + + if len(resp["RouteTables"]) == 0: + return None + return resp["RouteTables"][0] + + +def route_table_exists(ec2_client, route_table_id: str) -> bool: + return get_route_table(ec2_client, route_table_id) is not None + +@service_marker +@pytest.mark.canary +class TestRouteTable: + def test_create_delete(self, ec2_client): + test_resource_values = REPLACEMENT_VALUES.copy() + resource_name = random_suffix_name("route-table-test", 24) + test_vpc = get_bootstrap_resources().SharedTestVPC + vpc_id = test_vpc.vpc_id + + test_resource_values["ROUTE_TABLE_NAME"] = resource_name + test_resource_values["VPC_ID"] = vpc_id + + # Load Route Table CR + resource_data = load_ec2_resource( + "route_table", + 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"]["routeTableID"] + + time.sleep(CREATE_WAIT_AFTER_SECONDS) + + # Check Route Table exists + exists = route_table_exists(ec2_client, resource_id) + assert exists + + # Delete k8s resource + _, deleted = k8s.delete_custom_resource(ref) + assert deleted is True + + time.sleep(DELETE_WAIT_AFTER_SECONDS) + + # Check Route Table doesn't exist + exists = route_table_exists(ec2_client, resource_id) + assert not exists + + def test_terminal_condition(self): + test_resource_values = REPLACEMENT_VALUES.copy() + resource_name = random_suffix_name("route-table-fail", 24) + test_resource_values["ROUTE_TABLE_NAME"] = resource_name + test_resource_values["VPC_ID"] = "InvalidVpcId" + + # Load RouteTable CR + resource_data = load_ec2_resource( + "route_table", + 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) + + expected_msg = "InvalidVpcID.NotFound: The vpc ID 'InvalidVpcId' does not exist" + terminal_condition = k8s.get_resource_condition(ref, "ACK.Terminal") + # Example condition message: + # InvalidVpcID.NotFound: The vpc ID 'InvalidVpcId' does not exist + # status code: 400, request id: 5801fc80-67cf-465f-8b83-5e02d517d554 + # This check only verifies the error message; the request hash is irrelevant and therefore can be ignored. + assert expected_msg in terminal_condition['message'] \ No newline at end of file diff --git a/test/e2e/tests/test_subnet.py b/test/e2e/tests/test_subnet.py index eca12fe7..a41de718 100644 --- a/test/e2e/tests/test_subnet.py +++ b/test/e2e/tests/test_subnet.py @@ -23,6 +23,7 @@ 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 RESOURCE_PLURAL = "subnets" @@ -33,60 +34,34 @@ def ec2_client(): return boto3.client("ec2") -@pytest.fixture(scope="module") -def vpc_resource(): - resource_name = random_suffix_name("vpc-for-subnet", 24) - test_resource_values = REPLACEMENT_VALUES.copy() - test_resource_values["VPC_NAME"] = resource_name - test_resource_values["CIDR_BLOCK"] = "10.0.0.0/16" - resource_data = load_ec2_resource( - "vpc", - additional_replacements=test_resource_values, - ) - ref = k8s.CustomResourceReference( - CRD_GROUP, CRD_VERSION, "vpcs", - resource_name, namespace="default", - ) - k8s.create_custom_resource(ref, resource_data) - cr = k8s.wait_resource_consumed_by_controller(ref) +def get_subnet(ec2_client, subnet_id: str) -> dict: + try: + resp = ec2_client.describe_subnets( + Filters=[{"Name": "subnet-id", "Values": [subnet_id]}] + ) + except Exception as e: + logging.debug(e) + return None - assert cr is not None - assert k8s.get_resource_exists(ref) + if len(resp["Subnets"]) == 0: + return None + return resp["Subnets"][0] - resource = k8s.get_resource(ref) - test_resource_values["VPC_ID"] = resource["status"]["vpcID"] - yield ref, cr +def subnet_exists(ec2_client, subnet_id: str) -> bool: + return get_subnet(ec2_client, subnet_id) is not None - k8s.delete_custom_resource(ref) @service_marker @pytest.mark.canary class TestSubnet: - def get_subnet(self, ec2_client, subnet_id: str) -> dict: - try: - resp = ec2_client.describe_subnets() - except Exception as e: - logging.debug(e) - return None - - subnets = resp["Subnets"] - for subnet in subnets: - if subnet["SubnetId"] == subnet_id: - return subnet - - return None - - def subnet_exists(self, ec2_client, subnet_id: str) -> bool: - return self.get_subnet(ec2_client, subnet_id) is not None - - def test_create_delete(self, ec2_client, vpc_resource): + def test_create_delete(self, ec2_client): test_resource_values = REPLACEMENT_VALUES.copy() - resource_name = random_suffix_name("subnet-crud", 24) - _, vpc_cr = vpc_resource - vpc_id = vpc_cr['status']['vpcID'] - vpc_cidr = vpc_cr['spec']['cidrBlock'] + resource_name = random_suffix_name("subnet-test", 24) + test_vpc = get_bootstrap_resources().SharedTestVPC + vpc_id = test_vpc.vpc_id + vpc_cidr = test_vpc.vpc_cidr_block test_resource_values["SUBNET_NAME"] = resource_name test_resource_values["VPC_ID"] = vpc_id @@ -116,7 +91,7 @@ def test_create_delete(self, ec2_client, vpc_resource): time.sleep(CREATE_WAIT_AFTER_SECONDS) # Check Subnet exists - exists = self.subnet_exists(ec2_client, resource_id) + exists = subnet_exists(ec2_client, resource_id) assert exists # Delete k8s resource @@ -126,14 +101,14 @@ def test_create_delete(self, ec2_client, vpc_resource): time.sleep(DELETE_WAIT_AFTER_SECONDS) # Check Subnet doesn't exist - exists = self.subnet_exists(ec2_client, resource_id) + exists = subnet_exists(ec2_client, resource_id) assert not exists - def test_terminal_condition(self, vpc_resource): + def test_terminal_condition(self): test_resource_values = REPLACEMENT_VALUES.copy() resource_name = random_suffix_name("subnet-fail", 24) - _, vpc_cr = vpc_resource - vpc_cidr = vpc_cr['spec']['cidrBlock'] + test_vpc = get_bootstrap_resources().SharedTestVPC + vpc_cidr = test_vpc.vpc_cidr_block test_resource_values["SUBNET_NAME"] = resource_name test_resource_values["VPC_ID"] = "InvalidVpcId" @@ -163,5 +138,4 @@ def test_terminal_condition(self, vpc_resource): # InvalidVpcID.NotFound: The vpc ID 'InvalidVpcId' does not exist # status code: 400, request id: 5801fc80-67cf-465f-8b83-5e02d517d554 # This check only verifies the error message; the request hash is irrelevant and therefore can be ignored. - assert expected_msg in terminal_condition['message'] - + assert expected_msg in terminal_condition['message'] \ No newline at end of file diff --git a/test/e2e/tests/test_vpc.py b/test/e2e/tests/test_vpc.py index 4d7fa136..1960a784 100644 --- a/test/e2e/tests/test_vpc.py +++ b/test/e2e/tests/test_vpc.py @@ -33,26 +33,27 @@ def ec2_client(): return boto3.client("ec2") -@service_marker -@pytest.mark.canary -class TestVpc: - def get_vpc(self, ec2_client, vpc_id: str) -> dict: - try: - resp = ec2_client.describe_vpcs() - except Exception as e: - logging.debug(e) - return None - - vpcs = resp["Vpcs"] - for vpc in vpcs: - if vpc["VpcId"] == vpc_id: - return vpc +def get_vpc(ec2_client, vpc_id: str) -> dict: + try: + resp = ec2_client.describe_vpcs( + Filters=[{"Name": "vpc-id", "Values": [vpc_id]}] + ) + except Exception as e: + logging.debug(e) + return None + + if len(resp["Vpcs"]) == 0: return None + return resp["Vpcs"][0] + - def vpc_exists(self, ec2_client, vpc_id: str) -> bool: - return self.get_vpc(ec2_client, vpc_id) is not None +def vpc_exists(ec2_client, vpc_id: str) -> bool: + return get_vpc(ec2_client, vpc_id) is not None +@service_marker +@pytest.mark.canary +class TestVpc: def test_create_delete(self, ec2_client): resource_name = random_suffix_name("vpc-ack-test", 24) replacements = REPLACEMENT_VALUES.copy() @@ -83,7 +84,7 @@ def test_create_delete(self, ec2_client): time.sleep(CREATE_WAIT_AFTER_SECONDS) # Check VPC exists - exists = self.vpc_exists(ec2_client, resource_id) + exists = vpc_exists(ec2_client, resource_id) assert exists # Delete k8s resource @@ -93,5 +94,5 @@ def test_create_delete(self, ec2_client): time.sleep(DELETE_WAIT_AFTER_SECONDS) # Check VPC doesn't exist - exists = self.vpc_exists(ec2_client, resource_id) - assert not exists + exists = vpc_exists(ec2_client, resource_id) + assert not exists \ No newline at end of file