diff --git a/apis/v1alpha1/ack-generate-metadata.yaml b/apis/v1alpha1/ack-generate-metadata.yaml index 6f703d4..aa658ff 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-02-02T18:41:50Z" - build_hash: b55ae8752ece381c383ffe5b388ed2147c6b30d8 - go_version: go1.19 - version: v0.23.1 -api_directory_checksum: 16009bf6d9bb3a2d293a94bcb3984f9969770edb + build_date: "2023-02-07T14:20:32Z" + build_hash: 92c80ba0af0d3c80ed9d16126957523052fac5f4 + go_version: go1.19.5 + version: v0.23.1-4-g92c80ba +api_directory_checksum: cca146e2e27cc0c4dc130b6abe826ab55fb1aeeb api_version: v1alpha1 aws_sdk_go_version: v1.44.97 generator_config_info: - file_checksum: 3d9871178d681c1e829074d9106b5716399c98a8 + file_checksum: 94d41f45265086ed819689b75926237e8c985ebc original_file_name: generator.yaml last_modification: reason: API generation diff --git a/apis/v1alpha1/event_bus.go b/apis/v1alpha1/event_bus.go index 17ff30f..5e61a73 100644 --- a/apis/v1alpha1/event_bus.go +++ b/apis/v1alpha1/event_bus.go @@ -66,7 +66,7 @@ type EventBusStatus struct { // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="ARN",type=string,priority=0,JSONPath=`.status.ackResourceMetadata.arn` -// +kubebuilder:printcolumn:name="SYNCED",type=string,priority=0,JSONPath=`.status.conditions[?(@.type=="ACK.ResourceSynced")].status` +// +kubebuilder:printcolumn:name="Synced",type="string",priority=0,JSONPath=".status.conditions[?(@.type==\"ACK.ResourceSynced\")].status" // +kubebuilder:printcolumn:name="Age",type="date",priority=0,JSONPath=".metadata.creationTimestamp" // +kubebuilder:resource:shortName=eb;bus type EventBus struct { diff --git a/apis/v1alpha1/generator.yaml b/apis/v1alpha1/generator.yaml index ba07f29..5eabfb4 100644 --- a/apis/v1alpha1/generator.yaml +++ b/apis/v1alpha1/generator.yaml @@ -6,6 +6,12 @@ ignore: - Endpoint # - EventBus - PartnerEventSource +operations: + PutRule: + operation_type: + - Create + - Update + resource_name: Rule resources: EventBus: fields: @@ -22,14 +28,11 @@ resources: - bus print: add_age_column: true - add_synced_column: false + add_synced_column: true additional_columns: - name: ARN json_path: .status.ackResourceMetadata.arn type: string - - name: SYNCED - json_path: .status.conditions[?(@.type=="ACK.ResourceSynced")].status - type: string update_operation: custom_method_name: customUpdate hooks: @@ -44,3 +47,53 @@ resources: # no terminal code for validation errors to prevent dead-locking on delete # example: delete rule and bus - bus throws validation error on delete if it still has rules # making this terminal would leak bus resources in AWS and K8s control planes + Rule: + fields: + EventBusName: + is_immutable: true # seems to not affect EventBusRef + references: + resource: EventBus + path: Spec.Name + Tags: + compare: + is_ignored: true + Name: + is_immutable: true + Targets: + custom_field: + list_of: Target # note: does not add comment nor kube-markers to generated code + compare: + is_ignored: true + hooks: + sdk_read_one_post_set_output: + template_path: hooks/rule/sdk_read_one_post_set_output.go.tpl + sdk_create_pre_build_request: + template_path: hooks/rule/sdk_create_pre_build_request.go.tpl + sdk_create_post_set_output: + template_path: hooks/rule/sdk_create_post_set_output.go.tpl + sdk_update_pre_build_request: + template_path: hooks/rule/sdk_update_pre_build_request.go.tpl + sdk_delete_pre_build_request: + template_path: hooks/rule/sdk_delete_pre_build_request.go.tpl + sdk_file_end: + template_path: hooks/rule/sdk_file_end.go.tpl + delta_pre_compare: + code: customPreCompare(delta, a, b) + shortNames: + - er # event rule + print: + add_age_column: true + add_synced_column: true + additional_columns: + - name: ARN + json_path: .status.ackResourceMetadata.arn + type: string + exceptions: + errors: + 404: + code: ResourceNotFoundException + terminal_codes: + - InvalidEventPatternException + - ManagedRuleException # we don't support force because those rules are not managed by ACK + - ValidationError + - ValidationException diff --git a/apis/v1alpha1/rule.go b/apis/v1alpha1/rule.go new file mode 100644 index 0000000..cef6643 --- /dev/null +++ b/apis/v1alpha1/rule.go @@ -0,0 +1,95 @@ +// 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" +) + +// RuleSpec defines the desired state of Rule. +// +// Contains information about a rule in Amazon EventBridge. +type RuleSpec struct { + + // A description of the rule. + Description *string `json:"description,omitempty"` + // The name or ARN of the event bus to associate with this rule. If you omit + // this, the default event bus is used. + EventBusName *string `json:"eventBusName,omitempty"` + EventBusRef *ackv1alpha1.AWSResourceReferenceWrapper `json:"eventBusRef,omitempty"` + // The event pattern. For more information, see EventBridge event patterns (https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns.html.html) + // in the Amazon EventBridge User Guide. + EventPattern *string `json:"eventPattern,omitempty"` + // The name of the rule that you are creating or updating. + // +kubebuilder:validation:Required + Name *string `json:"name"` + // The Amazon Resource Name (ARN) of the IAM role associated with the rule. + // + // If you're setting an event bus in another account as the target and that + // account granted permission to your account through an organization instead + // of directly by the account ID, you must specify a RoleArn with proper permissions + // in the Target structure, instead of here in this parameter. + RoleARN *string `json:"roleARN,omitempty"` + // The scheduling expression. For example, "cron(0 20 * * ? *)" or "rate(5 minutes)". + ScheduleExpression *string `json:"scheduleExpression,omitempty"` + // Indicates whether the rule is enabled or disabled. + State *string `json:"state,omitempty"` + // The list of key-value pairs to associate with the rule. + Tags []*Tag `json:"tags,omitempty"` + Targets []*Target `json:"targets,omitempty"` +} + +// RuleStatus defines the observed state of Rule +type RuleStatus 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"` +} + +// Rule is the Schema for the Rules API +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="ARN",type=string,priority=0,JSONPath=`.status.ackResourceMetadata.arn` +// +kubebuilder:printcolumn:name="Synced",type="string",priority=0,JSONPath=".status.conditions[?(@.type==\"ACK.ResourceSynced\")].status" +// +kubebuilder:printcolumn:name="Age",type="date",priority=0,JSONPath=".metadata.creationTimestamp" +// +kubebuilder:resource:shortName=er +type Rule struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec RuleSpec `json:"spec,omitempty"` + Status RuleStatus `json:"status,omitempty"` +} + +// RuleList contains a list of Rule +// +kubebuilder:object:root=true +type RuleList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Rule `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Rule{}, &RuleList{}) +} diff --git a/apis/v1alpha1/types.go b/apis/v1alpha1/types.go index 31703d5..9ef58dd 100644 --- a/apis/v1alpha1/types.go +++ b/apis/v1alpha1/types.go @@ -28,10 +28,57 @@ var ( _ = ackv1alpha1.AWSAccountID("") ) +// This structure specifies the VPC subnets and security groups for the task, +// and whether a public IP address is to be used. This structure is relevant +// only for ECS tasks that use the awsvpc network mode. +type AWSVPCConfiguration struct { + AssignPublicIP *string `json:"assignPublicIP,omitempty"` + SecurityGroups []*string `json:"securityGroups,omitempty"` + Subnets []*string `json:"subnets,omitempty"` +} + +// An Archive object that contains details about an archive. +type Archive struct { + EventSourceARN *string `json:"eventSourceARN,omitempty"` +} + +// The array properties for the submitted job, such as the size of the array. +// The array size can be between 2 and 10,000. If you specify array properties +// for a job, it becomes an array job. This parameter is used only if the target +// is an Batch job. +type BatchArrayProperties struct { + Size *int64 `json:"size,omitempty"` +} + // The custom parameters to be used when the target is an Batch job. type BatchParameters struct { - JobDefinition *string `json:"jobDefinition,omitempty"` - JobName *string `json:"jobName,omitempty"` + // The array properties for the submitted job, such as the size of the array. + // The array size can be between 2 and 10,000. If you specify array properties + // for a job, it becomes an array job. This parameter is used only if the target + // is an Batch job. + ArrayProperties *BatchArrayProperties `json:"arrayProperties,omitempty"` + JobDefinition *string `json:"jobDefinition,omitempty"` + JobName *string `json:"jobName,omitempty"` + // The retry strategy to use for failed jobs, if the target is an Batch job. + // If you specify a retry strategy here, it overrides the retry strategy defined + // in the job definition. + RetryStrategy *BatchRetryStrategy `json:"retryStrategy,omitempty"` +} + +// The retry strategy to use for failed jobs, if the target is an Batch job. +// If you specify a retry strategy here, it overrides the retry strategy defined +// in the job definition. +type BatchRetryStrategy struct { + Attempts *int64 `json:"attempts,omitempty"` +} + +// The details of a capacity provider strategy. To learn more, see CapacityProviderStrategyItem +// (https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_CapacityProviderStrategyItem.html) +// in the Amazon ECS API Reference. +type CapacityProviderStrategyItem struct { + Base *int64 `json:"base,omitempty"` + CapacityProvider *string `json:"capacityProvider,omitempty"` + Weight *int64 `json:"weight,omitempty"` } // A JSON string which you can use to limit the event bus permissions you are @@ -51,15 +98,49 @@ type Condition struct { // Additional parameter included in the body. You can include up to 100 additional // body parameters per request. An event payload cannot exceed 64 KB. type ConnectionBodyParameter struct { - Key *string `json:"key,omitempty"` - Value *string `json:"value,omitempty"` + IsValueSecret *bool `json:"isValueSecret,omitempty"` + Key *string `json:"key,omitempty"` + Value *string `json:"value,omitempty"` +} + +// Additional parameter included in the header. You can include up to 100 additional +// header parameters per request. An event payload cannot exceed 64 KB. +type ConnectionHeaderParameter struct { + IsValueSecret *bool `json:"isValueSecret,omitempty"` + Value *string `json:"value,omitempty"` +} + +// Additional query string parameter for the connection. You can include up +// to 100 additional query string parameters per request. Each additional parameter +// counts towards the event payload size, which cannot exceed 64 KB. +type ConnectionQueryStringParameter struct { + IsValueSecret *bool `json:"isValueSecret,omitempty"` + Value *string `json:"value,omitempty"` +} + +// A DeadLetterConfig object that contains information about a dead-letter queue +// configuration. +type DeadLetterConfig struct { + ARN *string `json:"arn,omitempty"` } // The custom parameters to be used when the target is an Amazon ECS task. -type EcsParameters struct { - Group *string `json:"group,omitempty"` - PlatformVersion *string `json:"platformVersion,omitempty"` - Tags []*Tag `json:"tags,omitempty"` +type ECSParameters struct { + CapacityProviderStrategy []*CapacityProviderStrategyItem `json:"capacityProviderStrategy,omitempty"` + EnableECSManagedTags *bool `json:"enableECSManagedTags,omitempty"` + EnableExecuteCommand *bool `json:"enableExecuteCommand,omitempty"` + Group *string `json:"group,omitempty"` + LaunchType *string `json:"launchType,omitempty"` + // This structure specifies the network configuration for an ECS task. + NetworkConfiguration *NetworkConfiguration `json:"networkConfiguration,omitempty"` + PlacementConstraints []*PlacementConstraint `json:"placementConstraints,omitempty"` + PlacementStrategy []*PlacementStrategy `json:"placementStrategy,omitempty"` + PlatformVersion *string `json:"platformVersion,omitempty"` + PropagateTags *string `json:"propagateTags,omitempty"` + ReferenceID *string `json:"referenceID,omitempty"` + Tags []*Tag `json:"tags,omitempty"` + TaskCount *int64 `json:"taskCount,omitempty"` + TaskDefinitionARN *string `json:"taskDefinitionARN,omitempty"` } // An event bus receives events from a source and routes them to rules associated @@ -83,6 +164,39 @@ type EventSource struct { Name *string `json:"name,omitempty"` } +// These are custom parameter to be used when the target is an API Gateway REST +// APIs or EventBridge ApiDestinations. In the latter case, these are merged +// with any InvocationParameters specified on the Connection, with any values +// from the Connection taking precedence. +type HTTPParameters struct { + HeaderParameters map[string]*string `json:"headerParameters,omitempty"` + PathParameterValues []*string `json:"pathParameterValues,omitempty"` + QueryStringParameters map[string]*string `json:"queryStringParameters,omitempty"` +} + +// Contains the parameters needed for you to provide custom input to a target +// based on one or more pieces of data extracted from the event. +type InputTransformer struct { + InputPathsMap map[string]*string `json:"inputPathsMap,omitempty"` + InputTemplate *string `json:"inputTemplate,omitempty"` +} + +// This object enables you to specify a JSON path to extract from the event +// and use as the partition key for the Amazon Kinesis data stream, so that +// you can control the shard to which the event goes. If you do not include +// this parameter, the default is to use the eventId as the partition key. +type KinesisParameters struct { + PartitionKeyPath *string `json:"partitionKeyPath,omitempty"` +} + +// This structure specifies the network configuration for an ECS task. +type NetworkConfiguration struct { + // This structure specifies the VPC subnets and security groups for the task, + // and whether a public IP address is to be used. This structure is relevant + // only for ECS tasks that use the awsvpc network mode. + AWSVPCConfiguration *AWSVPCConfiguration `json:"awsVPCConfiguration,omitempty"` +} + // A partner event source is created by an SaaS partner. If a customer creates // a partner event bus that matches this event source, that Amazon Web Services // account can receive events from the partner's applications or services. @@ -91,6 +205,22 @@ type PartnerEventSource struct { Name *string `json:"name,omitempty"` } +// An object representing a constraint on task placement. To learn more, see +// Task Placement Constraints (https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-placement-constraints.html) +// in the Amazon Elastic Container Service Developer Guide. +type PlacementConstraint struct { + Expression *string `json:"expression,omitempty"` + Type *string `json:"type_,omitempty"` +} + +// The task placement strategy for a task or service. To learn more, see Task +// Placement Strategies (https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-placement-strategies.html) +// in the Amazon Elastic Container Service Service Developer Guide. +type PlacementStrategy struct { + Field *string `json:"field,omitempty"` + Type *string `json:"type_,omitempty"` +} + // Represents an event to be submitted. type PutEventsRequestEntry struct { Detail *string `json:"detail,omitempty"` @@ -105,9 +235,88 @@ type PutPartnerEventsRequestEntry struct { Source *string `json:"source,omitempty"` } +// Represents a target that failed to be added to a rule. +type PutTargetsResultEntry struct { + TargetID *string `json:"targetID,omitempty"` +} + +// These are custom parameters to be used when the target is a Amazon Redshift +// cluster to invoke the Amazon Redshift Data API ExecuteStatement based on +// EventBridge events. +type RedshiftDataParameters struct { + Database *string `json:"database,omitempty"` + DBUser *string `json:"dbUser,omitempty"` + SecretManagerARN *string `json:"secretManagerARN,omitempty"` + SQL *string `json:"sql,omitempty"` + StatementName *string `json:"statementName,omitempty"` + WithEvent *bool `json:"withEvent,omitempty"` +} + +// Represents a target that failed to be removed from a rule. +type RemoveTargetsResultEntry struct { + TargetID *string `json:"targetID,omitempty"` +} + +// A Replay object that contains details about a replay. +type Replay struct { + EventSourceARN *string `json:"eventSourceARN,omitempty"` +} + +// A ReplayDestination object that contains details about a replay. +type ReplayDestination struct { + ARN *string `json:"arn,omitempty"` +} + +// A RetryPolicy object that includes information about the retry policy settings. +type RetryPolicy struct { + MaximumEventAgeInSeconds *int64 `json:"maximumEventAgeInSeconds,omitempty"` + MaximumRetryAttempts *int64 `json:"maximumRetryAttempts,omitempty"` +} + // Contains information about a rule in Amazon EventBridge. -type Rule struct { - EventBusName *string `json:"eventBusName,omitempty"` +type Rule_SDK struct { + ARN *string `json:"arn,omitempty"` + Description *string `json:"description,omitempty"` + EventBusName *string `json:"eventBusName,omitempty"` + EventPattern *string `json:"eventPattern,omitempty"` + ManagedBy *string `json:"managedBy,omitempty"` + Name *string `json:"name,omitempty"` + RoleARN *string `json:"roleARN,omitempty"` + ScheduleExpression *string `json:"scheduleExpression,omitempty"` + State *string `json:"state,omitempty"` +} + +// This parameter contains the criteria (either InstanceIds or a tag) used to +// specify which EC2 instances are to be sent the command. +type RunCommandParameters struct { + RunCommandTargets []*RunCommandTarget `json:"runCommandTargets,omitempty"` +} + +// Information about the EC2 instances that are to be sent the command, specified +// as key-value pairs. Each RunCommandTarget block can include only one key, +// but this key may specify multiple values. +type RunCommandTarget struct { + Key *string `json:"key,omitempty"` + Values []*string `json:"values,omitempty"` +} + +// This structure includes the custom parameter to be used when the target is +// an SQS FIFO queue. +type SQSParameters struct { + MessageGroupID *string `json:"messageGroupID,omitempty"` +} + +// Name/Value pair of a parameter to start execution of a SageMaker Model Building +// Pipeline. +type SageMakerPipelineParameter struct { + Name *string `json:"name,omitempty"` + Value *string `json:"value,omitempty"` +} + +// These are custom parameters to use when the target is a SageMaker Model Building +// Pipeline that starts based on EventBridge events. +type SageMakerPipelineParameters struct { + PipelineParameterList []*SageMakerPipelineParameter `json:"pipelineParameterList,omitempty"` } // A key-value pair associated with an Amazon Web Services resource. In EventBridge, @@ -116,3 +325,56 @@ type Tag struct { Key *string `json:"key,omitempty"` Value *string `json:"value,omitempty"` } + +// Targets are the resources to be invoked when a rule is triggered. For a complete +// list of services and resources that can be set as a target, see PutTargets +// (https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutTargets.html). +// +// If you are setting the event bus of another account as the target, and that +// account granted permission to your account through an organization instead +// of directly by the account ID, then you must specify a RoleArn with proper +// permissions in the Target structure. For more information, see Sending and +// Receiving Events Between Amazon Web Services Accounts (https://docs.aws.amazon.com/eventbridge/latest/userguide/eventbridge-cross-account-event-delivery.html) +// in the Amazon EventBridge User Guide. +type Target struct { + ARN *string `json:"arn,omitempty"` + // The custom parameters to be used when the target is an Batch job. + BatchParameters *BatchParameters `json:"batchParameters,omitempty"` + // A DeadLetterConfig object that contains information about a dead-letter queue + // configuration. + DeadLetterConfig *DeadLetterConfig `json:"deadLetterConfig,omitempty"` + // The custom parameters to be used when the target is an Amazon ECS task. + ECSParameters *ECSParameters `json:"ecsParameters,omitempty"` + // These are custom parameter to be used when the target is an API Gateway REST + // APIs or EventBridge ApiDestinations. In the latter case, these are merged + // with any InvocationParameters specified on the Connection, with any values + // from the Connection taking precedence. + HTTPParameters *HTTPParameters `json:"httpParameters,omitempty"` + ID *string `json:"id,omitempty"` + Input *string `json:"input,omitempty"` + InputPath *string `json:"inputPath,omitempty"` + // Contains the parameters needed for you to provide custom input to a target + // based on one or more pieces of data extracted from the event. + InputTransformer *InputTransformer `json:"inputTransformer,omitempty"` + // This object enables you to specify a JSON path to extract from the event + // and use as the partition key for the Amazon Kinesis data stream, so that + // you can control the shard to which the event goes. If you do not include + // this parameter, the default is to use the eventId as the partition key. + KinesisParameters *KinesisParameters `json:"kinesisParameters,omitempty"` + // These are custom parameters to be used when the target is a Amazon Redshift + // cluster to invoke the Amazon Redshift Data API ExecuteStatement based on + // EventBridge events. + RedshiftDataParameters *RedshiftDataParameters `json:"redshiftDataParameters,omitempty"` + // A RetryPolicy object that includes information about the retry policy settings. + RetryPolicy *RetryPolicy `json:"retryPolicy,omitempty"` + RoleARN *string `json:"roleARN,omitempty"` + // This parameter contains the criteria (either InstanceIds or a tag) used to + // specify which EC2 instances are to be sent the command. + RunCommandParameters *RunCommandParameters `json:"runCommandParameters,omitempty"` + // These are custom parameters to use when the target is a SageMaker Model Building + // Pipeline that starts based on EventBridge events. + SageMakerPipelineParameters *SageMakerPipelineParameters `json:"sageMakerPipelineParameters,omitempty"` + // This structure includes the custom parameter to be used when the target is + // an SQS FIFO queue. + SQSParameters *SQSParameters `json:"sqsParameters,omitempty"` +} diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index 6354e5d..2e4263b 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -25,9 +25,96 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AWSVPCConfiguration) DeepCopyInto(out *AWSVPCConfiguration) { + *out = *in + if in.AssignPublicIP != nil { + in, out := &in.AssignPublicIP, &out.AssignPublicIP + *out = new(string) + **out = **in + } + if in.SecurityGroups != nil { + in, out := &in.SecurityGroups, &out.SecurityGroups + *out = make([]*string, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(string) + **out = **in + } + } + } + if in.Subnets != nil { + in, out := &in.Subnets, &out.Subnets + *out = make([]*string, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(string) + **out = **in + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSVPCConfiguration. +func (in *AWSVPCConfiguration) DeepCopy() *AWSVPCConfiguration { + if in == nil { + return nil + } + out := new(AWSVPCConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Archive) DeepCopyInto(out *Archive) { + *out = *in + if in.EventSourceARN != nil { + in, out := &in.EventSourceARN, &out.EventSourceARN + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Archive. +func (in *Archive) DeepCopy() *Archive { + if in == nil { + return nil + } + out := new(Archive) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BatchArrayProperties) DeepCopyInto(out *BatchArrayProperties) { + *out = *in + if in.Size != nil { + in, out := &in.Size, &out.Size + *out = new(int64) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BatchArrayProperties. +func (in *BatchArrayProperties) DeepCopy() *BatchArrayProperties { + if in == nil { + return nil + } + out := new(BatchArrayProperties) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BatchParameters) DeepCopyInto(out *BatchParameters) { *out = *in + if in.ArrayProperties != nil { + in, out := &in.ArrayProperties, &out.ArrayProperties + *out = new(BatchArrayProperties) + (*in).DeepCopyInto(*out) + } if in.JobDefinition != nil { in, out := &in.JobDefinition, &out.JobDefinition *out = new(string) @@ -38,6 +125,11 @@ func (in *BatchParameters) DeepCopyInto(out *BatchParameters) { *out = new(string) **out = **in } + if in.RetryStrategy != nil { + in, out := &in.RetryStrategy, &out.RetryStrategy + *out = new(BatchRetryStrategy) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BatchParameters. @@ -50,6 +142,56 @@ func (in *BatchParameters) DeepCopy() *BatchParameters { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BatchRetryStrategy) DeepCopyInto(out *BatchRetryStrategy) { + *out = *in + if in.Attempts != nil { + in, out := &in.Attempts, &out.Attempts + *out = new(int64) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BatchRetryStrategy. +func (in *BatchRetryStrategy) DeepCopy() *BatchRetryStrategy { + if in == nil { + return nil + } + out := new(BatchRetryStrategy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CapacityProviderStrategyItem) DeepCopyInto(out *CapacityProviderStrategyItem) { + *out = *in + if in.Base != nil { + in, out := &in.Base, &out.Base + *out = new(int64) + **out = **in + } + if in.CapacityProvider != nil { + in, out := &in.CapacityProvider, &out.CapacityProvider + *out = new(string) + **out = **in + } + if in.Weight != nil { + in, out := &in.Weight, &out.Weight + *out = new(int64) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapacityProviderStrategyItem. +func (in *CapacityProviderStrategyItem) DeepCopy() *CapacityProviderStrategyItem { + if in == nil { + return nil + } + out := new(CapacityProviderStrategyItem) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Condition) DeepCopyInto(out *Condition) { *out = *in @@ -83,6 +225,11 @@ func (in *Condition) DeepCopy() *Condition { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ConnectionBodyParameter) DeepCopyInto(out *ConnectionBodyParameter) { *out = *in + if in.IsValueSecret != nil { + in, out := &in.IsValueSecret, &out.IsValueSecret + *out = new(bool) + **out = **in + } if in.Key != nil { in, out := &in.Key, &out.Key *out = new(string) @@ -106,18 +253,151 @@ func (in *ConnectionBodyParameter) DeepCopy() *ConnectionBodyParameter { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EcsParameters) DeepCopyInto(out *EcsParameters) { +func (in *ConnectionHeaderParameter) DeepCopyInto(out *ConnectionHeaderParameter) { + *out = *in + if in.IsValueSecret != nil { + in, out := &in.IsValueSecret, &out.IsValueSecret + *out = new(bool) + **out = **in + } + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectionHeaderParameter. +func (in *ConnectionHeaderParameter) DeepCopy() *ConnectionHeaderParameter { + if in == nil { + return nil + } + out := new(ConnectionHeaderParameter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConnectionQueryStringParameter) DeepCopyInto(out *ConnectionQueryStringParameter) { + *out = *in + if in.IsValueSecret != nil { + in, out := &in.IsValueSecret, &out.IsValueSecret + *out = new(bool) + **out = **in + } + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectionQueryStringParameter. +func (in *ConnectionQueryStringParameter) DeepCopy() *ConnectionQueryStringParameter { + if in == nil { + return nil + } + out := new(ConnectionQueryStringParameter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeadLetterConfig) DeepCopyInto(out *DeadLetterConfig) { + *out = *in + if in.ARN != nil { + in, out := &in.ARN, &out.ARN + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeadLetterConfig. +func (in *DeadLetterConfig) DeepCopy() *DeadLetterConfig { + if in == nil { + return nil + } + out := new(DeadLetterConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ECSParameters) DeepCopyInto(out *ECSParameters) { *out = *in + if in.CapacityProviderStrategy != nil { + in, out := &in.CapacityProviderStrategy, &out.CapacityProviderStrategy + *out = make([]*CapacityProviderStrategyItem, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(CapacityProviderStrategyItem) + (*in).DeepCopyInto(*out) + } + } + } + if in.EnableECSManagedTags != nil { + in, out := &in.EnableECSManagedTags, &out.EnableECSManagedTags + *out = new(bool) + **out = **in + } + if in.EnableExecuteCommand != nil { + in, out := &in.EnableExecuteCommand, &out.EnableExecuteCommand + *out = new(bool) + **out = **in + } if in.Group != nil { in, out := &in.Group, &out.Group *out = new(string) **out = **in } + if in.LaunchType != nil { + in, out := &in.LaunchType, &out.LaunchType + *out = new(string) + **out = **in + } + if in.NetworkConfiguration != nil { + in, out := &in.NetworkConfiguration, &out.NetworkConfiguration + *out = new(NetworkConfiguration) + (*in).DeepCopyInto(*out) + } + if in.PlacementConstraints != nil { + in, out := &in.PlacementConstraints, &out.PlacementConstraints + *out = make([]*PlacementConstraint, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(PlacementConstraint) + (*in).DeepCopyInto(*out) + } + } + } + if in.PlacementStrategy != nil { + in, out := &in.PlacementStrategy, &out.PlacementStrategy + *out = make([]*PlacementStrategy, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(PlacementStrategy) + (*in).DeepCopyInto(*out) + } + } + } if in.PlatformVersion != nil { in, out := &in.PlatformVersion, &out.PlatformVersion *out = new(string) **out = **in } + if in.PropagateTags != nil { + in, out := &in.PropagateTags, &out.PropagateTags + *out = new(string) + **out = **in + } + if in.ReferenceID != nil { + in, out := &in.ReferenceID, &out.ReferenceID + *out = new(string) + **out = **in + } if in.Tags != nil { in, out := &in.Tags, &out.Tags *out = make([]*Tag, len(*in)) @@ -129,14 +409,24 @@ func (in *EcsParameters) DeepCopyInto(out *EcsParameters) { } } } + if in.TaskCount != nil { + in, out := &in.TaskCount, &out.TaskCount + *out = new(int64) + **out = **in + } + if in.TaskDefinitionARN != nil { + in, out := &in.TaskDefinitionARN, &out.TaskDefinitionARN + *out = new(string) + **out = **in + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EcsParameters. -func (in *EcsParameters) DeepCopy() *EcsParameters { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ECSParameters. +func (in *ECSParameters) DeepCopy() *ECSParameters { if in == nil { return nil } - out := new(EcsParameters) + out := new(ECSParameters) in.DeepCopyInto(out) return out } @@ -328,121 +618,787 @@ func (in *EventSource) DeepCopy() *EventSource { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PartnerEventSource) DeepCopyInto(out *PartnerEventSource) { +func (in *HTTPParameters) DeepCopyInto(out *HTTPParameters) { *out = *in - if in.ARN != nil { - in, out := &in.ARN, &out.ARN - *out = new(string) - **out = **in + if in.HeaderParameters != nil { + in, out := &in.HeaderParameters, &out.HeaderParameters + *out = make(map[string]*string, len(*in)) + for key, val := range *in { + var outVal *string + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = new(string) + **out = **in + } + (*out)[key] = outVal + } } - if in.Name != nil { - in, out := &in.Name, &out.Name - *out = new(string) - **out = **in + if in.PathParameterValues != nil { + in, out := &in.PathParameterValues, &out.PathParameterValues + *out = make([]*string, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(string) + **out = **in + } + } + } + if in.QueryStringParameters != nil { + in, out := &in.QueryStringParameters, &out.QueryStringParameters + *out = make(map[string]*string, len(*in)) + for key, val := range *in { + var outVal *string + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = new(string) + **out = **in + } + (*out)[key] = outVal + } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PartnerEventSource. -func (in *PartnerEventSource) DeepCopy() *PartnerEventSource { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPParameters. +func (in *HTTPParameters) DeepCopy() *HTTPParameters { if in == nil { return nil } - out := new(PartnerEventSource) + out := new(HTTPParameters) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PutEventsRequestEntry) DeepCopyInto(out *PutEventsRequestEntry) { +func (in *InputTransformer) DeepCopyInto(out *InputTransformer) { *out = *in - if in.Detail != nil { - in, out := &in.Detail, &out.Detail - *out = new(string) - **out = **in - } - if in.DetailType != nil { - in, out := &in.DetailType, &out.DetailType - *out = new(string) - **out = **in + if in.InputPathsMap != nil { + in, out := &in.InputPathsMap, &out.InputPathsMap + *out = make(map[string]*string, len(*in)) + for key, val := range *in { + var outVal *string + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = new(string) + **out = **in + } + (*out)[key] = outVal + } } - if in.Source != nil { - in, out := &in.Source, &out.Source + if in.InputTemplate != nil { + in, out := &in.InputTemplate, &out.InputTemplate *out = new(string) **out = **in } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PutEventsRequestEntry. -func (in *PutEventsRequestEntry) DeepCopy() *PutEventsRequestEntry { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InputTransformer. +func (in *InputTransformer) DeepCopy() *InputTransformer { if in == nil { return nil } - out := new(PutEventsRequestEntry) + out := new(InputTransformer) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PutPartnerEventsRequestEntry) DeepCopyInto(out *PutPartnerEventsRequestEntry) { +func (in *KinesisParameters) DeepCopyInto(out *KinesisParameters) { *out = *in - if in.Detail != nil { - in, out := &in.Detail, &out.Detail - *out = new(string) - **out = **in - } - if in.DetailType != nil { - in, out := &in.DetailType, &out.DetailType - *out = new(string) - **out = **in - } - if in.Source != nil { - in, out := &in.Source, &out.Source + if in.PartitionKeyPath != nil { + in, out := &in.PartitionKeyPath, &out.PartitionKeyPath *out = new(string) **out = **in } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PutPartnerEventsRequestEntry. -func (in *PutPartnerEventsRequestEntry) DeepCopy() *PutPartnerEventsRequestEntry { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KinesisParameters. +func (in *KinesisParameters) DeepCopy() *KinesisParameters { if in == nil { return nil } - out := new(PutPartnerEventsRequestEntry) + out := new(KinesisParameters) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Rule) DeepCopyInto(out *Rule) { +func (in *NetworkConfiguration) DeepCopyInto(out *NetworkConfiguration) { *out = *in - if in.EventBusName != nil { - in, out := &in.EventBusName, &out.EventBusName - *out = new(string) - **out = **in + if in.AWSVPCConfiguration != nil { + in, out := &in.AWSVPCConfiguration, &out.AWSVPCConfiguration + *out = new(AWSVPCConfiguration) + (*in).DeepCopyInto(*out) } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rule. -func (in *Rule) DeepCopy() *Rule { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkConfiguration. +func (in *NetworkConfiguration) DeepCopy() *NetworkConfiguration { if in == nil { return nil } - out := new(Rule) + out := new(NetworkConfiguration) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Tag) DeepCopyInto(out *Tag) { +func (in *PartnerEventSource) DeepCopyInto(out *PartnerEventSource) { *out = *in - if in.Key != nil { - in, out := &in.Key, &out.Key + if in.ARN != nil { + in, out := &in.ARN, &out.ARN *out = new(string) **out = **in } - if in.Value != nil { - in, out := &in.Value, &out.Value - *out = new(string) + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PartnerEventSource. +func (in *PartnerEventSource) DeepCopy() *PartnerEventSource { + if in == nil { + return nil + } + out := new(PartnerEventSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlacementConstraint) DeepCopyInto(out *PlacementConstraint) { + *out = *in + if in.Expression != nil { + in, out := &in.Expression, &out.Expression + *out = new(string) + **out = **in + } + if in.Type != nil { + in, out := &in.Type, &out.Type + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementConstraint. +func (in *PlacementConstraint) DeepCopy() *PlacementConstraint { + if in == nil { + return nil + } + out := new(PlacementConstraint) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlacementStrategy) DeepCopyInto(out *PlacementStrategy) { + *out = *in + if in.Field != nil { + in, out := &in.Field, &out.Field + *out = new(string) + **out = **in + } + if in.Type != nil { + in, out := &in.Type, &out.Type + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementStrategy. +func (in *PlacementStrategy) DeepCopy() *PlacementStrategy { + if in == nil { + return nil + } + out := new(PlacementStrategy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PutEventsRequestEntry) DeepCopyInto(out *PutEventsRequestEntry) { + *out = *in + if in.Detail != nil { + in, out := &in.Detail, &out.Detail + *out = new(string) + **out = **in + } + if in.DetailType != nil { + in, out := &in.DetailType, &out.DetailType + *out = new(string) + **out = **in + } + if in.Source != nil { + in, out := &in.Source, &out.Source + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PutEventsRequestEntry. +func (in *PutEventsRequestEntry) DeepCopy() *PutEventsRequestEntry { + if in == nil { + return nil + } + out := new(PutEventsRequestEntry) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PutPartnerEventsRequestEntry) DeepCopyInto(out *PutPartnerEventsRequestEntry) { + *out = *in + if in.Detail != nil { + in, out := &in.Detail, &out.Detail + *out = new(string) + **out = **in + } + if in.DetailType != nil { + in, out := &in.DetailType, &out.DetailType + *out = new(string) + **out = **in + } + if in.Source != nil { + in, out := &in.Source, &out.Source + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PutPartnerEventsRequestEntry. +func (in *PutPartnerEventsRequestEntry) DeepCopy() *PutPartnerEventsRequestEntry { + if in == nil { + return nil + } + out := new(PutPartnerEventsRequestEntry) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PutTargetsResultEntry) DeepCopyInto(out *PutTargetsResultEntry) { + *out = *in + if in.TargetID != nil { + in, out := &in.TargetID, &out.TargetID + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PutTargetsResultEntry. +func (in *PutTargetsResultEntry) DeepCopy() *PutTargetsResultEntry { + if in == nil { + return nil + } + out := new(PutTargetsResultEntry) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RedshiftDataParameters) DeepCopyInto(out *RedshiftDataParameters) { + *out = *in + if in.Database != nil { + in, out := &in.Database, &out.Database + *out = new(string) + **out = **in + } + if in.DBUser != nil { + in, out := &in.DBUser, &out.DBUser + *out = new(string) + **out = **in + } + if in.SecretManagerARN != nil { + in, out := &in.SecretManagerARN, &out.SecretManagerARN + *out = new(string) + **out = **in + } + if in.SQL != nil { + in, out := &in.SQL, &out.SQL + *out = new(string) + **out = **in + } + if in.StatementName != nil { + in, out := &in.StatementName, &out.StatementName + *out = new(string) + **out = **in + } + if in.WithEvent != nil { + in, out := &in.WithEvent, &out.WithEvent + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedshiftDataParameters. +func (in *RedshiftDataParameters) DeepCopy() *RedshiftDataParameters { + if in == nil { + return nil + } + out := new(RedshiftDataParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RemoveTargetsResultEntry) DeepCopyInto(out *RemoveTargetsResultEntry) { + *out = *in + if in.TargetID != nil { + in, out := &in.TargetID, &out.TargetID + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RemoveTargetsResultEntry. +func (in *RemoveTargetsResultEntry) DeepCopy() *RemoveTargetsResultEntry { + if in == nil { + return nil + } + out := new(RemoveTargetsResultEntry) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Replay) DeepCopyInto(out *Replay) { + *out = *in + if in.EventSourceARN != nil { + in, out := &in.EventSourceARN, &out.EventSourceARN + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Replay. +func (in *Replay) DeepCopy() *Replay { + if in == nil { + return nil + } + out := new(Replay) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplayDestination) DeepCopyInto(out *ReplayDestination) { + *out = *in + if in.ARN != nil { + in, out := &in.ARN, &out.ARN + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplayDestination. +func (in *ReplayDestination) DeepCopy() *ReplayDestination { + if in == nil { + return nil + } + out := new(ReplayDestination) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RetryPolicy) DeepCopyInto(out *RetryPolicy) { + *out = *in + if in.MaximumEventAgeInSeconds != nil { + in, out := &in.MaximumEventAgeInSeconds, &out.MaximumEventAgeInSeconds + *out = new(int64) + **out = **in + } + if in.MaximumRetryAttempts != nil { + in, out := &in.MaximumRetryAttempts, &out.MaximumRetryAttempts + *out = new(int64) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RetryPolicy. +func (in *RetryPolicy) DeepCopy() *RetryPolicy { + if in == nil { + return nil + } + out := new(RetryPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Rule) DeepCopyInto(out *Rule) { + *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 Rule. +func (in *Rule) DeepCopy() *Rule { + if in == nil { + return nil + } + out := new(Rule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Rule) 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 *RuleList) DeepCopyInto(out *RuleList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Rule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuleList. +func (in *RuleList) DeepCopy() *RuleList { + if in == nil { + return nil + } + out := new(RuleList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RuleList) 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 *RuleSpec) DeepCopyInto(out *RuleSpec) { + *out = *in + if in.Description != nil { + in, out := &in.Description, &out.Description + *out = new(string) + **out = **in + } + if in.EventBusName != nil { + in, out := &in.EventBusName, &out.EventBusName + *out = new(string) + **out = **in + } + if in.EventBusRef != nil { + in, out := &in.EventBusRef, &out.EventBusRef + *out = new(corev1alpha1.AWSResourceReferenceWrapper) + (*in).DeepCopyInto(*out) + } + if in.EventPattern != nil { + in, out := &in.EventPattern, &out.EventPattern + *out = new(string) + **out = **in + } + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + if in.RoleARN != nil { + in, out := &in.RoleARN, &out.RoleARN + *out = new(string) + **out = **in + } + if in.ScheduleExpression != nil { + in, out := &in.ScheduleExpression, &out.ScheduleExpression + *out = new(string) + **out = **in + } + if in.State != nil { + in, out := &in.State, &out.State + *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.Targets != nil { + in, out := &in.Targets, &out.Targets + *out = make([]*Target, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Target) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuleSpec. +func (in *RuleSpec) DeepCopy() *RuleSpec { + if in == nil { + return nil + } + out := new(RuleSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RuleStatus) DeepCopyInto(out *RuleStatus) { + *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) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuleStatus. +func (in *RuleStatus) DeepCopy() *RuleStatus { + if in == nil { + return nil + } + out := new(RuleStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Rule_SDK) DeepCopyInto(out *Rule_SDK) { + *out = *in + if in.ARN != nil { + in, out := &in.ARN, &out.ARN + *out = new(string) + **out = **in + } + if in.Description != nil { + in, out := &in.Description, &out.Description + *out = new(string) + **out = **in + } + if in.EventBusName != nil { + in, out := &in.EventBusName, &out.EventBusName + *out = new(string) + **out = **in + } + if in.EventPattern != nil { + in, out := &in.EventPattern, &out.EventPattern + *out = new(string) + **out = **in + } + if in.ManagedBy != nil { + in, out := &in.ManagedBy, &out.ManagedBy + *out = new(string) + **out = **in + } + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + if in.RoleARN != nil { + in, out := &in.RoleARN, &out.RoleARN + *out = new(string) + **out = **in + } + if in.ScheduleExpression != nil { + in, out := &in.ScheduleExpression, &out.ScheduleExpression + *out = new(string) + **out = **in + } + if in.State != nil { + in, out := &in.State, &out.State + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rule_SDK. +func (in *Rule_SDK) DeepCopy() *Rule_SDK { + if in == nil { + return nil + } + out := new(Rule_SDK) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RunCommandParameters) DeepCopyInto(out *RunCommandParameters) { + *out = *in + if in.RunCommandTargets != nil { + in, out := &in.RunCommandTargets, &out.RunCommandTargets + *out = make([]*RunCommandTarget, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(RunCommandTarget) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RunCommandParameters. +func (in *RunCommandParameters) DeepCopy() *RunCommandParameters { + if in == nil { + return nil + } + out := new(RunCommandParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RunCommandTarget) DeepCopyInto(out *RunCommandTarget) { + *out = *in + if in.Key != nil { + in, out := &in.Key, &out.Key + *out = new(string) + **out = **in + } + if in.Values != nil { + in, out := &in.Values, &out.Values + *out = make([]*string, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(string) + **out = **in + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RunCommandTarget. +func (in *RunCommandTarget) DeepCopy() *RunCommandTarget { + if in == nil { + return nil + } + out := new(RunCommandTarget) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SQSParameters) DeepCopyInto(out *SQSParameters) { + *out = *in + if in.MessageGroupID != nil { + in, out := &in.MessageGroupID, &out.MessageGroupID + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SQSParameters. +func (in *SQSParameters) DeepCopy() *SQSParameters { + if in == nil { + return nil + } + out := new(SQSParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SageMakerPipelineParameter) DeepCopyInto(out *SageMakerPipelineParameter) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SageMakerPipelineParameter. +func (in *SageMakerPipelineParameter) DeepCopy() *SageMakerPipelineParameter { + if in == nil { + return nil + } + out := new(SageMakerPipelineParameter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SageMakerPipelineParameters) DeepCopyInto(out *SageMakerPipelineParameters) { + *out = *in + if in.PipelineParameterList != nil { + in, out := &in.PipelineParameterList, &out.PipelineParameterList + *out = make([]*SageMakerPipelineParameter, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(SageMakerPipelineParameter) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SageMakerPipelineParameters. +func (in *SageMakerPipelineParameters) DeepCopy() *SageMakerPipelineParameters { + if in == nil { + return nil + } + out := new(SageMakerPipelineParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Tag) DeepCopyInto(out *Tag) { + *out = *in + if in.Key != nil { + in, out := &in.Key, &out.Key + *out = new(string) + **out = **in + } + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = new(string) **out = **in } } @@ -456,3 +1412,98 @@ func (in *Tag) DeepCopy() *Tag { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Target) DeepCopyInto(out *Target) { + *out = *in + if in.ARN != nil { + in, out := &in.ARN, &out.ARN + *out = new(string) + **out = **in + } + if in.BatchParameters != nil { + in, out := &in.BatchParameters, &out.BatchParameters + *out = new(BatchParameters) + (*in).DeepCopyInto(*out) + } + if in.DeadLetterConfig != nil { + in, out := &in.DeadLetterConfig, &out.DeadLetterConfig + *out = new(DeadLetterConfig) + (*in).DeepCopyInto(*out) + } + if in.ECSParameters != nil { + in, out := &in.ECSParameters, &out.ECSParameters + *out = new(ECSParameters) + (*in).DeepCopyInto(*out) + } + if in.HTTPParameters != nil { + in, out := &in.HTTPParameters, &out.HTTPParameters + *out = new(HTTPParameters) + (*in).DeepCopyInto(*out) + } + if in.ID != nil { + in, out := &in.ID, &out.ID + *out = new(string) + **out = **in + } + if in.Input != nil { + in, out := &in.Input, &out.Input + *out = new(string) + **out = **in + } + if in.InputPath != nil { + in, out := &in.InputPath, &out.InputPath + *out = new(string) + **out = **in + } + if in.InputTransformer != nil { + in, out := &in.InputTransformer, &out.InputTransformer + *out = new(InputTransformer) + (*in).DeepCopyInto(*out) + } + if in.KinesisParameters != nil { + in, out := &in.KinesisParameters, &out.KinesisParameters + *out = new(KinesisParameters) + (*in).DeepCopyInto(*out) + } + if in.RedshiftDataParameters != nil { + in, out := &in.RedshiftDataParameters, &out.RedshiftDataParameters + *out = new(RedshiftDataParameters) + (*in).DeepCopyInto(*out) + } + if in.RetryPolicy != nil { + in, out := &in.RetryPolicy, &out.RetryPolicy + *out = new(RetryPolicy) + (*in).DeepCopyInto(*out) + } + if in.RoleARN != nil { + in, out := &in.RoleARN, &out.RoleARN + *out = new(string) + **out = **in + } + if in.RunCommandParameters != nil { + in, out := &in.RunCommandParameters, &out.RunCommandParameters + *out = new(RunCommandParameters) + (*in).DeepCopyInto(*out) + } + if in.SageMakerPipelineParameters != nil { + in, out := &in.SageMakerPipelineParameters, &out.SageMakerPipelineParameters + *out = new(SageMakerPipelineParameters) + (*in).DeepCopyInto(*out) + } + if in.SQSParameters != nil { + in, out := &in.SQSParameters, &out.SQSParameters + *out = new(SQSParameters) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Target. +func (in *Target) DeepCopy() *Target { + if in == nil { + return nil + } + out := new(Target) + in.DeepCopyInto(out) + return out +} diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 504b276..70ba964 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -35,6 +35,7 @@ import ( svcresource "github.com/aws-controllers-k8s/eventbridge-controller/pkg/resource" _ "github.com/aws-controllers-k8s/eventbridge-controller/pkg/resource/event_bus" + _ "github.com/aws-controllers-k8s/eventbridge-controller/pkg/resource/rule" "github.com/aws-controllers-k8s/eventbridge-controller/pkg/version" ) diff --git a/config/crd/bases/eventbridge.services.k8s.aws_eventbuses.yaml b/config/crd/bases/eventbridge.services.k8s.aws_eventbuses.yaml index 33e929b..5e4adf4 100644 --- a/config/crd/bases/eventbridge.services.k8s.aws_eventbuses.yaml +++ b/config/crd/bases/eventbridge.services.k8s.aws_eventbuses.yaml @@ -23,7 +23,7 @@ spec: name: ARN type: string - jsonPath: .status.conditions[?(@.type=="ACK.ResourceSynced")].status - name: SYNCED + name: Synced type: string - jsonPath: .metadata.creationTimestamp name: Age diff --git a/config/crd/bases/eventbridge.services.k8s.aws_rules.yaml b/config/crd/bases/eventbridge.services.k8s.aws_rules.yaml new file mode 100644 index 0000000..aed6a11 --- /dev/null +++ b/config/crd/bases/eventbridge.services.k8s.aws_rules.yaml @@ -0,0 +1,458 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: rules.eventbridge.services.k8s.aws +spec: + group: eventbridge.services.k8s.aws + names: + kind: Rule + listKind: RuleList + plural: rules + shortNames: + - er + singular: rule + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.ackResourceMetadata.arn + name: ARN + type: string + - jsonPath: .status.conditions[?(@.type=="ACK.ResourceSynced")].status + name: Synced + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: Rule is the Schema for the Rules 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: "RuleSpec defines the desired state of Rule. \n Contains + information about a rule in Amazon EventBridge." + properties: + description: + description: A description of the rule. + type: string + eventBusName: + description: The name or ARN of the event bus to associate with this + rule. If you omit this, the default event bus is used. + type: string + eventBusRef: + description: 'AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference type to provide more user friendly syntax + for references using ''from'' field Ex: APIIDRef: from: name: my-api' + properties: + from: + description: AWSResourceReference provides all the values necessary + to reference another k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + type: object + type: object + eventPattern: + description: The event pattern. For more information, see EventBridge + event patterns (https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns.html.html) + in the Amazon EventBridge User Guide. + type: string + name: + description: The name of the rule that you are creating or updating. + type: string + roleARN: + description: "The Amazon Resource Name (ARN) of the IAM role associated + with the rule. \n If you're setting an event bus in another account + as the target and that account granted permission to your account + through an organization instead of directly by the account ID, you + must specify a RoleArn with proper permissions in the Target structure, + instead of here in this parameter." + type: string + scheduleExpression: + description: The scheduling expression. For example, "cron(0 20 * + * ? *)" or "rate(5 minutes)". + type: string + state: + description: Indicates whether the rule is enabled or disabled. + type: string + tags: + description: The list of key-value pairs to associate with the rule. + items: + description: A key-value pair associated with an Amazon Web Services + resource. In EventBridge, rules and event buses support tagging. + properties: + key: + type: string + value: + type: string + type: object + type: array + targets: + items: + description: "Targets are the resources to be invoked when a rule + is triggered. For a complete list of services and resources that + can be set as a target, see PutTargets (https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutTargets.html). + \n If you are setting the event bus of another account as the + target, and that account granted permission to your account through + an organization instead of directly by the account ID, then you + must specify a RoleArn with proper permissions in the Target structure. + For more information, see Sending and Receiving Events Between + Amazon Web Services Accounts (https://docs.aws.amazon.com/eventbridge/latest/userguide/eventbridge-cross-account-event-delivery.html) + in the Amazon EventBridge User Guide." + properties: + arn: + type: string + batchParameters: + description: The custom parameters to be used when the target + is an Batch job. + properties: + arrayProperties: + description: The array properties for the submitted job, + such as the size of the array. The array size can be between + 2 and 10,000. If you specify array properties for a job, + it becomes an array job. This parameter is used only if + the target is an Batch job. + properties: + size: + format: int64 + type: integer + type: object + jobDefinition: + type: string + jobName: + type: string + retryStrategy: + description: The retry strategy to use for failed jobs, + if the target is an Batch job. If you specify a retry + strategy here, it overrides the retry strategy defined + in the job definition. + properties: + attempts: + format: int64 + type: integer + type: object + type: object + deadLetterConfig: + description: A DeadLetterConfig object that contains information + about a dead-letter queue configuration. + properties: + arn: + type: string + type: object + ecsParameters: + description: The custom parameters to be used when the target + is an Amazon ECS task. + properties: + capacityProviderStrategy: + items: + description: The details of a capacity provider strategy. + To learn more, see CapacityProviderStrategyItem (https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_CapacityProviderStrategyItem.html) + in the Amazon ECS API Reference. + properties: + base: + format: int64 + type: integer + capacityProvider: + type: string + weight: + format: int64 + type: integer + type: object + type: array + enableECSManagedTags: + type: boolean + enableExecuteCommand: + type: boolean + group: + type: string + launchType: + type: string + networkConfiguration: + description: This structure specifies the network configuration + for an ECS task. + properties: + awsVPCConfiguration: + description: This structure specifies the VPC subnets + and security groups for the task, and whether a public + IP address is to be used. This structure is relevant + only for ECS tasks that use the awsvpc network mode. + properties: + assignPublicIP: + type: string + securityGroups: + items: + type: string + type: array + subnets: + items: + type: string + type: array + type: object + type: object + placementConstraints: + items: + description: An object representing a constraint on task + placement. To learn more, see Task Placement Constraints + (https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-placement-constraints.html) + in the Amazon Elastic Container Service Developer Guide. + properties: + expression: + type: string + type_: + type: string + type: object + type: array + placementStrategy: + items: + description: The task placement strategy for a task or + service. To learn more, see Task Placement Strategies + (https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-placement-strategies.html) + in the Amazon Elastic Container Service Service Developer + Guide. + properties: + field: + type: string + type_: + type: string + type: object + type: array + platformVersion: + type: string + propagateTags: + type: string + referenceID: + type: string + tags: + items: + description: A key-value pair associated with an Amazon + Web Services resource. In EventBridge, rules and event + buses support tagging. + properties: + key: + type: string + value: + type: string + type: object + type: array + taskCount: + format: int64 + type: integer + taskDefinitionARN: + type: string + type: object + httpParameters: + description: These are custom parameter to be used when the + target is an API Gateway REST APIs or EventBridge ApiDestinations. + In the latter case, these are merged with any InvocationParameters + specified on the Connection, with any values from the Connection + taking precedence. + properties: + headerParameters: + additionalProperties: + type: string + type: object + pathParameterValues: + items: + type: string + type: array + queryStringParameters: + additionalProperties: + type: string + type: object + type: object + id: + type: string + input: + type: string + inputPath: + type: string + inputTransformer: + description: Contains the parameters needed for you to provide + custom input to a target based on one or more pieces of data + extracted from the event. + properties: + inputPathsMap: + additionalProperties: + type: string + type: object + inputTemplate: + type: string + type: object + kinesisParameters: + description: This object enables you to specify a JSON path + to extract from the event and use as the partition key for + the Amazon Kinesis data stream, so that you can control the + shard to which the event goes. If you do not include this + parameter, the default is to use the eventId as the partition + key. + properties: + partitionKeyPath: + type: string + type: object + redshiftDataParameters: + description: These are custom parameters to be used when the + target is a Amazon Redshift cluster to invoke the Amazon Redshift + Data API ExecuteStatement based on EventBridge events. + properties: + database: + type: string + dbUser: + type: string + secretManagerARN: + type: string + sql: + type: string + statementName: + type: string + withEvent: + type: boolean + type: object + retryPolicy: + description: A RetryPolicy object that includes information + about the retry policy settings. + properties: + maximumEventAgeInSeconds: + format: int64 + type: integer + maximumRetryAttempts: + format: int64 + type: integer + type: object + roleARN: + type: string + runCommandParameters: + description: This parameter contains the criteria (either InstanceIds + or a tag) used to specify which EC2 instances are to be sent + the command. + properties: + runCommandTargets: + items: + description: Information about the EC2 instances that + are to be sent the command, specified as key-value pairs. + Each RunCommandTarget block can include only one key, + but this key may specify multiple values. + properties: + key: + type: string + values: + items: + type: string + type: array + type: object + type: array + type: object + sageMakerPipelineParameters: + description: These are custom parameters to use when the target + is a SageMaker Model Building Pipeline that starts based on + EventBridge events. + properties: + pipelineParameterList: + items: + description: Name/Value pair of a parameter to start execution + of a SageMaker Model Building Pipeline. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + sqsParameters: + description: This structure includes the custom parameter to + be used when the target is an SQS FIFO queue. + properties: + messageGroupID: + type: string + type: object + type: object + type: array + required: + - name + type: object + status: + description: RuleStatus defines the observed state of Rule + properties: + ackResourceMetadata: + description: All CRs managed by ACK have a common `Status.ACKResourceMetadata` + member that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: 'ARN is the Amazon Resource Name for the resource. + This is a globally-unique identifier and is set only by the + ACK service controller once the controller has orchestrated + the creation of the resource OR when it has verified that an + "adopted" resource (a resource where the ARN annotation was + set by the Kubernetes user on the CR) exists and matches the + supplied CR''s Spec field values. TODO(vijat@): Find a better + strategy for resources that do not have ARN in CreateOutputResponse + https://github.com/aws/aws-controllers-k8s/issues/270' + type: string + ownerAccountID: + description: OwnerAccountID is the AWS Account ID of the account + that owns the backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: All CRS managed by ACK have a common `Status.Conditions` + member that contains a collection of `ackv1alpha1.Condition` objects + that describe the various terminal states of the CR and its backend + AWS service API resource + items: + description: Condition is the common struct used by all CRDs managed + by ACK service controllers to indicate terminal states of the + CR and its backend AWS service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 4f1fc39..75ae832 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -4,3 +4,4 @@ bases: - common resources: - bases/eventbridge.services.k8s.aws_eventbuses.yaml + - bases/eventbridge.services.k8s.aws_rules.yaml diff --git a/config/rbac/cluster-role-controller.yaml b/config/rbac/cluster-role-controller.yaml index 6c946bb..aee2182 100644 --- a/config/rbac/cluster-role-controller.yaml +++ b/config/rbac/cluster-role-controller.yaml @@ -51,6 +51,26 @@ rules: - get - patch - update +- apiGroups: + - eventbridge.services.k8s.aws + resources: + - rules + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - eventbridge.services.k8s.aws + resources: + - rules/status + verbs: + - get + - patch + - update - apiGroups: - services.k8s.aws resources: diff --git a/config/rbac/role-reader.yaml b/config/rbac/role-reader.yaml index e94a87b..baafe5f 100644 --- a/config/rbac/role-reader.yaml +++ b/config/rbac/role-reader.yaml @@ -10,6 +10,7 @@ rules: - eventbridge.services.k8s.aws resources: - eventbuses + - rules verbs: - get - list diff --git a/config/rbac/role-writer.yaml b/config/rbac/role-writer.yaml index d8345a4..48a4b50 100644 --- a/config/rbac/role-writer.yaml +++ b/config/rbac/role-writer.yaml @@ -10,6 +10,7 @@ rules: - eventbridge.services.k8s.aws resources: - eventbuses + - rules verbs: - create - delete @@ -22,6 +23,7 @@ rules: - eventbridge.services.k8s.aws resources: - eventbuses + - rules verbs: - get - patch diff --git a/generator.yaml b/generator.yaml index ba07f29..5eabfb4 100644 --- a/generator.yaml +++ b/generator.yaml @@ -6,6 +6,12 @@ ignore: - Endpoint # - EventBus - PartnerEventSource +operations: + PutRule: + operation_type: + - Create + - Update + resource_name: Rule resources: EventBus: fields: @@ -22,14 +28,11 @@ resources: - bus print: add_age_column: true - add_synced_column: false + add_synced_column: true additional_columns: - name: ARN json_path: .status.ackResourceMetadata.arn type: string - - name: SYNCED - json_path: .status.conditions[?(@.type=="ACK.ResourceSynced")].status - type: string update_operation: custom_method_name: customUpdate hooks: @@ -44,3 +47,53 @@ resources: # no terminal code for validation errors to prevent dead-locking on delete # example: delete rule and bus - bus throws validation error on delete if it still has rules # making this terminal would leak bus resources in AWS and K8s control planes + Rule: + fields: + EventBusName: + is_immutable: true # seems to not affect EventBusRef + references: + resource: EventBus + path: Spec.Name + Tags: + compare: + is_ignored: true + Name: + is_immutable: true + Targets: + custom_field: + list_of: Target # note: does not add comment nor kube-markers to generated code + compare: + is_ignored: true + hooks: + sdk_read_one_post_set_output: + template_path: hooks/rule/sdk_read_one_post_set_output.go.tpl + sdk_create_pre_build_request: + template_path: hooks/rule/sdk_create_pre_build_request.go.tpl + sdk_create_post_set_output: + template_path: hooks/rule/sdk_create_post_set_output.go.tpl + sdk_update_pre_build_request: + template_path: hooks/rule/sdk_update_pre_build_request.go.tpl + sdk_delete_pre_build_request: + template_path: hooks/rule/sdk_delete_pre_build_request.go.tpl + sdk_file_end: + template_path: hooks/rule/sdk_file_end.go.tpl + delta_pre_compare: + code: customPreCompare(delta, a, b) + shortNames: + - er # event rule + print: + add_age_column: true + add_synced_column: true + additional_columns: + - name: ARN + json_path: .status.ackResourceMetadata.arn + type: string + exceptions: + errors: + 404: + code: ResourceNotFoundException + terminal_codes: + - InvalidEventPatternException + - ManagedRuleException # we don't support force because those rules are not managed by ACK + - ValidationError + - ValidationException diff --git a/helm/crds/eventbridge.services.k8s.aws_eventbuses.yaml b/helm/crds/eventbridge.services.k8s.aws_eventbuses.yaml index 33e929b..5e4adf4 100644 --- a/helm/crds/eventbridge.services.k8s.aws_eventbuses.yaml +++ b/helm/crds/eventbridge.services.k8s.aws_eventbuses.yaml @@ -23,7 +23,7 @@ spec: name: ARN type: string - jsonPath: .status.conditions[?(@.type=="ACK.ResourceSynced")].status - name: SYNCED + name: Synced type: string - jsonPath: .metadata.creationTimestamp name: Age diff --git a/helm/crds/eventbridge.services.k8s.aws_rules.yaml b/helm/crds/eventbridge.services.k8s.aws_rules.yaml new file mode 100644 index 0000000..aed6a11 --- /dev/null +++ b/helm/crds/eventbridge.services.k8s.aws_rules.yaml @@ -0,0 +1,458 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: rules.eventbridge.services.k8s.aws +spec: + group: eventbridge.services.k8s.aws + names: + kind: Rule + listKind: RuleList + plural: rules + shortNames: + - er + singular: rule + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.ackResourceMetadata.arn + name: ARN + type: string + - jsonPath: .status.conditions[?(@.type=="ACK.ResourceSynced")].status + name: Synced + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: Rule is the Schema for the Rules 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: "RuleSpec defines the desired state of Rule. \n Contains + information about a rule in Amazon EventBridge." + properties: + description: + description: A description of the rule. + type: string + eventBusName: + description: The name or ARN of the event bus to associate with this + rule. If you omit this, the default event bus is used. + type: string + eventBusRef: + description: 'AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference type to provide more user friendly syntax + for references using ''from'' field Ex: APIIDRef: from: name: my-api' + properties: + from: + description: AWSResourceReference provides all the values necessary + to reference another k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + type: object + type: object + eventPattern: + description: The event pattern. For more information, see EventBridge + event patterns (https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns.html.html) + in the Amazon EventBridge User Guide. + type: string + name: + description: The name of the rule that you are creating or updating. + type: string + roleARN: + description: "The Amazon Resource Name (ARN) of the IAM role associated + with the rule. \n If you're setting an event bus in another account + as the target and that account granted permission to your account + through an organization instead of directly by the account ID, you + must specify a RoleArn with proper permissions in the Target structure, + instead of here in this parameter." + type: string + scheduleExpression: + description: The scheduling expression. For example, "cron(0 20 * + * ? *)" or "rate(5 minutes)". + type: string + state: + description: Indicates whether the rule is enabled or disabled. + type: string + tags: + description: The list of key-value pairs to associate with the rule. + items: + description: A key-value pair associated with an Amazon Web Services + resource. In EventBridge, rules and event buses support tagging. + properties: + key: + type: string + value: + type: string + type: object + type: array + targets: + items: + description: "Targets are the resources to be invoked when a rule + is triggered. For a complete list of services and resources that + can be set as a target, see PutTargets (https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutTargets.html). + \n If you are setting the event bus of another account as the + target, and that account granted permission to your account through + an organization instead of directly by the account ID, then you + must specify a RoleArn with proper permissions in the Target structure. + For more information, see Sending and Receiving Events Between + Amazon Web Services Accounts (https://docs.aws.amazon.com/eventbridge/latest/userguide/eventbridge-cross-account-event-delivery.html) + in the Amazon EventBridge User Guide." + properties: + arn: + type: string + batchParameters: + description: The custom parameters to be used when the target + is an Batch job. + properties: + arrayProperties: + description: The array properties for the submitted job, + such as the size of the array. The array size can be between + 2 and 10,000. If you specify array properties for a job, + it becomes an array job. This parameter is used only if + the target is an Batch job. + properties: + size: + format: int64 + type: integer + type: object + jobDefinition: + type: string + jobName: + type: string + retryStrategy: + description: The retry strategy to use for failed jobs, + if the target is an Batch job. If you specify a retry + strategy here, it overrides the retry strategy defined + in the job definition. + properties: + attempts: + format: int64 + type: integer + type: object + type: object + deadLetterConfig: + description: A DeadLetterConfig object that contains information + about a dead-letter queue configuration. + properties: + arn: + type: string + type: object + ecsParameters: + description: The custom parameters to be used when the target + is an Amazon ECS task. + properties: + capacityProviderStrategy: + items: + description: The details of a capacity provider strategy. + To learn more, see CapacityProviderStrategyItem (https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_CapacityProviderStrategyItem.html) + in the Amazon ECS API Reference. + properties: + base: + format: int64 + type: integer + capacityProvider: + type: string + weight: + format: int64 + type: integer + type: object + type: array + enableECSManagedTags: + type: boolean + enableExecuteCommand: + type: boolean + group: + type: string + launchType: + type: string + networkConfiguration: + description: This structure specifies the network configuration + for an ECS task. + properties: + awsVPCConfiguration: + description: This structure specifies the VPC subnets + and security groups for the task, and whether a public + IP address is to be used. This structure is relevant + only for ECS tasks that use the awsvpc network mode. + properties: + assignPublicIP: + type: string + securityGroups: + items: + type: string + type: array + subnets: + items: + type: string + type: array + type: object + type: object + placementConstraints: + items: + description: An object representing a constraint on task + placement. To learn more, see Task Placement Constraints + (https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-placement-constraints.html) + in the Amazon Elastic Container Service Developer Guide. + properties: + expression: + type: string + type_: + type: string + type: object + type: array + placementStrategy: + items: + description: The task placement strategy for a task or + service. To learn more, see Task Placement Strategies + (https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-placement-strategies.html) + in the Amazon Elastic Container Service Service Developer + Guide. + properties: + field: + type: string + type_: + type: string + type: object + type: array + platformVersion: + type: string + propagateTags: + type: string + referenceID: + type: string + tags: + items: + description: A key-value pair associated with an Amazon + Web Services resource. In EventBridge, rules and event + buses support tagging. + properties: + key: + type: string + value: + type: string + type: object + type: array + taskCount: + format: int64 + type: integer + taskDefinitionARN: + type: string + type: object + httpParameters: + description: These are custom parameter to be used when the + target is an API Gateway REST APIs or EventBridge ApiDestinations. + In the latter case, these are merged with any InvocationParameters + specified on the Connection, with any values from the Connection + taking precedence. + properties: + headerParameters: + additionalProperties: + type: string + type: object + pathParameterValues: + items: + type: string + type: array + queryStringParameters: + additionalProperties: + type: string + type: object + type: object + id: + type: string + input: + type: string + inputPath: + type: string + inputTransformer: + description: Contains the parameters needed for you to provide + custom input to a target based on one or more pieces of data + extracted from the event. + properties: + inputPathsMap: + additionalProperties: + type: string + type: object + inputTemplate: + type: string + type: object + kinesisParameters: + description: This object enables you to specify a JSON path + to extract from the event and use as the partition key for + the Amazon Kinesis data stream, so that you can control the + shard to which the event goes. If you do not include this + parameter, the default is to use the eventId as the partition + key. + properties: + partitionKeyPath: + type: string + type: object + redshiftDataParameters: + description: These are custom parameters to be used when the + target is a Amazon Redshift cluster to invoke the Amazon Redshift + Data API ExecuteStatement based on EventBridge events. + properties: + database: + type: string + dbUser: + type: string + secretManagerARN: + type: string + sql: + type: string + statementName: + type: string + withEvent: + type: boolean + type: object + retryPolicy: + description: A RetryPolicy object that includes information + about the retry policy settings. + properties: + maximumEventAgeInSeconds: + format: int64 + type: integer + maximumRetryAttempts: + format: int64 + type: integer + type: object + roleARN: + type: string + runCommandParameters: + description: This parameter contains the criteria (either InstanceIds + or a tag) used to specify which EC2 instances are to be sent + the command. + properties: + runCommandTargets: + items: + description: Information about the EC2 instances that + are to be sent the command, specified as key-value pairs. + Each RunCommandTarget block can include only one key, + but this key may specify multiple values. + properties: + key: + type: string + values: + items: + type: string + type: array + type: object + type: array + type: object + sageMakerPipelineParameters: + description: These are custom parameters to use when the target + is a SageMaker Model Building Pipeline that starts based on + EventBridge events. + properties: + pipelineParameterList: + items: + description: Name/Value pair of a parameter to start execution + of a SageMaker Model Building Pipeline. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + sqsParameters: + description: This structure includes the custom parameter to + be used when the target is an SQS FIFO queue. + properties: + messageGroupID: + type: string + type: object + type: object + type: array + required: + - name + type: object + status: + description: RuleStatus defines the observed state of Rule + properties: + ackResourceMetadata: + description: All CRs managed by ACK have a common `Status.ACKResourceMetadata` + member that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: 'ARN is the Amazon Resource Name for the resource. + This is a globally-unique identifier and is set only by the + ACK service controller once the controller has orchestrated + the creation of the resource OR when it has verified that an + "adopted" resource (a resource where the ARN annotation was + set by the Kubernetes user on the CR) exists and matches the + supplied CR''s Spec field values. TODO(vijat@): Find a better + strategy for resources that do not have ARN in CreateOutputResponse + https://github.com/aws/aws-controllers-k8s/issues/270' + type: string + ownerAccountID: + description: OwnerAccountID is the AWS Account ID of the account + that owns the backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: All CRS managed by ACK have a common `Status.Conditions` + member that contains a collection of `ackv1alpha1.Condition` objects + that describe the various terminal states of the CR and its backend + AWS service API resource + items: + description: Condition is the common struct used by all CRDs managed + by ACK service controllers to indicate terminal states of the + CR and its backend AWS service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + 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 e52cf7c..babe31e 100644 --- a/helm/templates/cluster-role-controller.yaml +++ b/helm/templates/cluster-role-controller.yaml @@ -66,6 +66,26 @@ rules: - get - patch - update +- apiGroups: + - eventbridge.services.k8s.aws + resources: + - rules + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - eventbridge.services.k8s.aws + resources: + - rules/status + verbs: + - get + - patch + - update - apiGroups: - services.k8s.aws resources: diff --git a/helm/templates/role-reader.yaml b/helm/templates/role-reader.yaml index 7527ff8..5c878be 100644 --- a/helm/templates/role-reader.yaml +++ b/helm/templates/role-reader.yaml @@ -10,6 +10,7 @@ rules: - eventbridge.services.k8s.aws resources: - eventbuses + - rules verbs: - get - list diff --git a/helm/templates/role-writer.yaml b/helm/templates/role-writer.yaml index 3719084..26fa5c2 100644 --- a/helm/templates/role-writer.yaml +++ b/helm/templates/role-writer.yaml @@ -11,6 +11,8 @@ rules: resources: - eventbuses + - rules + verbs: - create - delete @@ -23,6 +25,7 @@ rules: - eventbridge.services.k8s.aws resources: - eventbuses + - rules verbs: - get - patch diff --git a/pkg/resource/event_bus/hooks.go b/pkg/resource/event_bus/hooks.go index ac6e871..9f76259 100644 --- a/pkg/resource/event_bus/hooks.go +++ b/pkg/resource/event_bus/hooks.go @@ -7,12 +7,12 @@ import ( ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" "github.com/aws-controllers-k8s/runtime/pkg/errors" - "github.com/aws-controllers-k8s/runtime/pkg/util" svcsdk "github.com/aws/aws-sdk-go/service/eventbridge" ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" svcapitypes "github.com/aws-controllers-k8s/eventbridge-controller/apis/v1alpha1" + pkgtags "github.com/aws-controllers-k8s/eventbridge-controller/pkg/tags" ) // setResourceAdditionalFields will set the fields that are not returned by @@ -102,7 +102,7 @@ func (rm *resourceManager) syncTags( exit := rlog.Trace("rm.syncTags") defer func() { exit(err) }() - missing, extra := computeTagsDelta(desired.ko.Spec.Tags, latest.ko.Spec.Tags) + missing, extra := pkgtags.ComputeTagsDelta(desired.ko.Spec.Tags, latest.ko.Spec.Tags) arn := (*string)(latest.ko.Status.ACKResourceMetadata.ARN) if len(extra) > 0 { @@ -135,47 +135,6 @@ func (rm *resourceManager) syncTags( return nil } -// computeTagsDelta compares two Tag arrays and return two different lists -// containing the added and removed tags. -// The removed tags list only contains the Key of tags -func computeTagsDelta( - desired []*svcapitypes.Tag, - latest []*svcapitypes.Tag, -) (missing, extra []*svcapitypes.Tag) { - var visitedIndexes []string -mainLoop: - for _, le := range latest { - visitedIndexes = append(visitedIndexes, *le.Key) - for _, de := range desired { - if equalStrings(le.Key, de.Key) { - if !equalStrings(le.Value, de.Value) { - missing = append(missing, de) - } - continue mainLoop - } - } - extra = append(extra, le) - } - for _, de := range desired { - if !util.InStrings(*de.Key, visitedIndexes) { - missing = append(missing, de) - } - } - return missing, extra -} - -func equalStrings(a, b *string) bool { - if a == nil { - return b == nil || *b == "" - } - - if a != nil && b == nil { - return false - } - - return (*a == "" && b == nil) || *a == *b -} - // sdkTagsFromResourceTags transforms a *svcapitypes.Tag array to a *svcsdk.Tag array. func sdkTagsFromResourceTags(rTags []*svcapitypes.Tag) []*svcsdk.Tag { tags := make([]*svcsdk.Tag, len(rTags)) @@ -208,17 +167,7 @@ func compareTags( delta.Add("Spec.Tags", desired.ko.Spec.Tags, latest.ko.Spec.Tags) return } - if !equalTags(desired.ko.Spec.Tags, latest.ko.Spec.Tags) { + if !pkgtags.EqualTags(desired.ko.Spec.Tags, latest.ko.Spec.Tags) { delta.Add("Spec.Tags", desired.ko.Spec.Tags, latest.ko.Spec.Tags) } } - -// equalTags returns true if two Tag arrays are equal regardless of the order -// of their elements. -func equalTags( - desired []*svcapitypes.Tag, - latest []*svcapitypes.Tag, -) bool { - addedOrUpdated, removed := computeTagsDelta(desired, latest) - return len(addedOrUpdated) == 0 && len(removed) == 0 -} diff --git a/pkg/resource/event_bus/hooks_test.go b/pkg/resource/event_bus/hooks_test.go index 6761076..c4435c1 100644 --- a/pkg/resource/event_bus/hooks_test.go +++ b/pkg/resource/event_bus/hooks_test.go @@ -15,6 +15,7 @@ import ( "gotest.tools/v3/assert" svcapitypes "github.com/aws-controllers-k8s/eventbridge-controller/apis/v1alpha1" + pkgtags "github.com/aws-controllers-k8s/eventbridge-controller/pkg/tags" ) var arn = v1alpha1.AWSResourceName("arn:some:bus") @@ -351,7 +352,7 @@ func Test_computeTagsDelta(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotMissing, gotExtra := computeTagsDelta(tt.args.desired, tt.args.latest) + gotMissing, gotExtra := pkgtags.ComputeTagsDelta(tt.args.desired, tt.args.latest) if !reflect.DeepEqual(gotMissing, tt.wantMissing) { t.Errorf("computeTagsDelta() gotMissing = %v, want %v", gotMissing, tt.wantMissing) } diff --git a/pkg/resource/event_bus/sdk.go b/pkg/resource/event_bus/sdk.go index 05b8976..d1dc022 100644 --- a/pkg/resource/event_bus/sdk.go +++ b/pkg/resource/event_bus/sdk.go @@ -104,6 +104,7 @@ func (rm *resourceManager) sdkFind( if err := rm.setResourceAdditionalFields(ctx, ko); err != nil { return nil, err } + return &resource{ko}, nil } @@ -356,12 +357,12 @@ func (rm *resourceManager) getImmutableFieldChanges( delta *ackcompare.Delta, ) []string { var fields []string - if delta.DifferentAt("Spec.EventBusName") { - fields = append(fields, "EventBusName") - } if delta.DifferentAt("Spec.Name") { fields = append(fields, "Name") } + if delta.DifferentAt("Spec.EventBusName") { + fields = append(fields, "EventBusName") + } return fields } diff --git a/pkg/resource/rule/delta.go b/pkg/resource/rule/delta.go new file mode 100644 index 0000000..2cec2a6 --- /dev/null +++ b/pkg/resource/rule/delta.go @@ -0,0 +1,99 @@ +// 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 rule + +import ( + "bytes" + "reflect" + + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" +) + +// Hack to avoid import errors during build... +var ( + _ = &bytes.Buffer{} + _ = &reflect.Method{} +) + +// newResourceDelta returns a new `ackcompare.Delta` used to compare two +// resources +func newResourceDelta( + a *resource, + b *resource, +) *ackcompare.Delta { + delta := ackcompare.NewDelta() + if (a == nil && b != nil) || + (a != nil && b == nil) { + delta.Add("", a, b) + return delta + } + customPreCompare(delta, a, b) + + if ackcompare.HasNilDifference(a.ko.Spec.Description, b.ko.Spec.Description) { + delta.Add("Spec.Description", a.ko.Spec.Description, b.ko.Spec.Description) + } else if a.ko.Spec.Description != nil && b.ko.Spec.Description != nil { + if *a.ko.Spec.Description != *b.ko.Spec.Description { + delta.Add("Spec.Description", a.ko.Spec.Description, b.ko.Spec.Description) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.EventBusName, b.ko.Spec.EventBusName) { + delta.Add("Spec.EventBusName", a.ko.Spec.EventBusName, b.ko.Spec.EventBusName) + } else if a.ko.Spec.EventBusName != nil && b.ko.Spec.EventBusName != nil { + if *a.ko.Spec.EventBusName != *b.ko.Spec.EventBusName { + delta.Add("Spec.EventBusName", a.ko.Spec.EventBusName, b.ko.Spec.EventBusName) + } + } + if !reflect.DeepEqual(a.ko.Spec.EventBusRef, b.ko.Spec.EventBusRef) { + delta.Add("Spec.EventBusRef", a.ko.Spec.EventBusRef, b.ko.Spec.EventBusRef) + } + if ackcompare.HasNilDifference(a.ko.Spec.EventPattern, b.ko.Spec.EventPattern) { + delta.Add("Spec.EventPattern", a.ko.Spec.EventPattern, b.ko.Spec.EventPattern) + } else if a.ko.Spec.EventPattern != nil && b.ko.Spec.EventPattern != nil { + if *a.ko.Spec.EventPattern != *b.ko.Spec.EventPattern { + delta.Add("Spec.EventPattern", a.ko.Spec.EventPattern, b.ko.Spec.EventPattern) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.Name, b.ko.Spec.Name) { + delta.Add("Spec.Name", a.ko.Spec.Name, b.ko.Spec.Name) + } else if a.ko.Spec.Name != nil && b.ko.Spec.Name != nil { + if *a.ko.Spec.Name != *b.ko.Spec.Name { + delta.Add("Spec.Name", a.ko.Spec.Name, b.ko.Spec.Name) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.RoleARN, b.ko.Spec.RoleARN) { + delta.Add("Spec.RoleARN", a.ko.Spec.RoleARN, b.ko.Spec.RoleARN) + } else if a.ko.Spec.RoleARN != nil && b.ko.Spec.RoleARN != nil { + if *a.ko.Spec.RoleARN != *b.ko.Spec.RoleARN { + delta.Add("Spec.RoleARN", a.ko.Spec.RoleARN, b.ko.Spec.RoleARN) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.ScheduleExpression, b.ko.Spec.ScheduleExpression) { + delta.Add("Spec.ScheduleExpression", a.ko.Spec.ScheduleExpression, b.ko.Spec.ScheduleExpression) + } else if a.ko.Spec.ScheduleExpression != nil && b.ko.Spec.ScheduleExpression != nil { + if *a.ko.Spec.ScheduleExpression != *b.ko.Spec.ScheduleExpression { + delta.Add("Spec.ScheduleExpression", a.ko.Spec.ScheduleExpression, b.ko.Spec.ScheduleExpression) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.State, b.ko.Spec.State) { + delta.Add("Spec.State", a.ko.Spec.State, b.ko.Spec.State) + } else if a.ko.Spec.State != nil && b.ko.Spec.State != nil { + if *a.ko.Spec.State != *b.ko.Spec.State { + delta.Add("Spec.State", a.ko.Spec.State, b.ko.Spec.State) + } + } + + return delta +} diff --git a/pkg/resource/rule/descriptor.go b/pkg/resource/rule/descriptor.go new file mode 100644 index 0000000..84e928a --- /dev/null +++ b/pkg/resource/rule/descriptor.go @@ -0,0 +1,154 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package rule + +import ( + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + rtclient "sigs.k8s.io/controller-runtime/pkg/client" + k8sctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + svcapitypes "github.com/aws-controllers-k8s/eventbridge-controller/apis/v1alpha1" +) + +const ( + finalizerString = "finalizers.eventbridge.services.k8s.aws/Rule" +) + +var ( + GroupVersionResource = svcapitypes.GroupVersion.WithResource("rules") + GroupKind = metav1.GroupKind{ + Group: "eventbridge.services.k8s.aws", + Kind: "Rule", + } +) + +// resourceDescriptor implements the +// `aws-service-operator-k8s/pkg/types.AWSResourceDescriptor` interface +type resourceDescriptor struct { +} + +// GroupKind returns a Kubernetes metav1.GroupKind struct that describes the +// API Group and Kind of CRs described by the descriptor +func (d *resourceDescriptor) GroupKind() *metav1.GroupKind { + return &GroupKind +} + +// EmptyRuntimeObject returns an empty object prototype that may be used in +// apimachinery and k8s client operations +func (d *resourceDescriptor) EmptyRuntimeObject() rtclient.Object { + return &svcapitypes.Rule{} +} + +// 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.Rule), + } +} + +// 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/rule/hooks.go b/pkg/resource/rule/hooks.go new file mode 100644 index 0000000..04eeeba --- /dev/null +++ b/pkg/resource/rule/hooks.go @@ -0,0 +1,123 @@ +package rule + +import ( + "context" + "fmt" + + 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/eventbridge" + + "github.com/aws-controllers-k8s/eventbridge-controller/apis/v1alpha1" + svcapitypes "github.com/aws-controllers-k8s/eventbridge-controller/apis/v1alpha1" + pkgtags "github.com/aws-controllers-k8s/eventbridge-controller/pkg/tags" +) + +type validationError struct { + field string + message string +} + +func (v validationError) Error() string { + return fmt.Sprintf("invalid Spec: %q: %s", v.field, v.message) +} + +func newValidationError(field, message string) validationError { + return validationError{ + field: field, + message: message, + } +} + +func validateRuleSpec(spec v1alpha1.RuleSpec) error { + var match bool + if s := spec.State; s != nil { + allowedValues := svcsdk.RuleState_Values() + for _, v := range allowedValues { + if *s == v { + match = true + } + } + if !match { + return newValidationError( + "spec.state", + fmt.Sprintf("supported states: %v", allowedValues), + ) + } + } + + emptyPattern := spec.EventPattern == nil || *spec.EventPattern == "" + emptySchedule := spec.ScheduleExpression == nil || *spec.ScheduleExpression == "" + + if emptySchedule && emptyPattern { + return newValidationError( + "spec", + fmt.Sprintf("at least one of %q or %q must be specified", + "spec.eventPattern", "spec.scheduleExpression"), + ) + } + + // TODO (@embano1): until code-gen can generate required markers for custom_field + for _, t := range spec.Targets { + arn := t.ARN + id := t.ID + + if arn == nil || *arn == "" || id == nil || *id == "" { + return newValidationError( + "spec.targets", + fmt.Sprintf("%q and %q must be specified for each target", "arn", "id"), + ) + } + } + + return nil +} + +// setResourceAdditionalFields will set the fields that are not returned by +// DescribeRule calls +func (rm *resourceManager) setResourceAdditionalFields( + ctx context.Context, + ko *svcapitypes.Rule, +) (err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.setResourceAdditionalFields") + defer func() { exit(err) }() + + if ko.Status.ACKResourceMetadata != nil && ko.Status.ACKResourceMetadata.ARN != nil && + *ko.Status.ACKResourceMetadata.ARN != "" { + // Set event data store tags + ko.Spec.Tags, err = rm.getTags(ctx, string(*ko.Status.ACKResourceMetadata.ARN)) + if err != nil { + return err + } + + ko.Spec.Targets, err = rm.getTargets(ctx, *ko.Spec.Name, *ko.Spec.EventBusName) + if err != nil { + return err + } + } + + return nil +} + +func customPreCompare( + delta *ackcompare.Delta, + desired *resource, + latest *resource, +) { + if len(desired.ko.Spec.Tags) != len(latest.ko.Spec.Tags) { + delta.Add("Spec.Tags", desired.ko.Spec.Tags, latest.ko.Spec.Tags) + } + + if !pkgtags.EqualTags(desired.ko.Spec.Tags, latest.ko.Spec.Tags) { + delta.Add("Spec.Tags", desired.ko.Spec.Tags, latest.ko.Spec.Tags) + } + + if len(desired.ko.Spec.Targets) != len(latest.ko.Spec.Targets) { + delta.Add("Spec.Targets", desired.ko.Spec.Targets, latest.ko.Spec.Targets) + } + + if !equalTargets(desired.ko.Spec.Targets, latest.ko.Spec.Targets) { + delta.Add("Spec.Targets", desired.ko.Spec.Targets, latest.ko.Spec.Targets) + } +} diff --git a/pkg/resource/rule/hooks_tags.go b/pkg/resource/rule/hooks_tags.go new file mode 100644 index 0000000..e46cfd3 --- /dev/null +++ b/pkg/resource/rule/hooks_tags.go @@ -0,0 +1,104 @@ +package rule + +import ( + "context" + + "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" + "github.com/aws/aws-sdk-go/service/eventbridge" + + svcapitypes "github.com/aws-controllers-k8s/eventbridge-controller/apis/v1alpha1" + pkgtags "github.com/aws-controllers-k8s/eventbridge-controller/pkg/tags" +) + +// getTags retrieves a resource list of tags. +func (rm *resourceManager) getTags( + ctx context.Context, + resourceARN string, +) (tags []*svcapitypes.Tag, err error) { + rlog := log.FromContext(ctx) + exit := rlog.Trace("rm.getTags") + defer func() { exit(err) }() + + var listTagsResponse *eventbridge.ListTagsForResourceOutput + listTagsResponse, err = rm.sdkapi.ListTagsForResourceWithContext( + ctx, + &eventbridge.ListTagsForResourceInput{ + ResourceARN: &resourceARN, + }, + ) + rm.metrics.RecordAPICall("GET", "ListTagsForResource", err) + if err != nil { + return nil, err + } + for _, tag := range listTagsResponse.Tags { + tags = append(tags, &svcapitypes.Tag{ + Key: tag.Key, + Value: tag.Value, + }) + } + return tags, nil +} + +// syncTags synchronizes rule tags +func (rm *resourceManager) syncTags( + ctx context.Context, + desired *resource, + latest *resource, +) (err error) { + rlog := log.FromContext(ctx) + exit := rlog.Trace("rm.syncTags") + defer func() { exit(err) }() + + missing, extra := pkgtags.ComputeTagsDelta(desired.ko.Spec.Tags, latest.ko.Spec.Tags) + + arn := (*string)(latest.ko.Status.ACKResourceMetadata.ARN) + if len(extra) > 0 { + _, err = rm.sdkapi.UntagResourceWithContext( + ctx, + &eventbridge.UntagResourceInput{ + ResourceARN: arn, + TagKeys: sdkTagStringsFromResourceTags(extra), + }) + + rm.metrics.RecordAPICall("UPDATE", "UntagResource", err) + if err != nil { + return err + } + } + + if len(missing) > 0 { + _, err = rm.sdkapi.TagResourceWithContext( + ctx, + &eventbridge.TagResourceInput{ + ResourceARN: arn, + Tags: sdkTagsFromResourceTags(missing), + }) + + rm.metrics.RecordAPICall("UPDATE", "TagResource", err) + if err != nil { + return err + } + } + return nil +} + +// sdkTagsFromResourceTags transforms a *svcapitypes.Tag array to a *svcsdk.Tag array. +func sdkTagsFromResourceTags(rTags []*svcapitypes.Tag) []*eventbridge.Tag { + tags := make([]*eventbridge.Tag, len(rTags)) + for i := range rTags { + tags[i] = &eventbridge.Tag{ + Key: rTags[i].Key, + Value: rTags[i].Value, + } + } + return tags +} + +// sdkTagStringsFromResourceTags transforms a *svcapitypes.Tag array to a string array. +func sdkTagStringsFromResourceTags(rTags []*svcapitypes.Tag) []*string { + tags := make([]*string, len(rTags)) + for i := range rTags { + tags[i] = rTags[i].Key + } + return tags +} diff --git a/pkg/resource/rule/hooks_tags_test.go b/pkg/resource/rule/hooks_tags_test.go new file mode 100644 index 0000000..51d3510 --- /dev/null +++ b/pkg/resource/rule/hooks_tags_test.go @@ -0,0 +1,238 @@ +package rule + +import ( + "context" + "errors" + "testing" + + "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackmetrics "github.com/aws-controllers-k8s/runtime/pkg/metrics" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/eventbridge" + "github.com/aws/aws-sdk-go/service/eventbridge/eventbridgeiface" + "gotest.tools/v3/assert" + + svcapitypes "github.com/aws-controllers-k8s/eventbridge-controller/apis/v1alpha1" +) + +var arn = v1alpha1.AWSResourceName("arn:some:bus") + +type ebAPIMockTagClient struct { + eventbridgeiface.EventBridgeAPI + tagInput *eventbridge.TagResourceInput + untagInput *eventbridge.UntagResourceInput + calls int + response error +} + +func (e *ebAPIMockTagClient) TagResourceWithContext(_ aws.Context, input *eventbridge.TagResourceInput, _ ...request.Option) (*eventbridge.TagResourceOutput, error) { + e.calls++ + e.tagInput = input + return nil, e.response +} + +func (e *ebAPIMockTagClient) UntagResourceWithContext(_ aws.Context, input *eventbridge.UntagResourceInput, _ ...request.Option) (*eventbridge.UntagResourceOutput, error) { + e.calls++ + e.untagInput = input + return nil, e.response +} + +func Test_resourceManager_syncTags(t *testing.T) { + type args struct { + latest *resource + desired *resource + } + tests := []struct { + name string + args args + wantCalls int + wantTagInput *eventbridge.TagResourceInput + wantUntagInput *eventbridge.UntagResourceInput + wantErr error + }{ + { + name: "api call fails untag one", + args: args{ + latest: &resource{getResource([]*svcapitypes.Tag{{ + Key: aws.String("key-1"), + Value: aws.String("value-1"), + }}...)}, + desired: &resource{getResource()}, + }, + wantCalls: 1, + wantTagInput: nil, + wantUntagInput: &eventbridge.UntagResourceInput{ + ResourceARN: (*string)(&arn), + TagKeys: []*string{aws.String("key-1")}, + }, + wantErr: errors.New("call failed"), + }, { + name: "remove one tag", + args: args{ + latest: &resource{getResource([]*svcapitypes.Tag{{ + Key: aws.String("key-1"), + Value: aws.String("value-1"), + }}...)}, + desired: &resource{getResource()}, + }, + wantCalls: 1, + wantTagInput: nil, + wantUntagInput: &eventbridge.UntagResourceInput{ + ResourceARN: (*string)(&arn), + TagKeys: []*string{aws.String("key-1")}, + }, + wantErr: nil, + }, { + name: "add tag one", + args: args{ + latest: &resource{getResource()}, + desired: &resource{getResource([]*svcapitypes.Tag{{ + Key: aws.String("key-1"), + Value: aws.String("value-1"), + }}...)}, + }, + wantCalls: 1, + wantTagInput: &eventbridge.TagResourceInput{ + ResourceARN: (*string)(&arn), + Tags: []*eventbridge.Tag{{ + Key: aws.String("key-1"), + Value: aws.String("value-1"), + }}, + }, + wantUntagInput: nil, + wantErr: nil, + }, { + name: "no changes", + args: args{ + latest: &resource{getResource([]*svcapitypes.Tag{{ + Key: aws.String("key-1"), + Value: aws.String("value-1"), + }}...)}, + desired: &resource{getResource([]*svcapitypes.Tag{{ + Key: aws.String("key-1"), + Value: aws.String("value-1"), + }}...)}, + }, + wantCalls: 0, + wantTagInput: nil, + wantUntagInput: nil, + wantErr: nil, + }, { + name: "two tags added, one remove", + args: args{ + latest: &resource{getResource([]*svcapitypes.Tag{{ + Key: aws.String("key-1"), + Value: aws.String("value-1"), + }}...)}, + desired: &resource{getResource([]*svcapitypes.Tag{ + { + Key: aws.String("key-2"), + Value: aws.String("value-2"), + }, + { + Key: aws.String("key-3"), + Value: aws.String("value-3"), + }, + }...)}, + }, + wantCalls: 2, + wantTagInput: &eventbridge.TagResourceInput{ + ResourceARN: (*string)(&arn), + Tags: []*eventbridge.Tag{ + { + Key: aws.String("key-2"), + Value: aws.String("value-2"), + }, { + Key: aws.String("key-3"), + Value: aws.String("value-3"), + }, + }, + }, + wantUntagInput: &eventbridge.UntagResourceInput{ + ResourceARN: (*string)(&arn), + TagKeys: []*string{aws.String("key-1")}, + }, + wantErr: nil, + }, { + name: "tags order changed, no api call needed", + args: args{ + latest: &resource{getResource([]*svcapitypes.Tag{ + { + Key: aws.String("key-1"), + Value: aws.String("value-1"), + }, + { + Key: aws.String("key-2"), + Value: aws.String("value-2"), + }, + }...)}, + desired: &resource{getResource([]*svcapitypes.Tag{ + { + Key: aws.String("key-2"), + Value: aws.String("value-2"), + }, + { + Key: aws.String("key-1"), + Value: aws.String("value-1"), + }, + }...)}, + }, + wantCalls: 0, + wantTagInput: nil, + wantUntagInput: nil, + wantErr: nil, + }, { + name: "one tag value changed", + args: args{ + latest: &resource{getResource([]*svcapitypes.Tag{{ + Key: aws.String("key-1"), + Value: aws.String("value-1"), + }}...)}, + desired: &resource{getResource([]*svcapitypes.Tag{{ + Key: aws.String("key-1"), + Value: aws.String("value-2"), + }}...)}, + }, + wantCalls: 1, + wantTagInput: &eventbridge.TagResourceInput{ + ResourceARN: (*string)(&arn), + Tags: []*eventbridge.Tag{{ + Key: aws.String("key-1"), + Value: aws.String("value-2"), + }}, + }, + wantUntagInput: nil, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + api := ebAPIMockTagClient{ + response: tt.wantErr, + } + rm := &resourceManager{ + metrics: ackmetrics.NewMetrics("eventbridge"), + sdkapi: &api, + } + err := rm.syncTags(context.TODO(), tt.args.desired, tt.args.latest) + assert.Equal(t, err, tt.wantErr) + assert.Equal(t, tt.wantCalls, api.calls) + assert.DeepEqual(t, tt.wantTagInput, api.tagInput) + assert.DeepEqual(t, tt.wantUntagInput, api.untagInput) + }) + } +} + +func getResource(tags ...*svcapitypes.Tag) *svcapitypes.Rule { + return &svcapitypes.Rule{ + Spec: svcapitypes.RuleSpec{ + Tags: tags, + }, + Status: svcapitypes.RuleStatus{ + ACKResourceMetadata: &v1alpha1.ResourceMetadata{ + ARN: &arn, + }, + }, + } +} diff --git a/pkg/resource/rule/hooks_targets.go b/pkg/resource/rule/hooks_targets.go new file mode 100644 index 0000000..88ba6d7 --- /dev/null +++ b/pkg/resource/rule/hooks_targets.go @@ -0,0 +1,142 @@ +package rule + +import ( + "context" + "errors" + "reflect" + + "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" + ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" + ackutil "github.com/aws-controllers-k8s/runtime/pkg/util" + "github.com/aws/aws-sdk-go/aws" + svcsdk "github.com/aws/aws-sdk-go/service/eventbridge" + + "github.com/aws-controllers-k8s/eventbridge-controller/apis/v1alpha1" + pkgtags "github.com/aws-controllers-k8s/eventbridge-controller/pkg/tags" + + svcapitypes "github.com/aws-controllers-k8s/eventbridge-controller/apis/v1alpha1" +) + +// TODO(embano1): add more input validation +func validateTargets(targets []*svcapitypes.Target) error { + seen := make(map[string]bool) + + for _, t := range targets { + if pkgtags.EqualZeroString(t.ID) || pkgtags.EqualZeroString(t.ARN) { + return errors.New("invalid target: target ID and ARN must be specified") + } + + if seen[*t.ID] { + return errors.New("invalid target: unique target ID is already used") + } + + seen[*t.ID] = true + } + + return nil +} + +// getTags retrieves a resource list of tags. +func (rm *resourceManager) getTargets(ctx context.Context, rule, bus string) (targets []*svcapitypes.Target, err error) { + rlog := log.FromContext(ctx) + exit := rlog.Trace("rm.getTargets") + defer func() { exit(err) }() + + var listTargetsResponse *svcsdk.ListTargetsByRuleOutput + listTargetsResponse, err = rm.sdkapi.ListTargetsByRuleWithContext( + ctx, + &svcsdk.ListTargetsByRuleInput{ + EventBusName: aws.String(bus), + Rule: aws.String(rule), + }, + ) + rm.metrics.RecordAPICall("GET", "ListTargetsByRule", err) + if err != nil { + return nil, err + } + + return resourceTargetsFromSDKTargets(listTargetsResponse.Targets), nil +} + +// syncTargets synchronizes rule targets +func (rm *resourceManager) syncTargets( + ctx context.Context, + ruleName *string, + eventBus *string, // name or arn + desired, latest []*v1alpha1.Target, +) (err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.syncTargets") + defer func() { exit(err) }() + + added, removed := computeTargetsDelta(latest, desired) + + if len(removed) > 0 { + _, err = rm.sdkapi.RemoveTargetsWithContext( + ctx, + &svcsdk.RemoveTargetsInput{ + // NOTE(a-hilaly,embano1): we might need to force the removal, in some cases? + // thinking annotations... terminal conditions... + Rule: ruleName, + EventBusName: eventBus, + Ids: removed, + }) + rm.metrics.RecordAPICall("UPDATE", "RemoveTargets", err) + if err != nil { + return err + } + } + + if len(added) > 0 { + _, err = rm.sdkapi.PutTargetsWithContext( + ctx, + &svcsdk.PutTargetsInput{ + Rule: ruleName, + EventBusName: eventBus, + Targets: sdkTargetsFromResourceTargets(added), + }) + rm.metrics.RecordAPICall("UPDATE", "PutTargets", err) + if err != nil { + return err + } + } + return nil +} + +// computeTargetsDelta computes the delta between the specified targets and +// returns added and removed targets +func computeTargetsDelta( + a []*svcapitypes.Target, + b []*svcapitypes.Target, +) (added []*svcapitypes.Target, removed []*string) { + var visitedIndexes []string +mainLoop: + for _, aElement := range a { + visitedIndexes = append(visitedIndexes, *aElement.ID) + for _, bElement := range b { + if pkgtags.EqualStrings(aElement.ID, bElement.ID) { + if !reflect.DeepEqual(aElement, bElement) { + added = append(added, bElement) + } + continue mainLoop + } + } + removed = append(removed, aElement.ID) + } + for _, bElement := range b { + if !ackutil.InStrings(*bElement.ID, visitedIndexes) { + added = append(added, bElement) + } + } + return added, removed +} + +// equalTargets returns true if two Tag arrays are equal regardless of the order +// of their elements. +func equalTargets( + a []*svcapitypes.Target, + b []*svcapitypes.Target, +) bool { + added, removed := computeTargetsDelta(a, b) + return len(added) == 0 && len(removed) == 0 +} diff --git a/pkg/resource/rule/hooks_targets_test.go b/pkg/resource/rule/hooks_targets_test.go new file mode 100644 index 0000000..9e13f72 --- /dev/null +++ b/pkg/resource/rule/hooks_targets_test.go @@ -0,0 +1,379 @@ +package rule + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/aws-controllers-k8s/runtime/pkg/metrics" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/eventbridge" + "github.com/aws/aws-sdk-go/service/eventbridge/eventbridgeiface" + "gotest.tools/v3/assert" + + svcapitypes "github.com/aws-controllers-k8s/eventbridge-controller/apis/v1alpha1" +) + +const ( + ruleName = "test-rule" + busName = "test-bus" + arnFormat = "arn:service:%d" + idFormat = "id-%d" +) + +type ebAPIMockTargetsClient struct { + eventbridgeiface.EventBridgeAPI + putInput *eventbridge.PutTargetsInput + removeInput *eventbridge.RemoveTargetsInput + calls int +} + +func (eb *ebAPIMockTargetsClient) PutTargetsWithContext(_ aws.Context, input *eventbridge.PutTargetsInput, _ ...request.Option) (*eventbridge.PutTargetsOutput, error) { + eb.calls++ + eb.putInput = input + + if len(input.Targets) > 5 { + return nil, errors.New("the requested resource exceeds the maximum number allowed") + } + + return nil, nil +} + +func (eb *ebAPIMockTargetsClient) RemoveTargetsWithContext(_ aws.Context, input *eventbridge.RemoveTargetsInput, _ ...request.Option) (*eventbridge.RemoveTargetsOutput, error) { + eb.calls++ + eb.removeInput = input + + return nil, nil +} + +func Test_validateTargets(t *testing.T) { + tests := []struct { + name string + targets []*svcapitypes.Target + wantErr string + }{ + { + name: "empty list of targets", + targets: nil, + wantErr: "", + }, { + name: "two targets, one without id", + targets: []*svcapitypes.Target{ + { + ARN: aws.String("arn:1"), + ID: nil, + }, { + ARN: aws.String("arn:2"), + ID: aws.String("id2"), + }, + }, + wantErr: "invalid target: target ID and ARN must be specified", + }, { + name: "two targets, one without arn", + targets: []*svcapitypes.Target{ + { + ARN: aws.String("arn:1"), + ID: aws.String("id1"), + }, { + ARN: nil, + ID: aws.String("id2"), + }, + }, + wantErr: "invalid target: target ID and ARN must be specified", + }, { + name: "two targets, duplicate ids", + targets: []*svcapitypes.Target{ + { + ARN: aws.String("arn:1"), + ID: aws.String("id1"), + }, { + ARN: aws.String("arn:2"), + ID: aws.String("id1"), + }, + }, + wantErr: "invalid target: unique target ID is already used", + }, { + name: "two valid targets, different ids same arn", + targets: []*svcapitypes.Target{ + { + ARN: aws.String("arn:1"), + ID: aws.String("id1"), + }, { + ARN: aws.String("arn:1"), + ID: aws.String("id2"), + }, + }, + wantErr: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateTargets(tt.targets) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + } else { + assert.NilError(t, err) + } + }) + } +} + +func Test_resourceManager_syncTargets(t *testing.T) { + tests := []struct { + name string + rule string + bus string + latest func() []*svcapitypes.Target + desired func() []*svcapitypes.Target + wantCalls int + wantPutInput *eventbridge.PutTargetsInput + wantRemoveInput *eventbridge.RemoveTargetsInput + wantErr string + }{ + { + name: "fails when adding more than 5 targets to an existing rule without targets", + rule: ruleName, + bus: busName, + latest: func() []*svcapitypes.Target { return nil }, + desired: func() []*svcapitypes.Target { + targets := make([]*svcapitypes.Target, 6) + for i := 0; i < 6; i++ { + targets[i] = createTarget(i) + } + return targets + }, + wantCalls: 1, + wantPutInput: &eventbridge.PutTargetsInput{ + EventBusName: aws.String(busName), + Rule: aws.String(ruleName), + Targets: []*eventbridge.Target{ + { + Arn: aws.String(fmt.Sprintf(arnFormat, 0)), + Id: aws.String(fmt.Sprintf(idFormat, 0)), + }, + { + Arn: aws.String(fmt.Sprintf(arnFormat, 1)), + Id: aws.String(fmt.Sprintf(idFormat, 1)), + }, + { + Arn: aws.String(fmt.Sprintf(arnFormat, 2)), + Id: aws.String(fmt.Sprintf(idFormat, 2)), + }, + { + Arn: aws.String(fmt.Sprintf(arnFormat, 3)), + Id: aws.String(fmt.Sprintf(idFormat, 3)), + }, + { + Arn: aws.String(fmt.Sprintf(arnFormat, 4)), + Id: aws.String(fmt.Sprintf(idFormat, 4)), + }, + { + Arn: aws.String(fmt.Sprintf(arnFormat, 5)), + Id: aws.String(fmt.Sprintf(idFormat, 5)), + }, + }, + }, + wantRemoveInput: nil, + wantErr: "the requested resource exceeds the maximum number allowed", + }, { + name: "no change to rule with no targets", + rule: ruleName, + bus: busName, + latest: func() []*svcapitypes.Target { return nil }, + desired: func() []*svcapitypes.Target { return nil }, + wantCalls: 0, + wantPutInput: nil, + wantRemoveInput: nil, + wantErr: "", + }, { + name: "add two targets to rule with no targets", + rule: ruleName, + bus: busName, + latest: func() []*svcapitypes.Target { return nil }, + desired: func() []*svcapitypes.Target { + targets := make([]*svcapitypes.Target, 2) + for i := 0; i < 2; i++ { + targets[i] = createTarget(i) + } + return targets + }, + wantCalls: 1, + wantPutInput: &eventbridge.PutTargetsInput{ + EventBusName: aws.String(busName), + Rule: aws.String(ruleName), + Targets: []*eventbridge.Target{ + { + Arn: aws.String(fmt.Sprintf(arnFormat, 0)), + Id: aws.String(fmt.Sprintf(idFormat, 0)), + }, + { + Arn: aws.String(fmt.Sprintf(arnFormat, 1)), + Id: aws.String(fmt.Sprintf(idFormat, 1)), + }, + }, + }, + wantRemoveInput: nil, + wantErr: "", + }, { + name: "add one, remove one from existing rule with two targets", + rule: ruleName, + bus: busName, + latest: func() []*svcapitypes.Target { + targets := make([]*svcapitypes.Target, 2) + for i := 0; i < 2; i++ { + targets[i] = createTarget(i) + } + return targets + }, + desired: func() []*svcapitypes.Target { + targets := make([]*svcapitypes.Target, 2) + targets[0] = createTarget(1) // means first target removed + targets[1] = createTarget(2) // added target + + return targets + }, + wantCalls: 2, + wantPutInput: &eventbridge.PutTargetsInput{ + EventBusName: aws.String(busName), + Rule: aws.String(ruleName), + Targets: []*eventbridge.Target{ + { + Arn: aws.String(fmt.Sprintf(arnFormat, 2)), + Id: aws.String(fmt.Sprintf(idFormat, 2)), + }, + }, + }, + wantRemoveInput: &eventbridge.RemoveTargetsInput{ + EventBusName: aws.String(busName), + Rule: aws.String(ruleName), + Ids: []*string{aws.String("id-0")}, + }, + wantErr: "", + }, { + name: "remove all from existing rule with two targets", + rule: ruleName, + bus: busName, + latest: func() []*svcapitypes.Target { + targets := make([]*svcapitypes.Target, 2) + for i := 0; i < 2; i++ { + targets[i] = createTarget(i) + } + return targets + }, + desired: func() []*svcapitypes.Target { return nil }, + wantCalls: 1, + wantPutInput: nil, + wantRemoveInput: &eventbridge.RemoveTargetsInput{ + EventBusName: aws.String(busName), + Rule: aws.String(ruleName), + Ids: []*string{aws.String("id-0"), aws.String("id-1")}, + }, + wantErr: "", + }, { + name: "update one target from rule with three targets", + rule: ruleName, + bus: busName, + latest: func() []*svcapitypes.Target { + targets := make([]*svcapitypes.Target, 3) + for i := 0; i < 3; i++ { + targets[i] = createTarget(i) + } + return targets + }, + desired: func() []*svcapitypes.Target { + targets := make([]*svcapitypes.Target, 3) + for i := 0; i < 3; i++ { + targets[i] = createTarget(i) + } + + // update first target + targets[0].Input = aws.String("some input") + return targets + }, + wantCalls: 1, + wantPutInput: &eventbridge.PutTargetsInput{ + EventBusName: aws.String(busName), + Rule: aws.String(ruleName), + Targets: []*eventbridge.Target{ + { + Arn: aws.String(fmt.Sprintf(arnFormat, 0)), + Id: aws.String(fmt.Sprintf(idFormat, 0)), + Input: aws.String("some input"), + }, + }, + }, + wantRemoveInput: nil, + wantErr: "", + }, { + name: "add one, update one, remove one target from rule with two targets", + rule: ruleName, + bus: busName, + latest: func() []*svcapitypes.Target { + targets := make([]*svcapitypes.Target, 2) + for i := 0; i < 2; i++ { + targets[i] = createTarget(i) + } + return targets + }, + desired: func() []*svcapitypes.Target { + targets := make([]*svcapitypes.Target, 2) + targets[0] = createTarget(1) // means first target removed + targets[1] = createTarget(2) // added target + + // update first target + targets[0].Input = aws.String("some input") + return targets + }, + wantCalls: 2, + wantPutInput: &eventbridge.PutTargetsInput{ + EventBusName: aws.String(busName), + Rule: aws.String(ruleName), + Targets: []*eventbridge.Target{ + { + Arn: aws.String(fmt.Sprintf(arnFormat, 1)), + Id: aws.String(fmt.Sprintf(idFormat, 1)), + Input: aws.String("some input"), + }, { + Arn: aws.String(fmt.Sprintf(arnFormat, 2)), + Id: aws.String(fmt.Sprintf(idFormat, 2)), + }, + }, + }, + wantRemoveInput: &eventbridge.RemoveTargetsInput{ + EventBusName: aws.String(busName), + Rule: aws.String(ruleName), + Ids: []*string{aws.String("id-0")}, + }, + wantErr: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ebClient := ebAPIMockTargetsClient{} + rm := &resourceManager{ + metrics: metrics.NewMetrics("eventbridge"), + sdkapi: &ebClient, + } + + err := rm.syncTargets(context.TODO(), &tt.rule, &tt.bus, tt.desired(), tt.latest()) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + } else { + assert.NilError(t, err) + } + + assert.Equal(t, ebClient.calls, tt.wantCalls) + assert.DeepEqual(t, ebClient.putInput, tt.wantPutInput) + assert.DeepEqual(t, ebClient.removeInput, tt.wantRemoveInput) + }) + } +} + +func createTarget(id int) *svcapitypes.Target { + return &svcapitypes.Target{ + ARN: aws.String(fmt.Sprintf(arnFormat, id)), + ID: aws.String(fmt.Sprintf(idFormat, id)), + } +} diff --git a/pkg/resource/rule/hooks_test.go b/pkg/resource/rule/hooks_test.go new file mode 100644 index 0000000..f64876a --- /dev/null +++ b/pkg/resource/rule/hooks_test.go @@ -0,0 +1,127 @@ +package rule + +import ( + "testing" + + "github.com/aws/aws-sdk-go/aws" + + "github.com/aws-controllers-k8s/eventbridge-controller/apis/v1alpha1" +) + +func Test_validateRuleSpec(t *testing.T) { + type args struct { + spec v1alpha1.RuleSpec + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "invalid state", + args: args{ + spec: v1alpha1.RuleSpec{ + State: aws.String("invalid"), + }, + }, + wantErr: true, + }, + { + name: "invalid state (empty string)", + args: args{ + spec: v1alpha1.RuleSpec{ + State: aws.String(""), + }, + }, + wantErr: true, + }, + { + name: "invalid target (missing arn)", + args: args{ + spec: v1alpha1.RuleSpec{ + State: aws.String("ENABLED"), + EventPattern: aws.String(`{"some":"pattern"}`), + Targets: []*v1alpha1.Target{ + { + ARN: nil, + ID: aws.String("some-id"), + }, + }, + }, + }, + wantErr: true, + }, + { + name: "invalid target (missing id)", + args: args{ + spec: v1alpha1.RuleSpec{ + State: aws.String("ENABLED"), + EventPattern: aws.String(`{"some":"pattern"}`), + Targets: []*v1alpha1.Target{ + { + ARN: aws.String("some-arn"), + ID: nil, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "invalid state (lower case)", + args: args{ + spec: v1alpha1.RuleSpec{ + State: aws.String("enabled"), + }, + }, + wantErr: true, + }, + { + name: "valid state, pattern missing", + args: args{ + spec: v1alpha1.RuleSpec{ + State: aws.String("ENABLED"), + }, + }, + wantErr: true, + }, + { + name: "valid state, rule and schedule pattern specified", + args: args{ + spec: v1alpha1.RuleSpec{ + State: aws.String("ENABLED"), + EventPattern: aws.String(`{"some":"pattern"}`), + ScheduleExpression: aws.String(`{"someschedule}`), + }, + }, + wantErr: false, + }, + { + name: "valid state and rule pattern", + args: args{ + spec: v1alpha1.RuleSpec{ + State: aws.String("ENABLED"), + EventPattern: aws.String(`{"some":"pattern"}`), // we don't verify rule syntax + }, + }, + wantErr: false, + }, + { + name: "valid state and schedule pattern", + args: args{ + spec: v1alpha1.RuleSpec{ + State: aws.String("ENABLED"), + ScheduleExpression: aws.String(`{"someschedule"}`), // we don't verify rule syntax + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := validateRuleSpec(tt.args.spec); (err != nil) != tt.wantErr { + t.Errorf("validateRuleSpec() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/pkg/resource/rule/identifiers.go b/pkg/resource/rule/identifiers.go new file mode 100644 index 0000000..96e165d --- /dev/null +++ b/pkg/resource/rule/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 rule + +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/rule/manager.go b/pkg/resource/rule/manager.go new file mode 100644 index 0000000..cab8add --- /dev/null +++ b/pkg/resource/rule/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 rule + +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/eventbridge" + svcsdkapi "github.com/aws/aws-sdk-go/service/eventbridge/eventbridgeiface" + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + + svcapitypes "github.com/aws-controllers-k8s/eventbridge-controller/apis/v1alpha1" +) + +var ( + _ = ackutil.InStrings + _ = acktags.NewTags() + _ = ackrt.MissingImageTagValue + _ = svcapitypes.Rule{} +) + +// +kubebuilder:rbac:groups=eventbridge.services.k8s.aws,resources=rules,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=eventbridge.services.k8s.aws,resources=rules/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.EventBridgeAPI +} + +// 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:eventbridge:%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/rule/manager_factory.go b/pkg/resource/rule/manager_factory.go new file mode 100644 index 0000000..74edf6b --- /dev/null +++ b/pkg/resource/rule/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 rule + +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/eventbridge-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/rule/references.go b/pkg/resource/rule/references.go new file mode 100644 index 0000000..9acfe5f --- /dev/null +++ b/pkg/resource/rule/references.go @@ -0,0 +1,134 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package rule + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcondition "github.com/aws-controllers-k8s/runtime/pkg/condition" + ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + + svcapitypes "github.com/aws-controllers-k8s/eventbridge-controller/apis/v1alpha1" +) + +// ResolveReferences finds if there are any Reference field(s) present +// inside AWSResource passed in the parameter and attempts to resolve +// those reference field(s) into target field(s). +// It returns an AWSResource with resolved reference(s), and an error if the +// passed AWSResource's reference field(s) cannot be resolved. +// This method also adds/updates the ConditionTypeReferencesResolved for the +// AWSResource. +func (rm *resourceManager) ResolveReferences( + ctx context.Context, + apiReader client.Reader, + res acktypes.AWSResource, +) (acktypes.AWSResource, error) { + namespace := res.MetaObject().GetNamespace() + ko := rm.concreteResource(res).ko.DeepCopy() + err := validateReferenceFields(ko) + if err == nil { + err = resolveReferenceForEventBusName(ctx, apiReader, namespace, ko) + } + + // If there was an error while resolving any reference, reset all the + // resolved values so that they do not get persisted inside etcd + if err != nil { + ko = rm.concreteResource(res).ko.DeepCopy() + } + if hasNonNilReferences(ko) { + return ackcondition.WithReferencesResolvedCondition(&resource{ko}, err) + } + return &resource{ko}, err +} + +// validateReferenceFields validates the reference field and corresponding +// identifier field. +func validateReferenceFields(ko *svcapitypes.Rule) error { + if ko.Spec.EventBusRef != nil && ko.Spec.EventBusName != nil { + return ackerr.ResourceReferenceAndIDNotSupportedFor("EventBusName", "EventBusRef") + } + return nil +} + +// hasNonNilReferences returns true if resource contains a reference to another +// resource +func hasNonNilReferences(ko *svcapitypes.Rule) bool { + return false || (ko.Spec.EventBusRef != nil) +} + +// resolveReferenceForEventBusName reads the resource referenced +// from EventBusRef field and sets the EventBusName +// from referenced resource +func resolveReferenceForEventBusName( + ctx context.Context, + apiReader client.Reader, + namespace string, + ko *svcapitypes.Rule, +) error { + if ko.Spec.EventBusRef != nil && + ko.Spec.EventBusRef.From != nil { + arr := ko.Spec.EventBusRef.From + if arr == nil || arr.Name == nil || *arr.Name == "" { + return fmt.Errorf("provided resource reference is nil or empty") + } + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: *arr.Name, + } + obj := svcapitypes.EventBus{} + err := apiReader.Get(ctx, namespacedName, &obj) + if err != nil { + return err + } + var refResourceSynced, refResourceTerminal bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeResourceSynced && + cond.Status == corev1.ConditionTrue { + refResourceSynced = true + } + if cond.Type == ackv1alpha1.ConditionTypeTerminal && + cond.Status == corev1.ConditionTrue { + refResourceTerminal = true + } + } + if refResourceTerminal { + return ackerr.ResourceReferenceTerminalFor( + "EventBus", + namespace, *arr.Name) + } + if !refResourceSynced { + return ackerr.ResourceReferenceNotSyncedFor( + "EventBus", + namespace, *arr.Name) + } + if obj.Spec.Name == nil { + return ackerr.ResourceReferenceMissingTargetFieldFor( + "EventBus", + namespace, *arr.Name, + "Spec.Name") + } + referencedValue := string(*obj.Spec.Name) + ko.Spec.EventBusName = &referencedValue + } + return nil +} diff --git a/pkg/resource/rule/resource.go b/pkg/resource/rule/resource.go new file mode 100644 index 0000000..b6f6df6 --- /dev/null +++ b/pkg/resource/rule/resource.go @@ -0,0 +1,105 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package rule + +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/eventbridge-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.Rule +} + +// Identifiers returns an AWSResourceIdentifiers object containing various +// identifying information, including the AWS account ID that owns the +// resource, the resource's AWS Resource Name (ARN) +func (r *resource) Identifiers() acktypes.AWSResourceIdentifiers { + return &resourceIdentifiers{r.ko.Status.ACKResourceMetadata} +} + +// IsBeingDeleted returns true if the Kubernetes resource has a non-zero +// deletion timestemp +func (r *resource) IsBeingDeleted() bool { + return !r.ko.DeletionTimestamp.IsZero() +} + +// RuntimeObject returns the Kubernetes apimachinery/runtime representation of +// the AWSResource +func (r *resource) RuntimeObject() rtclient.Object { + return r.ko +} + +// MetaObject returns the Kubernetes apimachinery/apis/meta/v1.Object +// representation of the AWSResource +func (r *resource) MetaObject() metav1.Object { + return r.ko.GetObjectMeta() +} + +// Conditions returns the ACK Conditions collection for the AWSResource +func (r *resource) Conditions() []*ackv1alpha1.Condition { + return r.ko.Status.Conditions +} + +// ReplaceConditions sets the Conditions status field for the resource +func (r *resource) ReplaceConditions(conditions []*ackv1alpha1.Condition) { + r.ko.Status.Conditions = conditions +} + +// SetObjectMeta sets the ObjectMeta field for the resource +func (r *resource) SetObjectMeta(meta metav1.ObjectMeta) { + r.ko.ObjectMeta = meta +} + +// SetStatus will set the Status field for the resource +func (r *resource) SetStatus(desired acktypes.AWSResource) { + r.ko.Status = desired.(*resource).ko.Status +} + +// SetIdentifiers sets the Spec or Status field that is referenced as the unique +// resource identifier +func (r *resource) SetIdentifiers(identifier *ackv1alpha1.AWSIdentifiers) error { + if identifier.NameOrID == "" { + return ackerrors.MissingNameIdentifier + } + r.ko.Spec.Name = &identifier.NameOrID + + f0, f0ok := identifier.AdditionalKeys["eventBusName"] + if f0ok { + r.ko.Spec.EventBusName = &f0 + } + + 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/rule/sdk.go b/pkg/resource/rule/sdk.go new file mode 100644 index 0000000..5b669ef --- /dev/null +++ b/pkg/resource/rule/sdk.go @@ -0,0 +1,1187 @@ +// 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 rule + +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/eventbridge" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + svcapitypes "github.com/aws-controllers-k8s/eventbridge-controller/apis/v1alpha1" +) + +// Hack to avoid import errors during build... +var ( + _ = &metav1.Time{} + _ = strings.ToLower("") + _ = &aws.JSONValue{} + _ = &svcsdk.EventBridge{} + _ = &svcapitypes.Rule{} + _ = 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 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.requiredFieldsMissingFromReadOneInput(r) { + return nil, ackerr.NotFound + } + + input, err := rm.newDescribeRequestPayload(r) + if err != nil { + return nil, err + } + + var resp *svcsdk.DescribeRuleOutput + resp, err = rm.sdkapi.DescribeRuleWithContext(ctx, input) + rm.metrics.RecordAPICall("READ_ONE", "DescribeRule", err) + if err != nil { + if awsErr, ok := ackerr.AWSError(err); ok && awsErr.Code() == "ResourceNotFoundException" { + 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() + + if ko.Status.ACKResourceMetadata == nil { + ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{} + } + if resp.Arn != nil { + arn := ackv1alpha1.AWSResourceName(*resp.Arn) + ko.Status.ACKResourceMetadata.ARN = &arn + } + if resp.Description != nil { + ko.Spec.Description = resp.Description + } else { + ko.Spec.Description = nil + } + if resp.EventBusName != nil { + ko.Spec.EventBusName = resp.EventBusName + } else { + ko.Spec.EventBusName = nil + } + if resp.EventPattern != nil { + ko.Spec.EventPattern = resp.EventPattern + } else { + ko.Spec.EventPattern = nil + } + if resp.Name != nil { + ko.Spec.Name = resp.Name + } else { + ko.Spec.Name = nil + } + if resp.RoleArn != nil { + ko.Spec.RoleARN = resp.RoleArn + } else { + ko.Spec.RoleARN = nil + } + if resp.ScheduleExpression != nil { + ko.Spec.ScheduleExpression = resp.ScheduleExpression + } else { + ko.Spec.ScheduleExpression = nil + } + if resp.State != nil { + ko.Spec.State = resp.State + } else { + ko.Spec.State = nil + } + + rm.setStatusDefaults(ko) + if err := rm.setResourceAdditionalFields(ctx, ko); err != nil { + return nil, err + } + + return &resource{ko}, nil +} + +// requiredFieldsMissingFromReadOneInput returns true if there are any fields +// for the ReadOne Input shape that are required but not present in the +// resource's Spec or Status +func (rm *resourceManager) requiredFieldsMissingFromReadOneInput( + r *resource, +) bool { + return r.ko.Spec.Name == nil + +} + +// newDescribeRequestPayload returns SDK-specific struct for the HTTP request +// payload of the Describe API call for the resource +func (rm *resourceManager) newDescribeRequestPayload( + r *resource, +) (*svcsdk.DescribeRuleInput, error) { + res := &svcsdk.DescribeRuleInput{} + + if r.ko.Spec.EventBusName != nil { + res.SetEventBusName(*r.ko.Spec.EventBusName) + } + if r.ko.Spec.Name != nil { + res.SetName(*r.ko.Spec.Name) + } + + 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) + }() + if err = validateRuleSpec(desired.ko.Spec); err != nil { + return nil, ackerr.NewTerminalError(err) + } + + input, err := rm.newCreateRequestPayload(ctx, desired) + if err != nil { + return nil, err + } + + var resp *svcsdk.PutRuleOutput + _ = resp + resp, err = rm.sdkapi.PutRuleWithContext(ctx, input) + rm.metrics.RecordAPICall("CREATE", "PutRule", 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 ko.Status.ACKResourceMetadata == nil { + ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{} + } + if resp.RuleArn != nil { + arn := ackv1alpha1.AWSResourceName(*resp.RuleArn) + ko.Status.ACKResourceMetadata.ARN = &arn + } + + rm.setStatusDefaults(ko) + if len(ko.Spec.Targets) > 0 { + if err = rm.syncTargets( + ctx, + ko.Spec.Name, ko.Spec.EventBusName, + ko.Spec.Targets, nil, + ); err != nil { + return nil, err + } + } + + 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.PutRuleInput, error) { + res := &svcsdk.PutRuleInput{} + + if r.ko.Spec.Description != nil { + res.SetDescription(*r.ko.Spec.Description) + } + if r.ko.Spec.EventBusName != nil { + res.SetEventBusName(*r.ko.Spec.EventBusName) + } + if r.ko.Spec.EventPattern != nil { + res.SetEventPattern(*r.ko.Spec.EventPattern) + } + if r.ko.Spec.Name != nil { + res.SetName(*r.ko.Spec.Name) + } + if r.ko.Spec.RoleARN != nil { + res.SetRoleArn(*r.ko.Spec.RoleARN) + } + if r.ko.Spec.ScheduleExpression != nil { + res.SetScheduleExpression(*r.ko.Spec.ScheduleExpression) + } + if r.ko.Spec.State != nil { + res.SetState(*r.ko.Spec.State) + } + if r.ko.Spec.Tags != nil { + f7 := []*svcsdk.Tag{} + for _, f7iter := range r.ko.Spec.Tags { + f7elem := &svcsdk.Tag{} + if f7iter.Key != nil { + f7elem.SetKey(*f7iter.Key) + } + if f7iter.Value != nil { + f7elem.SetValue(*f7iter.Value) + } + f7 = append(f7, f7elem) + } + res.SetTags(f7) + } + + 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, +) (updated *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkUpdate") + defer func() { + exit(err) + }() + if immutableFieldChanges := rm.getImmutableFieldChanges(delta); len(immutableFieldChanges) > 0 { + msg := fmt.Sprintf("Immutable Spec fields have been modified: %s", strings.Join(immutableFieldChanges, ",")) + return nil, ackerr.NewTerminalError(fmt.Errorf(msg)) + } + if err = validateRuleSpec(desired.ko.Spec); err != nil { + return nil, ackerr.NewTerminalError(err) + } + if delta.DifferentAt("Spec.Tags") { + if err = rm.syncTags(ctx, desired, latest); err != nil { + return nil, err + } + } + if delta.DifferentAt("Spec.Targets") { + if err = rm.syncTargets( + ctx, + desired.ko.Spec.Name, desired.ko.Spec.EventBusName, + desired.ko.Spec.Targets, latest.ko.Spec.Targets, + ); err != nil { + return nil, err + } + } + if !delta.DifferentExcept("Spec.Tags", "Spec.Targets") { + return desired, nil + } + + input, err := rm.newUpdateRequestPayload(ctx, desired) + if err != nil { + return nil, err + } + + var resp *svcsdk.PutRuleOutput + _ = resp + resp, err = rm.sdkapi.PutRuleWithContext(ctx, input) + rm.metrics.RecordAPICall("UPDATE", "PutRule", 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 ko.Status.ACKResourceMetadata == nil { + ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{} + } + if resp.RuleArn != nil { + arn := ackv1alpha1.AWSResourceName(*resp.RuleArn) + ko.Status.ACKResourceMetadata.ARN = &arn + } + + rm.setStatusDefaults(ko) + return &resource{ko}, nil +} + +// newUpdateRequestPayload returns an SDK-specific struct for the HTTP request +// payload of the Update API call for the resource +func (rm *resourceManager) newUpdateRequestPayload( + ctx context.Context, + r *resource, +) (*svcsdk.PutRuleInput, error) { + res := &svcsdk.PutRuleInput{} + + if r.ko.Spec.Description != nil { + res.SetDescription(*r.ko.Spec.Description) + } + if r.ko.Spec.EventBusName != nil { + res.SetEventBusName(*r.ko.Spec.EventBusName) + } + if r.ko.Spec.EventPattern != nil { + res.SetEventPattern(*r.ko.Spec.EventPattern) + } + if r.ko.Spec.Name != nil { + res.SetName(*r.ko.Spec.Name) + } + if r.ko.Spec.RoleARN != nil { + res.SetRoleArn(*r.ko.Spec.RoleARN) + } + if r.ko.Spec.ScheduleExpression != nil { + res.SetScheduleExpression(*r.ko.Spec.ScheduleExpression) + } + if r.ko.Spec.State != nil { + res.SetState(*r.ko.Spec.State) + } + if r.ko.Spec.Tags != nil { + f7 := []*svcsdk.Tag{} + for _, f7iter := range r.ko.Spec.Tags { + f7elem := &svcsdk.Tag{} + if f7iter.Key != nil { + f7elem.SetKey(*f7iter.Key) + } + if f7iter.Value != nil { + f7elem.SetValue(*f7iter.Value) + } + f7 = append(f7, f7elem) + } + res.SetTags(f7) + } + + return res, nil +} + +// 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) + }() + if len(r.ko.Spec.Targets) > 0 { + if err = rm.syncTargets( + ctx, + r.ko.Spec.Name, r.ko.Spec.EventBusName, + nil, r.ko.Spec.Targets, + ); err != nil { + return nil, err + } + } + + input, err := rm.newDeleteRequestPayload(r) + if err != nil { + return nil, err + } + var resp *svcsdk.DeleteRuleOutput + _ = resp + resp, err = rm.sdkapi.DeleteRuleWithContext(ctx, input) + rm.metrics.RecordAPICall("DELETE", "DeleteRule", 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.DeleteRuleInput, error) { + res := &svcsdk.DeleteRuleInput{} + + if r.ko.Spec.EventBusName != nil { + res.SetEventBusName(*r.ko.Spec.EventBusName) + } + if r.ko.Spec.Name != nil { + res.SetName(*r.ko.Spec.Name) + } + + return res, nil +} + +// setStatusDefaults sets default properties into supplied custom resource +func (rm *resourceManager) setStatusDefaults( + ko *svcapitypes.Rule, +) { + 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 "InvalidEventPatternException", + "ManagedRuleException", + "ValidationError", + "ValidationException": + return true + default: + return false + } +} + +// getImmutableFieldChanges returns list of immutable fields from the +func (rm *resourceManager) getImmutableFieldChanges( + delta *ackcompare.Delta, +) []string { + var fields []string + if delta.DifferentAt("Spec.EventBusName") { + fields = append(fields, "EventBusName") + } + if delta.DifferentAt("Spec.Name") { + fields = append(fields, "Name") + } + + return fields +} + +// sdkTargetsFromResourceTargets converts the given Kubernetes resource targets to AWS service targets +func sdkTargetsFromResourceTargets( + targets []*svcapitypes.Target, +) []*svcsdk.Target { + var res []*svcsdk.Target + for _, krTarget := range targets { + t := &svcsdk.Target{} + if krTarget.ARN != nil { + t.SetArn(*krTarget.ARN) + } + if krTarget.BatchParameters != nil { + tf1 := &svcsdk.BatchParameters{} + if krTarget.BatchParameters.ArrayProperties != nil { + tf1f0 := &svcsdk.BatchArrayProperties{} + if krTarget.BatchParameters.ArrayProperties.Size != nil { + tf1f0.SetSize(*krTarget.BatchParameters.ArrayProperties.Size) + } + tf1.SetArrayProperties(tf1f0) + } + if krTarget.BatchParameters.JobDefinition != nil { + tf1.SetJobDefinition(*krTarget.BatchParameters.JobDefinition) + } + if krTarget.BatchParameters.JobName != nil { + tf1.SetJobName(*krTarget.BatchParameters.JobName) + } + if krTarget.BatchParameters.RetryStrategy != nil { + tf1f3 := &svcsdk.BatchRetryStrategy{} + if krTarget.BatchParameters.RetryStrategy.Attempts != nil { + tf1f3.SetAttempts(*krTarget.BatchParameters.RetryStrategy.Attempts) + } + tf1.SetRetryStrategy(tf1f3) + } + t.SetBatchParameters(tf1) + } + if krTarget.DeadLetterConfig != nil { + tf2 := &svcsdk.DeadLetterConfig{} + if krTarget.DeadLetterConfig.ARN != nil { + tf2.SetArn(*krTarget.DeadLetterConfig.ARN) + } + t.SetDeadLetterConfig(tf2) + } + if krTarget.ECSParameters != nil { + tf3 := &svcsdk.EcsParameters{} + if krTarget.ECSParameters.CapacityProviderStrategy != nil { + tf3f0 := []*svcsdk.CapacityProviderStrategyItem{} + for _, tf3f0iter := range krTarget.ECSParameters.CapacityProviderStrategy { + tf3f0elem := &svcsdk.CapacityProviderStrategyItem{} + if tf3f0iter.Base != nil { + tf3f0elem.SetBase(*tf3f0iter.Base) + } + if tf3f0iter.CapacityProvider != nil { + tf3f0elem.SetCapacityProvider(*tf3f0iter.CapacityProvider) + } + if tf3f0iter.Weight != nil { + tf3f0elem.SetWeight(*tf3f0iter.Weight) + } + tf3f0 = append(tf3f0, tf3f0elem) + } + tf3.SetCapacityProviderStrategy(tf3f0) + } + if krTarget.ECSParameters.EnableECSManagedTags != nil { + tf3.SetEnableECSManagedTags(*krTarget.ECSParameters.EnableECSManagedTags) + } + if krTarget.ECSParameters.EnableExecuteCommand != nil { + tf3.SetEnableExecuteCommand(*krTarget.ECSParameters.EnableExecuteCommand) + } + if krTarget.ECSParameters.Group != nil { + tf3.SetGroup(*krTarget.ECSParameters.Group) + } + if krTarget.ECSParameters.LaunchType != nil { + tf3.SetLaunchType(*krTarget.ECSParameters.LaunchType) + } + if krTarget.ECSParameters.NetworkConfiguration != nil { + tf3f5 := &svcsdk.NetworkConfiguration{} + if krTarget.ECSParameters.NetworkConfiguration.AWSVPCConfiguration != nil { + tf3f5f0 := &svcsdk.AwsVpcConfiguration{} + if krTarget.ECSParameters.NetworkConfiguration.AWSVPCConfiguration.AssignPublicIP != nil { + tf3f5f0.SetAssignPublicIp(*krTarget.ECSParameters.NetworkConfiguration.AWSVPCConfiguration.AssignPublicIP) + } + if krTarget.ECSParameters.NetworkConfiguration.AWSVPCConfiguration.SecurityGroups != nil { + tf3f5f0f1 := []*string{} + for _, tf3f5f0f1iter := range krTarget.ECSParameters.NetworkConfiguration.AWSVPCConfiguration.SecurityGroups { + var tf3f5f0f1elem string + tf3f5f0f1elem = *tf3f5f0f1iter + tf3f5f0f1 = append(tf3f5f0f1, &tf3f5f0f1elem) + } + tf3f5f0.SetSecurityGroups(tf3f5f0f1) + } + if krTarget.ECSParameters.NetworkConfiguration.AWSVPCConfiguration.Subnets != nil { + tf3f5f0f2 := []*string{} + for _, tf3f5f0f2iter := range krTarget.ECSParameters.NetworkConfiguration.AWSVPCConfiguration.Subnets { + var tf3f5f0f2elem string + tf3f5f0f2elem = *tf3f5f0f2iter + tf3f5f0f2 = append(tf3f5f0f2, &tf3f5f0f2elem) + } + tf3f5f0.SetSubnets(tf3f5f0f2) + } + tf3f5.SetAwsvpcConfiguration(tf3f5f0) + } + tf3.SetNetworkConfiguration(tf3f5) + } + if krTarget.ECSParameters.PlacementConstraints != nil { + tf3f6 := []*svcsdk.PlacementConstraint{} + for _, tf3f6iter := range krTarget.ECSParameters.PlacementConstraints { + tf3f6elem := &svcsdk.PlacementConstraint{} + if tf3f6iter.Expression != nil { + tf3f6elem.SetExpression(*tf3f6iter.Expression) + } + if tf3f6iter.Type != nil { + tf3f6elem.SetType(*tf3f6iter.Type) + } + tf3f6 = append(tf3f6, tf3f6elem) + } + tf3.SetPlacementConstraints(tf3f6) + } + if krTarget.ECSParameters.PlacementStrategy != nil { + tf3f7 := []*svcsdk.PlacementStrategy{} + for _, tf3f7iter := range krTarget.ECSParameters.PlacementStrategy { + tf3f7elem := &svcsdk.PlacementStrategy{} + if tf3f7iter.Field != nil { + tf3f7elem.SetField(*tf3f7iter.Field) + } + if tf3f7iter.Type != nil { + tf3f7elem.SetType(*tf3f7iter.Type) + } + tf3f7 = append(tf3f7, tf3f7elem) + } + tf3.SetPlacementStrategy(tf3f7) + } + if krTarget.ECSParameters.PlatformVersion != nil { + tf3.SetPlatformVersion(*krTarget.ECSParameters.PlatformVersion) + } + if krTarget.ECSParameters.PropagateTags != nil { + tf3.SetPropagateTags(*krTarget.ECSParameters.PropagateTags) + } + if krTarget.ECSParameters.ReferenceID != nil { + tf3.SetReferenceId(*krTarget.ECSParameters.ReferenceID) + } + if krTarget.ECSParameters.Tags != nil { + tf3f11 := []*svcsdk.Tag{} + for _, tf3f11iter := range krTarget.ECSParameters.Tags { + tf3f11elem := &svcsdk.Tag{} + if tf3f11iter.Key != nil { + tf3f11elem.SetKey(*tf3f11iter.Key) + } + if tf3f11iter.Value != nil { + tf3f11elem.SetValue(*tf3f11iter.Value) + } + tf3f11 = append(tf3f11, tf3f11elem) + } + tf3.SetTags(tf3f11) + } + if krTarget.ECSParameters.TaskCount != nil { + tf3.SetTaskCount(*krTarget.ECSParameters.TaskCount) + } + if krTarget.ECSParameters.TaskDefinitionARN != nil { + tf3.SetTaskDefinitionArn(*krTarget.ECSParameters.TaskDefinitionARN) + } + t.SetEcsParameters(tf3) + } + if krTarget.HTTPParameters != nil { + tf4 := &svcsdk.HttpParameters{} + if krTarget.HTTPParameters.HeaderParameters != nil { + tf4f0 := map[string]*string{} + for tf4f0key, tf4f0valiter := range krTarget.HTTPParameters.HeaderParameters { + var tf4f0val string + tf4f0val = *tf4f0valiter + tf4f0[tf4f0key] = &tf4f0val + } + tf4.SetHeaderParameters(tf4f0) + } + if krTarget.HTTPParameters.PathParameterValues != nil { + tf4f1 := []*string{} + for _, tf4f1iter := range krTarget.HTTPParameters.PathParameterValues { + var tf4f1elem string + tf4f1elem = *tf4f1iter + tf4f1 = append(tf4f1, &tf4f1elem) + } + tf4.SetPathParameterValues(tf4f1) + } + if krTarget.HTTPParameters.QueryStringParameters != nil { + tf4f2 := map[string]*string{} + for tf4f2key, tf4f2valiter := range krTarget.HTTPParameters.QueryStringParameters { + var tf4f2val string + tf4f2val = *tf4f2valiter + tf4f2[tf4f2key] = &tf4f2val + } + tf4.SetQueryStringParameters(tf4f2) + } + t.SetHttpParameters(tf4) + } + if krTarget.ID != nil { + t.SetId(*krTarget.ID) + } + if krTarget.Input != nil { + t.SetInput(*krTarget.Input) + } + if krTarget.InputPath != nil { + t.SetInputPath(*krTarget.InputPath) + } + if krTarget.InputTransformer != nil { + tf8 := &svcsdk.InputTransformer{} + if krTarget.InputTransformer.InputPathsMap != nil { + tf8f0 := map[string]*string{} + for tf8f0key, tf8f0valiter := range krTarget.InputTransformer.InputPathsMap { + var tf8f0val string + tf8f0val = *tf8f0valiter + tf8f0[tf8f0key] = &tf8f0val + } + tf8.SetInputPathsMap(tf8f0) + } + if krTarget.InputTransformer.InputTemplate != nil { + tf8.SetInputTemplate(*krTarget.InputTransformer.InputTemplate) + } + t.SetInputTransformer(tf8) + } + if krTarget.KinesisParameters != nil { + tf9 := &svcsdk.KinesisParameters{} + if krTarget.KinesisParameters.PartitionKeyPath != nil { + tf9.SetPartitionKeyPath(*krTarget.KinesisParameters.PartitionKeyPath) + } + t.SetKinesisParameters(tf9) + } + if krTarget.RedshiftDataParameters != nil { + tf10 := &svcsdk.RedshiftDataParameters{} + if krTarget.RedshiftDataParameters.Database != nil { + tf10.SetDatabase(*krTarget.RedshiftDataParameters.Database) + } + if krTarget.RedshiftDataParameters.DBUser != nil { + tf10.SetDbUser(*krTarget.RedshiftDataParameters.DBUser) + } + if krTarget.RedshiftDataParameters.SecretManagerARN != nil { + tf10.SetSecretManagerArn(*krTarget.RedshiftDataParameters.SecretManagerARN) + } + if krTarget.RedshiftDataParameters.SQL != nil { + tf10.SetSql(*krTarget.RedshiftDataParameters.SQL) + } + if krTarget.RedshiftDataParameters.StatementName != nil { + tf10.SetStatementName(*krTarget.RedshiftDataParameters.StatementName) + } + if krTarget.RedshiftDataParameters.WithEvent != nil { + tf10.SetWithEvent(*krTarget.RedshiftDataParameters.WithEvent) + } + t.SetRedshiftDataParameters(tf10) + } + if krTarget.RetryPolicy != nil { + tf11 := &svcsdk.RetryPolicy{} + if krTarget.RetryPolicy.MaximumEventAgeInSeconds != nil { + tf11.SetMaximumEventAgeInSeconds(*krTarget.RetryPolicy.MaximumEventAgeInSeconds) + } + if krTarget.RetryPolicy.MaximumRetryAttempts != nil { + tf11.SetMaximumRetryAttempts(*krTarget.RetryPolicy.MaximumRetryAttempts) + } + t.SetRetryPolicy(tf11) + } + if krTarget.RoleARN != nil { + t.SetRoleArn(*krTarget.RoleARN) + } + if krTarget.RunCommandParameters != nil { + tf13 := &svcsdk.RunCommandParameters{} + if krTarget.RunCommandParameters.RunCommandTargets != nil { + tf13f0 := []*svcsdk.RunCommandTarget{} + for _, tf13f0iter := range krTarget.RunCommandParameters.RunCommandTargets { + tf13f0elem := &svcsdk.RunCommandTarget{} + if tf13f0iter.Key != nil { + tf13f0elem.SetKey(*tf13f0iter.Key) + } + if tf13f0iter.Values != nil { + tf13f0elemf1 := []*string{} + for _, tf13f0elemf1iter := range tf13f0iter.Values { + var tf13f0elemf1elem string + tf13f0elemf1elem = *tf13f0elemf1iter + tf13f0elemf1 = append(tf13f0elemf1, &tf13f0elemf1elem) + } + tf13f0elem.SetValues(tf13f0elemf1) + } + tf13f0 = append(tf13f0, tf13f0elem) + } + tf13.SetRunCommandTargets(tf13f0) + } + t.SetRunCommandParameters(tf13) + } + if krTarget.SageMakerPipelineParameters != nil { + tf14 := &svcsdk.SageMakerPipelineParameters{} + if krTarget.SageMakerPipelineParameters.PipelineParameterList != nil { + tf14f0 := []*svcsdk.SageMakerPipelineParameter{} + for _, tf14f0iter := range krTarget.SageMakerPipelineParameters.PipelineParameterList { + tf14f0elem := &svcsdk.SageMakerPipelineParameter{} + if tf14f0iter.Name != nil { + tf14f0elem.SetName(*tf14f0iter.Name) + } + if tf14f0iter.Value != nil { + tf14f0elem.SetValue(*tf14f0iter.Value) + } + tf14f0 = append(tf14f0, tf14f0elem) + } + tf14.SetPipelineParameterList(tf14f0) + } + t.SetSageMakerPipelineParameters(tf14) + } + if krTarget.SQSParameters != nil { + tf15 := &svcsdk.SqsParameters{} + if krTarget.SQSParameters.MessageGroupID != nil { + tf15.SetMessageGroupId(*krTarget.SQSParameters.MessageGroupID) + } + t.SetSqsParameters(tf15) + } + + res = append(res, t) + } + return res +} + +// resourceTargetsFromSDKTargets converts the given AWS service targets to Kubernetes resource targets +func resourceTargetsFromSDKTargets( + targets []*svcsdk.Target, +) []*svcapitypes.Target { + var res []*svcapitypes.Target + for _, sdkTarget := range targets { + t := &svcapitypes.Target{} + // test + if sdkTarget.Arn != nil { + t.ARN = sdkTarget.Arn + } + if sdkTarget.BatchParameters != nil { + tf1 := &svcapitypes.BatchParameters{} + if sdkTarget.BatchParameters.ArrayProperties != nil { + tf1f0 := &svcapitypes.BatchArrayProperties{} + if sdkTarget.BatchParameters.ArrayProperties.Size != nil { + tf1f0.Size = sdkTarget.BatchParameters.ArrayProperties.Size + } + tf1.ArrayProperties = tf1f0 + } + if sdkTarget.BatchParameters.JobDefinition != nil { + tf1.JobDefinition = sdkTarget.BatchParameters.JobDefinition + } + if sdkTarget.BatchParameters.JobName != nil { + tf1.JobName = sdkTarget.BatchParameters.JobName + } + if sdkTarget.BatchParameters.RetryStrategy != nil { + tf1f3 := &svcapitypes.BatchRetryStrategy{} + if sdkTarget.BatchParameters.RetryStrategy.Attempts != nil { + tf1f3.Attempts = sdkTarget.BatchParameters.RetryStrategy.Attempts + } + tf1.RetryStrategy = tf1f3 + } + t.BatchParameters = tf1 + } + if sdkTarget.DeadLetterConfig != nil { + tf2 := &svcapitypes.DeadLetterConfig{} + if sdkTarget.DeadLetterConfig.Arn != nil { + tf2.ARN = sdkTarget.DeadLetterConfig.Arn + } + t.DeadLetterConfig = tf2 + } + if sdkTarget.EcsParameters != nil { + tf3 := &svcapitypes.ECSParameters{} + if sdkTarget.EcsParameters.CapacityProviderStrategy != nil { + tf3f0 := []*svcapitypes.CapacityProviderStrategyItem{} + for _, tf3f0iter := range sdkTarget.EcsParameters.CapacityProviderStrategy { + tf3f0elem := &svcapitypes.CapacityProviderStrategyItem{} + if tf3f0iter.Base != nil { + tf3f0elem.Base = tf3f0iter.Base + } + if tf3f0iter.CapacityProvider != nil { + tf3f0elem.CapacityProvider = tf3f0iter.CapacityProvider + } + if tf3f0iter.Weight != nil { + tf3f0elem.Weight = tf3f0iter.Weight + } + tf3f0 = append(tf3f0, tf3f0elem) + } + tf3.CapacityProviderStrategy = tf3f0 + } + if sdkTarget.EcsParameters.EnableECSManagedTags != nil { + tf3.EnableECSManagedTags = sdkTarget.EcsParameters.EnableECSManagedTags + } + if sdkTarget.EcsParameters.EnableExecuteCommand != nil { + tf3.EnableExecuteCommand = sdkTarget.EcsParameters.EnableExecuteCommand + } + if sdkTarget.EcsParameters.Group != nil { + tf3.Group = sdkTarget.EcsParameters.Group + } + if sdkTarget.EcsParameters.LaunchType != nil { + tf3.LaunchType = sdkTarget.EcsParameters.LaunchType + } + if sdkTarget.EcsParameters.NetworkConfiguration != nil { + tf3f5 := &svcapitypes.NetworkConfiguration{} + if sdkTarget.EcsParameters.NetworkConfiguration.AwsvpcConfiguration != nil { + tf3f5f0 := &svcapitypes.AWSVPCConfiguration{} + if sdkTarget.EcsParameters.NetworkConfiguration.AwsvpcConfiguration.AssignPublicIp != nil { + tf3f5f0.AssignPublicIP = sdkTarget.EcsParameters.NetworkConfiguration.AwsvpcConfiguration.AssignPublicIp + } + if sdkTarget.EcsParameters.NetworkConfiguration.AwsvpcConfiguration.SecurityGroups != nil { + tf3f5f0f1 := []*string{} + for _, tf3f5f0f1iter := range sdkTarget.EcsParameters.NetworkConfiguration.AwsvpcConfiguration.SecurityGroups { + var tf3f5f0f1elem string + tf3f5f0f1elem = *tf3f5f0f1iter + tf3f5f0f1 = append(tf3f5f0f1, &tf3f5f0f1elem) + } + tf3f5f0.SecurityGroups = tf3f5f0f1 + } + if sdkTarget.EcsParameters.NetworkConfiguration.AwsvpcConfiguration.Subnets != nil { + tf3f5f0f2 := []*string{} + for _, tf3f5f0f2iter := range sdkTarget.EcsParameters.NetworkConfiguration.AwsvpcConfiguration.Subnets { + var tf3f5f0f2elem string + tf3f5f0f2elem = *tf3f5f0f2iter + tf3f5f0f2 = append(tf3f5f0f2, &tf3f5f0f2elem) + } + tf3f5f0.Subnets = tf3f5f0f2 + } + tf3f5.AWSVPCConfiguration = tf3f5f0 + } + tf3.NetworkConfiguration = tf3f5 + } + if sdkTarget.EcsParameters.PlacementConstraints != nil { + tf3f6 := []*svcapitypes.PlacementConstraint{} + for _, tf3f6iter := range sdkTarget.EcsParameters.PlacementConstraints { + tf3f6elem := &svcapitypes.PlacementConstraint{} + if tf3f6iter.Expression != nil { + tf3f6elem.Expression = tf3f6iter.Expression + } + if tf3f6iter.Type != nil { + tf3f6elem.Type = tf3f6iter.Type + } + tf3f6 = append(tf3f6, tf3f6elem) + } + tf3.PlacementConstraints = tf3f6 + } + if sdkTarget.EcsParameters.PlacementStrategy != nil { + tf3f7 := []*svcapitypes.PlacementStrategy{} + for _, tf3f7iter := range sdkTarget.EcsParameters.PlacementStrategy { + tf3f7elem := &svcapitypes.PlacementStrategy{} + if tf3f7iter.Field != nil { + tf3f7elem.Field = tf3f7iter.Field + } + if tf3f7iter.Type != nil { + tf3f7elem.Type = tf3f7iter.Type + } + tf3f7 = append(tf3f7, tf3f7elem) + } + tf3.PlacementStrategy = tf3f7 + } + if sdkTarget.EcsParameters.PlatformVersion != nil { + tf3.PlatformVersion = sdkTarget.EcsParameters.PlatformVersion + } + if sdkTarget.EcsParameters.PropagateTags != nil { + tf3.PropagateTags = sdkTarget.EcsParameters.PropagateTags + } + if sdkTarget.EcsParameters.ReferenceId != nil { + tf3.ReferenceID = sdkTarget.EcsParameters.ReferenceId + } + if sdkTarget.EcsParameters.Tags != nil { + tf3f11 := []*svcapitypes.Tag{} + for _, tf3f11iter := range sdkTarget.EcsParameters.Tags { + tf3f11elem := &svcapitypes.Tag{} + if tf3f11iter.Key != nil { + tf3f11elem.Key = tf3f11iter.Key + } + if tf3f11iter.Value != nil { + tf3f11elem.Value = tf3f11iter.Value + } + tf3f11 = append(tf3f11, tf3f11elem) + } + tf3.Tags = tf3f11 + } + if sdkTarget.EcsParameters.TaskCount != nil { + tf3.TaskCount = sdkTarget.EcsParameters.TaskCount + } + if sdkTarget.EcsParameters.TaskDefinitionArn != nil { + tf3.TaskDefinitionARN = sdkTarget.EcsParameters.TaskDefinitionArn + } + t.ECSParameters = tf3 + } + if sdkTarget.HttpParameters != nil { + tf4 := &svcapitypes.HTTPParameters{} + if sdkTarget.HttpParameters.HeaderParameters != nil { + tf4f0 := map[string]*string{} + for tf4f0key, tf4f0valiter := range sdkTarget.HttpParameters.HeaderParameters { + var tf4f0val string + tf4f0val = *tf4f0valiter + tf4f0[tf4f0key] = &tf4f0val + } + tf4.HeaderParameters = tf4f0 + } + if sdkTarget.HttpParameters.PathParameterValues != nil { + tf4f1 := []*string{} + for _, tf4f1iter := range sdkTarget.HttpParameters.PathParameterValues { + var tf4f1elem string + tf4f1elem = *tf4f1iter + tf4f1 = append(tf4f1, &tf4f1elem) + } + tf4.PathParameterValues = tf4f1 + } + if sdkTarget.HttpParameters.QueryStringParameters != nil { + tf4f2 := map[string]*string{} + for tf4f2key, tf4f2valiter := range sdkTarget.HttpParameters.QueryStringParameters { + var tf4f2val string + tf4f2val = *tf4f2valiter + tf4f2[tf4f2key] = &tf4f2val + } + tf4.QueryStringParameters = tf4f2 + } + t.HTTPParameters = tf4 + } + if sdkTarget.Id != nil { + t.ID = sdkTarget.Id + } + if sdkTarget.Input != nil { + t.Input = sdkTarget.Input + } + if sdkTarget.InputPath != nil { + t.InputPath = sdkTarget.InputPath + } + if sdkTarget.InputTransformer != nil { + tf8 := &svcapitypes.InputTransformer{} + if sdkTarget.InputTransformer.InputPathsMap != nil { + tf8f0 := map[string]*string{} + for tf8f0key, tf8f0valiter := range sdkTarget.InputTransformer.InputPathsMap { + var tf8f0val string + tf8f0val = *tf8f0valiter + tf8f0[tf8f0key] = &tf8f0val + } + tf8.InputPathsMap = tf8f0 + } + if sdkTarget.InputTransformer.InputTemplate != nil { + tf8.InputTemplate = sdkTarget.InputTransformer.InputTemplate + } + t.InputTransformer = tf8 + } + if sdkTarget.KinesisParameters != nil { + tf9 := &svcapitypes.KinesisParameters{} + if sdkTarget.KinesisParameters.PartitionKeyPath != nil { + tf9.PartitionKeyPath = sdkTarget.KinesisParameters.PartitionKeyPath + } + t.KinesisParameters = tf9 + } + if sdkTarget.RedshiftDataParameters != nil { + tf10 := &svcapitypes.RedshiftDataParameters{} + if sdkTarget.RedshiftDataParameters.Database != nil { + tf10.Database = sdkTarget.RedshiftDataParameters.Database + } + if sdkTarget.RedshiftDataParameters.DbUser != nil { + tf10.DBUser = sdkTarget.RedshiftDataParameters.DbUser + } + if sdkTarget.RedshiftDataParameters.SecretManagerArn != nil { + tf10.SecretManagerARN = sdkTarget.RedshiftDataParameters.SecretManagerArn + } + if sdkTarget.RedshiftDataParameters.Sql != nil { + tf10.SQL = sdkTarget.RedshiftDataParameters.Sql + } + if sdkTarget.RedshiftDataParameters.StatementName != nil { + tf10.StatementName = sdkTarget.RedshiftDataParameters.StatementName + } + if sdkTarget.RedshiftDataParameters.WithEvent != nil { + tf10.WithEvent = sdkTarget.RedshiftDataParameters.WithEvent + } + t.RedshiftDataParameters = tf10 + } + if sdkTarget.RetryPolicy != nil { + tf11 := &svcapitypes.RetryPolicy{} + if sdkTarget.RetryPolicy.MaximumEventAgeInSeconds != nil { + tf11.MaximumEventAgeInSeconds = sdkTarget.RetryPolicy.MaximumEventAgeInSeconds + } + if sdkTarget.RetryPolicy.MaximumRetryAttempts != nil { + tf11.MaximumRetryAttempts = sdkTarget.RetryPolicy.MaximumRetryAttempts + } + t.RetryPolicy = tf11 + } + if sdkTarget.RoleArn != nil { + t.RoleARN = sdkTarget.RoleArn + } + if sdkTarget.RunCommandParameters != nil { + tf13 := &svcapitypes.RunCommandParameters{} + if sdkTarget.RunCommandParameters.RunCommandTargets != nil { + tf13f0 := []*svcapitypes.RunCommandTarget{} + for _, tf13f0iter := range sdkTarget.RunCommandParameters.RunCommandTargets { + tf13f0elem := &svcapitypes.RunCommandTarget{} + if tf13f0iter.Key != nil { + tf13f0elem.Key = tf13f0iter.Key + } + if tf13f0iter.Values != nil { + tf13f0elemf1 := []*string{} + for _, tf13f0elemf1iter := range tf13f0iter.Values { + var tf13f0elemf1elem string + tf13f0elemf1elem = *tf13f0elemf1iter + tf13f0elemf1 = append(tf13f0elemf1, &tf13f0elemf1elem) + } + tf13f0elem.Values = tf13f0elemf1 + } + tf13f0 = append(tf13f0, tf13f0elem) + } + tf13.RunCommandTargets = tf13f0 + } + t.RunCommandParameters = tf13 + } + if sdkTarget.SageMakerPipelineParameters != nil { + tf14 := &svcapitypes.SageMakerPipelineParameters{} + if sdkTarget.SageMakerPipelineParameters.PipelineParameterList != nil { + tf14f0 := []*svcapitypes.SageMakerPipelineParameter{} + for _, tf14f0iter := range sdkTarget.SageMakerPipelineParameters.PipelineParameterList { + tf14f0elem := &svcapitypes.SageMakerPipelineParameter{} + if tf14f0iter.Name != nil { + tf14f0elem.Name = tf14f0iter.Name + } + if tf14f0iter.Value != nil { + tf14f0elem.Value = tf14f0iter.Value + } + tf14f0 = append(tf14f0, tf14f0elem) + } + tf14.PipelineParameterList = tf14f0 + } + t.SageMakerPipelineParameters = tf14 + } + if sdkTarget.SqsParameters != nil { + tf15 := &svcapitypes.SQSParameters{} + if sdkTarget.SqsParameters.MessageGroupId != nil { + tf15.MessageGroupID = sdkTarget.SqsParameters.MessageGroupId + } + t.SQSParameters = tf15 + } + + res = append(res, t) + } + return res +} diff --git a/pkg/resource/rule/tags.go b/pkg/resource/rule/tags.go new file mode 100644 index 0000000..c51e26c --- /dev/null +++ b/pkg/resource/rule/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 rule + +import ( + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" + + svcapitypes "github.com/aws-controllers-k8s/eventbridge-controller/apis/v1alpha1" +) + +var ( + _ = svcapitypes.Rule{} + _ = 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/pkg/tags/tags.go b/pkg/tags/tags.go new file mode 100644 index 0000000..295aa87 --- /dev/null +++ b/pkg/tags/tags.go @@ -0,0 +1,63 @@ +package tags + +import ( + "github.com/aws-controllers-k8s/runtime/pkg/util" + "github.com/aws/aws-sdk-go/aws" + + svcapitypes "github.com/aws-controllers-k8s/eventbridge-controller/apis/v1alpha1" +) + +// ComputeTagsDelta compares two Tag arrays and return two different lists +// containing the added and removed tags. The removed tags list only contains +// the Key of tags +func ComputeTagsDelta( + desired []*svcapitypes.Tag, + latest []*svcapitypes.Tag, +) (missing, extra []*svcapitypes.Tag) { + var visitedIndexes []string +mainLoop: + for _, le := range latest { + visitedIndexes = append(visitedIndexes, *le.Key) + for _, de := range desired { + if EqualStrings(le.Key, de.Key) { + if !EqualStrings(le.Value, de.Value) { + missing = append(missing, de) + } + continue mainLoop + } + } + extra = append(extra, le) + } + for _, de := range desired { + if !util.InStrings(*de.Key, visitedIndexes) { + missing = append(missing, de) + } + } + return missing, extra +} + +// EqualTags returns true if two Tag arrays are equal regardless of the order of +// their elements. +func EqualTags( + desired []*svcapitypes.Tag, + latest []*svcapitypes.Tag, +) bool { + addedOrUpdated, removed := ComputeTagsDelta(desired, latest) + return len(addedOrUpdated) == 0 && len(removed) == 0 +} + +func EqualStrings(a, b *string) bool { + if a == nil { + return b == nil || *b == "" + } + + if a != nil && b == nil { + return false + } + + return (*a == "" && b == nil) || *a == *b +} + +func EqualZeroString(a *string) bool { + return EqualStrings(a, aws.String("")) +} diff --git a/templates/hooks/eventbus/sdk_read_one_post_set_output.go.tpl b/templates/hooks/eventbus/sdk_read_one_post_set_output.go.tpl index 4e8ce2f..ac175b1 100644 --- a/templates/hooks/eventbus/sdk_read_one_post_set_output.go.tpl +++ b/templates/hooks/eventbus/sdk_read_one_post_set_output.go.tpl @@ -1,3 +1,3 @@ - if err := rm.setResourceAdditionalFields(ctx, ko); err != nil { - return nil, err - } \ No newline at end of file +if err := rm.setResourceAdditionalFields(ctx, ko); err != nil { + return nil, err +} diff --git a/templates/hooks/rule/sdk_create_post_set_output.go.tpl b/templates/hooks/rule/sdk_create_post_set_output.go.tpl new file mode 100644 index 0000000..0253c5e --- /dev/null +++ b/templates/hooks/rule/sdk_create_post_set_output.go.tpl @@ -0,0 +1,9 @@ +if len(ko.Spec.Targets) > 0 { + if err = rm.syncTargets( + ctx, + ko.Spec.Name, ko.Spec.EventBusName, + ko.Spec.Targets, nil, + ); err != nil { + return nil, err + } +} diff --git a/templates/hooks/rule/sdk_create_pre_build_request.go.tpl b/templates/hooks/rule/sdk_create_pre_build_request.go.tpl new file mode 100644 index 0000000..2b6d9b3 --- /dev/null +++ b/templates/hooks/rule/sdk_create_pre_build_request.go.tpl @@ -0,0 +1,3 @@ +if err = validateRuleSpec(desired.ko.Spec); err != nil { + return nil, ackerr.NewTerminalError(err) +} diff --git a/templates/hooks/rule/sdk_delete_pre_build_request.go.tpl b/templates/hooks/rule/sdk_delete_pre_build_request.go.tpl new file mode 100644 index 0000000..30e1f2c --- /dev/null +++ b/templates/hooks/rule/sdk_delete_pre_build_request.go.tpl @@ -0,0 +1,9 @@ +if len(r.ko.Spec.Targets) > 0 { + if err = rm.syncTargets( + ctx, + r.ko.Spec.Name, r.ko.Spec.EventBusName, + nil, r.ko.Spec.Targets, + ); err != nil { + return nil, err + } +} diff --git a/templates/hooks/rule/sdk_file_end.go.tpl b/templates/hooks/rule/sdk_file_end.go.tpl new file mode 100644 index 0000000..f05d285 --- /dev/null +++ b/templates/hooks/rule/sdk_file_end.go.tpl @@ -0,0 +1,29 @@ +// sdkTargetsFromResourceTargets converts the given Kubernetes resource targets to AWS service targets +func sdkTargetsFromResourceTargets( + targets []*svcapitypes.Target, +) ([]*svcsdk.Target) { + var res []*svcsdk.Target + {{- $field := (index .CRD.SpecFields "Targets" )}} + for _, krTarget := range targets { + t := &svcsdk.Target{} + {{ GoCodeSetSDKForStruct .CRD "" "t" $field.ShapeRef.Shape.MemberRef "" "krTarget" 1 }} + res = append(res, t) + } + return res +} + +// resourceTargetsFromSDKTargets converts the given AWS service targets to Kubernetes resource targets +func resourceTargetsFromSDKTargets( + targets []*svcsdk.Target, +) ([]*svcapitypes.Target) { + var res []*svcapitypes.Target + for _, sdkTarget := range targets { + t := &svcapitypes.Target{} + // test + {{- $operation := (index .SDKAPI.API.Operations "PutTargets")}} + {{- $targetsSDKShape := (index $operation.InputRef.Shape.MemberRefs "Targets")}} + {{ GoCodeSetResourceForStruct .CRD "" "t" $field.ShapeRef.Shape.MemberRef "sdkTarget" $targetsSDKShape.Shape.MemberRef 1 }} + res = append(res, t) + } + return res +} diff --git a/templates/hooks/rule/sdk_read_one_post_set_output.go.tpl b/templates/hooks/rule/sdk_read_one_post_set_output.go.tpl new file mode 100644 index 0000000..f9406b9 --- /dev/null +++ b/templates/hooks/rule/sdk_read_one_post_set_output.go.tpl @@ -0,0 +1,3 @@ +if err := rm.setResourceAdditionalFields(ctx, ko); err != nil { + return nil, err +} diff --git a/templates/hooks/rule/sdk_update_pre_build_request.go.tpl b/templates/hooks/rule/sdk_update_pre_build_request.go.tpl new file mode 100644 index 0000000..89ce56b --- /dev/null +++ b/templates/hooks/rule/sdk_update_pre_build_request.go.tpl @@ -0,0 +1,20 @@ +if err = validateRuleSpec(desired.ko.Spec); err != nil { + return nil, ackerr.NewTerminalError(err) +} +if delta.DifferentAt("Spec.Tags") { + if err = rm.syncTags(ctx, desired, latest); err != nil { + return nil, err + } +} +if delta.DifferentAt("Spec.Targets") { + if err = rm.syncTargets( + ctx, + desired.ko.Spec.Name, desired.ko.Spec.EventBusName, + desired.ko.Spec.Targets, latest.ko.Spec.Targets, + ); err != nil { + return nil, err + } +} +if !delta.DifferentExcept("Spec.Tags", "Spec.Targets") { + return desired, nil +} diff --git a/test/e2e/bootstrap_resources.py b/test/e2e/bootstrap_resources.py index 450a769..c5d26f5 100644 --- a/test/e2e/bootstrap_resources.py +++ b/test/e2e/bootstrap_resources.py @@ -16,12 +16,15 @@ """ from dataclasses import dataclass + from acktest.bootstrapping import Resources +from acktest.bootstrapping.sqs import Queue + from e2e import bootstrap_directory @dataclass class BootstrapResources(Resources): - pass + TargetQueue: Queue _bootstrap_resources = None diff --git a/test/e2e/resources/rule.yaml b/test/e2e/resources/rule.yaml new file mode 100644 index 0000000..bbd750d --- /dev/null +++ b/test/e2e/resources/rule.yaml @@ -0,0 +1,11 @@ +apiVersion: eventbridge.services.k8s.aws/v1alpha1 +kind: Rule +metadata: + name: $RULE_NAME +spec: + name: $RULE_NAME + eventBusName: $BUS_NAME + eventPattern: "$EVENT_PATTERN" + tags: + - key: env + value: testing \ No newline at end of file diff --git a/test/e2e/service_bootstrap.py b/test/e2e/service_bootstrap.py index 58f3dec..c0ea2ce 100644 --- a/test/e2e/service_bootstrap.py +++ b/test/e2e/service_bootstrap.py @@ -15,6 +15,7 @@ import logging from acktest.bootstrapping import Resources, BootstrapFailureException +from acktest.bootstrapping.sqs import Queue from e2e import bootstrap_directory from e2e.bootstrap_resources import BootstrapResources @@ -23,7 +24,9 @@ def service_bootstrap() -> Resources: logging.getLogger().setLevel(logging.INFO) resources = BootstrapResources( - # TODO: Add bootstrapping when you have defined the resources + TargetQueue=Queue( + "ack-eventbridge-controller-queue" + ), ) try: diff --git a/test/e2e/tests/helper.py b/test/e2e/tests/helper.py index 2071758..2c2600e 100644 --- a/test/e2e/tests/helper.py +++ b/test/e2e/tests/helper.py @@ -34,6 +34,27 @@ def get_event_bus(self, event_bus_name: str) -> dict: def event_bus_exists(self, event_bus_name) -> bool: return self.get_event_bus(event_bus_name) is not None + def get_rule(self, bus_name: str, rule_name: str) -> dict: + try: + resp = self.eventbridge_client.describe_rule( + Name=rule_name, + EventBusName=bus_name, + ) + except Exception as e: + logging.debug(e) + return None + return resp + + def rule_exists(self, bus_name: str, rule_name: str) -> bool: + return self.get_rule(bus_name, rule_name) is not None + + def get_rule_targets(self, bus_name: str, rule_name: str): + resource_targets = self.eventbridge_client.list_targets_by_rule( + Rule=rule_name, + EventBusName=bus_name, + ) + return resource_targets['Targets'] + def get_resource_tags(self, resource_arn: str): resource_tags = self.eventbridge_client.list_tags_for_resource( ResourceARN=resource_arn, diff --git a/test/e2e/tests/test_rule.py b/test/e2e/tests/test_rule.py new file mode 100644 index 0000000..001780e --- /dev/null +++ b/test/e2e/tests/test_rule.py @@ -0,0 +1,238 @@ +# 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 EventBridge Rule resource +""" + +import logging +import time +from typing import Dict + +import pytest + +from acktest import tags +from acktest.k8s import resource as k8s +from acktest.resources import random_suffix_name +from e2e import service_marker, CRD_GROUP, CRD_VERSION, load_eventbridge_resource +from e2e.replacement_values import REPLACEMENT_VALUES +from e2e.bootstrap_resources import get_bootstrap_resources +from e2e.tests.helper import EventBridgeValidator + +RESOURCE_PLURAL = "rules" + +CREATE_WAIT_AFTER_SECONDS = 10 +UPDATE_WAIT_AFTER_SECONDS = 10 +DELETE_WAIT_AFTER_SECONDS = 10 + +@pytest.fixture(scope="module") +def event_bus(): + resource_name = random_suffix_name("eventbridge-bus", 24) + + replacements = REPLACEMENT_VALUES.copy() + replacements["BUS_NAME"] = resource_name + + resource_data = load_eventbridge_resource( + "eventbus", + additional_replacements=replacements, + ) + logging.debug(resource_data) + + # Create the k8s resource + ref = k8s.CustomResourceReference( + CRD_GROUP, CRD_VERSION, "eventbuses", + resource_name, namespace="default", + ) + k8s.create_custom_resource(ref, resource_data) + + time.sleep(CREATE_WAIT_AFTER_SECONDS) + + # Get latest event_bus CR + 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 + +@pytest.fixture(scope="module") +def simple_rule(event_bus): + resource_name = random_suffix_name("eventbridge-rule", 24) + _, eb_cr = event_bus + + replacements = REPLACEMENT_VALUES.copy() + replacements["BUS_NAME"] = eb_cr["spec"]["name"] + replacements["RULE_NAME"] = resource_name + replacements["EVENT_PATTERN"] = "{\\\"detail-type\\\":[\\\"ack-event\\\"]}" + + resource_data = load_eventbridge_resource( + "rule", + additional_replacements=replacements, + ) + logging.debug(resource_data) + + # Create the 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) + + # Get latest rule CR + 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 TestRule: + def test_create_delete_with_tags(self, eventbridge_client, simple_rule): + (ref, cr) = simple_rule + + rule_name = cr["spec"]["name"] + rule_arn = cr["status"]["ackResourceMetadata"]["arn"] + event_bus_name = cr["spec"]["eventBusName"] + + eventbridge_validator = EventBridgeValidator(eventbridge_client) + # verify that rule exists + assert eventbridge_validator.rule_exists(event_bus_name, rule_name) + + # verify that rule tags are created + rule_tags = eventbridge_validator.get_resource_tags(rule_arn) + tags.assert_ack_system_tags( + tags=rule_tags, + ) + tags_dict = tags.to_dict( + cr["spec"]["tags"], + key_member_name = 'key', + value_member_name = 'value' + ) + tags.assert_equal_without_ack_tags( + actual=tags_dict, + expected=rule_tags, + ) + + # Delete k8s resource + _, deleted = k8s.delete_custom_resource(ref) + assert deleted is True + + time.sleep(DELETE_WAIT_AFTER_SECONDS) + + # Check rule doesn't exist + assert not eventbridge_validator.rule_exists(event_bus_name, rule_name) + + # def test_create_delete_with_targets + + def test_rule_simple_update(self, eventbridge_client, simple_rule): + (ref, cr) = simple_rule + + rule_name = cr["spec"]["name"] + rule_arn = cr["status"]["ackResourceMetadata"]["arn"] + event_bus_name = cr["spec"]["eventBusName"] + + eventbridge_validator = EventBridgeValidator(eventbridge_client) + # verify that rule exists + assert eventbridge_validator.rule_exists(event_bus_name, rule_name) + + # Update rule + + cr["spec"]["tags"] = [ + { + "key": "env", + "value": "prod" + } + ] + cr["spec"]["eventPattern"] = "{\"detail-type\":[\"another-ack-event\"]}" + + # Patch k8s resource + k8s.patch_custom_resource(ref, cr) + time.sleep(UPDATE_WAIT_AFTER_SECONDS) + + # verify that rule eventPattern is updated + rule = eventbridge_validator.get_rule(event_bus_name, rule_name) + assert rule["EventPattern"] == "{\"detail-type\":[\"another-ack-event\"]}" + + # verify that rule tags are updated + rule_tags = eventbridge_validator.get_resource_tags(rule_arn) + tags.assert_ack_system_tags( + tags=rule_tags, + ) + tags_dict = tags.to_dict( + cr["spec"]["tags"], + key_member_name = 'key', + value_member_name = 'value' + ) + tags.assert_equal_without_ack_tags( + actual=tags_dict, + expected=rule_tags, + ) + + # Delete k8s resource + _, deleted = k8s.delete_custom_resource(ref) + assert deleted is True + + time.sleep(DELETE_WAIT_AFTER_SECONDS) + + # Check rule doesn't exist + assert not eventbridge_validator.rule_exists(event_bus_name, rule_name) + + def test_rule_update_targets(self, eventbridge_client, simple_rule): + (ref, cr) = simple_rule + resources = get_bootstrap_resources() + + rule_name = cr["spec"]["name"] + event_bus_name = cr["spec"]["eventBusName"] + + eventbridge_validator = EventBridgeValidator(eventbridge_client) + # verify that rule exists + assert eventbridge_validator.rule_exists(event_bus_name, rule_name) + + cr["spec"]["targets"] = [{ + "arn" : resources.TargetQueue.arn, + "id": "sqs-queue", + }] + + # Patch k8s resource + k8s.patch_custom_resource(ref, cr) + time.sleep(UPDATE_WAIT_AFTER_SECONDS) + + targets = eventbridge_validator.get_rule_targets(event_bus_name, rule_name) + assert len(targets) == 1 + assert targets[0]["Id"] == "sqs-queue" + + # Delete k8s resource + _, deleted = k8s.delete_custom_resource(ref) + assert deleted is True + + time.sleep(DELETE_WAIT_AFTER_SECONDS) + + # Check rule doesn't exist + assert not eventbridge_validator.rule_exists(event_bus_name, rule_name) \ No newline at end of file