diff --git a/apis/v1alpha1/ack-generate-metadata.yaml b/apis/v1alpha1/ack-generate-metadata.yaml index 021d45f3..fab03c26 100755 --- a/apis/v1alpha1/ack-generate-metadata.yaml +++ b/apis/v1alpha1/ack-generate-metadata.yaml @@ -1,13 +1,13 @@ ack_generate_info: - build_date: "2023-05-15T23:06:36Z" - build_hash: 8f3ba427974fd6e769926778d54834eaee3b81a3 - go_version: go1.19 - version: v0.26.1 -api_directory_checksum: 6e86aa4f26729ce014485b1096286db654459af7 + build_date: "2023-06-14T17:05:27Z" + build_hash: e04fca8e1fa32e988ff4dd9425d758ecc8a2a178 + go_version: go1.20.3 + version: v0.26.1-6-ge04fca8 +api_directory_checksum: a930caa16911411d04c5e7c14b27e077993d93a1 api_version: v1alpha1 aws_sdk_go_version: v1.44.93 generator_config_info: - file_checksum: cc2c6590c6e77a6125d5eec82ff5f693109d4f99 + file_checksum: dbba229402fe462b5d6b0b57027b3cfafd6bad61 original_file_name: generator.yaml last_modification: reason: API generation diff --git a/apis/v1alpha1/flow_log.go b/apis/v1alpha1/flow_log.go new file mode 100644 index 00000000..b7b575e9 --- /dev/null +++ b/apis/v1alpha1/flow_log.go @@ -0,0 +1,146 @@ +// 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" +) + +// FlowLogSpec defines the desired state of FlowLog. +// +// Describes a flow log. +type FlowLogSpec struct { + + // The ARN for the IAM role that permits Amazon EC2 to publish flow logs to + // a CloudWatch Logs log group in your account. + // + // If you specify LogDestinationType as s3, do not specify DeliverLogsPermissionArn + // or LogGroupName. + DeliverLogsPermissionARN *string `json:"deliverLogsPermissionARN,omitempty"` + // The destination options. + DestinationOptions *DestinationOptionsRequest `json:"destinationOptions,omitempty"` + // The destination to which the flow log data is to be published. Flow log data + // can be published to a CloudWatch Logs log group or an Amazon S3 bucket. The + // value specified for this parameter depends on the value specified for LogDestinationType. + // + // If LogDestinationType is not specified or cloud-watch-logs, specify the Amazon + // Resource Name (ARN) of the CloudWatch Logs log group. For example, to publish + // to a log group called my-logs, specify arn:aws:logs:us-east-1:123456789012:log-group:my-logs. + // Alternatively, use LogGroupName instead. + // + // If LogDestinationType is s3, specify the ARN of the Amazon S3 bucket. You + // can also specify a subfolder in the bucket. To specify a subfolder in the + // bucket, use the following ARN format: bucket_ARN/subfolder_name/. For example, + // to specify a subfolder named my-logs in a bucket named my-bucket, use the + // following ARN: arn:aws:s3:::my-bucket/my-logs/. You cannot use AWSLogs as + // a subfolder name. This is a reserved term. + LogDestination *string `json:"logDestination,omitempty"` + // The type of destination to which the flow log data is to be published. Flow + // log data can be published to CloudWatch Logs or Amazon S3. To publish flow + // log data to CloudWatch Logs, specify cloud-watch-logs. To publish flow log + // data to Amazon S3, specify s3. + // + // If you specify LogDestinationType as s3, do not specify DeliverLogsPermissionArn + // or LogGroupName. + // + // Default: cloud-watch-logs + LogDestinationType *string `json:"logDestinationType,omitempty"` + // The fields to include in the flow log record, in the order in which they + // should appear. For a list of available fields, see Flow log records (https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html#flow-log-records). + // If you omit this parameter, the flow log is created using the default format. + // If you specify this parameter, you must specify at least one field. + // + // Specify the fields using the ${field-id} format, separated by spaces. For + // the CLI, surround this parameter value with single quotes on Linux or double + // quotes on Windows. + LogFormat *string `json:"logFormat,omitempty"` + // The name of a new or existing CloudWatch Logs log group where Amazon EC2 + // publishes your flow logs. + // + // If you specify LogDestinationType as s3, do not specify DeliverLogsPermissionArn + // or LogGroupName. + LogGroupName *string `json:"logGroupName,omitempty"` + // The maximum interval of time during which a flow of packets is captured and + // aggregated into a flow log record. You can specify 60 seconds (1 minute) + // or 600 seconds (10 minutes). + // + // When a network interface is attached to a Nitro-based instance (https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html#ec2-nitro-instances), + // the aggregation interval is always 60 seconds or less, regardless of the + // value that you specify. + // + // Default: 600 + MaxAggregationInterval *int64 `json:"maxAggregationInterval,omitempty"` + // +kubebuilder:validation:Required + ResourceID *string `json:"resourceID"` + // The type of resource for which to create the flow log. For example, if you + // specified a VPC ID for the ResourceId property, specify VPC for this property. + // +kubebuilder:validation:Required + ResourceType *string `json:"resourceType"` + // The tags. The value parameter is required, but if you don't want the tag + // to have a value, specify the parameter with no value, and we set the value + // to an empty string. + Tags []*Tag `json:"tags,omitempty"` + // The type of traffic to log. You can log traffic that the resource accepts + // or rejects, or all traffic. + TrafficType *string `json:"trafficType,omitempty"` +} + +// FlowLogStatus defines the observed state of FlowLog +type FlowLogStatus 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"` + // Unique, case-sensitive identifier that you provide to ensure the idempotency + // of the request. + // +kubebuilder:validation:Optional + ClientToken *string `json:"clientToken,omitempty"` + // +kubebuilder:validation:Optional + FlowLogID *string `json:"flowLogID,omitempty"` + // Information about the flow logs that could not be created successfully. + // +kubebuilder:validation:Optional + Unsuccessful []*UnsuccessfulItem `json:"unsuccessful,omitempty"` +} + +// FlowLog is the Schema for the FlowLogs API +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type FlowLog struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec FlowLogSpec `json:"spec,omitempty"` + Status FlowLogStatus `json:"status,omitempty"` +} + +// FlowLogList contains a list of FlowLog +// +kubebuilder:object:root=true +type FlowLogList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []FlowLog `json:"items"` +} + +func init() { + SchemeBuilder.Register(&FlowLog{}, &FlowLogList{}) +} diff --git a/apis/v1alpha1/generator.yaml b/apis/v1alpha1/generator.yaml index a5df9075..5b666b49 100644 --- a/apis/v1alpha1/generator.yaml +++ b/apis/v1alpha1/generator.yaml @@ -7,6 +7,11 @@ ignore: - AllocateAddressInput.TagSpecifications - CreateDhcpOptionsInput.DryRun - CreateDhcpOptionsInput.TagSpecifications + - CreateFlowLogsInput.DryRun + - CreateFlowLogsInput.ClientToken + - CreateFlowLogsInput.TagSpecifications + - CreateFlowLogsInput.ResourceIds + - CreateFlowLogsOutput.FlowLogIds - CreateInternetGatewayInput.DryRun - CreateInternetGatewayInput.TagSpecifications - CreateNatGatewayInput.ClientToken @@ -141,6 +146,14 @@ operations: operation_type: - Delete resource_name: VpcEndpoint + CreateFlowLogs: + operation_type: + - Create + resource_name: FlowLog + DeleteFlowLogs: + operation_type: + - Delete + resource_name: FlowLog RunInstances: #ouput shape: Reservation output_wrapper_field_path: Instances @@ -268,6 +281,42 @@ resources: template_path: hooks/elastic_ip_address/sdk_file_end.go.tpl update_operation: custom_method_name: customUpdateElasticIP + FlowLog: + exceptions: + terminal_codes: + - InvalidParameter + - InvalidParameterValue + fields: + ResourceID: + type: string + is_required: true + FlowLogID: + type: string + is_read_only: true + is_primary_key: true + Tags: + from: + operation: CreateTags + path: Tags + compare: + is_ignored: True + hooks: + delta_pre_compare: + code: compareTags(delta, a, b) + sdk_create_post_build_request: + template_path: hooks/flow_log/sdk_create_post_build_request.go.tpl + sdk_create_post_set_output: + template_path: hooks/flow_log/sdk_create_post_set_output.go.tpl + sdk_delete_post_build_request: + template_path: hooks/flow_log/sdk_delete_post_build_request.go.tpl + sdk_read_many_pre_build_request: + template_path: hooks/flow_log/sdk_read_many_pre_build_request.go.tpl + sdk_read_many_post_build_request: + template_path: hooks/flow_log/sdk_read_many_post_build_request.go.tpl + sdk_file_end: + template_path: hooks/flow_log/sdk_file_end.go.tpl + update_operation: + custom_method_name: customUpdateFlowLog InternetGateway: fields: Tags: @@ -649,4 +698,4 @@ resources: template_path: hooks/vpc_endpoint/sdk_file_end.go.tpl update_operation: custom_method_name: customUpdateVPCEndpoint - \ No newline at end of file + diff --git a/apis/v1alpha1/types.go b/apis/v1alpha1/types.go index 0f9bb7b7..8b9682ec 100644 --- a/apis/v1alpha1/types.go +++ b/apis/v1alpha1/types.go @@ -817,14 +817,16 @@ type DescribeFleetsInstances struct { // Describes the destination options for a flow log. type DestinationOptionsRequest struct { - HiveCompatiblePartitions *bool `json:"hiveCompatiblePartitions,omitempty"` - PerHourPartition *bool `json:"perHourPartition,omitempty"` + FileFormat *string `json:"fileFormat,omitempty"` + HiveCompatiblePartitions *bool `json:"hiveCompatiblePartitions,omitempty"` + PerHourPartition *bool `json:"perHourPartition,omitempty"` } // Describes the destination options for a flow log. type DestinationOptionsResponse struct { - HiveCompatiblePartitions *bool `json:"hiveCompatiblePartitions,omitempty"` - PerHourPartition *bool `json:"perHourPartition,omitempty"` + FileFormat *string `json:"fileFormat,omitempty"` + HiveCompatiblePartitions *bool `json:"hiveCompatiblePartitions,omitempty"` + PerHourPartition *bool `json:"perHourPartition,omitempty"` } // Describes an Active Directory. @@ -1244,19 +1246,23 @@ type FleetSpotCapacityRebalanceRequest struct { } // Describes a flow log. -type FlowLog struct { +type FlowLog_SDK struct { CreationTime *metav1.Time `json:"creationTime,omitempty"` DeliverLogsErrorMessage *string `json:"deliverLogsErrorMessage,omitempty"` DeliverLogsPermissionARN *string `json:"deliverLogsPermissionARN,omitempty"` DeliverLogsStatus *string `json:"deliverLogsStatus,omitempty"` - FlowLogID *string `json:"flowLogID,omitempty"` - FlowLogStatus *string `json:"flowLogStatus,omitempty"` - LogDestination *string `json:"logDestination,omitempty"` - LogFormat *string `json:"logFormat,omitempty"` - LogGroupName *string `json:"logGroupName,omitempty"` - MaxAggregationInterval *int64 `json:"maxAggregationInterval,omitempty"` - ResourceID *string `json:"resourceID,omitempty"` - Tags []*Tag `json:"tags,omitempty"` + // Describes the destination options for a flow log. + DestinationOptions *DestinationOptionsResponse `json:"destinationOptions,omitempty"` + FlowLogID *string `json:"flowLogID,omitempty"` + FlowLogStatus *string `json:"flowLogStatus,omitempty"` + LogDestination *string `json:"logDestination,omitempty"` + LogDestinationType *string `json:"logDestinationType,omitempty"` + LogFormat *string `json:"logFormat,omitempty"` + LogGroupName *string `json:"logGroupName,omitempty"` + MaxAggregationInterval *int64 `json:"maxAggregationInterval,omitempty"` + ResourceID *string `json:"resourceID,omitempty"` + Tags []*Tag `json:"tags,omitempty"` + TrafficType *string `json:"trafficType,omitempty"` } // Describes a security group. diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index b76e40b0..cf3a5744 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -3376,6 +3376,11 @@ func (in *DescribeFleetsInstances) DeepCopy() *DescribeFleetsInstances { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DestinationOptionsRequest) DeepCopyInto(out *DestinationOptionsRequest) { *out = *in + if in.FileFormat != nil { + in, out := &in.FileFormat, &out.FileFormat + *out = new(string) + **out = **in + } if in.HiveCompatiblePartitions != nil { in, out := &in.HiveCompatiblePartitions, &out.HiveCompatiblePartitions *out = new(bool) @@ -3401,6 +3406,11 @@ func (in *DestinationOptionsRequest) DeepCopy() *DestinationOptionsRequest { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DestinationOptionsResponse) DeepCopyInto(out *DestinationOptionsResponse) { *out = *in + if in.FileFormat != nil { + in, out := &in.FileFormat, &out.FileFormat + *out = new(string) + **out = **in + } if in.HiveCompatiblePartitions != nil { in, out := &in.HiveCompatiblePartitions, &out.HiveCompatiblePartitions *out = new(bool) @@ -5271,6 +5281,193 @@ func (in *FleetSpotCapacityRebalanceRequest) DeepCopy() *FleetSpotCapacityRebala // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FlowLog) DeepCopyInto(out *FlowLog) { + *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 FlowLog. +func (in *FlowLog) DeepCopy() *FlowLog { + if in == nil { + return nil + } + out := new(FlowLog) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FlowLog) 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 *FlowLogList) DeepCopyInto(out *FlowLogList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]FlowLog, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlowLogList. +func (in *FlowLogList) DeepCopy() *FlowLogList { + if in == nil { + return nil + } + out := new(FlowLogList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FlowLogList) 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 *FlowLogSpec) DeepCopyInto(out *FlowLogSpec) { + *out = *in + if in.DeliverLogsPermissionARN != nil { + in, out := &in.DeliverLogsPermissionARN, &out.DeliverLogsPermissionARN + *out = new(string) + **out = **in + } + if in.DestinationOptions != nil { + in, out := &in.DestinationOptions, &out.DestinationOptions + *out = new(DestinationOptionsRequest) + (*in).DeepCopyInto(*out) + } + if in.LogDestination != nil { + in, out := &in.LogDestination, &out.LogDestination + *out = new(string) + **out = **in + } + if in.LogDestinationType != nil { + in, out := &in.LogDestinationType, &out.LogDestinationType + *out = new(string) + **out = **in + } + if in.LogFormat != nil { + in, out := &in.LogFormat, &out.LogFormat + *out = new(string) + **out = **in + } + if in.LogGroupName != nil { + in, out := &in.LogGroupName, &out.LogGroupName + *out = new(string) + **out = **in + } + if in.MaxAggregationInterval != nil { + in, out := &in.MaxAggregationInterval, &out.MaxAggregationInterval + *out = new(int64) + **out = **in + } + if in.ResourceID != nil { + in, out := &in.ResourceID, &out.ResourceID + *out = new(string) + **out = **in + } + if in.ResourceType != nil { + in, out := &in.ResourceType, &out.ResourceType + *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.TrafficType != nil { + in, out := &in.TrafficType, &out.TrafficType + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlowLogSpec. +func (in *FlowLogSpec) DeepCopy() *FlowLogSpec { + if in == nil { + return nil + } + out := new(FlowLogSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FlowLogStatus) DeepCopyInto(out *FlowLogStatus) { + *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.ClientToken != nil { + in, out := &in.ClientToken, &out.ClientToken + *out = new(string) + **out = **in + } + if in.FlowLogID != nil { + in, out := &in.FlowLogID, &out.FlowLogID + *out = new(string) + **out = **in + } + if in.Unsuccessful != nil { + in, out := &in.Unsuccessful, &out.Unsuccessful + *out = make([]*UnsuccessfulItem, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(UnsuccessfulItem) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlowLogStatus. +func (in *FlowLogStatus) DeepCopy() *FlowLogStatus { + if in == nil { + return nil + } + out := new(FlowLogStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FlowLog_SDK) DeepCopyInto(out *FlowLog_SDK) { *out = *in if in.CreationTime != nil { in, out := &in.CreationTime, &out.CreationTime @@ -5291,6 +5488,11 @@ func (in *FlowLog) DeepCopyInto(out *FlowLog) { *out = new(string) **out = **in } + if in.DestinationOptions != nil { + in, out := &in.DestinationOptions, &out.DestinationOptions + *out = new(DestinationOptionsResponse) + (*in).DeepCopyInto(*out) + } if in.FlowLogID != nil { in, out := &in.FlowLogID, &out.FlowLogID *out = new(string) @@ -5306,6 +5508,11 @@ func (in *FlowLog) DeepCopyInto(out *FlowLog) { *out = new(string) **out = **in } + if in.LogDestinationType != nil { + in, out := &in.LogDestinationType, &out.LogDestinationType + *out = new(string) + **out = **in + } if in.LogFormat != nil { in, out := &in.LogFormat, &out.LogFormat *out = new(string) @@ -5337,14 +5544,19 @@ func (in *FlowLog) DeepCopyInto(out *FlowLog) { } } } + if in.TrafficType != nil { + in, out := &in.TrafficType, &out.TrafficType + *out = new(string) + **out = **in + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlowLog. -func (in *FlowLog) DeepCopy() *FlowLog { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlowLog_SDK. +func (in *FlowLog_SDK) DeepCopy() *FlowLog_SDK { if in == nil { return nil } - out := new(FlowLog) + out := new(FlowLog_SDK) in.DeepCopyInto(out) return out } diff --git a/cmd/controller/main.go b/cmd/controller/main.go index ffcef0c4..3d14770d 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -37,6 +37,7 @@ import ( _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/dhcp_options" _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/elastic_ip_address" + _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/flow_log" _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/instance" _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/internet_gateway" _ "github.com/aws-controllers-k8s/ec2-controller/pkg/resource/nat_gateway" diff --git a/config/crd/bases/ec2.services.k8s.aws_flowlogs.yaml b/config/crd/bases/ec2.services.k8s.aws_flowlogs.yaml new file mode 100644 index 00000000..a245242c --- /dev/null +++ b/config/crd/bases/ec2.services.k8s.aws_flowlogs.yaml @@ -0,0 +1,226 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: flowlogs.ec2.services.k8s.aws +spec: + group: ec2.services.k8s.aws + names: + kind: FlowLog + listKind: FlowLogList + plural: flowlogs + singular: flowlog + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: FlowLog is the Schema for the FlowLogs 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: "FlowLogSpec defines the desired state of FlowLog. \n Describes + a flow log." + properties: + deliverLogsPermissionARN: + description: "The ARN for the IAM role that permits Amazon EC2 to + publish flow logs to a CloudWatch Logs log group in your account. + \n If you specify LogDestinationType as s3, do not specify DeliverLogsPermissionArn + or LogGroupName." + type: string + destinationOptions: + description: The destination options. + properties: + fileFormat: + type: string + hiveCompatiblePartitions: + type: boolean + perHourPartition: + type: boolean + type: object + logDestination: + description: "The destination to which the flow log data is to be + published. Flow log data can be published to a CloudWatch Logs log + group or an Amazon S3 bucket. The value specified for this parameter + depends on the value specified for LogDestinationType. \n If LogDestinationType + is not specified or cloud-watch-logs, specify the Amazon Resource + Name (ARN) of the CloudWatch Logs log group. For example, to publish + to a log group called my-logs, specify arn:aws:logs:us-east-1:123456789012:log-group:my-logs. + Alternatively, use LogGroupName instead. \n If LogDestinationType + is s3, specify the ARN of the Amazon S3 bucket. You can also specify + a subfolder in the bucket. To specify a subfolder in the bucket, + use the following ARN format: bucket_ARN/subfolder_name/. For example, + to specify a subfolder named my-logs in a bucket named my-bucket, + use the following ARN: arn:aws:s3:::my-bucket/my-logs/. You cannot + use AWSLogs as a subfolder name. This is a reserved term." + type: string + logDestinationType: + description: "The type of destination to which the flow log data is + to be published. Flow log data can be published to CloudWatch Logs + or Amazon S3. To publish flow log data to CloudWatch Logs, specify + cloud-watch-logs. To publish flow log data to Amazon S3, specify + s3. \n If you specify LogDestinationType as s3, do not specify DeliverLogsPermissionArn + or LogGroupName. \n Default: cloud-watch-logs" + type: string + logFormat: + description: "The fields to include in the flow log record, in the + order in which they should appear. For a list of available fields, + see Flow log records (https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html#flow-log-records). + If you omit this parameter, the flow log is created using the default + format. If you specify this parameter, you must specify at least + one field. \n Specify the fields using the ${field-id} format, separated + by spaces. For the CLI, surround this parameter value with single + quotes on Linux or double quotes on Windows." + type: string + logGroupName: + description: "The name of a new or existing CloudWatch Logs log group + where Amazon EC2 publishes your flow logs. \n If you specify LogDestinationType + as s3, do not specify DeliverLogsPermissionArn or LogGroupName." + type: string + maxAggregationInterval: + description: "The maximum interval of time during which a flow of + packets is captured and aggregated into a flow log record. You can + specify 60 seconds (1 minute) or 600 seconds (10 minutes). \n When + a network interface is attached to a Nitro-based instance (https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html#ec2-nitro-instances), + the aggregation interval is always 60 seconds or less, regardless + of the value that you specify. \n Default: 600" + format: int64 + type: integer + resourceID: + type: string + resourceType: + description: The type of resource for which to create the flow log. + For example, if you specified a VPC ID for the ResourceId property, + specify VPC for this property. + type: string + tags: + description: The tags. The value parameter is required, but if you + don't want the tag to have a value, specify the parameter with no + value, and we set the value to an empty string. + items: + description: Describes a tag. + properties: + key: + type: string + value: + type: string + type: object + type: array + trafficType: + description: The type of traffic to log. You can log traffic that + the resource accepts or rejects, or all traffic. + type: string + required: + - resourceID + - resourceType + type: object + status: + description: FlowLogStatus defines the observed state of FlowLog + properties: + ackResourceMetadata: + description: All CRs managed by ACK have a common `Status.ACKResourceMetadata` + member that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: 'ARN is the Amazon Resource Name for the resource. + This is a globally-unique identifier and is set only by the + ACK service controller once the controller has orchestrated + the creation of the resource OR when it has verified that an + "adopted" resource (a resource where the ARN annotation was + set by the Kubernetes user on the CR) exists and matches the + supplied CR''s Spec field values. TODO(vijat@): Find a better + strategy for resources that do not have ARN in CreateOutputResponse + https://github.com/aws/aws-controllers-k8s/issues/270' + type: string + ownerAccountID: + description: OwnerAccountID is the AWS Account ID of the account + that owns the backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + clientToken: + description: Unique, case-sensitive identifier that you provide to + ensure the idempotency of the request. + type: string + 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 + flowLogID: + type: string + unsuccessful: + description: Information about the flow logs that could not be created + successfully. + items: + description: Information about items that were not successfully + processed in a batch call. + properties: + error: + description: Information about the error that occurred. For + more information about errors, see Error codes (https://docs.aws.amazon.com/AWSEC2/latest/APIReference/errors-overview.html). + properties: + code: + type: string + message: + type: string + type: object + resourceID: + type: string + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 82bd249a..8a56d410 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -4,6 +4,7 @@ resources: - common - bases/ec2.services.k8s.aws_dhcpoptions.yaml - bases/ec2.services.k8s.aws_elasticipaddresses.yaml + - bases/ec2.services.k8s.aws_flowlogs.yaml - bases/ec2.services.k8s.aws_instances.yaml - bases/ec2.services.k8s.aws_internetgateways.yaml - bases/ec2.services.k8s.aws_natgateways.yaml diff --git a/config/rbac/cluster-role-controller.yaml b/config/rbac/cluster-role-controller.yaml index 1a34cee5..61a9d0ab 100644 --- a/config/rbac/cluster-role-controller.yaml +++ b/config/rbac/cluster-role-controller.yaml @@ -71,6 +71,26 @@ rules: - get - patch - update +- apiGroups: + - ec2.services.k8s.aws + resources: + - flowlogs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - ec2.services.k8s.aws + resources: + - flowlogs/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 e7793f78..1bb46dcb 100644 --- a/config/rbac/role-reader.yaml +++ b/config/rbac/role-reader.yaml @@ -11,6 +11,7 @@ rules: resources: - dhcpoptions - elasticipaddresses + - flowlogs - instances - internetgateways - natgateways diff --git a/config/rbac/role-writer.yaml b/config/rbac/role-writer.yaml index 0d62a00d..2e6d826d 100644 --- a/config/rbac/role-writer.yaml +++ b/config/rbac/role-writer.yaml @@ -11,6 +11,7 @@ rules: resources: - dhcpoptions - elasticipaddresses + - flowlogs - instances - internetgateways - natgateways @@ -33,6 +34,7 @@ rules: resources: - dhcpoptions - elasticipaddresses + - flowlogs - instances - internetgateways - natgateways diff --git a/generator.yaml b/generator.yaml index a5df9075..5b666b49 100644 --- a/generator.yaml +++ b/generator.yaml @@ -7,6 +7,11 @@ ignore: - AllocateAddressInput.TagSpecifications - CreateDhcpOptionsInput.DryRun - CreateDhcpOptionsInput.TagSpecifications + - CreateFlowLogsInput.DryRun + - CreateFlowLogsInput.ClientToken + - CreateFlowLogsInput.TagSpecifications + - CreateFlowLogsInput.ResourceIds + - CreateFlowLogsOutput.FlowLogIds - CreateInternetGatewayInput.DryRun - CreateInternetGatewayInput.TagSpecifications - CreateNatGatewayInput.ClientToken @@ -141,6 +146,14 @@ operations: operation_type: - Delete resource_name: VpcEndpoint + CreateFlowLogs: + operation_type: + - Create + resource_name: FlowLog + DeleteFlowLogs: + operation_type: + - Delete + resource_name: FlowLog RunInstances: #ouput shape: Reservation output_wrapper_field_path: Instances @@ -268,6 +281,42 @@ resources: template_path: hooks/elastic_ip_address/sdk_file_end.go.tpl update_operation: custom_method_name: customUpdateElasticIP + FlowLog: + exceptions: + terminal_codes: + - InvalidParameter + - InvalidParameterValue + fields: + ResourceID: + type: string + is_required: true + FlowLogID: + type: string + is_read_only: true + is_primary_key: true + Tags: + from: + operation: CreateTags + path: Tags + compare: + is_ignored: True + hooks: + delta_pre_compare: + code: compareTags(delta, a, b) + sdk_create_post_build_request: + template_path: hooks/flow_log/sdk_create_post_build_request.go.tpl + sdk_create_post_set_output: + template_path: hooks/flow_log/sdk_create_post_set_output.go.tpl + sdk_delete_post_build_request: + template_path: hooks/flow_log/sdk_delete_post_build_request.go.tpl + sdk_read_many_pre_build_request: + template_path: hooks/flow_log/sdk_read_many_pre_build_request.go.tpl + sdk_read_many_post_build_request: + template_path: hooks/flow_log/sdk_read_many_post_build_request.go.tpl + sdk_file_end: + template_path: hooks/flow_log/sdk_file_end.go.tpl + update_operation: + custom_method_name: customUpdateFlowLog InternetGateway: fields: Tags: @@ -649,4 +698,4 @@ resources: template_path: hooks/vpc_endpoint/sdk_file_end.go.tpl update_operation: custom_method_name: customUpdateVPCEndpoint - \ No newline at end of file + diff --git a/helm/crds/ec2.services.k8s.aws_flowlogs.yaml b/helm/crds/ec2.services.k8s.aws_flowlogs.yaml new file mode 100644 index 00000000..a245242c --- /dev/null +++ b/helm/crds/ec2.services.k8s.aws_flowlogs.yaml @@ -0,0 +1,226 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: flowlogs.ec2.services.k8s.aws +spec: + group: ec2.services.k8s.aws + names: + kind: FlowLog + listKind: FlowLogList + plural: flowlogs + singular: flowlog + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: FlowLog is the Schema for the FlowLogs 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: "FlowLogSpec defines the desired state of FlowLog. \n Describes + a flow log." + properties: + deliverLogsPermissionARN: + description: "The ARN for the IAM role that permits Amazon EC2 to + publish flow logs to a CloudWatch Logs log group in your account. + \n If you specify LogDestinationType as s3, do not specify DeliverLogsPermissionArn + or LogGroupName." + type: string + destinationOptions: + description: The destination options. + properties: + fileFormat: + type: string + hiveCompatiblePartitions: + type: boolean + perHourPartition: + type: boolean + type: object + logDestination: + description: "The destination to which the flow log data is to be + published. Flow log data can be published to a CloudWatch Logs log + group or an Amazon S3 bucket. The value specified for this parameter + depends on the value specified for LogDestinationType. \n If LogDestinationType + is not specified or cloud-watch-logs, specify the Amazon Resource + Name (ARN) of the CloudWatch Logs log group. For example, to publish + to a log group called my-logs, specify arn:aws:logs:us-east-1:123456789012:log-group:my-logs. + Alternatively, use LogGroupName instead. \n If LogDestinationType + is s3, specify the ARN of the Amazon S3 bucket. You can also specify + a subfolder in the bucket. To specify a subfolder in the bucket, + use the following ARN format: bucket_ARN/subfolder_name/. For example, + to specify a subfolder named my-logs in a bucket named my-bucket, + use the following ARN: arn:aws:s3:::my-bucket/my-logs/. You cannot + use AWSLogs as a subfolder name. This is a reserved term." + type: string + logDestinationType: + description: "The type of destination to which the flow log data is + to be published. Flow log data can be published to CloudWatch Logs + or Amazon S3. To publish flow log data to CloudWatch Logs, specify + cloud-watch-logs. To publish flow log data to Amazon S3, specify + s3. \n If you specify LogDestinationType as s3, do not specify DeliverLogsPermissionArn + or LogGroupName. \n Default: cloud-watch-logs" + type: string + logFormat: + description: "The fields to include in the flow log record, in the + order in which they should appear. For a list of available fields, + see Flow log records (https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html#flow-log-records). + If you omit this parameter, the flow log is created using the default + format. If you specify this parameter, you must specify at least + one field. \n Specify the fields using the ${field-id} format, separated + by spaces. For the CLI, surround this parameter value with single + quotes on Linux or double quotes on Windows." + type: string + logGroupName: + description: "The name of a new or existing CloudWatch Logs log group + where Amazon EC2 publishes your flow logs. \n If you specify LogDestinationType + as s3, do not specify DeliverLogsPermissionArn or LogGroupName." + type: string + maxAggregationInterval: + description: "The maximum interval of time during which a flow of + packets is captured and aggregated into a flow log record. You can + specify 60 seconds (1 minute) or 600 seconds (10 minutes). \n When + a network interface is attached to a Nitro-based instance (https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html#ec2-nitro-instances), + the aggregation interval is always 60 seconds or less, regardless + of the value that you specify. \n Default: 600" + format: int64 + type: integer + resourceID: + type: string + resourceType: + description: The type of resource for which to create the flow log. + For example, if you specified a VPC ID for the ResourceId property, + specify VPC for this property. + type: string + tags: + description: The tags. The value parameter is required, but if you + don't want the tag to have a value, specify the parameter with no + value, and we set the value to an empty string. + items: + description: Describes a tag. + properties: + key: + type: string + value: + type: string + type: object + type: array + trafficType: + description: The type of traffic to log. You can log traffic that + the resource accepts or rejects, or all traffic. + type: string + required: + - resourceID + - resourceType + type: object + status: + description: FlowLogStatus defines the observed state of FlowLog + properties: + ackResourceMetadata: + description: All CRs managed by ACK have a common `Status.ACKResourceMetadata` + member that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: 'ARN is the Amazon Resource Name for the resource. + This is a globally-unique identifier and is set only by the + ACK service controller once the controller has orchestrated + the creation of the resource OR when it has verified that an + "adopted" resource (a resource where the ARN annotation was + set by the Kubernetes user on the CR) exists and matches the + supplied CR''s Spec field values. TODO(vijat@): Find a better + strategy for resources that do not have ARN in CreateOutputResponse + https://github.com/aws/aws-controllers-k8s/issues/270' + type: string + ownerAccountID: + description: OwnerAccountID is the AWS Account ID of the account + that owns the backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + clientToken: + description: Unique, case-sensitive identifier that you provide to + ensure the idempotency of the request. + type: string + 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 + flowLogID: + type: string + unsuccessful: + description: Information about the flow logs that could not be created + successfully. + items: + description: Information about items that were not successfully + processed in a batch call. + properties: + error: + description: Information about the error that occurred. For + more information about errors, see Error codes (https://docs.aws.amazon.com/AWSEC2/latest/APIReference/errors-overview.html). + properties: + code: + type: string + message: + type: string + type: object + resourceID: + type: string + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/templates/cluster-role-controller.yaml b/helm/templates/cluster-role-controller.yaml index 15543962..ae58ffb0 100644 --- a/helm/templates/cluster-role-controller.yaml +++ b/helm/templates/cluster-role-controller.yaml @@ -86,6 +86,26 @@ rules: - get - patch - update +- apiGroups: + - ec2.services.k8s.aws + resources: + - flowlogs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - ec2.services.k8s.aws + resources: + - flowlogs/status + verbs: + - get + - patch + - update - apiGroups: - ec2.services.k8s.aws resources: diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml index 7504a614..4d087a52 100644 --- a/helm/templates/deployment.yaml +++ b/helm/templates/deployment.yaml @@ -18,10 +18,12 @@ spec: app.kubernetes.io/instance: {{ .Release.Name }} template: metadata: +{{- if .Values.deployment.annotations }} annotations: {{- range $key, $value := .Values.deployment.annotations }} {{ $key }}: {{ $value | quote }} {{- end }} +{{- end }} labels: app.kubernetes.io/name: {{ include "app.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} @@ -104,11 +106,19 @@ spec: value: {{ include "aws.credentials.path" . }} - name: AWS_PROFILE value: {{ .Values.aws.credentials.profile }} + {{- end }} + {{- if .Values.deployment.extraEnvVars -}} + {{ toYaml .Values.deployment.extraEnvVars | nindent 8 }} + {{- end }} volumeMounts: + {{- if .Values.aws.credentials.secretName }} - name: {{ .Values.aws.credentials.secretName }} mountPath: {{ include "aws.credentials.secret_mount_path" . }} readOnly: true {{- end }} + {{- if .Values.deployment.extraVolumeMounts -}} + {{ toYaml .Values.deployment.extraVolumeMounts | nindent 12 }} + {{- end }} securityContext: allowPrivilegeEscalation: false privileged: false @@ -133,9 +143,12 @@ spec: hostIPC: false hostNetwork: false hostPID: false - {{ if .Values.aws.credentials.secretName -}} volumes: + {{- if .Values.aws.credentials.secretName -}} - name: {{ .Values.aws.credentials.secretName }} secret: secretName: {{ .Values.aws.credentials.secretName }} {{ end -}} +{{- if .Values.deployment.extraVolumes }} +{{ toYaml .Values.deployment.extraVolumes | indent 8}} +{{- end }} diff --git a/helm/templates/role-reader.yaml b/helm/templates/role-reader.yaml index 08760026..0b07c30c 100644 --- a/helm/templates/role-reader.yaml +++ b/helm/templates/role-reader.yaml @@ -11,6 +11,7 @@ rules: resources: - dhcpoptions - elasticipaddresses + - flowlogs - instances - internetgateways - natgateways diff --git a/helm/templates/role-writer.yaml b/helm/templates/role-writer.yaml index 86939537..2d6bc20a 100644 --- a/helm/templates/role-writer.yaml +++ b/helm/templates/role-writer.yaml @@ -13,6 +13,8 @@ rules: - elasticipaddresses + - flowlogs + - instances - internetgateways @@ -44,6 +46,7 @@ rules: resources: - dhcpoptions - elasticipaddresses + - flowlogs - instances - internetgateways - natgateways diff --git a/helm/values.schema.json b/helm/values.schema.json index 79fd18ce..fb4437b7 100644 --- a/helm/values.schema.json +++ b/helm/values.schema.json @@ -58,6 +58,15 @@ }, "priorityClassName": { "type": "string" + }, + "extraVolumeMounts": { + "type": "array" + }, + "extraVolumes": { + "type": "array" + }, + "extraEnvVars": { + "type": "array" } }, "required": [ diff --git a/helm/values.yaml b/helm/values.yaml index 8c6e90cd..b47c78c1 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -28,6 +28,26 @@ deployment: # Which priorityClassName to set? # See: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/#pod-priority priorityClassName: "" + extraVolumes: [] + extraVolumeMounts: [] + + # Additional server container environment variables + # + # You specify this manually like you would a raw deployment manifest. + # This means you can bind in environment variables from secrets. + # + # e.g. static environment variable: + # - name: DEMO_GREETING + # value: "Hello from the environment" + # + # e.g. secret environment variable: + # - name: USERNAME + # valueFrom: + # secretKeyRef: + # name: mysecret + # key: username + extraEnvVars: [] + # If "installScope: cluster" then these labels will be applied to ClusterRole role: diff --git a/pkg/resource/flow_log/delta.go b/pkg/resource/flow_log/delta.go new file mode 100644 index 00000000..41b99cdb --- /dev/null +++ b/pkg/resource/flow_log/delta.go @@ -0,0 +1,137 @@ +// 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 flow_log + +import ( + "bytes" + "reflect" + + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" +) + +// Hack to avoid import errors during build... +var ( + _ = &bytes.Buffer{} + _ = &reflect.Method{} + _ = &acktags.Tags{} +) + +// newResourceDelta returns a new `ackcompare.Delta` used to compare two +// resources +func newResourceDelta( + a *resource, + b *resource, +) *ackcompare.Delta { + delta := ackcompare.NewDelta() + if (a == nil && b != nil) || + (a != nil && b == nil) { + delta.Add("", a, b) + return delta + } + compareTags(delta, a, b) + + if ackcompare.HasNilDifference(a.ko.Spec.DeliverLogsPermissionARN, b.ko.Spec.DeliverLogsPermissionARN) { + delta.Add("Spec.DeliverLogsPermissionARN", a.ko.Spec.DeliverLogsPermissionARN, b.ko.Spec.DeliverLogsPermissionARN) + } else if a.ko.Spec.DeliverLogsPermissionARN != nil && b.ko.Spec.DeliverLogsPermissionARN != nil { + if *a.ko.Spec.DeliverLogsPermissionARN != *b.ko.Spec.DeliverLogsPermissionARN { + delta.Add("Spec.DeliverLogsPermissionARN", a.ko.Spec.DeliverLogsPermissionARN, b.ko.Spec.DeliverLogsPermissionARN) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.DestinationOptions, b.ko.Spec.DestinationOptions) { + delta.Add("Spec.DestinationOptions", a.ko.Spec.DestinationOptions, b.ko.Spec.DestinationOptions) + } else if a.ko.Spec.DestinationOptions != nil && b.ko.Spec.DestinationOptions != nil { + if ackcompare.HasNilDifference(a.ko.Spec.DestinationOptions.FileFormat, b.ko.Spec.DestinationOptions.FileFormat) { + delta.Add("Spec.DestinationOptions.FileFormat", a.ko.Spec.DestinationOptions.FileFormat, b.ko.Spec.DestinationOptions.FileFormat) + } else if a.ko.Spec.DestinationOptions.FileFormat != nil && b.ko.Spec.DestinationOptions.FileFormat != nil { + if *a.ko.Spec.DestinationOptions.FileFormat != *b.ko.Spec.DestinationOptions.FileFormat { + delta.Add("Spec.DestinationOptions.FileFormat", a.ko.Spec.DestinationOptions.FileFormat, b.ko.Spec.DestinationOptions.FileFormat) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.DestinationOptions.HiveCompatiblePartitions, b.ko.Spec.DestinationOptions.HiveCompatiblePartitions) { + delta.Add("Spec.DestinationOptions.HiveCompatiblePartitions", a.ko.Spec.DestinationOptions.HiveCompatiblePartitions, b.ko.Spec.DestinationOptions.HiveCompatiblePartitions) + } else if a.ko.Spec.DestinationOptions.HiveCompatiblePartitions != nil && b.ko.Spec.DestinationOptions.HiveCompatiblePartitions != nil { + if *a.ko.Spec.DestinationOptions.HiveCompatiblePartitions != *b.ko.Spec.DestinationOptions.HiveCompatiblePartitions { + delta.Add("Spec.DestinationOptions.HiveCompatiblePartitions", a.ko.Spec.DestinationOptions.HiveCompatiblePartitions, b.ko.Spec.DestinationOptions.HiveCompatiblePartitions) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.DestinationOptions.PerHourPartition, b.ko.Spec.DestinationOptions.PerHourPartition) { + delta.Add("Spec.DestinationOptions.PerHourPartition", a.ko.Spec.DestinationOptions.PerHourPartition, b.ko.Spec.DestinationOptions.PerHourPartition) + } else if a.ko.Spec.DestinationOptions.PerHourPartition != nil && b.ko.Spec.DestinationOptions.PerHourPartition != nil { + if *a.ko.Spec.DestinationOptions.PerHourPartition != *b.ko.Spec.DestinationOptions.PerHourPartition { + delta.Add("Spec.DestinationOptions.PerHourPartition", a.ko.Spec.DestinationOptions.PerHourPartition, b.ko.Spec.DestinationOptions.PerHourPartition) + } + } + } + if ackcompare.HasNilDifference(a.ko.Spec.LogDestination, b.ko.Spec.LogDestination) { + delta.Add("Spec.LogDestination", a.ko.Spec.LogDestination, b.ko.Spec.LogDestination) + } else if a.ko.Spec.LogDestination != nil && b.ko.Spec.LogDestination != nil { + if *a.ko.Spec.LogDestination != *b.ko.Spec.LogDestination { + delta.Add("Spec.LogDestination", a.ko.Spec.LogDestination, b.ko.Spec.LogDestination) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.LogDestinationType, b.ko.Spec.LogDestinationType) { + delta.Add("Spec.LogDestinationType", a.ko.Spec.LogDestinationType, b.ko.Spec.LogDestinationType) + } else if a.ko.Spec.LogDestinationType != nil && b.ko.Spec.LogDestinationType != nil { + if *a.ko.Spec.LogDestinationType != *b.ko.Spec.LogDestinationType { + delta.Add("Spec.LogDestinationType", a.ko.Spec.LogDestinationType, b.ko.Spec.LogDestinationType) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.LogFormat, b.ko.Spec.LogFormat) { + delta.Add("Spec.LogFormat", a.ko.Spec.LogFormat, b.ko.Spec.LogFormat) + } else if a.ko.Spec.LogFormat != nil && b.ko.Spec.LogFormat != nil { + if *a.ko.Spec.LogFormat != *b.ko.Spec.LogFormat { + delta.Add("Spec.LogFormat", a.ko.Spec.LogFormat, b.ko.Spec.LogFormat) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.LogGroupName, b.ko.Spec.LogGroupName) { + delta.Add("Spec.LogGroupName", a.ko.Spec.LogGroupName, b.ko.Spec.LogGroupName) + } else if a.ko.Spec.LogGroupName != nil && b.ko.Spec.LogGroupName != nil { + if *a.ko.Spec.LogGroupName != *b.ko.Spec.LogGroupName { + delta.Add("Spec.LogGroupName", a.ko.Spec.LogGroupName, b.ko.Spec.LogGroupName) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.MaxAggregationInterval, b.ko.Spec.MaxAggregationInterval) { + delta.Add("Spec.MaxAggregationInterval", a.ko.Spec.MaxAggregationInterval, b.ko.Spec.MaxAggregationInterval) + } else if a.ko.Spec.MaxAggregationInterval != nil && b.ko.Spec.MaxAggregationInterval != nil { + if *a.ko.Spec.MaxAggregationInterval != *b.ko.Spec.MaxAggregationInterval { + delta.Add("Spec.MaxAggregationInterval", a.ko.Spec.MaxAggregationInterval, b.ko.Spec.MaxAggregationInterval) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.ResourceID, b.ko.Spec.ResourceID) { + delta.Add("Spec.ResourceID", a.ko.Spec.ResourceID, b.ko.Spec.ResourceID) + } else if a.ko.Spec.ResourceID != nil && b.ko.Spec.ResourceID != nil { + if *a.ko.Spec.ResourceID != *b.ko.Spec.ResourceID { + delta.Add("Spec.ResourceID", a.ko.Spec.ResourceID, b.ko.Spec.ResourceID) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.ResourceType, b.ko.Spec.ResourceType) { + delta.Add("Spec.ResourceType", a.ko.Spec.ResourceType, b.ko.Spec.ResourceType) + } else if a.ko.Spec.ResourceType != nil && b.ko.Spec.ResourceType != nil { + if *a.ko.Spec.ResourceType != *b.ko.Spec.ResourceType { + delta.Add("Spec.ResourceType", a.ko.Spec.ResourceType, b.ko.Spec.ResourceType) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.TrafficType, b.ko.Spec.TrafficType) { + delta.Add("Spec.TrafficType", a.ko.Spec.TrafficType, b.ko.Spec.TrafficType) + } else if a.ko.Spec.TrafficType != nil && b.ko.Spec.TrafficType != nil { + if *a.ko.Spec.TrafficType != *b.ko.Spec.TrafficType { + delta.Add("Spec.TrafficType", a.ko.Spec.TrafficType, b.ko.Spec.TrafficType) + } + } + + return delta +} diff --git a/pkg/resource/flow_log/descriptor.go b/pkg/resource/flow_log/descriptor.go new file mode 100644 index 00000000..700408e2 --- /dev/null +++ b/pkg/resource/flow_log/descriptor.go @@ -0,0 +1,155 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package flow_log + +import ( + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + rtclient "sigs.k8s.io/controller-runtime/pkg/client" + k8sctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" +) + +const ( + finalizerString = "finalizers.ec2.services.k8s.aws/FlowLog" +) + +var ( + GroupVersionResource = svcapitypes.GroupVersion.WithResource("flowlogs") + GroupKind = metav1.GroupKind{ + Group: "ec2.services.k8s.aws", + Kind: "FlowLog", + } +) + +// resourceDescriptor implements the +// `aws-service-operator-k8s/pkg/types.AWSResourceDescriptor` interface +type resourceDescriptor struct { +} + +// GroupVersionKind returns a Kubernetes schema.GroupVersionKind struct that +// describes the API Group, Version and Kind of CRs described by the descriptor +func (d *resourceDescriptor) GroupVersionKind() schema.GroupVersionKind { + return svcapitypes.GroupVersion.WithKind(GroupKind.Kind) +} + +// EmptyRuntimeObject returns an empty object prototype that may be used in +// apimachinery and k8s client operations +func (d *resourceDescriptor) EmptyRuntimeObject() rtclient.Object { + return &svcapitypes.FlowLog{} +} + +// ResourceFromRuntimeObject returns an AWSResource that has been initialized +// with the supplied runtime.Object +func (d *resourceDescriptor) ResourceFromRuntimeObject( + obj rtclient.Object, +) acktypes.AWSResource { + return &resource{ + ko: obj.(*svcapitypes.FlowLog), + } +} + +// Delta returns an `ackcompare.Delta` object containing the difference between +// one `AWSResource` and another. +func (d *resourceDescriptor) Delta(a, b acktypes.AWSResource) *ackcompare.Delta { + return newResourceDelta(a.(*resource), b.(*resource)) +} + +// IsManaged returns true if the supplied AWSResource is under the management +// of an ACK service controller. What this means in practice is that the +// underlying custom resource (CR) in the AWSResource has had a +// resource-specific finalizer associated with it. +func (d *resourceDescriptor) IsManaged( + res acktypes.AWSResource, +) bool { + obj := res.RuntimeObject() + if obj == nil { + // Should not happen. If it does, there is a bug in the code + panic("nil RuntimeMetaObject in AWSResource") + } + // Remove use of custom code once + // https://github.com/kubernetes-sigs/controller-runtime/issues/994 is + // fixed. This should be able to be: + // + // return k8sctrlutil.ContainsFinalizer(obj, finalizerString) + return containsFinalizer(obj, finalizerString) +} + +// Remove once https://github.com/kubernetes-sigs/controller-runtime/issues/994 +// is fixed. +func containsFinalizer(obj rtclient.Object, finalizer string) bool { + f := obj.GetFinalizers() + for _, e := range f { + if e == finalizer { + return true + } + } + return false +} + +// MarkManaged places the supplied resource under the management of ACK. What +// this typically means is that the resource manager will decorate the +// underlying custom resource (CR) with a finalizer that indicates ACK is +// managing the resource and the underlying CR may not be deleted until ACK is +// finished cleaning up any backend AWS service resources associated with the +// CR. +func (d *resourceDescriptor) MarkManaged( + res acktypes.AWSResource, +) { + obj := res.RuntimeObject() + if obj == nil { + // Should not happen. If it does, there is a bug in the code + panic("nil RuntimeMetaObject in AWSResource") + } + k8sctrlutil.AddFinalizer(obj, finalizerString) +} + +// MarkUnmanaged removes the supplied resource from management by ACK. What +// this typically means is that the resource manager will remove a finalizer +// underlying custom resource (CR) that indicates ACK is managing the resource. +// This will allow the Kubernetes API server to delete the underlying CR. +func (d *resourceDescriptor) MarkUnmanaged( + res acktypes.AWSResource, +) { + obj := res.RuntimeObject() + if obj == nil { + // Should not happen. If it does, there is a bug in the code + panic("nil RuntimeMetaObject in AWSResource") + } + k8sctrlutil.RemoveFinalizer(obj, finalizerString) +} + +// MarkAdopted places descriptors on the custom resource that indicate the +// resource was not created from within ACK. +func (d *resourceDescriptor) MarkAdopted( + res acktypes.AWSResource, +) { + obj := res.RuntimeObject() + if obj == nil { + // Should not happen. If it does, there is a bug in the code + panic("nil RuntimeObject in AWSResource") + } + curr := obj.GetAnnotations() + if curr == nil { + curr = make(map[string]string) + } + curr[ackv1alpha1.AnnotationAdopted] = "true" + obj.SetAnnotations(curr) +} diff --git a/pkg/resource/flow_log/hooks.go b/pkg/resource/flow_log/hooks.go new file mode 100644 index 00000000..d69c1754 --- /dev/null +++ b/pkg/resource/flow_log/hooks.go @@ -0,0 +1,189 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package flow_log + +import ( + "context" + + svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" + svcsdk "github.com/aws/aws-sdk-go/service/ec2" +) + +func (rm *resourceManager) customUpdateFlowLog( + ctx context.Context, + desired *resource, + latest *resource, + delta *ackcompare.Delta, +) (updated *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.customUpdateFlowLog") + defer exit(err) + + // Default `updated` to `desired` because it is likely + // EC2 `modify` APIs do NOT return output, only errors. + // If the `modify` calls (i.e. `sync`) do NOT return + // an error, then the update was successful and desired.Spec + // (now updated.Spec) reflects the latest resource state. + updated = rm.concreteResource(desired.DeepCopy()) + + if delta.DifferentAt("Spec.Tags") { + if err := rm.syncTags(ctx, desired, latest); err != nil { + return nil, err + } + } + + return updated, nil +} + +// syncTags used to keep tags in sync by calling Create and Delete API's +func (rm *resourceManager) syncTags( + ctx context.Context, + desired *resource, + latest *resource, +) (err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.syncTags") + defer func(err error) { + exit(err) + }(err) + + resourceId := []*string{latest.ko.Status.FlowLogID} + + toAdd, toDelete := computeTagsDelta( + desired.ko.Spec.Tags, latest.ko.Spec.Tags, + ) + + if len(toDelete) > 0 { + rlog.Debug("removing tags from FlowLog resource", "tags", toDelete) + _, err = rm.sdkapi.DeleteTagsWithContext( + ctx, + &svcsdk.DeleteTagsInput{ + Resources: resourceId, + Tags: rm.sdkTags(toDelete), + }, + ) + rm.metrics.RecordAPICall("UPDATE", "DeleteTags", err) + if err != nil { + return err + } + + } + + if len(toAdd) > 0 { + rlog.Debug("adding tags to FlowLog resource", "tags", toAdd) + _, err = rm.sdkapi.CreateTagsWithContext( + ctx, + &svcsdk.CreateTagsInput{ + Resources: resourceId, + Tags: rm.sdkTags(toAdd), + }, + ) + rm.metrics.RecordAPICall("UPDATE", "CreateTags", err) + if err != nil { + return err + } + } + + return nil +} + +// sdkTags converts *svcapitypes.Tag array to a *svcsdk.Tag array +func (rm *resourceManager) sdkTags( + tags []*svcapitypes.Tag, +) (sdktags []*svcsdk.Tag) { + + for _, i := range tags { + sdktag := rm.newTag(*i) + sdktags = append(sdktags, sdktag) + } + + return sdktags +} + +// computeTagsDelta returns tags to be added and removed from the resource +func computeTagsDelta( + desired []*svcapitypes.Tag, + latest []*svcapitypes.Tag, +) (toAdd []*svcapitypes.Tag, toDelete []*svcapitypes.Tag) { + + desiredTags := map[string]string{} + for _, tag := range desired { + desiredTags[*tag.Key] = *tag.Value + } + + latestTags := map[string]string{} + for _, tag := range latest { + latestTags[*tag.Key] = *tag.Value + } + + for _, tag := range desired { + val, ok := latestTags[*tag.Key] + if !ok || val != *tag.Value { + toAdd = append(toAdd, tag) + } + } + + for _, tag := range latest { + _, ok := desiredTags[*tag.Key] + if !ok { + toDelete = append(toDelete, tag) + } + } + + return toAdd, toDelete + +} + +// compareTags is a custom comparison function for comparing lists of Tag +// structs where the order of the structs in the list is not important. +func compareTags( + delta *ackcompare.Delta, + a *resource, + b *resource, +) { + if len(a.ko.Spec.Tags) != len(b.ko.Spec.Tags) { + delta.Add("Spec.Tags", a.ko.Spec.Tags, b.ko.Spec.Tags) + } else if len(a.ko.Spec.Tags) > 0 { + addedOrUpdated, removed := computeTagsDelta(a.ko.Spec.Tags, b.ko.Spec.Tags) + if len(addedOrUpdated) != 0 || len(removed) != 0 { + delta.Add("Spec.Tags", a.ko.Spec.Tags, b.ko.Spec.Tags) + } + } +} + +// updateTagSpecificationsInCreateRequest adds +// Tags defined in the Spec to CreateFlowLogsInput.TagSpecification +// and ensures the ResourceType is always set to 'FlowLog' +func updateTagSpecificationsInCreateRequest(r *resource, + input *svcsdk.CreateFlowLogsInput) { + input.TagSpecifications = nil + desiredTagSpecs := svcsdk.TagSpecification{} + if r.ko.Spec.Tags != nil { + requestedTags := []*svcsdk.Tag{} + for _, desiredTag := range r.ko.Spec.Tags { + // Add in tags defined in the Spec + tag := &svcsdk.Tag{} + if desiredTag.Key != nil && desiredTag.Value != nil { + tag.SetKey(*desiredTag.Key) + tag.SetValue(*desiredTag.Value) + } + requestedTags = append(requestedTags, tag) + } + desiredTagSpecs.SetResourceType("vpc-flow-log") + desiredTagSpecs.SetTags(requestedTags) + input.TagSpecifications = []*svcsdk.TagSpecification{&desiredTagSpecs} + } +} diff --git a/pkg/resource/flow_log/identifiers.go b/pkg/resource/flow_log/identifiers.go new file mode 100644 index 00000000..f13b9fc9 --- /dev/null +++ b/pkg/resource/flow_log/identifiers.go @@ -0,0 +1,55 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package flow_log + +import ( + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" +) + +// resourceIdentifiers implements the +// `aws-service-operator-k8s/pkg/types.AWSResourceIdentifiers` interface +type resourceIdentifiers struct { + meta *ackv1alpha1.ResourceMetadata +} + +// ARN returns the AWS Resource Name for the backend AWS resource. If nil, +// this means the resource has not yet been created in the backend AWS +// service. +func (ri *resourceIdentifiers) ARN() *ackv1alpha1.AWSResourceName { + if ri.meta != nil { + return ri.meta.ARN + } + return nil +} + +// OwnerAccountID returns the AWS account identifier in which the +// backend AWS resource resides, or nil if this information is not known +// for the resource +func (ri *resourceIdentifiers) OwnerAccountID() *ackv1alpha1.AWSAccountID { + if ri.meta != nil { + return ri.meta.OwnerAccountID + } + return nil +} + +// Region returns the AWS region in which the resource exists, or +// nil if this information is not known. +func (ri *resourceIdentifiers) Region() *ackv1alpha1.AWSRegion { + if ri.meta != nil { + return ri.meta.Region + } + return nil +} diff --git a/pkg/resource/flow_log/manager.go b/pkg/resource/flow_log/manager.go new file mode 100644 index 00000000..5fce2c8b --- /dev/null +++ b/pkg/resource/flow_log/manager.go @@ -0,0 +1,360 @@ +// 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 flow_log + +import ( + "context" + "fmt" + "time" + + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + ackcondition "github.com/aws-controllers-k8s/runtime/pkg/condition" + ackcfg "github.com/aws-controllers-k8s/runtime/pkg/config" + ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" + ackmetrics "github.com/aws-controllers-k8s/runtime/pkg/metrics" + ackrequeue "github.com/aws-controllers-k8s/runtime/pkg/requeue" + ackrt "github.com/aws-controllers-k8s/runtime/pkg/runtime" + ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + ackutil "github.com/aws-controllers-k8s/runtime/pkg/util" + "github.com/aws/aws-sdk-go/aws/session" + svcsdk "github.com/aws/aws-sdk-go/service/ec2" + svcsdkapi "github.com/aws/aws-sdk-go/service/ec2/ec2iface" + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + + svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" +) + +var ( + _ = ackutil.InStrings + _ = acktags.NewTags() + _ = ackrt.MissingImageTagValue + _ = svcapitypes.FlowLog{} +) + +// +kubebuilder:rbac:groups=ec2.services.k8s.aws,resources=flowlogs,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=ec2.services.k8s.aws,resources=flowlogs/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 { + if created != nil { + return rm.onError(created, err) + } + return rm.onError(r, err) + } + return rm.onSuccess(created) +} + +// Update attempts to mutate the supplied desired AWSResource in the backend AWS +// service API, returning an AWSResource representing the newly-mutated +// resource. +// Note for specialized logic implementers can check to see how the latest +// observed resource differs from the supplied desired state. The +// higher-level reonciler determines whether or not the desired differs +// from the latest observed and decides whether to call the resource +// manager's Update method +func (rm *resourceManager) Update( + ctx context.Context, + resDesired acktypes.AWSResource, + resLatest acktypes.AWSResource, + delta *ackcompare.Delta, +) (acktypes.AWSResource, error) { + desired := rm.concreteResource(resDesired) + latest := rm.concreteResource(resLatest) + if desired.ko == nil || latest.ko == nil { + // Should never happen... if it does, it's buggy code. + panic("resource manager's Update() method received resource with nil CR object") + } + updated, err := rm.sdkUpdate(ctx, desired, latest, delta) + if err != nil { + if updated != nil { + return rm.onError(updated, err) + } + return rm.onError(latest, err) + } + return rm.onSuccess(updated) +} + +// Delete attempts to destroy the supplied AWSResource in the backend AWS +// service API, returning an AWSResource representing the +// resource being deleted (if delete is asynchronous and takes time) +func (rm *resourceManager) Delete( + ctx context.Context, + res acktypes.AWSResource, +) (acktypes.AWSResource, error) { + r := rm.concreteResource(res) + if r.ko == nil { + // Should never happen... if it does, it's buggy code. + panic("resource manager's Update() method received resource with nil CR object") + } + observed, err := rm.sdkDelete(ctx, r) + if err != nil { + if observed != nil { + return rm.onError(observed, err) + } + return rm.onError(r, err) + } + + return rm.onSuccess(observed) +} + +// ARNFromName returns an AWS Resource Name from a given string name. This +// is useful for constructing ARNs for APIs that require ARNs in their +// GetAttributes operations but all we have (for new CRs at least) is a +// name for the resource +func (rm *resourceManager) ARNFromName(name string) string { + return fmt.Sprintf( + "arn:aws:ec2:%s:%s:%s", + rm.awsRegion, + rm.awsAccountID, + name, + ) +} + +// LateInitialize returns an acktypes.AWSResource after setting the late initialized +// fields from the readOne call. This method will initialize the optional fields +// which were not provided by the k8s user but were defaulted by the AWS service. +// If there are no such fields to be initialized, the returned object is similar to +// object passed in the parameter. +func (rm *resourceManager) LateInitialize( + ctx context.Context, + latest acktypes.AWSResource, +) (acktypes.AWSResource, error) { + rlog := ackrtlog.FromContext(ctx) + // If there are no fields to late initialize, do nothing + if len(lateInitializeFieldNames) == 0 { + rlog.Debug("no late initialization required.") + return latest, nil + } + latestCopy := latest.DeepCopy() + lateInitConditionReason := "" + lateInitConditionMessage := "" + observed, err := rm.ReadOne(ctx, latestCopy) + if err != nil { + lateInitConditionMessage = "Unable to complete Read operation required for late initialization" + lateInitConditionReason = "Late Initialization Failure" + ackcondition.SetLateInitialized(latestCopy, corev1.ConditionFalse, &lateInitConditionMessage, &lateInitConditionReason) + ackcondition.SetSynced(latestCopy, corev1.ConditionFalse, nil, nil) + return latestCopy, err + } + lateInitializedRes := rm.lateInitializeFromReadOneOutput(observed, latestCopy) + incompleteInitialization := rm.incompleteLateInitialization(lateInitializedRes) + if incompleteInitialization { + // Add the condition with LateInitialized=False + lateInitConditionMessage = "Late initialization did not complete, requeuing with delay of 5 seconds" + lateInitConditionReason = "Delayed Late Initialization" + ackcondition.SetLateInitialized(lateInitializedRes, corev1.ConditionFalse, &lateInitConditionMessage, &lateInitConditionReason) + ackcondition.SetSynced(lateInitializedRes, corev1.ConditionFalse, nil, nil) + return lateInitializedRes, ackrequeue.NeededAfter(nil, time.Duration(5)*time.Second) + } + // Set LateInitialized condition to True + lateInitConditionMessage = "Late initialization successful" + lateInitConditionReason = "Late initialization successful" + ackcondition.SetLateInitialized(lateInitializedRes, corev1.ConditionTrue, &lateInitConditionMessage, &lateInitConditionReason) + return lateInitializedRes, nil +} + +// incompleteLateInitialization return true if there are fields which were supposed to be +// late initialized but are not. If all the fields are late initialized, false is returned +func (rm *resourceManager) incompleteLateInitialization( + res acktypes.AWSResource, +) bool { + return false +} + +// lateInitializeFromReadOneOutput late initializes the 'latest' resource from the 'observed' +// resource and returns 'latest' resource +func (rm *resourceManager) lateInitializeFromReadOneOutput( + observed acktypes.AWSResource, + latest acktypes.AWSResource, +) acktypes.AWSResource { + return latest +} + +// IsSynced returns true if the resource is synced. +func (rm *resourceManager) IsSynced(ctx context.Context, res acktypes.AWSResource) (bool, error) { + r := rm.concreteResource(res) + if r.ko == nil { + // Should never happen... if it does, it's buggy code. + panic("resource manager's IsSynced() method received resource with nil CR object") + } + + return true, nil +} + +// EnsureTags ensures that tags are present inside the AWSResource. +// If the AWSResource does not have any existing resource tags, the 'tags' +// field is initialized and the controller tags are added. +// If the AWSResource has existing resource tags, then controller tags are +// added to the existing resource tags without overriding them. +// If the AWSResource does not support tags, only then the controller tags +// will not be added to the AWSResource. +func (rm *resourceManager) EnsureTags( + ctx context.Context, + res acktypes.AWSResource, + md acktypes.ServiceControllerMetadata, +) error { + r := rm.concreteResource(res) + if r.ko == nil { + // Should never happen... if it does, it's buggy code. + panic("resource manager's EnsureTags method received resource with nil CR object") + } + defaultTags := ackrt.GetDefaultTags(&rm.cfg, r.ko, md) + var existingTags []*svcapitypes.Tag + existingTags = r.ko.Spec.Tags + resourceTags := ToACKTags(existingTags) + tags := acktags.Merge(resourceTags, defaultTags) + r.ko.Spec.Tags = FromACKTags(tags) + return nil +} + +// newResourceManager returns a new struct implementing +// acktypes.AWSResourceManager +func newResourceManager( + cfg ackcfg.Config, + log logr.Logger, + metrics *ackmetrics.Metrics, + rr acktypes.Reconciler, + sess *session.Session, + id ackv1alpha1.AWSAccountID, + region ackv1alpha1.AWSRegion, +) (*resourceManager, error) { + return &resourceManager{ + cfg: cfg, + log: log, + metrics: metrics, + rr: rr, + awsAccountID: id, + awsRegion: region, + sess: sess, + sdkapi: svcsdk.New(sess), + }, nil +} + +// onError updates resource conditions and returns updated resource +// it returns nil if no condition is updated. +func (rm *resourceManager) onError( + r *resource, + err error, +) (acktypes.AWSResource, error) { + if r == nil { + return nil, err + } + r1, updated := rm.updateConditions(r, false, err) + if !updated { + return r, err + } + for _, condition := range r1.Conditions() { + if condition.Type == ackv1alpha1.ConditionTypeTerminal && + condition.Status == corev1.ConditionTrue { + // resource is in Terminal condition + // return Terminal error + return r1, ackerr.Terminal + } + } + return r1, err +} + +// onSuccess updates resource conditions and returns updated resource +// it returns the supplied resource if no condition is updated. +func (rm *resourceManager) onSuccess( + r *resource, +) (acktypes.AWSResource, error) { + if r == nil { + return nil, nil + } + r1, updated := rm.updateConditions(r, true, nil) + if !updated { + return r, nil + } + return r1, nil +} diff --git a/pkg/resource/flow_log/manager_factory.go b/pkg/resource/flow_log/manager_factory.go new file mode 100644 index 00000000..2d52a0c9 --- /dev/null +++ b/pkg/resource/flow_log/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 flow_log + +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/flow_log/references.go b/pkg/resource/flow_log/references.go new file mode 100644 index 00000000..cf658ef6 --- /dev/null +++ b/pkg/resource/flow_log/references.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 flow_log + +import ( + "context" + "sigs.k8s.io/controller-runtime/pkg/client" + + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + + svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" +) + +// ClearResolvedReferences removes any reference values that were made +// concrete in the spec. It returns a copy of the input AWSResource which +// contains the original *Ref values, but none of their respective concrete +// values. +func (rm *resourceManager) ClearResolvedReferences(res acktypes.AWSResource) acktypes.AWSResource { + ko := rm.concreteResource(res).ko.DeepCopy() + + return &resource{ko} +} + +// ResolveReferences finds if there are any Reference field(s) present +// inside AWSResource passed in the parameter and attempts to resolve those +// reference field(s) into their respective target field(s). It returns a +// copy of the input AWSResource with resolved reference(s), a boolean which +// is set to true if the resource contains any references (regardless of if +// they are resolved successfully) and an error if the passed AWSResource's +// reference field(s) could not be resolved. +func (rm *resourceManager) ResolveReferences( + ctx context.Context, + apiReader client.Reader, + res acktypes.AWSResource, +) (acktypes.AWSResource, bool, error) { + return res, false, nil +} + +// validateReferenceFields validates the reference field and corresponding +// identifier field. +func validateReferenceFields(ko *svcapitypes.FlowLog) error { + return nil +} diff --git a/pkg/resource/flow_log/resource.go b/pkg/resource/flow_log/resource.go new file mode 100644 index 00000000..990204aa --- /dev/null +++ b/pkg/resource/flow_log/resource.go @@ -0,0 +1,100 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package flow_log + +import ( + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackerrors "github.com/aws-controllers-k8s/runtime/pkg/errors" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + rtclient "sigs.k8s.io/controller-runtime/pkg/client" + + svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" +) + +// Hack to avoid import errors during build... +var ( + _ = &ackerrors.MissingNameIdentifier +) + +// resource implements the `aws-controller-k8s/runtime/pkg/types.AWSResource` +// interface +type resource struct { + // The Kubernetes-native CR representing the resource + ko *svcapitypes.FlowLog +} + +// Identifiers returns an AWSResourceIdentifiers object containing various +// identifying information, including the AWS account ID that owns the +// resource, the resource's AWS Resource Name (ARN) +func (r *resource) Identifiers() acktypes.AWSResourceIdentifiers { + return &resourceIdentifiers{r.ko.Status.ACKResourceMetadata} +} + +// IsBeingDeleted returns true if the Kubernetes resource has a non-zero +// deletion timestamp +func (r *resource) IsBeingDeleted() bool { + return !r.ko.DeletionTimestamp.IsZero() +} + +// RuntimeObject returns the Kubernetes apimachinery/runtime representation of +// the AWSResource +func (r *resource) RuntimeObject() rtclient.Object { + return r.ko +} + +// MetaObject returns the Kubernetes apimachinery/apis/meta/v1.Object +// representation of the AWSResource +func (r *resource) MetaObject() metav1.Object { + return r.ko.GetObjectMeta() +} + +// Conditions returns the ACK Conditions collection for the AWSResource +func (r *resource) Conditions() []*ackv1alpha1.Condition { + return r.ko.Status.Conditions +} + +// ReplaceConditions sets the Conditions status field for the resource +func (r *resource) ReplaceConditions(conditions []*ackv1alpha1.Condition) { + r.ko.Status.Conditions = conditions +} + +// SetObjectMeta sets the ObjectMeta field for the resource +func (r *resource) SetObjectMeta(meta metav1.ObjectMeta) { + r.ko.ObjectMeta = meta +} + +// SetStatus will set the Status field for the resource +func (r *resource) SetStatus(desired acktypes.AWSResource) { + r.ko.Status = desired.(*resource).ko.Status +} + +// SetIdentifiers sets the Spec or Status field that is referenced as the unique +// resource identifier +func (r *resource) SetIdentifiers(identifier *ackv1alpha1.AWSIdentifiers) error { + if identifier.NameOrID == "" { + return ackerrors.MissingNameIdentifier + } + r.ko.Status.FlowLogID = &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/flow_log/sdk.go b/pkg/resource/flow_log/sdk.go new file mode 100644 index 00000000..845c0d9e --- /dev/null +++ b/pkg/resource/flow_log/sdk.go @@ -0,0 +1,479 @@ +// 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 flow_log + +import ( + "context" + "errors" + "fmt" + "reflect" + "strings" + + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + ackcondition "github.com/aws-controllers-k8s/runtime/pkg/condition" + ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" + ackrequeue "github.com/aws-controllers-k8s/runtime/pkg/requeue" + ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" + "github.com/aws/aws-sdk-go/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.FlowLog{} + _ = ackv1alpha1.AWSAccountID("") + _ = &ackerr.NotFound + _ = &ackcondition.NotManagedMessage + _ = &reflect.Value{} + _ = fmt.Sprintf("") + _ = &ackrequeue.NoRequeue{} +) + +// sdkFind returns SDK-specific information about a supplied resource +func (rm *resourceManager) sdkFind( + ctx context.Context, + r *resource, +) (latest *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkFind") + defer func() { + exit(err) + }() + if r.ko.Status.FlowLogID == nil { + return nil, ackerr.NotFound + } + // 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 + } + input.SetFlowLogIds([]*string{r.ko.Status.FlowLogID}) + var resp *svcsdk.DescribeFlowLogsOutput + resp, err = rm.sdkapi.DescribeFlowLogsWithContext(ctx, input) + rm.metrics.RecordAPICall("READ_MANY", "DescribeFlowLogs", 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.FlowLogs { + if elem.DeliverLogsPermissionArn != nil { + ko.Spec.DeliverLogsPermissionARN = elem.DeliverLogsPermissionArn + } else { + ko.Spec.DeliverLogsPermissionARN = nil + } + if elem.DestinationOptions != nil { + f4 := &svcapitypes.DestinationOptionsRequest{} + if elem.DestinationOptions.FileFormat != nil { + f4.FileFormat = elem.DestinationOptions.FileFormat + } + if elem.DestinationOptions.HiveCompatiblePartitions != nil { + f4.HiveCompatiblePartitions = elem.DestinationOptions.HiveCompatiblePartitions + } + if elem.DestinationOptions.PerHourPartition != nil { + f4.PerHourPartition = elem.DestinationOptions.PerHourPartition + } + ko.Spec.DestinationOptions = f4 + } else { + ko.Spec.DestinationOptions = nil + } + if elem.LogDestination != nil { + ko.Spec.LogDestination = elem.LogDestination + } else { + ko.Spec.LogDestination = nil + } + if elem.LogDestinationType != nil { + ko.Spec.LogDestinationType = elem.LogDestinationType + } else { + ko.Spec.LogDestinationType = nil + } + if elem.LogFormat != nil { + ko.Spec.LogFormat = elem.LogFormat + } else { + ko.Spec.LogFormat = nil + } + if elem.LogGroupName != nil { + ko.Spec.LogGroupName = elem.LogGroupName + } else { + ko.Spec.LogGroupName = nil + } + if elem.MaxAggregationInterval != nil { + ko.Spec.MaxAggregationInterval = elem.MaxAggregationInterval + } else { + ko.Spec.MaxAggregationInterval = nil + } + if elem.Tags != nil { + f13 := []*svcapitypes.Tag{} + for _, f13iter := range elem.Tags { + f13elem := &svcapitypes.Tag{} + if f13iter.Key != nil { + f13elem.Key = f13iter.Key + } + if f13iter.Value != nil { + f13elem.Value = f13iter.Value + } + f13 = append(f13, f13elem) + } + ko.Spec.Tags = f13 + } else { + ko.Spec.Tags = nil + } + if elem.TrafficType != nil { + ko.Spec.TrafficType = elem.TrafficType + } else { + ko.Spec.TrafficType = 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 false +} + +// newListRequestPayload returns SDK-specific struct for the HTTP request +// payload of the List API call for the resource +func (rm *resourceManager) newListRequestPayload( + r *resource, +) (*svcsdk.DescribeFlowLogsInput, error) { + res := &svcsdk.DescribeFlowLogsInput{} + + return res, nil +} + +// sdkCreate creates the supplied resource in the backend AWS service API and +// returns a copy of the resource with resource fields (in both Spec and +// Status) filled in with values from the CREATE API operation's Output shape. +func (rm *resourceManager) sdkCreate( + ctx context.Context, + desired *resource, +) (created *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkCreate") + defer func() { + exit(err) + }() + input, err := rm.newCreateRequestPayload(ctx, desired) + if err != nil { + return nil, err + } + updateTagSpecificationsInCreateRequest(desired, input) + input.SetResourceIds([]*string{desired.ko.Spec.ResourceID}) + + var resp *svcsdk.CreateFlowLogsOutput + _ = resp + resp, err = rm.sdkapi.CreateFlowLogsWithContext(ctx, input) + rm.metrics.RecordAPICall("CREATE", "CreateFlowLogs", 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.ClientToken != nil { + ko.Status.ClientToken = resp.ClientToken + } else { + ko.Status.ClientToken = nil + } + if resp.Unsuccessful != nil { + f1 := []*svcapitypes.UnsuccessfulItem{} + for _, f1iter := range resp.Unsuccessful { + f1elem := &svcapitypes.UnsuccessfulItem{} + if f1iter.Error != nil { + f1elemf0 := &svcapitypes.UnsuccessfulItemError{} + if f1iter.Error.Code != nil { + f1elemf0.Code = f1iter.Error.Code + } + if f1iter.Error.Message != nil { + f1elemf0.Message = f1iter.Error.Message + } + f1elem.Error = f1elemf0 + } + if f1iter.ResourceId != nil { + f1elem.ResourceID = f1iter.ResourceId + } + f1 = append(f1, f1elem) + } + ko.Status.Unsuccessful = f1 + } else { + ko.Status.Unsuccessful = nil + } + + rm.setStatusDefaults(ko) + if resp.FlowLogIds[0] != nil { + ko.Status.FlowLogID = resp.FlowLogIds[0] + } + 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.CreateFlowLogsInput, error) { + res := &svcsdk.CreateFlowLogsInput{} + + if r.ko.Spec.DeliverLogsPermissionARN != nil { + res.SetDeliverLogsPermissionArn(*r.ko.Spec.DeliverLogsPermissionARN) + } + if r.ko.Spec.DestinationOptions != nil { + f1 := &svcsdk.DestinationOptionsRequest{} + if r.ko.Spec.DestinationOptions.FileFormat != nil { + f1.SetFileFormat(*r.ko.Spec.DestinationOptions.FileFormat) + } + if r.ko.Spec.DestinationOptions.HiveCompatiblePartitions != nil { + f1.SetHiveCompatiblePartitions(*r.ko.Spec.DestinationOptions.HiveCompatiblePartitions) + } + if r.ko.Spec.DestinationOptions.PerHourPartition != nil { + f1.SetPerHourPartition(*r.ko.Spec.DestinationOptions.PerHourPartition) + } + res.SetDestinationOptions(f1) + } + if r.ko.Spec.LogDestination != nil { + res.SetLogDestination(*r.ko.Spec.LogDestination) + } + if r.ko.Spec.LogDestinationType != nil { + res.SetLogDestinationType(*r.ko.Spec.LogDestinationType) + } + if r.ko.Spec.LogFormat != nil { + res.SetLogFormat(*r.ko.Spec.LogFormat) + } + if r.ko.Spec.LogGroupName != nil { + res.SetLogGroupName(*r.ko.Spec.LogGroupName) + } + if r.ko.Spec.MaxAggregationInterval != nil { + res.SetMaxAggregationInterval(*r.ko.Spec.MaxAggregationInterval) + } + if r.ko.Spec.ResourceType != nil { + res.SetResourceType(*r.ko.Spec.ResourceType) + } + if r.ko.Spec.TrafficType != nil { + res.SetTrafficType(*r.ko.Spec.TrafficType) + } + + return res, nil +} + +// sdkUpdate patches the supplied resource in the backend AWS service API and +// returns a new resource with updated fields. +func (rm *resourceManager) sdkUpdate( + ctx context.Context, + desired *resource, + latest *resource, + delta *ackcompare.Delta, +) (*resource, error) { + return rm.customUpdateFlowLog(ctx, desired, latest, delta) +} + +// sdkDelete deletes the supplied resource in the backend AWS service API +func (rm *resourceManager) sdkDelete( + ctx context.Context, + r *resource, +) (latest *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkDelete") + defer func() { + exit(err) + }() + input, err := rm.newDeleteRequestPayload(r) + if err != nil { + return nil, err + } + if r.ko.Status.FlowLogID == nil { + return nil, ackerr.NotFound + } + input.SetFlowLogIds([]*string{r.ko.Status.FlowLogID}) + var resp *svcsdk.DeleteFlowLogsOutput + _ = resp + resp, err = rm.sdkapi.DeleteFlowLogsWithContext(ctx, input) + rm.metrics.RecordAPICall("DELETE", "DeleteFlowLogs", 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.DeleteFlowLogsInput, error) { + res := &svcsdk.DeleteFlowLogsInput{} + + return res, nil +} + +// setStatusDefaults sets default properties into supplied custom resource +func (rm *resourceManager) setStatusDefaults( + ko *svcapitypes.FlowLog, +) { + if ko.Status.ACKResourceMetadata == nil { + ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{} + } + if ko.Status.ACKResourceMetadata.Region == nil { + ko.Status.ACKResourceMetadata.Region = &rm.awsRegion + } + if ko.Status.ACKResourceMetadata.OwnerAccountID == nil { + ko.Status.ACKResourceMetadata.OwnerAccountID = &rm.awsAccountID + } + if ko.Status.Conditions == nil { + ko.Status.Conditions = []*ackv1alpha1.Condition{} + } +} + +// updateConditions returns updated resource, true; if conditions were updated +// else it returns nil, false +func (rm *resourceManager) updateConditions( + r *resource, + onSuccess bool, + err error, +) (*resource, bool) { + ko := r.ko.DeepCopy() + rm.setStatusDefaults(ko) + + // Terminal condition + var terminalCondition *ackv1alpha1.Condition = nil + var recoverableCondition *ackv1alpha1.Condition = nil + var syncCondition *ackv1alpha1.Condition = nil + for _, condition := range ko.Status.Conditions { + if condition.Type == ackv1alpha1.ConditionTypeTerminal { + terminalCondition = condition + } + if condition.Type == ackv1alpha1.ConditionTypeRecoverable { + recoverableCondition = condition + } + if condition.Type == ackv1alpha1.ConditionTypeResourceSynced { + syncCondition = condition + } + } + var termError *ackerr.TerminalError + if rm.terminalAWSError(err) || err == ackerr.SecretTypeNotSupported || err == ackerr.SecretNotFound || errors.As(err, &termError) { + if terminalCondition == nil { + terminalCondition = &ackv1alpha1.Condition{ + Type: ackv1alpha1.ConditionTypeTerminal, + } + ko.Status.Conditions = append(ko.Status.Conditions, terminalCondition) + } + var errorMessage = "" + if err == ackerr.SecretTypeNotSupported || err == ackerr.SecretNotFound || errors.As(err, &termError) { + errorMessage = err.Error() + } else { + awsErr, _ := ackerr.AWSError(err) + errorMessage = awsErr.Error() + } + terminalCondition.Status = corev1.ConditionTrue + terminalCondition.Message = &errorMessage + } else { + // Clear the terminal condition if no longer present + if terminalCondition != nil { + terminalCondition.Status = corev1.ConditionFalse + terminalCondition.Message = nil + } + // Handling Recoverable Conditions + if err != nil { + if recoverableCondition == nil { + // Add a new Condition containing a non-terminal error + recoverableCondition = &ackv1alpha1.Condition{ + Type: ackv1alpha1.ConditionTypeRecoverable, + } + ko.Status.Conditions = append(ko.Status.Conditions, recoverableCondition) + } + recoverableCondition.Status = corev1.ConditionTrue + awsErr, _ := ackerr.AWSError(err) + errorMessage := err.Error() + if awsErr != nil { + errorMessage = awsErr.Error() + } + recoverableCondition.Message = &errorMessage + } else if recoverableCondition != nil { + recoverableCondition.Status = corev1.ConditionFalse + recoverableCondition.Message = nil + } + } + // Required to avoid the "declared but not used" error in the default case + _ = syncCondition + if terminalCondition != nil || recoverableCondition != nil || syncCondition != nil { + return &resource{ko}, true // updated + } + return nil, false // not updated +} + +// terminalAWSError returns awserr, true; if the supplied error is an aws Error type +// and if the exception indicates that it is a Terminal exception +// 'Terminal' exception are specified in generator configuration +func (rm *resourceManager) terminalAWSError(err error) bool { + if err == nil { + return false + } + awsErr, ok := ackerr.AWSError(err) + if !ok { + return false + } + switch awsErr.Code() { + case "InvalidParameter", + "InvalidParameterValue": + return true + default: + return false + } +} + +func (rm *resourceManager) newTag( + c svcapitypes.Tag, +) *svcsdk.Tag { + res := &svcsdk.Tag{} + if c.Key != nil { + res.SetKey(*c.Key) + } + if c.Value != nil { + res.SetValue(*c.Value) + } + + return res +} diff --git a/pkg/resource/flow_log/tags.go b/pkg/resource/flow_log/tags.go new file mode 100644 index 00000000..11ec1226 --- /dev/null +++ b/pkg/resource/flow_log/tags.go @@ -0,0 +1,63 @@ +// 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 flow_log + +import ( + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" + + svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" +) + +var ( + _ = svcapitypes.FlowLog{} + _ = acktags.NewTags() +) + +// ToACKTags converts the tags parameter into 'acktags.Tags' shape. +// This method helps in creating the hub(acktags.Tags) for merging +// default controller tags with existing resource tags. +func ToACKTags(tags []*svcapitypes.Tag) acktags.Tags { + result := acktags.NewTags() + if tags == nil || len(tags) == 0 { + return result + } + + for _, t := range tags { + if t.Key != nil { + if t.Value == nil { + result[*t.Key] = "" + } else { + result[*t.Key] = *t.Value + } + } + } + + return result +} + +// FromACKTags converts the tags parameter into []*svcapitypes.Tag shape. +// This method helps in setting the tags back inside AWSResource after merging +// default controller tags with existing resource tags. +func FromACKTags(tags acktags.Tags) []*svcapitypes.Tag { + result := []*svcapitypes.Tag{} + for k, v := range tags { + kCopy := k + vCopy := v + tag := svcapitypes.Tag{Key: &kCopy, Value: &vCopy} + result = append(result, &tag) + } + return result +} diff --git a/templates/hooks/flow_log/sdk_create_post_build_request.go.tpl b/templates/hooks/flow_log/sdk_create_post_build_request.go.tpl new file mode 100644 index 00000000..82bfb4e8 --- /dev/null +++ b/templates/hooks/flow_log/sdk_create_post_build_request.go.tpl @@ -0,0 +1,2 @@ + updateTagSpecificationsInCreateRequest(desired, input) + input.SetResourceIds([]*string{desired.ko.Spec.ResourceID}) \ No newline at end of file diff --git a/templates/hooks/flow_log/sdk_create_post_set_output.go.tpl b/templates/hooks/flow_log/sdk_create_post_set_output.go.tpl new file mode 100644 index 00000000..3721735a --- /dev/null +++ b/templates/hooks/flow_log/sdk_create_post_set_output.go.tpl @@ -0,0 +1,3 @@ + if resp.FlowLogIds[0] != nil { + ko.Status.FlowLogID = resp.FlowLogIds[0] + } \ No newline at end of file diff --git a/templates/hooks/flow_log/sdk_delete_post_build_request.go.tpl b/templates/hooks/flow_log/sdk_delete_post_build_request.go.tpl new file mode 100644 index 00000000..1a8b8f46 --- /dev/null +++ b/templates/hooks/flow_log/sdk_delete_post_build_request.go.tpl @@ -0,0 +1,4 @@ + if r.ko.Status.FlowLogID == nil { + return nil, ackerr.NotFound + } + input.SetFlowLogIds([]*string{r.ko.Status.FlowLogID}) \ No newline at end of file diff --git a/templates/hooks/flow_log/sdk_file_end.go.tpl b/templates/hooks/flow_log/sdk_file_end.go.tpl new file mode 100644 index 00000000..c45964d8 --- /dev/null +++ b/templates/hooks/flow_log/sdk_file_end.go.tpl @@ -0,0 +1,23 @@ +{{ $CRD := .CRD }} +{{ $SDKAPI := .SDKAPI }} + +{{/* Generate helper methods for Flow Log */}} +{{- range $specFieldName, $specField := $CRD.Config.Resources.FlowLog.Fields }} +{{- if $specField.From }} +{{- $operationName := $specField.From.Operation }} +{{- $operation := (index $SDKAPI.API.Operations $operationName) -}} +{{- range $flowLogRefName, $flowLogMemberRefs := $operation.InputRef.Shape.MemberRefs -}} +{{- if eq $flowLogRefName "Tags" }} +{{- $flowLogRef := $flowLogMemberRefs.Shape.MemberRef }} +{{- $flowLogRefName = "Tag" }} +func (rm *resourceManager) new{{ $flowLogRefName }}( + c svcapitypes.{{ $flowLogRefName }}, +) *svcsdk.{{ $flowLogRefName }} { + res := &svcsdk.{{ $flowLogRefName }}{} +{{ GoCodeSetSDKForStruct $CRD "" "res" $flowLogRef "" "c" 1 }} + return res +} +{{- end }} +{{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/templates/hooks/flow_log/sdk_read_many_post_build_request.go.tpl b/templates/hooks/flow_log/sdk_read_many_post_build_request.go.tpl new file mode 100644 index 00000000..60828d59 --- /dev/null +++ b/templates/hooks/flow_log/sdk_read_many_post_build_request.go.tpl @@ -0,0 +1 @@ + input.SetFlowLogIds([]*string{r.ko.Status.FlowLogID}) \ No newline at end of file diff --git a/templates/hooks/flow_log/sdk_read_many_pre_build_request.go.tpl b/templates/hooks/flow_log/sdk_read_many_pre_build_request.go.tpl new file mode 100644 index 00000000..0574a1f3 --- /dev/null +++ b/templates/hooks/flow_log/sdk_read_many_pre_build_request.go.tpl @@ -0,0 +1,3 @@ + if r.ko.Status.FlowLogID == nil { + return nil, ackerr.NotFound + } \ No newline at end of file diff --git a/test/e2e/bootstrap_resources.py b/test/e2e/bootstrap_resources.py index 15263c69..7d427544 100644 --- a/test/e2e/bootstrap_resources.py +++ b/test/e2e/bootstrap_resources.py @@ -15,11 +15,13 @@ """ from dataclasses import dataclass from acktest.bootstrapping import Resources +from acktest.bootstrapping.s3 import Bucket from acktest.bootstrapping.vpc import VPC from e2e import bootstrap_directory @dataclass class BootstrapResources(Resources): + FlowLogsBucket: Bucket SharedTestVPC: VPC _bootstrap_resources = None @@ -28,4 +30,4 @@ def get_bootstrap_resources(bootstrap_file_name: str = "bootstrap.pkl") -> Boots global _bootstrap_resources if _bootstrap_resources is None: _bootstrap_resources = BootstrapResources.deserialize(bootstrap_directory, bootstrap_file_name=bootstrap_file_name) - return _bootstrap_resources \ No newline at end of file + return _bootstrap_resources diff --git a/test/e2e/resources/flow_log.yaml b/test/e2e/resources/flow_log.yaml new file mode 100644 index 00000000..661384a3 --- /dev/null +++ b/test/e2e/resources/flow_log.yaml @@ -0,0 +1,13 @@ +apiVersion: ec2.services.k8s.aws/v1alpha1 +kind: FlowLog +metadata: + name: $FLOWLOG_NAME +spec: + resourceID: $RESOURCE_ID + resourceType: $RESOURCE_TYPE + logDestinationType: $LOG_DESTINATION_TYPE + logDestination: $LOG_DESTINATION + trafficType: $TRAFFIC_TYPE + tags: + - key: $TAG_KEY + value: $TAG_VALUE diff --git a/test/e2e/resources/invalid/flow_log_invalid_parameter.yaml b/test/e2e/resources/invalid/flow_log_invalid_parameter.yaml new file mode 100644 index 00000000..35c95c2a --- /dev/null +++ b/test/e2e/resources/invalid/flow_log_invalid_parameter.yaml @@ -0,0 +1,12 @@ +apiVersion: ec2.services.k8s.aws/v1alpha1 +kind: FlowLog +metadata: + name: $FLOWLOG_NAME +spec: + resourceID: $RESOURCE_ID + resourceType: $RESOURCE_TYPE + logDestinationType: $LOG_DESTINATION_TYPE + trafficType: $TRAFFIC_TYPE + tags: + - key: $TAG_KEY + value: $TAG_VALUE diff --git a/test/e2e/service_bootstrap.py b/test/e2e/service_bootstrap.py index f1e96539..ba7faf29 100644 --- a/test/e2e/service_bootstrap.py +++ b/test/e2e/service_bootstrap.py @@ -17,6 +17,7 @@ from acktest.bootstrapping import Resources, BootstrapFailureException from acktest.bootstrapping.vpc import VPC +from acktest.bootstrapping.s3 import Bucket from e2e import bootstrap_directory from e2e.bootstrap_resources import BootstrapResources @@ -24,7 +25,10 @@ def service_bootstrap() -> Resources: logging.getLogger().setLevel(logging.INFO) resources = BootstrapResources( - SharedTestVPC=VPC(name_prefix="e2e-test-vpc", num_public_subnet=1, num_private_subnet=0) + SharedTestVPC=VPC(name_prefix="e2e-test-vpc", num_public_subnet=1, num_private_subnet=0), + FlowLogsBucket=Bucket( + "ack-ec2-controller-flow-log-tests", + ), ) try: @@ -37,4 +41,4 @@ def service_bootstrap() -> Resources: if __name__ == "__main__": config = service_bootstrap() # Write config to current directory by default - config.serialize(bootstrap_directory) \ No newline at end of file + config.serialize(bootstrap_directory) diff --git a/test/e2e/tests/test_flow_logs.py b/test/e2e/tests/test_flow_logs.py new file mode 100644 index 00000000..345ad24f --- /dev/null +++ b/test/e2e/tests/test_flow_logs.py @@ -0,0 +1,150 @@ +# 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 Flow Log API. +""" + +import pytest +import time +import logging + +from acktest import tags +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 = "flowlogs" + +CREATE_WAIT_AFTER_SECONDS = 10 +DELETE_WAIT_AFTER_SECONDS = 10 +MODIFY_WAIT_AFTER_SECONDS = 5 + + +def get_flow_log_ids(ec2_client, flow_log_id: str) -> list: + flow_log_ids = [flow_log_id] + try: + resp = ec2_client.describe_flow_logs( + FlowLogIds=flow_log_ids + ) + except Exception as e: + logging.debug(e) + return None + + flow_log_ids = [] + for flow_log in resp['FlowLogs']: + flow_log_ids.append(flow_log['FlowLogId']) + + if len(flow_log_ids) == 0: + return None + return flow_log_ids + + +def flow_log_exists(ec2_client, flow_log_id: str) -> bool: + return get_flow_log_ids(ec2_client, flow_log_id) is not None + +@pytest.fixture +def simple_flow_log(request): + resource_name = random_suffix_name("flow-log-ack-test", 24) + resource_file = "flow_log" + resources = get_bootstrap_resources() + + + replacements = REPLACEMENT_VALUES.copy() + replacements["FLOWLOG_NAME"] = resource_name + replacements["RESOURCE_ID"] = resources.SharedTestVPC.vpc_id + replacements["RESOURCE_TYPE"] = "VPC" + replacements["LOG_DESTINATION_TYPE"] = "s3" + replacements["LOG_DESTINATION"] = "arn:aws:s3:::" + resources.FlowLogsBucket.name + replacements["TRAFFIC_TYPE"] = "ALL" + replacements["TAG_KEY"] = "Name" + replacements["TAG_VALUE"] = resource_name + + marker = request.node.get_closest_marker("resource_data") + if marker is not None: + data = marker.args[0] + if 'resource_file' in data: + resource_file = data['resource_file'] + if 'resource_type' in data: + replacements["RESOURCE_TYPE"] = data['resource_type'] + + # Load FlowLog CR + resource_data = load_ec2_resource( + resource_file, + additional_replacements=replacements, + ) + logging.debug(resource_data) + + # Create k8s resource + ref = k8s.CustomResourceReference( + CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL, + resource_name, namespace="default", + ) + k8s.create_custom_resource(ref, resource_data) + time.sleep(CREATE_WAIT_AFTER_SECONDS) + + cr = k8s.wait_resource_consumed_by_controller(ref) + assert cr is not None + assert k8s.get_resource_exists(ref) + + yield (ref, cr) + + # Try to delete, if doesn't already exist + try: + _, deleted = k8s.delete_custom_resource(ref, 3, 10) + assert deleted + except: + pass + +@service_marker +@pytest.mark.canary +class TestFlowLogs: + def test_create_delete(self, ec2_client, simple_flow_log): + (ref, cr) = simple_flow_log + resource_id = cr["status"]["flowLogID"] + + # Check Flow Log exists + exists = flow_log_exists(ec2_client, resource_id) + assert exists + + # Delete k8s resource + _, deleted = k8s.delete_custom_resource(ref, 2, 5) + assert deleted is True + + time.sleep(DELETE_WAIT_AFTER_SECONDS) + + # Check Flow Log doesn't exist + exists = flow_log_exists(ec2_client, resource_id) + assert not exists + + @pytest.mark.resource_data({'resource_type': 'InvalidResource'}) + def test_terminal_condition_invalid_parameter_value(self, simple_flow_log): + (ref, cr) = simple_flow_log + + expected_msg = "InvalidParameterValue: " + terminal_condition = k8s.get_resource_condition(ref, "ACK.Terminal") + # Example condition message: + # InvalidParameterValue: 1 validation error detected: Value 'S3' at 'resourceType' + # has an invalid format + assert expected_msg in terminal_condition['message'] + + @pytest.mark.resource_data({'resource_file': 'invalid/flow_log_invalid_parameter'}) + def test_terminal_condition_invalid_parameter(self, simple_flow_log): + (ref, cr) = simple_flow_log + + expected_msg = "InvalidParameter: " + terminal_condition = k8s.get_resource_condition(ref, "ACK.Terminal") + # Example condition message: + # InvalidParameter: LogDestination can't be empty if LogGroupName is not provided. + assert expected_msg in terminal_condition['message']