diff --git a/apis/v1alpha1/ack-generate-metadata.yaml b/apis/v1alpha1/ack-generate-metadata.yaml index 40a51fb..4cc7053 100755 --- a/apis/v1alpha1/ack-generate-metadata.yaml +++ b/apis/v1alpha1/ack-generate-metadata.yaml @@ -1,13 +1,13 @@ ack_generate_info: - build_date: "2024-10-10T04:11:56Z" - build_hash: 36c2d234498c2bc4f60773ab8df632af4067f43b - go_version: go1.23.2 - version: v0.39.1 -api_directory_checksum: e6e32cdfd8eea9dc8ced5c64c857335c872e49e2 + build_date: "2024-11-15T12:28:35Z" + build_hash: 9715a2a715317a76ae83825294ca50cde9afd97b + go_version: go1.22.5 + version: v0.39.1-4-g9715a2a +api_directory_checksum: bec5824e121b02c227862a05ba3ad534a6dd71bf api_version: v1alpha1 aws_sdk_go_version: v1.55.5 generator_config_info: - file_checksum: fc6b419506aa1e1e3d52fc1512129f10ffbc9b6a + file_checksum: 8509dd7b6855643c5491564f8df855b403d0a9c3 original_file_name: generator.yaml last_modification: reason: API generation diff --git a/apis/v1alpha1/configuration_set.go b/apis/v1alpha1/configuration_set.go new file mode 100644 index 0000000..e42cd18 --- /dev/null +++ b/apis/v1alpha1/configuration_set.go @@ -0,0 +1,70 @@ +// 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" +) + +// ConfigurationSetSpec defines the desired state of ConfigurationSet. +// +// The name of the configuration set. +// +// Configuration sets let you create groups of rules that you can apply to the +// emails you send using Amazon SES. For more information about using configuration +// sets, see Using Amazon SES Configuration Sets (https://docs.aws.amazon.com/ses/latest/dg/using-configuration-sets.html) +// in the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/). +type ConfigurationSetSpec struct { + Name *string `json:"name,omitempty"` +} + +// ConfigurationSetStatus defines the observed state of ConfigurationSet +type ConfigurationSetStatus 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"` +} + +// ConfigurationSet is the Schema for the ConfigurationSets API +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type ConfigurationSet struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec ConfigurationSetSpec `json:"spec,omitempty"` + Status ConfigurationSetStatus `json:"status,omitempty"` +} + +// ConfigurationSetList contains a list of ConfigurationSet +// +kubebuilder:object:root=true +type ConfigurationSetList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ConfigurationSet `json:"items"` +} + +func init() { + SchemeBuilder.Register(&ConfigurationSet{}, &ConfigurationSetList{}) +} diff --git a/apis/v1alpha1/configuration_set_event_destination.go b/apis/v1alpha1/configuration_set_event_destination.go new file mode 100644 index 0000000..9cae861 --- /dev/null +++ b/apis/v1alpha1/configuration_set_event_destination.go @@ -0,0 +1,71 @@ +// 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" +) + +// ConfigurationSetEventDestinationSpec defines the desired state of ConfigurationSetEventDestination. +type ConfigurationSetEventDestinationSpec struct { + + // The name of the configuration set that the event destination should be associated + // with. + ConfigurationSetName *string `json:"configurationSetName,omitempty"` + ConfigurationSetRef *ackv1alpha1.AWSResourceReferenceWrapper `json:"configurationSetRef,omitempty"` + // An object that describes the Amazon Web Services service that email sending + // event where information is published. + // +kubebuilder:validation:Required + EventDestination *EventDestination `json:"eventDestination"` +} + +// ConfigurationSetEventDestinationStatus defines the observed state of ConfigurationSetEventDestination +type ConfigurationSetEventDestinationStatus 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"` +} + +// ConfigurationSetEventDestination is the Schema for the ConfigurationSetEventDestinations API +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type ConfigurationSetEventDestination struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec ConfigurationSetEventDestinationSpec `json:"spec,omitempty"` + Status ConfigurationSetEventDestinationStatus `json:"status,omitempty"` +} + +// ConfigurationSetEventDestinationList contains a list of ConfigurationSetEventDestination +// +kubebuilder:object:root=true +type ConfigurationSetEventDestinationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ConfigurationSetEventDestination `json:"items"` +} + +func init() { + SchemeBuilder.Register(&ConfigurationSetEventDestination{}, &ConfigurationSetEventDestinationList{}) +} diff --git a/apis/v1alpha1/custom_verification_email_template.go b/apis/v1alpha1/custom_verification_email_template.go new file mode 100644 index 0000000..210919e --- /dev/null +++ b/apis/v1alpha1/custom_verification_email_template.go @@ -0,0 +1,89 @@ +// 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" +) + +// CustomVerificationEmailTemplateSpec defines the desired state of CustomVerificationEmailTemplate. +// +// Contains information about a custom verification email template. +type CustomVerificationEmailTemplateSpec struct { + + // The URL that the recipient of the verification email is sent to if his or + // her address is not successfully verified. + // +kubebuilder:validation:Required + FailureRedirectionURL *string `json:"failureRedirectionURL"` + // The email address that the custom verification email is sent from. + // +kubebuilder:validation:Required + FromEmailAddress *string `json:"fromEmailAddress"` + // The URL that the recipient of the verification email is sent to if his or + // her address is successfully verified. + // +kubebuilder:validation:Required + SuccessRedirectionURL *string `json:"successRedirectionURL"` + // The content of the custom verification email. The total size of the email + // must be less than 10 MB. The message body may contain HTML, with some limitations. + // For more information, see Custom Verification Email Frequently Asked Questions + // (https://docs.aws.amazon.com/ses/latest/dg/creating-identities.html#send-email-verify-address-custom) + // in the Amazon SES Developer Guide. + // +kubebuilder:validation:Required + TemplateContent *string `json:"templateContent"` + // The name of the custom verification email template. + TemplateName *string `json:"templateName,omitempty"` + TemplateRef *ackv1alpha1.AWSResourceReferenceWrapper `json:"templateRef,omitempty"` + // The subject line of the custom verification email. + // +kubebuilder:validation:Required + TemplateSubject *string `json:"templateSubject"` +} + +// CustomVerificationEmailTemplateStatus defines the observed state of CustomVerificationEmailTemplate +type CustomVerificationEmailTemplateStatus 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"` +} + +// CustomVerificationEmailTemplate is the Schema for the CustomVerificationEmailTemplates API +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type CustomVerificationEmailTemplate struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec CustomVerificationEmailTemplateSpec `json:"spec,omitempty"` + Status CustomVerificationEmailTemplateStatus `json:"status,omitempty"` +} + +// CustomVerificationEmailTemplateList contains a list of CustomVerificationEmailTemplate +// +kubebuilder:object:root=true +type CustomVerificationEmailTemplateList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []CustomVerificationEmailTemplate `json:"items"` +} + +func init() { + SchemeBuilder.Register(&CustomVerificationEmailTemplate{}, &CustomVerificationEmailTemplateList{}) +} diff --git a/apis/v1alpha1/generator.yaml b/apis/v1alpha1/generator.yaml index f4a427f..5af5564 100644 --- a/apis/v1alpha1/generator.yaml +++ b/apis/v1alpha1/generator.yaml @@ -1,10 +1,235 @@ ignore: resource_names: - - ConfigurationSet - - ConfigurationSetEventDestination - - CustomVerificationEmailTemplate - - ReceiptFilter - - ReceiptRule - - ReceiptRuleSet - - Template +# - ConfigurationSet +# - ConfigurationSetEventDestination +# - CustomVerificationEmailTemplate +# - ReceiptFilter +# - ReceiptRule +# - ReceiptRuleSet +# - Template + + field_paths: + - CreateConfigurationSetInput.ConfigurationSet + - CreateTemplateInput.Template + - CreateReceiptFilterInput.Filter.Name model_name: email + +operations: + DescribeConfigurationSet: + operation_type: + - Get + resource_name: ConfigurationSetEventDestination + +resources: + ConfigurationSet: + fields: + Name: + is_primary_key: true + is_immutable: true + type: string + list_operation: + match_fields: + - Name + update_operation: + custom_method_name: customUpdate + renames: + operations: + DescribeConfigurationSet: + input_fields: + ConfigurationSetName: Name + DeleteConfigurationSet: + input_fields: + ConfigurationSetName: Name + tags: + ignore: true + exceptions: + terminal_codes: + - ConfigurationSetAlreadyExists + - InvalidConfigurationSet + hooks: + sdk_create_post_build_request: + template_path: hooks/configuration_set/sdk_create_post_build_request.go.tpl + sdk_read_one_post_request: + template_path: hooks/configuration_set/sdk_read_one_post_request.go.tpl + + ReceiptRuleSet: + fields: + RuleSetName: + is_primary_key: true + is_immutable: true + update_operation: + custom_method_name: customUpdate + tags: + ignore: true + exceptions: + terminal_codes: + - AlreadyExists + hooks: + sdk_read_one_post_request: + template_path: hooks/receipt_rule_set/sdk_read_one_post_request.go.tpl + + ReceiptRule: + find_operation: + custom_method_name: customFind + fields: + Rule.Name: + is_immutable: true + is_required: true + RuleSetName: + is_primary_key: true + is_immutable: true + is_required: true + references: + resource: ReceiptRuleSet + path: Spec.RuleSetName + tags: + ignore: true + exceptions: + terminal_codes: + - AlreadyExists + - InvalidLambdaFunction + - InvalidS3Configuration + - InvalidSnsTopic + - RuleSetDoesNotExist + hooks: + sdk_file_end: + template_path: hooks/receipt_rule/sdk_file_end.go.tpl + sdk_delete_post_build_request: + template_path: hooks/receipt_rule/sdk_delete_post_build_request.go.tpl + + Template: + fields: + Name: + type: string + is_primary_key: true + is_immutable: true + HTMLPart: + type: string + SubjectPart: + type: string + TextPart: + type: string + tags: + ignore: true + exceptions: + terminal_codes: + - AlreadyExists + - InvalidTemplate + renames: + operations: + GetTemplate: + input_fields: + TemplateName: Name + output_fields: + HtmlPart: HTMLPart + DeleteTemplate: + input_fields: + TemplateName: Name + hooks: + sdk_create_post_build_request: + template_path: hooks/template/sdk_create_post_build_request.go.tpl + sdk_read_one_post_request: + template_path: hooks/template/sdk_read_one_post_request.go.tpl + sdk_update_post_build_request: + template_path: hooks/template/sdk_update_post_build_request.go.tpl + + ReceiptFilter: + list_operation: + match_fields: + - Name + find_operation: + custom_method_name: customFind + update_operation: + custom_method_name: customUpdate + fields: + Name: + is_primary_key: true + is_immutable: true + is_required: true + type: string + Filter.IPFilter.CIDR: + is_immutable: true + Filter.IPFilter.Policy: + is_immutable: true + tags: + ignore: true + exceptions: + terminal_codes: + - AlreadyExists + renames: + operations: + CreateReceiptFilter: + input_fields: + Filter.Name: Name + Name: Filter.Name + DeleteReceiptFilter: + input_fields: + FilterName: Name + hooks: + sdk_file_end: + template_path: hooks/receipt_filter/sdk_file_end.go.tpl + sdk_create_post_build_request: + template_path: hooks/receipt_filter/sdk_create_post_build_request.go.tpl + sdk_read_many_post_request: + template_path: hooks/receipt_filter/sdk_read_many_post_request.go.tpl + + ConfigurationSetEventDestination: + fields: + ConfigurationSetName: + is_primary_key: true + is_immutable: true + references: + resource: ConfigurationSet + path: Spec.Name + EventDestination.Name: + is_required: true + is_immutable: true + EventDestination.MatchingEventTypes: + is_required: true + EventDestination.SNSDestination.TopicARN: + is_required: true + references: + service_name: sns + resource: Topic + path: Status.TopicARN + tags: + ignore: true + exceptions: + terminal_codes: + - ConfigurationSetDoesNotExist + - EventDestinationAlreadyExists + - InvalidCloudWatchDestination + - InvalidFirehoseDestination + - InvalidSNSDestination + - LimitExceeded + hooks: + sdk_read_one_post_request: + template_path: hooks/configuration_set_event_destination/sdk_read_one_post_request.go.tpl + sdk_read_one_pre_build_request: + template_path: hooks/configuration_set_event_destination/sdk_read_one_pre_build_request.go.tpl + sdk_read_one_post_build_request: + template_path: hooks/configuration_set_event_destination/sdk_read_one_post_build_request.go.tpl + sdk_read_one_post_set_output: + template_path: hooks/configuration_set_event_destination/sdk_read_one_post_set_output.go.tpl + sdk_delete_post_build_request: + template_path: hooks/configuration_set_event_destination/sdk_delete_post_build_request.go.tpl + sdk_file_end: + template_path: hooks/configuration_set_event_destination/sdk_file_end.go.tpl + + CustomVerificationEmailTemplate: + fields: + TemplateName: + is_primary_key: true + is_immutable: true + references: + resource: Template + path: Spec.Name + tags: + ignore: true + exceptions: + terminal_codes: + - CustomVerificationEmailInvalidContent + - CustomVerificationEmailTemplateAlreadyExists + hooks: + sdk_read_one_post_request: + template_path: hooks/custom_verification_email_template/sdk_read_one_post_request.go.tpl diff --git a/apis/v1alpha1/receipt_filter.go b/apis/v1alpha1/receipt_filter.go new file mode 100644 index 0000000..22b0e84 --- /dev/null +++ b/apis/v1alpha1/receipt_filter.go @@ -0,0 +1,75 @@ +// 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" +) + +// ReceiptFilterSpec defines the desired state of ReceiptFilter. +// +// A receipt IP address filter enables you to specify whether to accept or reject +// mail originating from an IP address or range of IP addresses. +// +// For information about setting up IP address filters, see the Amazon SES Developer +// Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-ip-filtering-console-walkthrough.html). +type ReceiptFilterSpec struct { + + // A data structure that describes the IP address filter to create, which consists + // of a name, an IP address range, and whether to allow or block mail from it. + // +kubebuilder:validation:Required + Filter *ReceiptFilter_SDK `json:"filter"` + // +kubebuilder:validation:Required + Name *string `json:"name"` +} + +// ReceiptFilterStatus defines the observed state of ReceiptFilter +type ReceiptFilterStatus 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"` +} + +// ReceiptFilter is the Schema for the ReceiptFilters API +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type ReceiptFilter struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec ReceiptFilterSpec `json:"spec,omitempty"` + Status ReceiptFilterStatus `json:"status,omitempty"` +} + +// ReceiptFilterList contains a list of ReceiptFilter +// +kubebuilder:object:root=true +type ReceiptFilterList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ReceiptFilter `json:"items"` +} + +func init() { + SchemeBuilder.Register(&ReceiptFilter{}, &ReceiptFilterList{}) +} diff --git a/apis/v1alpha1/receipt_rule.go b/apis/v1alpha1/receipt_rule.go new file mode 100644 index 0000000..67c4ad5 --- /dev/null +++ b/apis/v1alpha1/receipt_rule.go @@ -0,0 +1,86 @@ +// 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" +) + +// ReceiptRuleSpec defines the desired state of ReceiptRule. +// +// Receipt rules enable you to specify which actions Amazon SES should take +// when it receives mail on behalf of one or more email addresses or domains +// that you own. +// +// Each receipt rule defines a set of email addresses or domains that it applies +// to. If the email addresses or domains match at least one recipient address +// of the message, Amazon SES executes all of the receipt rule's actions on +// the message. +// +// For information about setting up receipt rules, see the Amazon SES Developer +// Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-receipt-rules-console-walkthrough.html). +type ReceiptRuleSpec struct { + + // The name of an existing rule after which the new rule is placed. If this + // parameter is null, the new rule is inserted at the beginning of the rule + // list. + After *string `json:"after,omitempty"` + // A data structure that contains the specified rule's name, actions, recipients, + // domains, enabled status, scan status, and TLS policy. + // +kubebuilder:validation:Required + Rule *ReceiptRule_SDK `json:"rule"` + // The name of the rule set where the receipt rule is added. + RuleSetName *string `json:"ruleSetName,omitempty"` + RuleSetRef *ackv1alpha1.AWSResourceReferenceWrapper `json:"ruleSetRef,omitempty"` +} + +// ReceiptRuleStatus defines the observed state of ReceiptRule +type ReceiptRuleStatus 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"` +} + +// ReceiptRule is the Schema for the ReceiptRules API +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type ReceiptRule struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec ReceiptRuleSpec `json:"spec,omitempty"` + Status ReceiptRuleStatus `json:"status,omitempty"` +} + +// ReceiptRuleList contains a list of ReceiptRule +// +kubebuilder:object:root=true +type ReceiptRuleList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ReceiptRule `json:"items"` +} + +func init() { + SchemeBuilder.Register(&ReceiptRule{}, &ReceiptRuleList{}) +} diff --git a/apis/v1alpha1/receipt_rule_set.go b/apis/v1alpha1/receipt_rule_set.go new file mode 100644 index 0000000..bf95942 --- /dev/null +++ b/apis/v1alpha1/receipt_rule_set.go @@ -0,0 +1,74 @@ +// 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" +) + +// ReceiptRuleSetSpec defines the desired state of ReceiptRuleSet. +type ReceiptRuleSetSpec struct { + + // The name of the rule set to create. The name must meet the following requirements: + // + // - Contain only ASCII letters (a-z, A-Z), numbers (0-9), underscores (_), + // or dashes (-). + // + // - Start and end with a letter or number. + // + // - Contain 64 characters or fewer. + // + // +kubebuilder:validation:Required + RuleSetName *string `json:"ruleSetName"` +} + +// ReceiptRuleSetStatus defines the observed state of ReceiptRuleSet +type ReceiptRuleSetStatus 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"` +} + +// ReceiptRuleSet is the Schema for the ReceiptRuleSets API +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type ReceiptRuleSet struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec ReceiptRuleSetSpec `json:"spec,omitempty"` + Status ReceiptRuleSetStatus `json:"status,omitempty"` +} + +// ReceiptRuleSetList contains a list of ReceiptRuleSet +// +kubebuilder:object:root=true +type ReceiptRuleSetList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ReceiptRuleSet `json:"items"` +} + +func init() { + SchemeBuilder.Register(&ReceiptRuleSet{}, &ReceiptRuleSetList{}) +} diff --git a/apis/v1alpha1/template.go b/apis/v1alpha1/template.go new file mode 100644 index 0000000..9c5dea6 --- /dev/null +++ b/apis/v1alpha1/template.go @@ -0,0 +1,69 @@ +// 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" +) + +// TemplateSpec defines the desired state of Template. +// +// The content of the email, composed of a subject line and either an HTML part +// or a text-only part. +type TemplateSpec struct { + HTMLPart *string `json:"htmlPart,omitempty"` + Name *string `json:"name,omitempty"` + SubjectPart *string `json:"subjectPart,omitempty"` + TextPart *string `json:"textPart,omitempty"` +} + +// TemplateStatus defines the observed state of Template +type TemplateStatus 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"` +} + +// Template is the Schema for the Templates API +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type Template struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec TemplateSpec `json:"spec,omitempty"` + Status TemplateStatus `json:"status,omitempty"` +} + +// TemplateList contains a list of Template +// +kubebuilder:object:root=true +type TemplateList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Template `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Template{}, &TemplateList{}) +} diff --git a/apis/v1alpha1/types.go b/apis/v1alpha1/types.go index 9b23f5e..66e8bc2 100644 --- a/apis/v1alpha1/types.go +++ b/apis/v1alpha1/types.go @@ -27,3 +27,434 @@ var ( _ = &aws.JSONValue{} _ = ackv1alpha1.AWSAccountID("") ) + +// When included in a receipt rule, this action adds a header to the received +// email. +// +// For information about adding a header using a receipt rule, see the Amazon +// SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-add-header.html). +type AddHeaderAction struct { + HeaderName *string `json:"headerName,omitempty"` + HeaderValue *string `json:"headerValue,omitempty"` +} + +// When included in a receipt rule, this action rejects the received email by +// returning a bounce response to the sender and, optionally, publishes a notification +// to Amazon Simple Notification Service (Amazon SNS). +// +// For information about sending a bounce message in response to a received +// email, see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-bounce.html). +type BounceAction struct { + Message *string `json:"message,omitempty"` + Sender *string `json:"sender,omitempty"` + SmtpReplyCode *string `json:"smtpReplyCode,omitempty"` + StatusCode *string `json:"statusCode,omitempty"` + TopicARN *string `json:"topicARN,omitempty"` +} + +// Recipient-related information to include in the Delivery Status Notification +// (DSN) when an email that Amazon SES receives on your behalf bounces. +// +// For information about receiving email through Amazon SES, see the Amazon +// SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email.html). +type BouncedRecipientInfo struct { + Recipient *string `json:"recipient,omitempty"` + RecipientARN *string `json:"recipientARN,omitempty"` +} + +// Contains information associated with an Amazon CloudWatch event destination +// to which email sending events are published. +// +// Event destinations, such as Amazon CloudWatch, are associated with configuration +// sets, which enable you to publish email sending events. For information about +// using configuration sets, see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity.html). +type CloudWatchDestination struct { + DimensionConfigurations []*CloudWatchDimensionConfiguration `json:"dimensionConfigurations,omitempty"` +} + +// Contains the dimension configuration to use when you publish email sending +// events to Amazon CloudWatch. +// +// For information about publishing email sending events to Amazon CloudWatch, +// see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity.html). +type CloudWatchDimensionConfiguration struct { + DefaultDimensionValue *string `json:"defaultDimensionValue,omitempty"` + DimensionName *string `json:"dimensionName,omitempty"` + DimensionValueSource *string `json:"dimensionValueSource,omitempty"` +} + +// The name of the configuration set. +// +// Configuration sets let you create groups of rules that you can apply to the +// emails you send using Amazon SES. For more information about using configuration +// sets, see Using Amazon SES Configuration Sets (https://docs.aws.amazon.com/ses/latest/dg/using-configuration-sets.html) +// in the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/). +type ConfigurationSet_SDK struct { + Name *string `json:"name,omitempty"` +} + +// Contains information about a custom verification email template. +type CustomVerificationEmailTemplate_SDK struct { + FailureRedirectionURL *string `json:"failureRedirectionURL,omitempty"` + FromEmailAddress *string `json:"fromEmailAddress,omitempty"` + SuccessRedirectionURL *string `json:"successRedirectionURL,omitempty"` + TemplateName *string `json:"templateName,omitempty"` + TemplateSubject *string `json:"templateSubject,omitempty"` +} + +// Specifies whether messages that use the configuration set are required to +// use Transport Layer Security (TLS). +type DeliveryOptions struct { + TLSPolicy *string `json:"tlsPolicy,omitempty"` +} + +// Contains information about an event destination. +// +// When you create or update an event destination, you must provide one, and +// only one, destination. The destination can be Amazon CloudWatch, Amazon Kinesis +// Firehose or Amazon Simple Notification Service (Amazon SNS). +// +// Event destinations are associated with configuration sets, which enable you +// to publish email sending events to Amazon CloudWatch, Amazon Kinesis Firehose, +// or Amazon Simple Notification Service (Amazon SNS). For information about +// using configuration sets, see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity.html). +type EventDestination struct { + // Contains information associated with an Amazon CloudWatch event destination + // to which email sending events are published. + // + // Event destinations, such as Amazon CloudWatch, are associated with configuration + // sets, which enable you to publish email sending events. For information about + // using configuration sets, see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity.html). + CloudWatchDestination *CloudWatchDestination `json:"cloudWatchDestination,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + // Contains the delivery stream ARN and the IAM role ARN associated with an + // Amazon Kinesis Firehose event destination. + // + // Event destinations, such as Amazon Kinesis Firehose, are associated with + // configuration sets, which enable you to publish email sending events. For + // information about using configuration sets, see the Amazon SES Developer + // Guide (https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity.html). + KinesisFirehoseDestination *KinesisFirehoseDestination `json:"kinesisFirehoseDestination,omitempty"` + MatchingEventTypes []*string `json:"matchingEventTypes,omitempty"` + Name *string `json:"name,omitempty"` + // Contains the topic ARN associated with an Amazon Simple Notification Service + // (Amazon SNS) event destination. + // + // Event destinations, such as Amazon SNS, are associated with configuration + // sets, which enable you to publish email sending events. For information about + // using configuration sets, see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity.html). + SNSDestination *SNSDestination `json:"snsDestination,omitempty"` +} + +// Represents the DKIM attributes of a verified email address or a domain. +type IdentityDkimAttributes struct { + DkimEnabled *bool `json:"dkimEnabled,omitempty"` +} + +// Represents the notification attributes of an identity, including whether +// an identity has Amazon Simple Notification Service (Amazon SNS) topics set +// for bounce, complaint, and/or delivery notifications, and whether feedback +// forwarding is enabled for bounce and complaint notifications. +type IdentityNotificationAttributes struct { + ForwardingEnabled *bool `json:"forwardingEnabled,omitempty"` + HeadersInBounceNotificationsEnabled *bool `json:"headersInBounceNotificationsEnabled,omitempty"` + HeadersInComplaintNotificationsEnabled *bool `json:"headersInComplaintNotificationsEnabled,omitempty"` + HeadersInDeliveryNotificationsEnabled *bool `json:"headersInDeliveryNotificationsEnabled,omitempty"` +} + +// Contains the delivery stream ARN and the IAM role ARN associated with an +// Amazon Kinesis Firehose event destination. +// +// Event destinations, such as Amazon Kinesis Firehose, are associated with +// configuration sets, which enable you to publish email sending events. For +// information about using configuration sets, see the Amazon SES Developer +// Guide (https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity.html). +type KinesisFirehoseDestination struct { + DeliveryStreamARN *string `json:"deliveryStreamARN,omitempty"` + IAMRoleARN *string `json:"iamRoleARN,omitempty"` +} + +// When included in a receipt rule, this action calls an Amazon Web Services +// Lambda function and, optionally, publishes a notification to Amazon Simple +// Notification Service (Amazon SNS). +// +// To enable Amazon SES to call your Amazon Web Services Lambda function or +// to publish to an Amazon SNS topic of another account, Amazon SES must have +// permission to access those resources. For information about giving permissions, +// see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-permissions.html). +// +// For information about using Amazon Web Services Lambda actions in receipt +// rules, see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-lambda.html). +type LambdaAction struct { + FunctionARN *string `json:"functionARN,omitempty"` + InvocationType *string `json:"invocationType,omitempty"` + TopicARN *string `json:"topicARN,omitempty"` +} + +// An action that Amazon SES can take when it receives an email on behalf of +// one or more email addresses or domains that you own. An instance of this +// data type can represent only one action. +// +// For information about setting up receipt rules, see the Amazon SES Developer +// Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-receipt-rules-console-walkthrough.html). +type ReceiptAction struct { + // When included in a receipt rule, this action adds a header to the received + // email. + // + // For information about adding a header using a receipt rule, see the Amazon + // SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-add-header.html). + AddHeaderAction *AddHeaderAction `json:"addHeaderAction,omitempty"` + // When included in a receipt rule, this action rejects the received email by + // returning a bounce response to the sender and, optionally, publishes a notification + // to Amazon Simple Notification Service (Amazon SNS). + // + // For information about sending a bounce message in response to a received + // email, see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-bounce.html). + BounceAction *BounceAction `json:"bounceAction,omitempty"` + // When included in a receipt rule, this action calls an Amazon Web Services + // Lambda function and, optionally, publishes a notification to Amazon Simple + // Notification Service (Amazon SNS). + // + // To enable Amazon SES to call your Amazon Web Services Lambda function or + // to publish to an Amazon SNS topic of another account, Amazon SES must have + // permission to access those resources. For information about giving permissions, + // see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-permissions.html). + // + // For information about using Amazon Web Services Lambda actions in receipt + // rules, see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-lambda.html). + LambdaAction *LambdaAction `json:"lambdaAction,omitempty"` + // When included in a receipt rule, this action saves the received message to + // an Amazon Simple Storage Service (Amazon S3) bucket and, optionally, publishes + // a notification to Amazon Simple Notification Service (Amazon SNS). + // + // To enable Amazon SES to write emails to your Amazon S3 bucket, use an Amazon + // Web Services KMS key to encrypt your emails, or publish to an Amazon SNS + // topic of another account, Amazon SES must have permission to access those + // resources. For information about granting permissions, see the Amazon SES + // Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-permissions.html). + // + // When you save your emails to an Amazon S3 bucket, the maximum email size + // (including headers) is 40 MB. Emails larger than that bounces. + // + // For information about specifying Amazon S3 actions in receipt rules, see + // the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-s3.html). + S3Action *S3Action `json:"s3Action,omitempty"` + // When included in a receipt rule, this action publishes a notification to + // Amazon Simple Notification Service (Amazon SNS). This action includes a complete + // copy of the email content in the Amazon SNS notifications. Amazon SNS notifications + // for all other actions simply provide information about the email. They do + // not include the email content itself. + // + // If you own the Amazon SNS topic, you don't need to do anything to give Amazon + // SES permission to publish emails to it. However, if you don't own the Amazon + // SNS topic, you need to attach a policy to the topic to give Amazon SES permissions + // to access it. For information about giving permissions, see the Amazon SES + // Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-permissions.html). + // + // You can only publish emails that are 150 KB or less (including the header) + // to Amazon SNS. Larger emails bounce. If you anticipate emails larger than + // 150 KB, use the S3 action instead. + // + // For information about using a receipt rule to publish an Amazon SNS notification, + // see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-sns.html). + SNSAction *SNSAction `json:"snsAction,omitempty"` + // When included in a receipt rule, this action terminates the evaluation of + // the receipt rule set and, optionally, publishes a notification to Amazon + // Simple Notification Service (Amazon SNS). + // + // For information about setting a stop action in a receipt rule, see the Amazon + // SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-stop.html). + StopAction *StopAction `json:"stopAction,omitempty"` + // When included in a receipt rule, this action calls Amazon WorkMail and, optionally, + // publishes a notification to Amazon Simple Notification Service (Amazon SNS). + // It usually isn't necessary to set this up manually, because Amazon WorkMail + // adds the rule automatically during its setup procedure. + // + // For information using a receipt rule to call Amazon WorkMail, see the Amazon + // SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-workmail.html). + WorkmailAction *WorkmailAction `json:"workmailAction,omitempty"` +} + +// A receipt IP address filter enables you to specify whether to accept or reject +// mail originating from an IP address or range of IP addresses. +// +// For information about setting up IP address filters, see the Amazon SES Developer +// Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-ip-filtering-console-walkthrough.html). +type ReceiptFilter_SDK struct { + // A receipt IP address filter enables you to specify whether to accept or reject + // mail originating from an IP address or range of IP addresses. + // + // For information about setting up IP address filters, see the Amazon SES Developer + // Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-ip-filtering-console-walkthrough.html). + IPFilter *ReceiptIPFilter `json:"ipFilter,omitempty"` +} + +// A receipt IP address filter enables you to specify whether to accept or reject +// mail originating from an IP address or range of IP addresses. +// +// For information about setting up IP address filters, see the Amazon SES Developer +// Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-ip-filtering-console-walkthrough.html). +type ReceiptIPFilter struct { + CIDR *string `json:"cidr,omitempty"` + Policy *string `json:"policy,omitempty"` +} + +// Information about a receipt rule set. +// +// A receipt rule set is a collection of rules that specify what Amazon SES +// should do with mail it receives on behalf of your account's verified domains. +// +// For information about setting up receipt rule sets, see the Amazon SES Developer +// Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-concepts.html#receiving-email-concepts-rules). +type ReceiptRuleSetMetadata struct { + CreatedTimestamp *metav1.Time `json:"createdTimestamp,omitempty"` + Name *string `json:"name,omitempty"` +} + +// Receipt rules enable you to specify which actions Amazon SES should take +// when it receives mail on behalf of one or more email addresses or domains +// that you own. +// +// Each receipt rule defines a set of email addresses or domains that it applies +// to. If the email addresses or domains match at least one recipient address +// of the message, Amazon SES executes all of the receipt rule's actions on +// the message. +// +// For information about setting up receipt rules, see the Amazon SES Developer +// Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-receipt-rules-console-walkthrough.html). +type ReceiptRule_SDK struct { + Actions []*ReceiptAction `json:"actions,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + Name *string `json:"name,omitempty"` + Recipients []*string `json:"recipients,omitempty"` + ScanEnabled *bool `json:"scanEnabled,omitempty"` + TLSPolicy *string `json:"tlsPolicy,omitempty"` +} + +// Recipient-related information to include in the Delivery Status Notification +// (DSN) when an email that Amazon SES receives on your behalf bounces. +// +// For information about receiving email through Amazon SES, see the Amazon +// SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email.html). +type RecipientDsnFields struct { + FinalRecipient *string `json:"finalRecipient,omitempty"` +} + +// Contains information about the reputation settings for a configuration set. +type ReputationOptions struct { + LastFreshStart *metav1.Time `json:"lastFreshStart,omitempty"` + ReputationMetricsEnabled *bool `json:"reputationMetricsEnabled,omitempty"` + SendingEnabled *bool `json:"sendingEnabled,omitempty"` +} + +// When included in a receipt rule, this action saves the received message to +// an Amazon Simple Storage Service (Amazon S3) bucket and, optionally, publishes +// a notification to Amazon Simple Notification Service (Amazon SNS). +// +// To enable Amazon SES to write emails to your Amazon S3 bucket, use an Amazon +// Web Services KMS key to encrypt your emails, or publish to an Amazon SNS +// topic of another account, Amazon SES must have permission to access those +// resources. For information about granting permissions, see the Amazon SES +// Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-permissions.html). +// +// When you save your emails to an Amazon S3 bucket, the maximum email size +// (including headers) is 40 MB. Emails larger than that bounces. +// +// For information about specifying Amazon S3 actions in receipt rules, see +// the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-s3.html). +type S3Action struct { + BucketName *string `json:"bucketName,omitempty"` + KMSKeyARN *string `json:"kmsKeyARN,omitempty"` + ObjectKeyPrefix *string `json:"objectKeyPrefix,omitempty"` + TopicARN *string `json:"topicARN,omitempty"` +} + +// When included in a receipt rule, this action publishes a notification to +// Amazon Simple Notification Service (Amazon SNS). This action includes a complete +// copy of the email content in the Amazon SNS notifications. Amazon SNS notifications +// for all other actions simply provide information about the email. They do +// not include the email content itself. +// +// If you own the Amazon SNS topic, you don't need to do anything to give Amazon +// SES permission to publish emails to it. However, if you don't own the Amazon +// SNS topic, you need to attach a policy to the topic to give Amazon SES permissions +// to access it. For information about giving permissions, see the Amazon SES +// Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-permissions.html). +// +// You can only publish emails that are 150 KB or less (including the header) +// to Amazon SNS. Larger emails bounce. If you anticipate emails larger than +// 150 KB, use the S3 action instead. +// +// For information about using a receipt rule to publish an Amazon SNS notification, +// see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-sns.html). +type SNSAction struct { + Encoding *string `json:"encoding,omitempty"` + TopicARN *string `json:"topicARN,omitempty"` +} + +// Contains the topic ARN associated with an Amazon Simple Notification Service +// (Amazon SNS) event destination. +// +// Event destinations, such as Amazon SNS, are associated with configuration +// sets, which enable you to publish email sending events. For information about +// using configuration sets, see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity.html). +type SNSDestination struct { + TopicARN *string `json:"topicARN,omitempty"` + // Reference field for TopicARN + TopicRef *ackv1alpha1.AWSResourceReferenceWrapper `json:"topicRef,omitempty"` +} + +// Represents sending statistics data. Each SendDataPoint contains statistics +// for a 15-minute period of sending activity. +type SendDataPoint struct { + Timestamp *metav1.Time `json:"timestamp,omitempty"` +} + +// When included in a receipt rule, this action terminates the evaluation of +// the receipt rule set and, optionally, publishes a notification to Amazon +// Simple Notification Service (Amazon SNS). +// +// For information about setting a stop action in a receipt rule, see the Amazon +// SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-stop.html). +type StopAction struct { + Scope *string `json:"scope,omitempty"` + TopicARN *string `json:"topicARN,omitempty"` +} + +// Contains information about an email template. +type TemplateMetadata struct { + CreatedTimestamp *metav1.Time `json:"createdTimestamp,omitempty"` + Name *string `json:"name,omitempty"` +} + +// The content of the email, composed of a subject line and either an HTML part +// or a text-only part. +type Template_SDK struct { + HTMLPart *string `json:"htmlPart,omitempty"` + SubjectPart *string `json:"subjectPart,omitempty"` + TemplateName *string `json:"templateName,omitempty"` + TextPart *string `json:"textPart,omitempty"` +} + +// A domain that is used to redirect email recipients to an Amazon SES-operated +// domain. This domain captures open and click events generated by Amazon SES +// emails. +// +// For more information, see Configuring Custom Domains to Handle Open and Click +// Tracking (https://docs.aws.amazon.com/ses/latest/dg/configure-custom-open-click-domains.html) +// in the Amazon SES Developer Guide. +type TrackingOptions struct { + CustomRedirectDomain *string `json:"customRedirectDomain,omitempty"` +} + +// When included in a receipt rule, this action calls Amazon WorkMail and, optionally, +// publishes a notification to Amazon Simple Notification Service (Amazon SNS). +// It usually isn't necessary to set this up manually, because Amazon WorkMail +// adds the rule automatically during its setup procedure. +// +// For information using a receipt rule to call Amazon WorkMail, see the Amazon +// SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-workmail.html). +type WorkmailAction struct { + OrganizationARN *string `json:"organizationARN,omitempty"` + TopicARN *string `json:"topicARN,omitempty"` +} diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000..70625f4 --- /dev/null +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,1715 @@ +//go:build !ignore_autogenerated + +// 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. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + corev1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + 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 *AddHeaderAction) DeepCopyInto(out *AddHeaderAction) { + *out = *in + if in.HeaderName != nil { + in, out := &in.HeaderName, &out.HeaderName + *out = new(string) + **out = **in + } + if in.HeaderValue != nil { + in, out := &in.HeaderValue, &out.HeaderValue + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddHeaderAction. +func (in *AddHeaderAction) DeepCopy() *AddHeaderAction { + if in == nil { + return nil + } + out := new(AddHeaderAction) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BounceAction) DeepCopyInto(out *BounceAction) { + *out = *in + if in.Message != nil { + in, out := &in.Message, &out.Message + *out = new(string) + **out = **in + } + if in.Sender != nil { + in, out := &in.Sender, &out.Sender + *out = new(string) + **out = **in + } + if in.SmtpReplyCode != nil { + in, out := &in.SmtpReplyCode, &out.SmtpReplyCode + *out = new(string) + **out = **in + } + if in.StatusCode != nil { + in, out := &in.StatusCode, &out.StatusCode + *out = new(string) + **out = **in + } + if in.TopicARN != nil { + in, out := &in.TopicARN, &out.TopicARN + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BounceAction. +func (in *BounceAction) DeepCopy() *BounceAction { + if in == nil { + return nil + } + out := new(BounceAction) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BouncedRecipientInfo) DeepCopyInto(out *BouncedRecipientInfo) { + *out = *in + if in.Recipient != nil { + in, out := &in.Recipient, &out.Recipient + *out = new(string) + **out = **in + } + if in.RecipientARN != nil { + in, out := &in.RecipientARN, &out.RecipientARN + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BouncedRecipientInfo. +func (in *BouncedRecipientInfo) DeepCopy() *BouncedRecipientInfo { + if in == nil { + return nil + } + out := new(BouncedRecipientInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CloudWatchDestination) DeepCopyInto(out *CloudWatchDestination) { + *out = *in + if in.DimensionConfigurations != nil { + in, out := &in.DimensionConfigurations, &out.DimensionConfigurations + *out = make([]*CloudWatchDimensionConfiguration, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(CloudWatchDimensionConfiguration) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CloudWatchDestination. +func (in *CloudWatchDestination) DeepCopy() *CloudWatchDestination { + if in == nil { + return nil + } + out := new(CloudWatchDestination) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CloudWatchDimensionConfiguration) DeepCopyInto(out *CloudWatchDimensionConfiguration) { + *out = *in + if in.DefaultDimensionValue != nil { + in, out := &in.DefaultDimensionValue, &out.DefaultDimensionValue + *out = new(string) + **out = **in + } + if in.DimensionName != nil { + in, out := &in.DimensionName, &out.DimensionName + *out = new(string) + **out = **in + } + if in.DimensionValueSource != nil { + in, out := &in.DimensionValueSource, &out.DimensionValueSource + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CloudWatchDimensionConfiguration. +func (in *CloudWatchDimensionConfiguration) DeepCopy() *CloudWatchDimensionConfiguration { + if in == nil { + return nil + } + out := new(CloudWatchDimensionConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigurationSet) DeepCopyInto(out *ConfigurationSet) { + *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 ConfigurationSet. +func (in *ConfigurationSet) DeepCopy() *ConfigurationSet { + if in == nil { + return nil + } + out := new(ConfigurationSet) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ConfigurationSet) 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 *ConfigurationSetEventDestination) DeepCopyInto(out *ConfigurationSetEventDestination) { + *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 ConfigurationSetEventDestination. +func (in *ConfigurationSetEventDestination) DeepCopy() *ConfigurationSetEventDestination { + if in == nil { + return nil + } + out := new(ConfigurationSetEventDestination) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ConfigurationSetEventDestination) 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 *ConfigurationSetEventDestinationList) DeepCopyInto(out *ConfigurationSetEventDestinationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ConfigurationSetEventDestination, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigurationSetEventDestinationList. +func (in *ConfigurationSetEventDestinationList) DeepCopy() *ConfigurationSetEventDestinationList { + if in == nil { + return nil + } + out := new(ConfigurationSetEventDestinationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ConfigurationSetEventDestinationList) 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 *ConfigurationSetEventDestinationSpec) DeepCopyInto(out *ConfigurationSetEventDestinationSpec) { + *out = *in + if in.ConfigurationSetName != nil { + in, out := &in.ConfigurationSetName, &out.ConfigurationSetName + *out = new(string) + **out = **in + } + if in.ConfigurationSetRef != nil { + in, out := &in.ConfigurationSetRef, &out.ConfigurationSetRef + *out = new(corev1alpha1.AWSResourceReferenceWrapper) + (*in).DeepCopyInto(*out) + } + if in.EventDestination != nil { + in, out := &in.EventDestination, &out.EventDestination + *out = new(EventDestination) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigurationSetEventDestinationSpec. +func (in *ConfigurationSetEventDestinationSpec) DeepCopy() *ConfigurationSetEventDestinationSpec { + if in == nil { + return nil + } + out := new(ConfigurationSetEventDestinationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigurationSetEventDestinationStatus) DeepCopyInto(out *ConfigurationSetEventDestinationStatus) { + *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 ConfigurationSetEventDestinationStatus. +func (in *ConfigurationSetEventDestinationStatus) DeepCopy() *ConfigurationSetEventDestinationStatus { + if in == nil { + return nil + } + out := new(ConfigurationSetEventDestinationStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigurationSetList) DeepCopyInto(out *ConfigurationSetList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ConfigurationSet, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigurationSetList. +func (in *ConfigurationSetList) DeepCopy() *ConfigurationSetList { + if in == nil { + return nil + } + out := new(ConfigurationSetList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ConfigurationSetList) 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 *ConfigurationSetSpec) DeepCopyInto(out *ConfigurationSetSpec) { + *out = *in + 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 ConfigurationSetSpec. +func (in *ConfigurationSetSpec) DeepCopy() *ConfigurationSetSpec { + if in == nil { + return nil + } + out := new(ConfigurationSetSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigurationSetStatus) DeepCopyInto(out *ConfigurationSetStatus) { + *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 ConfigurationSetStatus. +func (in *ConfigurationSetStatus) DeepCopy() *ConfigurationSetStatus { + if in == nil { + return nil + } + out := new(ConfigurationSetStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigurationSet_SDK) DeepCopyInto(out *ConfigurationSet_SDK) { + *out = *in + 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 ConfigurationSet_SDK. +func (in *ConfigurationSet_SDK) DeepCopy() *ConfigurationSet_SDK { + if in == nil { + return nil + } + out := new(ConfigurationSet_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 *CustomVerificationEmailTemplate) DeepCopyInto(out *CustomVerificationEmailTemplate) { + *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 CustomVerificationEmailTemplate. +func (in *CustomVerificationEmailTemplate) DeepCopy() *CustomVerificationEmailTemplate { + if in == nil { + return nil + } + out := new(CustomVerificationEmailTemplate) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CustomVerificationEmailTemplate) 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 *CustomVerificationEmailTemplateList) DeepCopyInto(out *CustomVerificationEmailTemplateList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]CustomVerificationEmailTemplate, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomVerificationEmailTemplateList. +func (in *CustomVerificationEmailTemplateList) DeepCopy() *CustomVerificationEmailTemplateList { + if in == nil { + return nil + } + out := new(CustomVerificationEmailTemplateList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CustomVerificationEmailTemplateList) 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 *CustomVerificationEmailTemplateSpec) DeepCopyInto(out *CustomVerificationEmailTemplateSpec) { + *out = *in + if in.FailureRedirectionURL != nil { + in, out := &in.FailureRedirectionURL, &out.FailureRedirectionURL + *out = new(string) + **out = **in + } + if in.FromEmailAddress != nil { + in, out := &in.FromEmailAddress, &out.FromEmailAddress + *out = new(string) + **out = **in + } + if in.SuccessRedirectionURL != nil { + in, out := &in.SuccessRedirectionURL, &out.SuccessRedirectionURL + *out = new(string) + **out = **in + } + if in.TemplateContent != nil { + in, out := &in.TemplateContent, &out.TemplateContent + *out = new(string) + **out = **in + } + if in.TemplateName != nil { + in, out := &in.TemplateName, &out.TemplateName + *out = new(string) + **out = **in + } + if in.TemplateRef != nil { + in, out := &in.TemplateRef, &out.TemplateRef + *out = new(corev1alpha1.AWSResourceReferenceWrapper) + (*in).DeepCopyInto(*out) + } + if in.TemplateSubject != nil { + in, out := &in.TemplateSubject, &out.TemplateSubject + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomVerificationEmailTemplateSpec. +func (in *CustomVerificationEmailTemplateSpec) DeepCopy() *CustomVerificationEmailTemplateSpec { + if in == nil { + return nil + } + out := new(CustomVerificationEmailTemplateSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CustomVerificationEmailTemplateStatus) DeepCopyInto(out *CustomVerificationEmailTemplateStatus) { + *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 CustomVerificationEmailTemplateStatus. +func (in *CustomVerificationEmailTemplateStatus) DeepCopy() *CustomVerificationEmailTemplateStatus { + if in == nil { + return nil + } + out := new(CustomVerificationEmailTemplateStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CustomVerificationEmailTemplate_SDK) DeepCopyInto(out *CustomVerificationEmailTemplate_SDK) { + *out = *in + if in.FailureRedirectionURL != nil { + in, out := &in.FailureRedirectionURL, &out.FailureRedirectionURL + *out = new(string) + **out = **in + } + if in.FromEmailAddress != nil { + in, out := &in.FromEmailAddress, &out.FromEmailAddress + *out = new(string) + **out = **in + } + if in.SuccessRedirectionURL != nil { + in, out := &in.SuccessRedirectionURL, &out.SuccessRedirectionURL + *out = new(string) + **out = **in + } + if in.TemplateName != nil { + in, out := &in.TemplateName, &out.TemplateName + *out = new(string) + **out = **in + } + if in.TemplateSubject != nil { + in, out := &in.TemplateSubject, &out.TemplateSubject + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomVerificationEmailTemplate_SDK. +func (in *CustomVerificationEmailTemplate_SDK) DeepCopy() *CustomVerificationEmailTemplate_SDK { + if in == nil { + return nil + } + out := new(CustomVerificationEmailTemplate_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 *DeliveryOptions) DeepCopyInto(out *DeliveryOptions) { + *out = *in + if in.TLSPolicy != nil { + in, out := &in.TLSPolicy, &out.TLSPolicy + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeliveryOptions. +func (in *DeliveryOptions) DeepCopy() *DeliveryOptions { + if in == nil { + return nil + } + out := new(DeliveryOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EventDestination) DeepCopyInto(out *EventDestination) { + *out = *in + if in.CloudWatchDestination != nil { + in, out := &in.CloudWatchDestination, &out.CloudWatchDestination + *out = new(CloudWatchDestination) + (*in).DeepCopyInto(*out) + } + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } + if in.KinesisFirehoseDestination != nil { + in, out := &in.KinesisFirehoseDestination, &out.KinesisFirehoseDestination + *out = new(KinesisFirehoseDestination) + (*in).DeepCopyInto(*out) + } + if in.MatchingEventTypes != nil { + in, out := &in.MatchingEventTypes, &out.MatchingEventTypes + *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.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + if in.SNSDestination != nil { + in, out := &in.SNSDestination, &out.SNSDestination + *out = new(SNSDestination) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventDestination. +func (in *EventDestination) DeepCopy() *EventDestination { + if in == nil { + return nil + } + out := new(EventDestination) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IdentityDkimAttributes) DeepCopyInto(out *IdentityDkimAttributes) { + *out = *in + if in.DkimEnabled != nil { + in, out := &in.DkimEnabled, &out.DkimEnabled + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IdentityDkimAttributes. +func (in *IdentityDkimAttributes) DeepCopy() *IdentityDkimAttributes { + if in == nil { + return nil + } + out := new(IdentityDkimAttributes) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IdentityNotificationAttributes) DeepCopyInto(out *IdentityNotificationAttributes) { + *out = *in + if in.ForwardingEnabled != nil { + in, out := &in.ForwardingEnabled, &out.ForwardingEnabled + *out = new(bool) + **out = **in + } + if in.HeadersInBounceNotificationsEnabled != nil { + in, out := &in.HeadersInBounceNotificationsEnabled, &out.HeadersInBounceNotificationsEnabled + *out = new(bool) + **out = **in + } + if in.HeadersInComplaintNotificationsEnabled != nil { + in, out := &in.HeadersInComplaintNotificationsEnabled, &out.HeadersInComplaintNotificationsEnabled + *out = new(bool) + **out = **in + } + if in.HeadersInDeliveryNotificationsEnabled != nil { + in, out := &in.HeadersInDeliveryNotificationsEnabled, &out.HeadersInDeliveryNotificationsEnabled + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IdentityNotificationAttributes. +func (in *IdentityNotificationAttributes) DeepCopy() *IdentityNotificationAttributes { + if in == nil { + return nil + } + out := new(IdentityNotificationAttributes) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KinesisFirehoseDestination) DeepCopyInto(out *KinesisFirehoseDestination) { + *out = *in + if in.DeliveryStreamARN != nil { + in, out := &in.DeliveryStreamARN, &out.DeliveryStreamARN + *out = new(string) + **out = **in + } + if in.IAMRoleARN != nil { + in, out := &in.IAMRoleARN, &out.IAMRoleARN + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KinesisFirehoseDestination. +func (in *KinesisFirehoseDestination) DeepCopy() *KinesisFirehoseDestination { + if in == nil { + return nil + } + out := new(KinesisFirehoseDestination) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LambdaAction) DeepCopyInto(out *LambdaAction) { + *out = *in + if in.FunctionARN != nil { + in, out := &in.FunctionARN, &out.FunctionARN + *out = new(string) + **out = **in + } + if in.InvocationType != nil { + in, out := &in.InvocationType, &out.InvocationType + *out = new(string) + **out = **in + } + if in.TopicARN != nil { + in, out := &in.TopicARN, &out.TopicARN + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LambdaAction. +func (in *LambdaAction) DeepCopy() *LambdaAction { + if in == nil { + return nil + } + out := new(LambdaAction) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReceiptAction) DeepCopyInto(out *ReceiptAction) { + *out = *in + if in.AddHeaderAction != nil { + in, out := &in.AddHeaderAction, &out.AddHeaderAction + *out = new(AddHeaderAction) + (*in).DeepCopyInto(*out) + } + if in.BounceAction != nil { + in, out := &in.BounceAction, &out.BounceAction + *out = new(BounceAction) + (*in).DeepCopyInto(*out) + } + if in.LambdaAction != nil { + in, out := &in.LambdaAction, &out.LambdaAction + *out = new(LambdaAction) + (*in).DeepCopyInto(*out) + } + if in.S3Action != nil { + in, out := &in.S3Action, &out.S3Action + *out = new(S3Action) + (*in).DeepCopyInto(*out) + } + if in.SNSAction != nil { + in, out := &in.SNSAction, &out.SNSAction + *out = new(SNSAction) + (*in).DeepCopyInto(*out) + } + if in.StopAction != nil { + in, out := &in.StopAction, &out.StopAction + *out = new(StopAction) + (*in).DeepCopyInto(*out) + } + if in.WorkmailAction != nil { + in, out := &in.WorkmailAction, &out.WorkmailAction + *out = new(WorkmailAction) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReceiptAction. +func (in *ReceiptAction) DeepCopy() *ReceiptAction { + if in == nil { + return nil + } + out := new(ReceiptAction) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReceiptFilter) DeepCopyInto(out *ReceiptFilter) { + *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 ReceiptFilter. +func (in *ReceiptFilter) DeepCopy() *ReceiptFilter { + if in == nil { + return nil + } + out := new(ReceiptFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ReceiptFilter) 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 *ReceiptFilterList) DeepCopyInto(out *ReceiptFilterList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ReceiptFilter, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReceiptFilterList. +func (in *ReceiptFilterList) DeepCopy() *ReceiptFilterList { + if in == nil { + return nil + } + out := new(ReceiptFilterList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ReceiptFilterList) 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 *ReceiptFilterSpec) DeepCopyInto(out *ReceiptFilterSpec) { + *out = *in + if in.Filter != nil { + in, out := &in.Filter, &out.Filter + *out = new(ReceiptFilter_SDK) + (*in).DeepCopyInto(*out) + } + 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 ReceiptFilterSpec. +func (in *ReceiptFilterSpec) DeepCopy() *ReceiptFilterSpec { + if in == nil { + return nil + } + out := new(ReceiptFilterSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReceiptFilterStatus) DeepCopyInto(out *ReceiptFilterStatus) { + *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 ReceiptFilterStatus. +func (in *ReceiptFilterStatus) DeepCopy() *ReceiptFilterStatus { + if in == nil { + return nil + } + out := new(ReceiptFilterStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReceiptFilter_SDK) DeepCopyInto(out *ReceiptFilter_SDK) { + *out = *in + if in.IPFilter != nil { + in, out := &in.IPFilter, &out.IPFilter + *out = new(ReceiptIPFilter) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReceiptFilter_SDK. +func (in *ReceiptFilter_SDK) DeepCopy() *ReceiptFilter_SDK { + if in == nil { + return nil + } + out := new(ReceiptFilter_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 *ReceiptIPFilter) DeepCopyInto(out *ReceiptIPFilter) { + *out = *in + if in.CIDR != nil { + in, out := &in.CIDR, &out.CIDR + *out = new(string) + **out = **in + } + if in.Policy != nil { + in, out := &in.Policy, &out.Policy + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReceiptIPFilter. +func (in *ReceiptIPFilter) DeepCopy() *ReceiptIPFilter { + if in == nil { + return nil + } + out := new(ReceiptIPFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReceiptRule) DeepCopyInto(out *ReceiptRule) { + *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 ReceiptRule. +func (in *ReceiptRule) DeepCopy() *ReceiptRule { + if in == nil { + return nil + } + out := new(ReceiptRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ReceiptRule) 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 *ReceiptRuleList) DeepCopyInto(out *ReceiptRuleList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ReceiptRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReceiptRuleList. +func (in *ReceiptRuleList) DeepCopy() *ReceiptRuleList { + if in == nil { + return nil + } + out := new(ReceiptRuleList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ReceiptRuleList) 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 *ReceiptRuleSet) DeepCopyInto(out *ReceiptRuleSet) { + *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 ReceiptRuleSet. +func (in *ReceiptRuleSet) DeepCopy() *ReceiptRuleSet { + if in == nil { + return nil + } + out := new(ReceiptRuleSet) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ReceiptRuleSet) 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 *ReceiptRuleSetList) DeepCopyInto(out *ReceiptRuleSetList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ReceiptRuleSet, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReceiptRuleSetList. +func (in *ReceiptRuleSetList) DeepCopy() *ReceiptRuleSetList { + if in == nil { + return nil + } + out := new(ReceiptRuleSetList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ReceiptRuleSetList) 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 *ReceiptRuleSetMetadata) DeepCopyInto(out *ReceiptRuleSetMetadata) { + *out = *in + if in.CreatedTimestamp != nil { + in, out := &in.CreatedTimestamp, &out.CreatedTimestamp + *out = (*in).DeepCopy() + } + 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 ReceiptRuleSetMetadata. +func (in *ReceiptRuleSetMetadata) DeepCopy() *ReceiptRuleSetMetadata { + if in == nil { + return nil + } + out := new(ReceiptRuleSetMetadata) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReceiptRuleSetSpec) DeepCopyInto(out *ReceiptRuleSetSpec) { + *out = *in + if in.RuleSetName != nil { + in, out := &in.RuleSetName, &out.RuleSetName + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReceiptRuleSetSpec. +func (in *ReceiptRuleSetSpec) DeepCopy() *ReceiptRuleSetSpec { + if in == nil { + return nil + } + out := new(ReceiptRuleSetSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReceiptRuleSetStatus) DeepCopyInto(out *ReceiptRuleSetStatus) { + *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 ReceiptRuleSetStatus. +func (in *ReceiptRuleSetStatus) DeepCopy() *ReceiptRuleSetStatus { + if in == nil { + return nil + } + out := new(ReceiptRuleSetStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReceiptRuleSpec) DeepCopyInto(out *ReceiptRuleSpec) { + *out = *in + if in.After != nil { + in, out := &in.After, &out.After + *out = new(string) + **out = **in + } + if in.Rule != nil { + in, out := &in.Rule, &out.Rule + *out = new(ReceiptRule_SDK) + (*in).DeepCopyInto(*out) + } + if in.RuleSetName != nil { + in, out := &in.RuleSetName, &out.RuleSetName + *out = new(string) + **out = **in + } + if in.RuleSetRef != nil { + in, out := &in.RuleSetRef, &out.RuleSetRef + *out = new(corev1alpha1.AWSResourceReferenceWrapper) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReceiptRuleSpec. +func (in *ReceiptRuleSpec) DeepCopy() *ReceiptRuleSpec { + if in == nil { + return nil + } + out := new(ReceiptRuleSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReceiptRuleStatus) DeepCopyInto(out *ReceiptRuleStatus) { + *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 ReceiptRuleStatus. +func (in *ReceiptRuleStatus) DeepCopy() *ReceiptRuleStatus { + if in == nil { + return nil + } + out := new(ReceiptRuleStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReceiptRule_SDK) DeepCopyInto(out *ReceiptRule_SDK) { + *out = *in + if in.Actions != nil { + in, out := &in.Actions, &out.Actions + *out = make([]*ReceiptAction, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(ReceiptAction) + (*in).DeepCopyInto(*out) + } + } + } + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + if in.Recipients != nil { + in, out := &in.Recipients, &out.Recipients + *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.ScanEnabled != nil { + in, out := &in.ScanEnabled, &out.ScanEnabled + *out = new(bool) + **out = **in + } + if in.TLSPolicy != nil { + in, out := &in.TLSPolicy, &out.TLSPolicy + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReceiptRule_SDK. +func (in *ReceiptRule_SDK) DeepCopy() *ReceiptRule_SDK { + if in == nil { + return nil + } + out := new(ReceiptRule_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 *RecipientDsnFields) DeepCopyInto(out *RecipientDsnFields) { + *out = *in + if in.FinalRecipient != nil { + in, out := &in.FinalRecipient, &out.FinalRecipient + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RecipientDsnFields. +func (in *RecipientDsnFields) DeepCopy() *RecipientDsnFields { + if in == nil { + return nil + } + out := new(RecipientDsnFields) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReputationOptions) DeepCopyInto(out *ReputationOptions) { + *out = *in + if in.LastFreshStart != nil { + in, out := &in.LastFreshStart, &out.LastFreshStart + *out = (*in).DeepCopy() + } + if in.ReputationMetricsEnabled != nil { + in, out := &in.ReputationMetricsEnabled, &out.ReputationMetricsEnabled + *out = new(bool) + **out = **in + } + if in.SendingEnabled != nil { + in, out := &in.SendingEnabled, &out.SendingEnabled + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReputationOptions. +func (in *ReputationOptions) DeepCopy() *ReputationOptions { + if in == nil { + return nil + } + out := new(ReputationOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *S3Action) DeepCopyInto(out *S3Action) { + *out = *in + if in.BucketName != nil { + in, out := &in.BucketName, &out.BucketName + *out = new(string) + **out = **in + } + if in.KMSKeyARN != nil { + in, out := &in.KMSKeyARN, &out.KMSKeyARN + *out = new(string) + **out = **in + } + if in.ObjectKeyPrefix != nil { + in, out := &in.ObjectKeyPrefix, &out.ObjectKeyPrefix + *out = new(string) + **out = **in + } + if in.TopicARN != nil { + in, out := &in.TopicARN, &out.TopicARN + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new S3Action. +func (in *S3Action) DeepCopy() *S3Action { + if in == nil { + return nil + } + out := new(S3Action) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SNSAction) DeepCopyInto(out *SNSAction) { + *out = *in + if in.Encoding != nil { + in, out := &in.Encoding, &out.Encoding + *out = new(string) + **out = **in + } + if in.TopicARN != nil { + in, out := &in.TopicARN, &out.TopicARN + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SNSAction. +func (in *SNSAction) DeepCopy() *SNSAction { + if in == nil { + return nil + } + out := new(SNSAction) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SNSDestination) DeepCopyInto(out *SNSDestination) { + *out = *in + if in.TopicARN != nil { + in, out := &in.TopicARN, &out.TopicARN + *out = new(string) + **out = **in + } + if in.TopicRef != nil { + in, out := &in.TopicRef, &out.TopicRef + *out = new(corev1alpha1.AWSResourceReferenceWrapper) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SNSDestination. +func (in *SNSDestination) DeepCopy() *SNSDestination { + if in == nil { + return nil + } + out := new(SNSDestination) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SendDataPoint) DeepCopyInto(out *SendDataPoint) { + *out = *in + if in.Timestamp != nil { + in, out := &in.Timestamp, &out.Timestamp + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SendDataPoint. +func (in *SendDataPoint) DeepCopy() *SendDataPoint { + if in == nil { + return nil + } + out := new(SendDataPoint) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StopAction) DeepCopyInto(out *StopAction) { + *out = *in + if in.Scope != nil { + in, out := &in.Scope, &out.Scope + *out = new(string) + **out = **in + } + if in.TopicARN != nil { + in, out := &in.TopicARN, &out.TopicARN + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StopAction. +func (in *StopAction) DeepCopy() *StopAction { + if in == nil { + return nil + } + out := new(StopAction) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Template) DeepCopyInto(out *Template) { + *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 Template. +func (in *Template) DeepCopy() *Template { + if in == nil { + return nil + } + out := new(Template) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Template) 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 *TemplateList) DeepCopyInto(out *TemplateList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Template, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TemplateList. +func (in *TemplateList) DeepCopy() *TemplateList { + if in == nil { + return nil + } + out := new(TemplateList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TemplateList) 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 *TemplateMetadata) DeepCopyInto(out *TemplateMetadata) { + *out = *in + if in.CreatedTimestamp != nil { + in, out := &in.CreatedTimestamp, &out.CreatedTimestamp + *out = (*in).DeepCopy() + } + 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 TemplateMetadata. +func (in *TemplateMetadata) DeepCopy() *TemplateMetadata { + if in == nil { + return nil + } + out := new(TemplateMetadata) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TemplateSpec) DeepCopyInto(out *TemplateSpec) { + *out = *in + if in.HTMLPart != nil { + in, out := &in.HTMLPart, &out.HTMLPart + *out = new(string) + **out = **in + } + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + if in.SubjectPart != nil { + in, out := &in.SubjectPart, &out.SubjectPart + *out = new(string) + **out = **in + } + if in.TextPart != nil { + in, out := &in.TextPart, &out.TextPart + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TemplateSpec. +func (in *TemplateSpec) DeepCopy() *TemplateSpec { + if in == nil { + return nil + } + out := new(TemplateSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TemplateStatus) DeepCopyInto(out *TemplateStatus) { + *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 TemplateStatus. +func (in *TemplateStatus) DeepCopy() *TemplateStatus { + if in == nil { + return nil + } + out := new(TemplateStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Template_SDK) DeepCopyInto(out *Template_SDK) { + *out = *in + if in.HTMLPart != nil { + in, out := &in.HTMLPart, &out.HTMLPart + *out = new(string) + **out = **in + } + if in.SubjectPart != nil { + in, out := &in.SubjectPart, &out.SubjectPart + *out = new(string) + **out = **in + } + if in.TemplateName != nil { + in, out := &in.TemplateName, &out.TemplateName + *out = new(string) + **out = **in + } + if in.TextPart != nil { + in, out := &in.TextPart, &out.TextPart + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Template_SDK. +func (in *Template_SDK) DeepCopy() *Template_SDK { + if in == nil { + return nil + } + out := new(Template_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 *TrackingOptions) DeepCopyInto(out *TrackingOptions) { + *out = *in + if in.CustomRedirectDomain != nil { + in, out := &in.CustomRedirectDomain, &out.CustomRedirectDomain + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrackingOptions. +func (in *TrackingOptions) DeepCopy() *TrackingOptions { + if in == nil { + return nil + } + out := new(TrackingOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkmailAction) DeepCopyInto(out *WorkmailAction) { + *out = *in + if in.OrganizationARN != nil { + in, out := &in.OrganizationARN, &out.OrganizationARN + *out = new(string) + **out = **in + } + if in.TopicARN != nil { + in, out := &in.TopicARN, &out.TopicARN + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkmailAction. +func (in *WorkmailAction) DeepCopy() *WorkmailAction { + if in == nil { + return nil + } + out := new(WorkmailAction) + in.DeepCopyInto(out) + return out +} diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 783a61b..3dcbf64 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -24,6 +24,7 @@ import ( acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" ackrtutil "github.com/aws-controllers-k8s/runtime/pkg/util" ackrtwebhook "github.com/aws-controllers-k8s/runtime/pkg/webhook" + snsapitypes "github.com/aws-controllers-k8s/sns-controller/apis/v1alpha1" flag "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -39,6 +40,14 @@ import ( svcresource "github.com/aws-controllers-k8s/ses-controller/pkg/resource" svcsdk "github.com/aws/aws-sdk-go/service/ses" + _ "github.com/aws-controllers-k8s/ses-controller/pkg/resource/configuration_set" + _ "github.com/aws-controllers-k8s/ses-controller/pkg/resource/configuration_set_event_destination" + _ "github.com/aws-controllers-k8s/ses-controller/pkg/resource/custom_verification_email_template" + _ "github.com/aws-controllers-k8s/ses-controller/pkg/resource/receipt_filter" + _ "github.com/aws-controllers-k8s/ses-controller/pkg/resource/receipt_rule" + _ "github.com/aws-controllers-k8s/ses-controller/pkg/resource/receipt_rule_set" + _ "github.com/aws-controllers-k8s/ses-controller/pkg/resource/template" + "github.com/aws-controllers-k8s/ses-controller/pkg/version" ) @@ -55,6 +64,7 @@ func init() { _ = svctypes.AddToScheme(scheme) _ = ackv1alpha1.AddToScheme(scheme) + _ = snsapitypes.AddToScheme(scheme) } func main() { diff --git a/config/crd/bases/ses.services.k8s.aws_configurationseteventdestinations.yaml b/config/crd/bases/ses.services.k8s.aws_configurationseteventdestinations.yaml new file mode 100644 index 0000000..c224f55 --- /dev/null +++ b/config/crd/bases/ses.services.k8s.aws_configurationseteventdestinations.yaml @@ -0,0 +1,224 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.2 + name: configurationseteventdestinations.ses.services.k8s.aws +spec: + group: ses.services.k8s.aws + names: + kind: ConfigurationSetEventDestination + listKind: ConfigurationSetEventDestinationList + plural: configurationseteventdestinations + singular: configurationseteventdestination + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ConfigurationSetEventDestination is the Schema for the ConfigurationSetEventDestinations + 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: ConfigurationSetEventDestinationSpec defines the desired + state of ConfigurationSetEventDestination. + properties: + configurationSetName: + description: |- + The name of the configuration set that the event destination should be associated + with. + type: string + configurationSetRef: + description: "AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference\ntype to provide more user friendly syntax + for references using 'from' field\nEx:\nAPIIDRef:\n\n\tfrom:\n\t + \ 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 + namespace: + type: string + type: object + type: object + eventDestination: + description: |- + An object that describes the Amazon Web Services service that email sending + event where information is published. + properties: + cloudWatchDestination: + description: |- + Contains information associated with an Amazon CloudWatch event destination + to which email sending events are published. + + Event destinations, such as Amazon CloudWatch, are associated with configuration + sets, which enable you to publish email sending events. For information about + using configuration sets, see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity.html). + properties: + dimensionConfigurations: + items: + description: |- + Contains the dimension configuration to use when you publish email sending + events to Amazon CloudWatch. + + For information about publishing email sending events to Amazon CloudWatch, + see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity.html). + properties: + defaultDimensionValue: + type: string + dimensionName: + type: string + dimensionValueSource: + type: string + type: object + type: array + type: object + enabled: + type: boolean + kinesisFirehoseDestination: + description: |- + Contains the delivery stream ARN and the IAM role ARN associated with an + Amazon Kinesis Firehose event destination. + + Event destinations, such as Amazon Kinesis Firehose, are associated with + configuration sets, which enable you to publish email sending events. For + information about using configuration sets, see the Amazon SES Developer + Guide (https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity.html). + properties: + deliveryStreamARN: + type: string + iamRoleARN: + type: string + type: object + matchingEventTypes: + items: + type: string + type: array + name: + type: string + snsDestination: + description: |- + Contains the topic ARN associated with an Amazon Simple Notification Service + (Amazon SNS) event destination. + + Event destinations, such as Amazon SNS, are associated with configuration + sets, which enable you to publish email sending events. For information about + using configuration sets, see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity.html). + properties: + topicARN: + type: string + topicRef: + description: Reference field for TopicARN + 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 + namespace: + type: string + type: object + type: object + type: object + type: object + required: + - eventDestination + type: object + status: + description: ConfigurationSetEventDestinationStatus defines the observed + state of ConfigurationSetEventDestination + properties: + ackResourceMetadata: + description: |- + All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: |- + ARN is the Amazon Resource Name for the resource. This is a + globally-unique identifier and is set only by the ACK service controller + once the controller has orchestrated the creation of the resource OR + when it has verified that an "adopted" resource (a resource where the + ARN annotation was set by the Kubernetes user on the CR) exists and + matches the supplied CR's Spec field values. + https://github.com/aws/aws-controllers-k8s/issues/270 + type: string + ownerAccountID: + description: |- + OwnerAccountID is the AWS Account ID of the account that owns the + backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: |- + All CRS managed by ACK have a common `Status.Conditions` member that + contains a collection of `ackv1alpha1.Condition` objects that describe + the various terminal states of the CR and its backend AWS service API + resource + items: + description: |- + Condition is the common struct used by all CRDs managed by ACK service + controllers to indicate terminal states of the CR and its backend AWS + service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/ses.services.k8s.aws_configurationsets.yaml b/config/crd/bases/ses.services.k8s.aws_configurationsets.yaml new file mode 100644 index 0000000..db9f597 --- /dev/null +++ b/config/crd/bases/ses.services.k8s.aws_configurationsets.yaml @@ -0,0 +1,125 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.2 + name: configurationsets.ses.services.k8s.aws +spec: + group: ses.services.k8s.aws + names: + kind: ConfigurationSet + listKind: ConfigurationSetList + plural: configurationsets + singular: configurationset + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ConfigurationSet is the Schema for the ConfigurationSets 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: |- + ConfigurationSetSpec defines the desired state of ConfigurationSet. + + The name of the configuration set. + + Configuration sets let you create groups of rules that you can apply to the + emails you send using Amazon SES. For more information about using configuration + sets, see Using Amazon SES Configuration Sets (https://docs.aws.amazon.com/ses/latest/dg/using-configuration-sets.html) + in the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/). + properties: + name: + type: string + type: object + status: + description: ConfigurationSetStatus defines the observed state of ConfigurationSet + properties: + ackResourceMetadata: + description: |- + All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: |- + ARN is the Amazon Resource Name for the resource. This is a + globally-unique identifier and is set only by the ACK service controller + once the controller has orchestrated the creation of the resource OR + when it has verified that an "adopted" resource (a resource where the + ARN annotation was set by the Kubernetes user on the CR) exists and + matches the supplied CR's Spec field values. + https://github.com/aws/aws-controllers-k8s/issues/270 + type: string + ownerAccountID: + description: |- + OwnerAccountID is the AWS Account ID of the account that owns the + backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: |- + All CRS managed by ACK have a common `Status.Conditions` member that + contains a collection of `ackv1alpha1.Condition` objects that describe + the various terminal states of the CR and its backend AWS service API + resource + items: + description: |- + Condition is the common struct used by all CRDs managed by ACK service + controllers to indicate terminal states of the CR and its backend AWS + service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/ses.services.k8s.aws_customverificationemailtemplates.yaml b/config/crd/bases/ses.services.k8s.aws_customverificationemailtemplates.yaml new file mode 100644 index 0000000..f0c6030 --- /dev/null +++ b/config/crd/bases/ses.services.k8s.aws_customverificationemailtemplates.yaml @@ -0,0 +1,171 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.2 + name: customverificationemailtemplates.ses.services.k8s.aws +spec: + group: ses.services.k8s.aws + names: + kind: CustomVerificationEmailTemplate + listKind: CustomVerificationEmailTemplateList + plural: customverificationemailtemplates + singular: customverificationemailtemplate + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: CustomVerificationEmailTemplate is the Schema for the CustomVerificationEmailTemplates + 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: |- + CustomVerificationEmailTemplateSpec defines the desired state of CustomVerificationEmailTemplate. + + Contains information about a custom verification email template. + properties: + failureRedirectionURL: + description: |- + The URL that the recipient of the verification email is sent to if his or + her address is not successfully verified. + type: string + fromEmailAddress: + description: The email address that the custom verification email + is sent from. + type: string + successRedirectionURL: + description: |- + The URL that the recipient of the verification email is sent to if his or + her address is successfully verified. + type: string + templateContent: + description: |- + The content of the custom verification email. The total size of the email + must be less than 10 MB. The message body may contain HTML, with some limitations. + For more information, see Custom Verification Email Frequently Asked Questions + (https://docs.aws.amazon.com/ses/latest/dg/creating-identities.html#send-email-verify-address-custom) + in the Amazon SES Developer Guide. + type: string + templateName: + description: The name of the custom verification email template. + type: string + templateRef: + description: "AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference\ntype to provide more user friendly syntax + for references using 'from' field\nEx:\nAPIIDRef:\n\n\tfrom:\n\t + \ 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 + namespace: + type: string + type: object + type: object + templateSubject: + description: The subject line of the custom verification email. + type: string + required: + - failureRedirectionURL + - fromEmailAddress + - successRedirectionURL + - templateContent + - templateSubject + type: object + status: + description: CustomVerificationEmailTemplateStatus defines the observed + state of CustomVerificationEmailTemplate + properties: + ackResourceMetadata: + description: |- + All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: |- + ARN is the Amazon Resource Name for the resource. This is a + globally-unique identifier and is set only by the ACK service controller + once the controller has orchestrated the creation of the resource OR + when it has verified that an "adopted" resource (a resource where the + ARN annotation was set by the Kubernetes user on the CR) exists and + matches the supplied CR's Spec field values. + https://github.com/aws/aws-controllers-k8s/issues/270 + type: string + ownerAccountID: + description: |- + OwnerAccountID is the AWS Account ID of the account that owns the + backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: |- + All CRS managed by ACK have a common `Status.Conditions` member that + contains a collection of `ackv1alpha1.Condition` objects that describe + the various terminal states of the CR and its backend AWS service API + resource + items: + description: |- + Condition is the common struct used by all CRDs managed by ACK service + controllers to indicate terminal states of the CR and its backend AWS + service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/ses.services.k8s.aws_receiptfilters.yaml b/config/crd/bases/ses.services.k8s.aws_receiptfilters.yaml new file mode 100644 index 0000000..46186a9 --- /dev/null +++ b/config/crd/bases/ses.services.k8s.aws_receiptfilters.yaml @@ -0,0 +1,146 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.2 + name: receiptfilters.ses.services.k8s.aws +spec: + group: ses.services.k8s.aws + names: + kind: ReceiptFilter + listKind: ReceiptFilterList + plural: receiptfilters + singular: receiptfilter + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ReceiptFilter is the Schema for the ReceiptFilters 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: |- + ReceiptFilterSpec defines the desired state of ReceiptFilter. + + A receipt IP address filter enables you to specify whether to accept or reject + mail originating from an IP address or range of IP addresses. + + For information about setting up IP address filters, see the Amazon SES Developer + Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-ip-filtering-console-walkthrough.html). + properties: + filter: + description: |- + A data structure that describes the IP address filter to create, which consists + of a name, an IP address range, and whether to allow or block mail from it. + properties: + ipFilter: + description: |- + A receipt IP address filter enables you to specify whether to accept or reject + mail originating from an IP address or range of IP addresses. + + For information about setting up IP address filters, see the Amazon SES Developer + Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-ip-filtering-console-walkthrough.html). + properties: + cidr: + type: string + policy: + type: string + type: object + type: object + name: + type: string + required: + - filter + - name + type: object + status: + description: ReceiptFilterStatus defines the observed state of ReceiptFilter + properties: + ackResourceMetadata: + description: |- + All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: |- + ARN is the Amazon Resource Name for the resource. This is a + globally-unique identifier and is set only by the ACK service controller + once the controller has orchestrated the creation of the resource OR + when it has verified that an "adopted" resource (a resource where the + ARN annotation was set by the Kubernetes user on the CR) exists and + matches the supplied CR's Spec field values. + https://github.com/aws/aws-controllers-k8s/issues/270 + type: string + ownerAccountID: + description: |- + OwnerAccountID is the AWS Account ID of the account that owns the + backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: |- + All CRS managed by ACK have a common `Status.Conditions` member that + contains a collection of `ackv1alpha1.Condition` objects that describe + the various terminal states of the CR and its backend AWS service API + resource + items: + description: |- + Condition is the common struct used by all CRDs managed by ACK service + controllers to indicate terminal states of the CR and its backend AWS + service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/ses.services.k8s.aws_receiptrules.yaml b/config/crd/bases/ses.services.k8s.aws_receiptrules.yaml new file mode 100644 index 0000000..530d7eb --- /dev/null +++ b/config/crd/bases/ses.services.k8s.aws_receiptrules.yaml @@ -0,0 +1,322 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.2 + name: receiptrules.ses.services.k8s.aws +spec: + group: ses.services.k8s.aws + names: + kind: ReceiptRule + listKind: ReceiptRuleList + plural: receiptrules + singular: receiptrule + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ReceiptRule is the Schema for the ReceiptRules 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: |- + ReceiptRuleSpec defines the desired state of ReceiptRule. + + Receipt rules enable you to specify which actions Amazon SES should take + when it receives mail on behalf of one or more email addresses or domains + that you own. + + Each receipt rule defines a set of email addresses or domains that it applies + to. If the email addresses or domains match at least one recipient address + of the message, Amazon SES executes all of the receipt rule's actions on + the message. + + For information about setting up receipt rules, see the Amazon SES Developer + Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-receipt-rules-console-walkthrough.html). + properties: + after: + description: |- + The name of an existing rule after which the new rule is placed. If this + parameter is null, the new rule is inserted at the beginning of the rule + list. + type: string + rule: + description: |- + A data structure that contains the specified rule's name, actions, recipients, + domains, enabled status, scan status, and TLS policy. + properties: + actions: + items: + description: |- + An action that Amazon SES can take when it receives an email on behalf of + one or more email addresses or domains that you own. An instance of this + data type can represent only one action. + + For information about setting up receipt rules, see the Amazon SES Developer + Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-receipt-rules-console-walkthrough.html). + properties: + addHeaderAction: + description: |- + When included in a receipt rule, this action adds a header to the received + email. + + For information about adding a header using a receipt rule, see the Amazon + SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-add-header.html). + properties: + headerName: + type: string + headerValue: + type: string + type: object + bounceAction: + description: |- + When included in a receipt rule, this action rejects the received email by + returning a bounce response to the sender and, optionally, publishes a notification + to Amazon Simple Notification Service (Amazon SNS). + + For information about sending a bounce message in response to a received + email, see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-bounce.html). + properties: + message: + type: string + sender: + type: string + smtpReplyCode: + type: string + statusCode: + type: string + topicARN: + type: string + type: object + lambdaAction: + description: |- + When included in a receipt rule, this action calls an Amazon Web Services + Lambda function and, optionally, publishes a notification to Amazon Simple + Notification Service (Amazon SNS). + + To enable Amazon SES to call your Amazon Web Services Lambda function or + to publish to an Amazon SNS topic of another account, Amazon SES must have + permission to access those resources. For information about giving permissions, + see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-permissions.html). + + For information about using Amazon Web Services Lambda actions in receipt + rules, see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-lambda.html). + properties: + functionARN: + type: string + invocationType: + type: string + topicARN: + type: string + type: object + s3Action: + description: |- + When included in a receipt rule, this action saves the received message to + an Amazon Simple Storage Service (Amazon S3) bucket and, optionally, publishes + a notification to Amazon Simple Notification Service (Amazon SNS). + + To enable Amazon SES to write emails to your Amazon S3 bucket, use an Amazon + Web Services KMS key to encrypt your emails, or publish to an Amazon SNS + topic of another account, Amazon SES must have permission to access those + resources. For information about granting permissions, see the Amazon SES + Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-permissions.html). + + When you save your emails to an Amazon S3 bucket, the maximum email size + (including headers) is 40 MB. Emails larger than that bounces. + + For information about specifying Amazon S3 actions in receipt rules, see + the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-s3.html). + properties: + bucketName: + type: string + kmsKeyARN: + type: string + objectKeyPrefix: + type: string + topicARN: + type: string + type: object + snsAction: + description: |- + When included in a receipt rule, this action publishes a notification to + Amazon Simple Notification Service (Amazon SNS). This action includes a complete + copy of the email content in the Amazon SNS notifications. Amazon SNS notifications + for all other actions simply provide information about the email. They do + not include the email content itself. + + If you own the Amazon SNS topic, you don't need to do anything to give Amazon + SES permission to publish emails to it. However, if you don't own the Amazon + SNS topic, you need to attach a policy to the topic to give Amazon SES permissions + to access it. For information about giving permissions, see the Amazon SES + Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-permissions.html). + + You can only publish emails that are 150 KB or less (including the header) + to Amazon SNS. Larger emails bounce. If you anticipate emails larger than + 150 KB, use the S3 action instead. + + For information about using a receipt rule to publish an Amazon SNS notification, + see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-sns.html). + properties: + encoding: + type: string + topicARN: + type: string + type: object + stopAction: + description: |- + When included in a receipt rule, this action terminates the evaluation of + the receipt rule set and, optionally, publishes a notification to Amazon + Simple Notification Service (Amazon SNS). + + For information about setting a stop action in a receipt rule, see the Amazon + SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-stop.html). + properties: + scope: + type: string + topicARN: + type: string + type: object + workmailAction: + description: |- + When included in a receipt rule, this action calls Amazon WorkMail and, optionally, + publishes a notification to Amazon Simple Notification Service (Amazon SNS). + It usually isn't necessary to set this up manually, because Amazon WorkMail + adds the rule automatically during its setup procedure. + + For information using a receipt rule to call Amazon WorkMail, see the Amazon + SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-workmail.html). + properties: + organizationARN: + type: string + topicARN: + type: string + type: object + type: object + type: array + enabled: + type: boolean + name: + type: string + recipients: + items: + type: string + type: array + scanEnabled: + type: boolean + tlsPolicy: + type: string + type: object + ruleSetName: + description: The name of the rule set where the receipt rule is added. + type: string + ruleSetRef: + description: "AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference\ntype to provide more user friendly syntax + for references using 'from' field\nEx:\nAPIIDRef:\n\n\tfrom:\n\t + \ 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 + namespace: + type: string + type: object + type: object + required: + - rule + type: object + status: + description: ReceiptRuleStatus defines the observed state of ReceiptRule + properties: + ackResourceMetadata: + description: |- + All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: |- + ARN is the Amazon Resource Name for the resource. This is a + globally-unique identifier and is set only by the ACK service controller + once the controller has orchestrated the creation of the resource OR + when it has verified that an "adopted" resource (a resource where the + ARN annotation was set by the Kubernetes user on the CR) exists and + matches the supplied CR's Spec field values. + https://github.com/aws/aws-controllers-k8s/issues/270 + type: string + ownerAccountID: + description: |- + OwnerAccountID is the AWS Account ID of the account that owns the + backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: |- + All CRS managed by ACK have a common `Status.Conditions` member that + contains a collection of `ackv1alpha1.Condition` objects that describe + the various terminal states of the CR and its backend AWS service API + resource + items: + description: |- + Condition is the common struct used by all CRDs managed by ACK service + controllers to indicate terminal states of the CR and its backend AWS + service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/ses.services.k8s.aws_receiptrulesets.yaml b/config/crd/bases/ses.services.k8s.aws_receiptrulesets.yaml new file mode 100644 index 0000000..352662b --- /dev/null +++ b/config/crd/bases/ses.services.k8s.aws_receiptrulesets.yaml @@ -0,0 +1,128 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.2 + name: receiptrulesets.ses.services.k8s.aws +spec: + group: ses.services.k8s.aws + names: + kind: ReceiptRuleSet + listKind: ReceiptRuleSetList + plural: receiptrulesets + singular: receiptruleset + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ReceiptRuleSet is the Schema for the ReceiptRuleSets 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: ReceiptRuleSetSpec defines the desired state of ReceiptRuleSet. + properties: + ruleSetName: + description: |- + The name of the rule set to create. The name must meet the following requirements: + + * Contain only ASCII letters (a-z, A-Z), numbers (0-9), underscores (_), + or dashes (-). + + * Start and end with a letter or number. + + * Contain 64 characters or fewer. + type: string + required: + - ruleSetName + type: object + status: + description: ReceiptRuleSetStatus defines the observed state of ReceiptRuleSet + properties: + ackResourceMetadata: + description: |- + All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: |- + ARN is the Amazon Resource Name for the resource. This is a + globally-unique identifier and is set only by the ACK service controller + once the controller has orchestrated the creation of the resource OR + when it has verified that an "adopted" resource (a resource where the + ARN annotation was set by the Kubernetes user on the CR) exists and + matches the supplied CR's Spec field values. + https://github.com/aws/aws-controllers-k8s/issues/270 + type: string + ownerAccountID: + description: |- + OwnerAccountID is the AWS Account ID of the account that owns the + backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: |- + All CRS managed by ACK have a common `Status.Conditions` member that + contains a collection of `ackv1alpha1.Condition` objects that describe + the various terminal states of the CR and its backend AWS service API + resource + items: + description: |- + Condition is the common struct used by all CRDs managed by ACK service + controllers to indicate terminal states of the CR and its backend AWS + service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/ses.services.k8s.aws_templates.yaml b/config/crd/bases/ses.services.k8s.aws_templates.yaml new file mode 100644 index 0000000..f8b8512 --- /dev/null +++ b/config/crd/bases/ses.services.k8s.aws_templates.yaml @@ -0,0 +1,127 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.2 + name: templates.ses.services.k8s.aws +spec: + group: ses.services.k8s.aws + names: + kind: Template + listKind: TemplateList + plural: templates + singular: template + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Template is the Schema for the Templates 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: |- + TemplateSpec defines the desired state of Template. + + The content of the email, composed of a subject line and either an HTML part + or a text-only part. + properties: + htmlPart: + type: string + name: + type: string + subjectPart: + type: string + textPart: + type: string + type: object + status: + description: TemplateStatus defines the observed state of Template + properties: + ackResourceMetadata: + description: |- + All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: |- + ARN is the Amazon Resource Name for the resource. This is a + globally-unique identifier and is set only by the ACK service controller + once the controller has orchestrated the creation of the resource OR + when it has verified that an "adopted" resource (a resource where the + ARN annotation was set by the Kubernetes user on the CR) exists and + matches the supplied CR's Spec field values. + https://github.com/aws/aws-controllers-k8s/issues/270 + type: string + ownerAccountID: + description: |- + OwnerAccountID is the AWS Account ID of the account that owns the + backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: |- + All CRS managed by ACK have a common `Status.Conditions` member that + contains a collection of `ackv1alpha1.Condition` objects that describe + the various terminal states of the CR and its backend AWS service API + resource + items: + description: |- + Condition is the common struct used by all CRDs managed by ACK service + controllers to indicate terminal states of the CR and its backend AWS + service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index ea0b795..16b46f1 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -2,3 +2,10 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - common + - bases/ses.services.k8s.aws_configurationsets.yaml + - bases/ses.services.k8s.aws_configurationseteventdestinations.yaml + - bases/ses.services.k8s.aws_customverificationemailtemplates.yaml + - bases/ses.services.k8s.aws_receiptfilters.yaml + - bases/ses.services.k8s.aws_receiptrules.yaml + - bases/ses.services.k8s.aws_receiptrulesets.yaml + - bases/ses.services.k8s.aws_templates.yaml diff --git a/config/iam/recommended-policy-arn b/config/iam/recommended-policy-arn new file mode 100644 index 0000000..480832f --- /dev/null +++ b/config/iam/recommended-policy-arn @@ -0,0 +1 @@ +arn:aws:iam::aws:policy/AmazonSESFullAccess diff --git a/config/rbac/cluster-role-controller.yaml b/config/rbac/cluster-role-controller.yaml index de36fb4..d9e3455 100644 --- a/config/rbac/cluster-role-controller.yaml +++ b/config/rbac/cluster-role-controller.yaml @@ -44,3 +44,43 @@ rules: - get - patch - update +- apiGroups: + - ses.services.k8s.aws + resources: + - configurationseteventdestinations + - configurationsets + - customverificationemailtemplates + - receiptfilters + - receiptrules + - receiptrulesets + - templates + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - ses.services.k8s.aws + resources: + - configurationseteventdestinations/status + - configurationsets/status + - customverificationemailtemplates/status + - receiptfilters/status + - receiptrules/status + - receiptrulesets/status + - templates/status + verbs: + - get + - patch + - update +- apiGroups: + - sns.services.k8s.aws + resources: + - topics + - topics/status + verbs: + - get + - list diff --git a/config/rbac/role-reader.yaml b/config/rbac/role-reader.yaml index 97ef029..8c37ccd 100644 --- a/config/rbac/role-reader.yaml +++ b/config/rbac/role-reader.yaml @@ -9,6 +9,13 @@ rules: - apiGroups: - ses.services.k8s.aws resources: + - configurationsets + - configurationseteventdestinations + - customverificationemailtemplates + - receiptfilters + - receiptrules + - receiptrulesets + - templates verbs: - get - list diff --git a/config/rbac/role-writer.yaml b/config/rbac/role-writer.yaml index 27f58d2..16cb6a4 100644 --- a/config/rbac/role-writer.yaml +++ b/config/rbac/role-writer.yaml @@ -9,6 +9,13 @@ rules: - apiGroups: - ses.services.k8s.aws resources: + - configurationsets + - configurationseteventdestinations + - customverificationemailtemplates + - receiptfilters + - receiptrules + - receiptrulesets + - templates verbs: - create - delete @@ -20,6 +27,13 @@ rules: - apiGroups: - ses.services.k8s.aws resources: + - configurationsets + - configurationseteventdestinations + - customverificationemailtemplates + - receiptfilters + - receiptrules + - receiptrulesets + - templates verbs: - get - patch diff --git a/generator.yaml b/generator.yaml index f4a427f..5af5564 100644 --- a/generator.yaml +++ b/generator.yaml @@ -1,10 +1,235 @@ ignore: resource_names: - - ConfigurationSet - - ConfigurationSetEventDestination - - CustomVerificationEmailTemplate - - ReceiptFilter - - ReceiptRule - - ReceiptRuleSet - - Template +# - ConfigurationSet +# - ConfigurationSetEventDestination +# - CustomVerificationEmailTemplate +# - ReceiptFilter +# - ReceiptRule +# - ReceiptRuleSet +# - Template + + field_paths: + - CreateConfigurationSetInput.ConfigurationSet + - CreateTemplateInput.Template + - CreateReceiptFilterInput.Filter.Name model_name: email + +operations: + DescribeConfigurationSet: + operation_type: + - Get + resource_name: ConfigurationSetEventDestination + +resources: + ConfigurationSet: + fields: + Name: + is_primary_key: true + is_immutable: true + type: string + list_operation: + match_fields: + - Name + update_operation: + custom_method_name: customUpdate + renames: + operations: + DescribeConfigurationSet: + input_fields: + ConfigurationSetName: Name + DeleteConfigurationSet: + input_fields: + ConfigurationSetName: Name + tags: + ignore: true + exceptions: + terminal_codes: + - ConfigurationSetAlreadyExists + - InvalidConfigurationSet + hooks: + sdk_create_post_build_request: + template_path: hooks/configuration_set/sdk_create_post_build_request.go.tpl + sdk_read_one_post_request: + template_path: hooks/configuration_set/sdk_read_one_post_request.go.tpl + + ReceiptRuleSet: + fields: + RuleSetName: + is_primary_key: true + is_immutable: true + update_operation: + custom_method_name: customUpdate + tags: + ignore: true + exceptions: + terminal_codes: + - AlreadyExists + hooks: + sdk_read_one_post_request: + template_path: hooks/receipt_rule_set/sdk_read_one_post_request.go.tpl + + ReceiptRule: + find_operation: + custom_method_name: customFind + fields: + Rule.Name: + is_immutable: true + is_required: true + RuleSetName: + is_primary_key: true + is_immutable: true + is_required: true + references: + resource: ReceiptRuleSet + path: Spec.RuleSetName + tags: + ignore: true + exceptions: + terminal_codes: + - AlreadyExists + - InvalidLambdaFunction + - InvalidS3Configuration + - InvalidSnsTopic + - RuleSetDoesNotExist + hooks: + sdk_file_end: + template_path: hooks/receipt_rule/sdk_file_end.go.tpl + sdk_delete_post_build_request: + template_path: hooks/receipt_rule/sdk_delete_post_build_request.go.tpl + + Template: + fields: + Name: + type: string + is_primary_key: true + is_immutable: true + HTMLPart: + type: string + SubjectPart: + type: string + TextPart: + type: string + tags: + ignore: true + exceptions: + terminal_codes: + - AlreadyExists + - InvalidTemplate + renames: + operations: + GetTemplate: + input_fields: + TemplateName: Name + output_fields: + HtmlPart: HTMLPart + DeleteTemplate: + input_fields: + TemplateName: Name + hooks: + sdk_create_post_build_request: + template_path: hooks/template/sdk_create_post_build_request.go.tpl + sdk_read_one_post_request: + template_path: hooks/template/sdk_read_one_post_request.go.tpl + sdk_update_post_build_request: + template_path: hooks/template/sdk_update_post_build_request.go.tpl + + ReceiptFilter: + list_operation: + match_fields: + - Name + find_operation: + custom_method_name: customFind + update_operation: + custom_method_name: customUpdate + fields: + Name: + is_primary_key: true + is_immutable: true + is_required: true + type: string + Filter.IPFilter.CIDR: + is_immutable: true + Filter.IPFilter.Policy: + is_immutable: true + tags: + ignore: true + exceptions: + terminal_codes: + - AlreadyExists + renames: + operations: + CreateReceiptFilter: + input_fields: + Filter.Name: Name + Name: Filter.Name + DeleteReceiptFilter: + input_fields: + FilterName: Name + hooks: + sdk_file_end: + template_path: hooks/receipt_filter/sdk_file_end.go.tpl + sdk_create_post_build_request: + template_path: hooks/receipt_filter/sdk_create_post_build_request.go.tpl + sdk_read_many_post_request: + template_path: hooks/receipt_filter/sdk_read_many_post_request.go.tpl + + ConfigurationSetEventDestination: + fields: + ConfigurationSetName: + is_primary_key: true + is_immutable: true + references: + resource: ConfigurationSet + path: Spec.Name + EventDestination.Name: + is_required: true + is_immutable: true + EventDestination.MatchingEventTypes: + is_required: true + EventDestination.SNSDestination.TopicARN: + is_required: true + references: + service_name: sns + resource: Topic + path: Status.TopicARN + tags: + ignore: true + exceptions: + terminal_codes: + - ConfigurationSetDoesNotExist + - EventDestinationAlreadyExists + - InvalidCloudWatchDestination + - InvalidFirehoseDestination + - InvalidSNSDestination + - LimitExceeded + hooks: + sdk_read_one_post_request: + template_path: hooks/configuration_set_event_destination/sdk_read_one_post_request.go.tpl + sdk_read_one_pre_build_request: + template_path: hooks/configuration_set_event_destination/sdk_read_one_pre_build_request.go.tpl + sdk_read_one_post_build_request: + template_path: hooks/configuration_set_event_destination/sdk_read_one_post_build_request.go.tpl + sdk_read_one_post_set_output: + template_path: hooks/configuration_set_event_destination/sdk_read_one_post_set_output.go.tpl + sdk_delete_post_build_request: + template_path: hooks/configuration_set_event_destination/sdk_delete_post_build_request.go.tpl + sdk_file_end: + template_path: hooks/configuration_set_event_destination/sdk_file_end.go.tpl + + CustomVerificationEmailTemplate: + fields: + TemplateName: + is_primary_key: true + is_immutable: true + references: + resource: Template + path: Spec.Name + tags: + ignore: true + exceptions: + terminal_codes: + - CustomVerificationEmailInvalidContent + - CustomVerificationEmailTemplateAlreadyExists + hooks: + sdk_read_one_post_request: + template_path: hooks/custom_verification_email_template/sdk_read_one_post_request.go.tpl diff --git a/go.mod b/go.mod index eb0fcae..7f15011 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,11 @@ toolchain go1.22.3 require ( github.com/aws-controllers-k8s/runtime v0.39.0 + github.com/aws-controllers-k8s/sns-controller v1.1.1 github.com/aws/aws-sdk-go v1.55.5 + github.com/go-logr/logr v1.4.2 github.com/spf13/pflag v1.0.5 + k8s.io/api v0.31.0 k8s.io/apimachinery v0.31.0 k8s.io/client-go v0.31.0 sigs.k8s.io/controller-runtime v0.19.0 @@ -22,7 +25,6 @@ require ( github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect @@ -66,7 +68,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.31.0 // indirect k8s.io/apiextensions-apiserver v0.31.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect diff --git a/go.sum b/go.sum index 5fcf3d5..002591b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/aws-controllers-k8s/runtime v0.39.0 h1:IgOXluSzvb4UcDr9eU7SPw5MJnL7kt5R6DuF5Qu9zVQ= github.com/aws-controllers-k8s/runtime v0.39.0/go.mod h1:G07g26y1cxyZO6Ngp+LwXf03CqFyLNL7os4Py4IdyGY= +github.com/aws-controllers-k8s/sns-controller v1.1.1 h1:M0dumcl4YaY/eVGw+uu3/Vuv0rdousCSOWfHT/k9kBM= +github.com/aws-controllers-k8s/sns-controller v1.1.1/go.mod h1:AKNp2q8clvjSvRjgomKAlIf9n8CZAVkz8WZy9inmR9I= github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= diff --git a/helm/crds/ses.services.k8s.aws_configurationseteventdestinations.yaml b/helm/crds/ses.services.k8s.aws_configurationseteventdestinations.yaml new file mode 100644 index 0000000..c224f55 --- /dev/null +++ b/helm/crds/ses.services.k8s.aws_configurationseteventdestinations.yaml @@ -0,0 +1,224 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.2 + name: configurationseteventdestinations.ses.services.k8s.aws +spec: + group: ses.services.k8s.aws + names: + kind: ConfigurationSetEventDestination + listKind: ConfigurationSetEventDestinationList + plural: configurationseteventdestinations + singular: configurationseteventdestination + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ConfigurationSetEventDestination is the Schema for the ConfigurationSetEventDestinations + 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: ConfigurationSetEventDestinationSpec defines the desired + state of ConfigurationSetEventDestination. + properties: + configurationSetName: + description: |- + The name of the configuration set that the event destination should be associated + with. + type: string + configurationSetRef: + description: "AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference\ntype to provide more user friendly syntax + for references using 'from' field\nEx:\nAPIIDRef:\n\n\tfrom:\n\t + \ 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 + namespace: + type: string + type: object + type: object + eventDestination: + description: |- + An object that describes the Amazon Web Services service that email sending + event where information is published. + properties: + cloudWatchDestination: + description: |- + Contains information associated with an Amazon CloudWatch event destination + to which email sending events are published. + + Event destinations, such as Amazon CloudWatch, are associated with configuration + sets, which enable you to publish email sending events. For information about + using configuration sets, see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity.html). + properties: + dimensionConfigurations: + items: + description: |- + Contains the dimension configuration to use when you publish email sending + events to Amazon CloudWatch. + + For information about publishing email sending events to Amazon CloudWatch, + see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity.html). + properties: + defaultDimensionValue: + type: string + dimensionName: + type: string + dimensionValueSource: + type: string + type: object + type: array + type: object + enabled: + type: boolean + kinesisFirehoseDestination: + description: |- + Contains the delivery stream ARN and the IAM role ARN associated with an + Amazon Kinesis Firehose event destination. + + Event destinations, such as Amazon Kinesis Firehose, are associated with + configuration sets, which enable you to publish email sending events. For + information about using configuration sets, see the Amazon SES Developer + Guide (https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity.html). + properties: + deliveryStreamARN: + type: string + iamRoleARN: + type: string + type: object + matchingEventTypes: + items: + type: string + type: array + name: + type: string + snsDestination: + description: |- + Contains the topic ARN associated with an Amazon Simple Notification Service + (Amazon SNS) event destination. + + Event destinations, such as Amazon SNS, are associated with configuration + sets, which enable you to publish email sending events. For information about + using configuration sets, see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity.html). + properties: + topicARN: + type: string + topicRef: + description: Reference field for TopicARN + 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 + namespace: + type: string + type: object + type: object + type: object + type: object + required: + - eventDestination + type: object + status: + description: ConfigurationSetEventDestinationStatus defines the observed + state of ConfigurationSetEventDestination + properties: + ackResourceMetadata: + description: |- + All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: |- + ARN is the Amazon Resource Name for the resource. This is a + globally-unique identifier and is set only by the ACK service controller + once the controller has orchestrated the creation of the resource OR + when it has verified that an "adopted" resource (a resource where the + ARN annotation was set by the Kubernetes user on the CR) exists and + matches the supplied CR's Spec field values. + https://github.com/aws/aws-controllers-k8s/issues/270 + type: string + ownerAccountID: + description: |- + OwnerAccountID is the AWS Account ID of the account that owns the + backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: |- + All CRS managed by ACK have a common `Status.Conditions` member that + contains a collection of `ackv1alpha1.Condition` objects that describe + the various terminal states of the CR and its backend AWS service API + resource + items: + description: |- + Condition is the common struct used by all CRDs managed by ACK service + controllers to indicate terminal states of the CR and its backend AWS + service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/crds/ses.services.k8s.aws_configurationsets.yaml b/helm/crds/ses.services.k8s.aws_configurationsets.yaml new file mode 100644 index 0000000..db9f597 --- /dev/null +++ b/helm/crds/ses.services.k8s.aws_configurationsets.yaml @@ -0,0 +1,125 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.2 + name: configurationsets.ses.services.k8s.aws +spec: + group: ses.services.k8s.aws + names: + kind: ConfigurationSet + listKind: ConfigurationSetList + plural: configurationsets + singular: configurationset + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ConfigurationSet is the Schema for the ConfigurationSets 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: |- + ConfigurationSetSpec defines the desired state of ConfigurationSet. + + The name of the configuration set. + + Configuration sets let you create groups of rules that you can apply to the + emails you send using Amazon SES. For more information about using configuration + sets, see Using Amazon SES Configuration Sets (https://docs.aws.amazon.com/ses/latest/dg/using-configuration-sets.html) + in the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/). + properties: + name: + type: string + type: object + status: + description: ConfigurationSetStatus defines the observed state of ConfigurationSet + properties: + ackResourceMetadata: + description: |- + All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: |- + ARN is the Amazon Resource Name for the resource. This is a + globally-unique identifier and is set only by the ACK service controller + once the controller has orchestrated the creation of the resource OR + when it has verified that an "adopted" resource (a resource where the + ARN annotation was set by the Kubernetes user on the CR) exists and + matches the supplied CR's Spec field values. + https://github.com/aws/aws-controllers-k8s/issues/270 + type: string + ownerAccountID: + description: |- + OwnerAccountID is the AWS Account ID of the account that owns the + backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: |- + All CRS managed by ACK have a common `Status.Conditions` member that + contains a collection of `ackv1alpha1.Condition` objects that describe + the various terminal states of the CR and its backend AWS service API + resource + items: + description: |- + Condition is the common struct used by all CRDs managed by ACK service + controllers to indicate terminal states of the CR and its backend AWS + service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/crds/ses.services.k8s.aws_customverificationemailtemplates.yaml b/helm/crds/ses.services.k8s.aws_customverificationemailtemplates.yaml new file mode 100644 index 0000000..f0c6030 --- /dev/null +++ b/helm/crds/ses.services.k8s.aws_customverificationemailtemplates.yaml @@ -0,0 +1,171 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.2 + name: customverificationemailtemplates.ses.services.k8s.aws +spec: + group: ses.services.k8s.aws + names: + kind: CustomVerificationEmailTemplate + listKind: CustomVerificationEmailTemplateList + plural: customverificationemailtemplates + singular: customverificationemailtemplate + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: CustomVerificationEmailTemplate is the Schema for the CustomVerificationEmailTemplates + 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: |- + CustomVerificationEmailTemplateSpec defines the desired state of CustomVerificationEmailTemplate. + + Contains information about a custom verification email template. + properties: + failureRedirectionURL: + description: |- + The URL that the recipient of the verification email is sent to if his or + her address is not successfully verified. + type: string + fromEmailAddress: + description: The email address that the custom verification email + is sent from. + type: string + successRedirectionURL: + description: |- + The URL that the recipient of the verification email is sent to if his or + her address is successfully verified. + type: string + templateContent: + description: |- + The content of the custom verification email. The total size of the email + must be less than 10 MB. The message body may contain HTML, with some limitations. + For more information, see Custom Verification Email Frequently Asked Questions + (https://docs.aws.amazon.com/ses/latest/dg/creating-identities.html#send-email-verify-address-custom) + in the Amazon SES Developer Guide. + type: string + templateName: + description: The name of the custom verification email template. + type: string + templateRef: + description: "AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference\ntype to provide more user friendly syntax + for references using 'from' field\nEx:\nAPIIDRef:\n\n\tfrom:\n\t + \ 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 + namespace: + type: string + type: object + type: object + templateSubject: + description: The subject line of the custom verification email. + type: string + required: + - failureRedirectionURL + - fromEmailAddress + - successRedirectionURL + - templateContent + - templateSubject + type: object + status: + description: CustomVerificationEmailTemplateStatus defines the observed + state of CustomVerificationEmailTemplate + properties: + ackResourceMetadata: + description: |- + All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: |- + ARN is the Amazon Resource Name for the resource. This is a + globally-unique identifier and is set only by the ACK service controller + once the controller has orchestrated the creation of the resource OR + when it has verified that an "adopted" resource (a resource where the + ARN annotation was set by the Kubernetes user on the CR) exists and + matches the supplied CR's Spec field values. + https://github.com/aws/aws-controllers-k8s/issues/270 + type: string + ownerAccountID: + description: |- + OwnerAccountID is the AWS Account ID of the account that owns the + backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: |- + All CRS managed by ACK have a common `Status.Conditions` member that + contains a collection of `ackv1alpha1.Condition` objects that describe + the various terminal states of the CR and its backend AWS service API + resource + items: + description: |- + Condition is the common struct used by all CRDs managed by ACK service + controllers to indicate terminal states of the CR and its backend AWS + service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/crds/ses.services.k8s.aws_receiptfilters.yaml b/helm/crds/ses.services.k8s.aws_receiptfilters.yaml new file mode 100644 index 0000000..46186a9 --- /dev/null +++ b/helm/crds/ses.services.k8s.aws_receiptfilters.yaml @@ -0,0 +1,146 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.2 + name: receiptfilters.ses.services.k8s.aws +spec: + group: ses.services.k8s.aws + names: + kind: ReceiptFilter + listKind: ReceiptFilterList + plural: receiptfilters + singular: receiptfilter + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ReceiptFilter is the Schema for the ReceiptFilters 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: |- + ReceiptFilterSpec defines the desired state of ReceiptFilter. + + A receipt IP address filter enables you to specify whether to accept or reject + mail originating from an IP address or range of IP addresses. + + For information about setting up IP address filters, see the Amazon SES Developer + Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-ip-filtering-console-walkthrough.html). + properties: + filter: + description: |- + A data structure that describes the IP address filter to create, which consists + of a name, an IP address range, and whether to allow or block mail from it. + properties: + ipFilter: + description: |- + A receipt IP address filter enables you to specify whether to accept or reject + mail originating from an IP address or range of IP addresses. + + For information about setting up IP address filters, see the Amazon SES Developer + Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-ip-filtering-console-walkthrough.html). + properties: + cidr: + type: string + policy: + type: string + type: object + type: object + name: + type: string + required: + - filter + - name + type: object + status: + description: ReceiptFilterStatus defines the observed state of ReceiptFilter + properties: + ackResourceMetadata: + description: |- + All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: |- + ARN is the Amazon Resource Name for the resource. This is a + globally-unique identifier and is set only by the ACK service controller + once the controller has orchestrated the creation of the resource OR + when it has verified that an "adopted" resource (a resource where the + ARN annotation was set by the Kubernetes user on the CR) exists and + matches the supplied CR's Spec field values. + https://github.com/aws/aws-controllers-k8s/issues/270 + type: string + ownerAccountID: + description: |- + OwnerAccountID is the AWS Account ID of the account that owns the + backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: |- + All CRS managed by ACK have a common `Status.Conditions` member that + contains a collection of `ackv1alpha1.Condition` objects that describe + the various terminal states of the CR and its backend AWS service API + resource + items: + description: |- + Condition is the common struct used by all CRDs managed by ACK service + controllers to indicate terminal states of the CR and its backend AWS + service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/crds/ses.services.k8s.aws_receiptrules.yaml b/helm/crds/ses.services.k8s.aws_receiptrules.yaml new file mode 100644 index 0000000..530d7eb --- /dev/null +++ b/helm/crds/ses.services.k8s.aws_receiptrules.yaml @@ -0,0 +1,322 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.2 + name: receiptrules.ses.services.k8s.aws +spec: + group: ses.services.k8s.aws + names: + kind: ReceiptRule + listKind: ReceiptRuleList + plural: receiptrules + singular: receiptrule + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ReceiptRule is the Schema for the ReceiptRules 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: |- + ReceiptRuleSpec defines the desired state of ReceiptRule. + + Receipt rules enable you to specify which actions Amazon SES should take + when it receives mail on behalf of one or more email addresses or domains + that you own. + + Each receipt rule defines a set of email addresses or domains that it applies + to. If the email addresses or domains match at least one recipient address + of the message, Amazon SES executes all of the receipt rule's actions on + the message. + + For information about setting up receipt rules, see the Amazon SES Developer + Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-receipt-rules-console-walkthrough.html). + properties: + after: + description: |- + The name of an existing rule after which the new rule is placed. If this + parameter is null, the new rule is inserted at the beginning of the rule + list. + type: string + rule: + description: |- + A data structure that contains the specified rule's name, actions, recipients, + domains, enabled status, scan status, and TLS policy. + properties: + actions: + items: + description: |- + An action that Amazon SES can take when it receives an email on behalf of + one or more email addresses or domains that you own. An instance of this + data type can represent only one action. + + For information about setting up receipt rules, see the Amazon SES Developer + Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-receipt-rules-console-walkthrough.html). + properties: + addHeaderAction: + description: |- + When included in a receipt rule, this action adds a header to the received + email. + + For information about adding a header using a receipt rule, see the Amazon + SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-add-header.html). + properties: + headerName: + type: string + headerValue: + type: string + type: object + bounceAction: + description: |- + When included in a receipt rule, this action rejects the received email by + returning a bounce response to the sender and, optionally, publishes a notification + to Amazon Simple Notification Service (Amazon SNS). + + For information about sending a bounce message in response to a received + email, see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-bounce.html). + properties: + message: + type: string + sender: + type: string + smtpReplyCode: + type: string + statusCode: + type: string + topicARN: + type: string + type: object + lambdaAction: + description: |- + When included in a receipt rule, this action calls an Amazon Web Services + Lambda function and, optionally, publishes a notification to Amazon Simple + Notification Service (Amazon SNS). + + To enable Amazon SES to call your Amazon Web Services Lambda function or + to publish to an Amazon SNS topic of another account, Amazon SES must have + permission to access those resources. For information about giving permissions, + see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-permissions.html). + + For information about using Amazon Web Services Lambda actions in receipt + rules, see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-lambda.html). + properties: + functionARN: + type: string + invocationType: + type: string + topicARN: + type: string + type: object + s3Action: + description: |- + When included in a receipt rule, this action saves the received message to + an Amazon Simple Storage Service (Amazon S3) bucket and, optionally, publishes + a notification to Amazon Simple Notification Service (Amazon SNS). + + To enable Amazon SES to write emails to your Amazon S3 bucket, use an Amazon + Web Services KMS key to encrypt your emails, or publish to an Amazon SNS + topic of another account, Amazon SES must have permission to access those + resources. For information about granting permissions, see the Amazon SES + Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-permissions.html). + + When you save your emails to an Amazon S3 bucket, the maximum email size + (including headers) is 40 MB. Emails larger than that bounces. + + For information about specifying Amazon S3 actions in receipt rules, see + the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-s3.html). + properties: + bucketName: + type: string + kmsKeyARN: + type: string + objectKeyPrefix: + type: string + topicARN: + type: string + type: object + snsAction: + description: |- + When included in a receipt rule, this action publishes a notification to + Amazon Simple Notification Service (Amazon SNS). This action includes a complete + copy of the email content in the Amazon SNS notifications. Amazon SNS notifications + for all other actions simply provide information about the email. They do + not include the email content itself. + + If you own the Amazon SNS topic, you don't need to do anything to give Amazon + SES permission to publish emails to it. However, if you don't own the Amazon + SNS topic, you need to attach a policy to the topic to give Amazon SES permissions + to access it. For information about giving permissions, see the Amazon SES + Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-permissions.html). + + You can only publish emails that are 150 KB or less (including the header) + to Amazon SNS. Larger emails bounce. If you anticipate emails larger than + 150 KB, use the S3 action instead. + + For information about using a receipt rule to publish an Amazon SNS notification, + see the Amazon SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-sns.html). + properties: + encoding: + type: string + topicARN: + type: string + type: object + stopAction: + description: |- + When included in a receipt rule, this action terminates the evaluation of + the receipt rule set and, optionally, publishes a notification to Amazon + Simple Notification Service (Amazon SNS). + + For information about setting a stop action in a receipt rule, see the Amazon + SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-stop.html). + properties: + scope: + type: string + topicARN: + type: string + type: object + workmailAction: + description: |- + When included in a receipt rule, this action calls Amazon WorkMail and, optionally, + publishes a notification to Amazon Simple Notification Service (Amazon SNS). + It usually isn't necessary to set this up manually, because Amazon WorkMail + adds the rule automatically during its setup procedure. + + For information using a receipt rule to call Amazon WorkMail, see the Amazon + SES Developer Guide (https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-workmail.html). + properties: + organizationARN: + type: string + topicARN: + type: string + type: object + type: object + type: array + enabled: + type: boolean + name: + type: string + recipients: + items: + type: string + type: array + scanEnabled: + type: boolean + tlsPolicy: + type: string + type: object + ruleSetName: + description: The name of the rule set where the receipt rule is added. + type: string + ruleSetRef: + description: "AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference\ntype to provide more user friendly syntax + for references using 'from' field\nEx:\nAPIIDRef:\n\n\tfrom:\n\t + \ 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 + namespace: + type: string + type: object + type: object + required: + - rule + type: object + status: + description: ReceiptRuleStatus defines the observed state of ReceiptRule + properties: + ackResourceMetadata: + description: |- + All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: |- + ARN is the Amazon Resource Name for the resource. This is a + globally-unique identifier and is set only by the ACK service controller + once the controller has orchestrated the creation of the resource OR + when it has verified that an "adopted" resource (a resource where the + ARN annotation was set by the Kubernetes user on the CR) exists and + matches the supplied CR's Spec field values. + https://github.com/aws/aws-controllers-k8s/issues/270 + type: string + ownerAccountID: + description: |- + OwnerAccountID is the AWS Account ID of the account that owns the + backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: |- + All CRS managed by ACK have a common `Status.Conditions` member that + contains a collection of `ackv1alpha1.Condition` objects that describe + the various terminal states of the CR and its backend AWS service API + resource + items: + description: |- + Condition is the common struct used by all CRDs managed by ACK service + controllers to indicate terminal states of the CR and its backend AWS + service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/crds/ses.services.k8s.aws_receiptrulesets.yaml b/helm/crds/ses.services.k8s.aws_receiptrulesets.yaml new file mode 100644 index 0000000..00bb779 --- /dev/null +++ b/helm/crds/ses.services.k8s.aws_receiptrulesets.yaml @@ -0,0 +1,128 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.2 + name: receiptrulesets.ses.services.k8s.aws +spec: + group: ses.services.k8s.aws + names: + kind: ReceiptRuleSet + listKind: ReceiptRuleSetList + plural: receiptrulesets + singular: receiptruleset + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ReceiptRuleSet is the Schema for the ReceiptRuleSets 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: ReceiptRuleSetSpec defines the desired state of ReceiptRuleSet. + properties: + ruleSetName: + description: |- + The name of the rule set to create. The name must meet the following requirements: + + - Contain only ASCII letters (a-z, A-Z), numbers (0-9), underscores (_), + or dashes (-). + + - Start and end with a letter or number. + + - Contain 64 characters or fewer. + type: string + required: + - ruleSetName + type: object + status: + description: ReceiptRuleSetStatus defines the observed state of ReceiptRuleSet + properties: + ackResourceMetadata: + description: |- + All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: |- + ARN is the Amazon Resource Name for the resource. This is a + globally-unique identifier and is set only by the ACK service controller + once the controller has orchestrated the creation of the resource OR + when it has verified that an "adopted" resource (a resource where the + ARN annotation was set by the Kubernetes user on the CR) exists and + matches the supplied CR's Spec field values. + https://github.com/aws/aws-controllers-k8s/issues/270 + type: string + ownerAccountID: + description: |- + OwnerAccountID is the AWS Account ID of the account that owns the + backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: |- + All CRS managed by ACK have a common `Status.Conditions` member that + contains a collection of `ackv1alpha1.Condition` objects that describe + the various terminal states of the CR and its backend AWS service API + resource + items: + description: |- + Condition is the common struct used by all CRDs managed by ACK service + controllers to indicate terminal states of the CR and its backend AWS + service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/crds/ses.services.k8s.aws_templates.yaml b/helm/crds/ses.services.k8s.aws_templates.yaml new file mode 100644 index 0000000..f8b8512 --- /dev/null +++ b/helm/crds/ses.services.k8s.aws_templates.yaml @@ -0,0 +1,127 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.2 + name: templates.ses.services.k8s.aws +spec: + group: ses.services.k8s.aws + names: + kind: Template + listKind: TemplateList + plural: templates + singular: template + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Template is the Schema for the Templates 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: |- + TemplateSpec defines the desired state of Template. + + The content of the email, composed of a subject line and either an HTML part + or a text-only part. + properties: + htmlPart: + type: string + name: + type: string + subjectPart: + type: string + textPart: + type: string + type: object + status: + description: TemplateStatus defines the observed state of Template + properties: + ackResourceMetadata: + description: |- + All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: |- + ARN is the Amazon Resource Name for the resource. This is a + globally-unique identifier and is set only by the ACK service controller + once the controller has orchestrated the creation of the resource OR + when it has verified that an "adopted" resource (a resource where the + ARN annotation was set by the Kubernetes user on the CR) exists and + matches the supplied CR's Spec field values. + https://github.com/aws/aws-controllers-k8s/issues/270 + type: string + ownerAccountID: + description: |- + OwnerAccountID is the AWS Account ID of the account that owns the + backend AWS service API resource. + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: |- + All CRS managed by ACK have a common `Status.Conditions` member that + contains a collection of `ackv1alpha1.Condition` objects that describe + the various terminal states of the CR and its backend AWS service API + resource + items: + description: |- + Condition is the common struct used by all CRDs managed by ACK service + controllers to indicate terminal states of the CR and its backend AWS + service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl index 4bfb662..18d87b3 100644 --- a/helm/templates/_helpers.tpl +++ b/helm/templates/_helpers.tpl @@ -91,6 +91,46 @@ rules: - get - patch - update +- apiGroups: + - ses.services.k8s.aws + resources: + - configurationseteventdestinations + - configurationsets + - customverificationemailtemplates + - receiptfilters + - receiptrules + - receiptrulesets + - templates + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - ses.services.k8s.aws + resources: + - configurationseteventdestinations/status + - configurationsets/status + - customverificationemailtemplates/status + - receiptfilters/status + - receiptrules/status + - receiptrulesets/status + - templates/status + verbs: + - get + - patch + - update +- apiGroups: + - sns.services.k8s.aws + resources: + - topics + - topics/status + verbs: + - get + - list {{- end }} {{/* Convert k/v map to string like: "key1=value1,key2=value2,..." */}} diff --git a/helm/templates/caches-role-binding.yaml b/helm/templates/caches-role-binding.yaml index f0f2914..a2c4873 100644 --- a/helm/templates/caches-role-binding.yaml +++ b/helm/templates/caches-role-binding.yaml @@ -8,7 +8,7 @@ roleRef: name: ack-namespaces-cache-ses-controller subjects: - kind: ServiceAccount - name: ack-ses-controller + name: {{ include "ack-ses-controller.service-account.name" . }} namespace: {{ .Release.Namespace }} --- apiVersion: rbac.authorization.k8s.io/v1 @@ -22,5 +22,5 @@ roleRef: name: ack-configmaps-cache-ses-controller subjects: - kind: ServiceAccount - name: ack-ses-controller - namespace: {{ .Release.Namespace }} \ No newline at end of file + name: {{ include "ack-ses-controller.service-account.name" . }} + namespace: {{ .Release.Namespace }} diff --git a/helm/templates/role-reader.yaml b/helm/templates/role-reader.yaml index 10e6725..8633893 100644 --- a/helm/templates/role-reader.yaml +++ b/helm/templates/role-reader.yaml @@ -9,6 +9,13 @@ rules: - apiGroups: - ses.services.k8s.aws resources: + - configurationsets + - configurationseteventdestinations + - customverificationemailtemplates + - receiptfilters + - receiptrules + - receiptrulesets + - templates verbs: - get - list diff --git a/helm/templates/role-writer.yaml b/helm/templates/role-writer.yaml index 5b5eca0..89dda7c 100644 --- a/helm/templates/role-writer.yaml +++ b/helm/templates/role-writer.yaml @@ -9,6 +9,13 @@ rules: - apiGroups: - ses.services.k8s.aws resources: + - configurationsets + - configurationseteventdestinations + - customverificationemailtemplates + - receiptfilters + - receiptrules + - receiptrulesets + - templates verbs: - create - delete @@ -20,6 +27,13 @@ rules: - apiGroups: - ses.services.k8s.aws resources: + - configurationsets + - configurationseteventdestinations + - customverificationemailtemplates + - receiptfilters + - receiptrules + - receiptrulesets + - templates verbs: - get - patch diff --git a/pkg/resource/configuration_set/delta.go b/pkg/resource/configuration_set/delta.go new file mode 100644 index 0000000..f1dcf1c --- /dev/null +++ b/pkg/resource/configuration_set/delta.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 configuration_set + +import ( + "bytes" + "reflect" + + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" +) + +// Hack to avoid import errors during build... +var ( + _ = &bytes.Buffer{} + _ = &reflect.Method{} + _ = &acktags.Tags{} +) + +// newResourceDelta returns a new `ackcompare.Delta` used to compare two +// resources +func newResourceDelta( + a *resource, + b *resource, +) *ackcompare.Delta { + delta := ackcompare.NewDelta() + if (a == nil && b != nil) || + (a != nil && b == nil) { + delta.Add("", a, b) + return delta + } + + if ackcompare.HasNilDifference(a.ko.Spec.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) + } + } + + return delta +} diff --git a/pkg/resource/configuration_set/descriptor.go b/pkg/resource/configuration_set/descriptor.go new file mode 100644 index 0000000..ee3fad9 --- /dev/null +++ b/pkg/resource/configuration_set/descriptor.go @@ -0,0 +1,155 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package configuration_set + +import ( + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + rtclient "sigs.k8s.io/controller-runtime/pkg/client" + k8sctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +const ( + FinalizerString = "finalizers.ses.services.k8s.aws/ConfigurationSet" +) + +var ( + GroupVersionResource = svcapitypes.GroupVersion.WithResource("configurationsets") + GroupKind = metav1.GroupKind{ + Group: "ses.services.k8s.aws", + Kind: "ConfigurationSet", + } +) + +// resourceDescriptor implements the +// `aws-service-operator-k8s/pkg/types.AWSResourceDescriptor` interface +type resourceDescriptor struct { +} + +// GroupVersionKind returns a Kubernetes schema.GroupVersionKind struct that +// describes the API Group, Version and Kind of CRs described by the descriptor +func (d *resourceDescriptor) GroupVersionKind() schema.GroupVersionKind { + return svcapitypes.GroupVersion.WithKind(GroupKind.Kind) +} + +// EmptyRuntimeObject returns an empty object prototype that may be used in +// apimachinery and k8s client operations +func (d *resourceDescriptor) EmptyRuntimeObject() rtclient.Object { + return &svcapitypes.ConfigurationSet{} +} + +// 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.ConfigurationSet), + } +} + +// 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/configuration_set/hooks.go b/pkg/resource/configuration_set/hooks.go new file mode 100644 index 0000000..8b8d4dd --- /dev/null +++ b/pkg/resource/configuration_set/hooks.go @@ -0,0 +1,18 @@ +package configuration_set + +import ( + "context" + + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + + "github.com/aws-controllers-k8s/ses-controller/pkg/util" +) + +func (rm *resourceManager) customUpdate( + ctx context.Context, + desired *resource, + _ *resource, + delta *ackcompare.Delta, +) (updated *resource, err error) { + return util.ValidateImmutableResource(ctx, rm.getImmutableFieldChanges(delta), desired) +} diff --git a/pkg/resource/configuration_set/identifiers.go b/pkg/resource/configuration_set/identifiers.go new file mode 100644 index 0000000..5a85c15 --- /dev/null +++ b/pkg/resource/configuration_set/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 configuration_set + +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/configuration_set/manager.go b/pkg/resource/configuration_set/manager.go new file mode 100644 index 0000000..0f33a7f --- /dev/null +++ b/pkg/resource/configuration_set/manager.go @@ -0,0 +1,350 @@ +// 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 configuration_set + +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/ses" + svcsdkapi "github.com/aws/aws-sdk-go/service/ses/sesiface" + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +var ( + _ = ackutil.InStrings + _ = acktags.NewTags() + _ = ackrt.MissingImageTagValue + _ = svcapitypes.ConfigurationSet{} +) + +// +kubebuilder:rbac:groups=ses.services.k8s.aws,resources=configurationsets,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=ses.services.k8s.aws,resources=configurationsets/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.SESAPI +} + +// 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:ses:%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 { + + 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/configuration_set/manager_factory.go b/pkg/resource/configuration_set/manager_factory.go new file mode 100644 index 0000000..1d23827 --- /dev/null +++ b/pkg/resource/configuration_set/manager_factory.go @@ -0,0 +1,100 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package configuration_set + +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/ses-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, + roleARN ackv1alpha1.AWSResourceName, +) (acktypes.AWSResourceManager, error) { + // We use the account ID, region, and role ARN to uniquely identify a + // resource manager. This helps us to avoid creating multiple resource + // managers for the same account/region/roleARN combination. + rmId := fmt.Sprintf("%s/%s/%s", id, region, roleARN) + f.RLock() + rm, found := f.rmCache[rmId] + f.RUnlock() + + if found { + return rm, nil + } + + f.Lock() + defer f.Unlock() + + rm, err := newResourceManager(cfg, 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/configuration_set/references.go b/pkg/resource/configuration_set/references.go new file mode 100644 index 0000000..fae22a0 --- /dev/null +++ b/pkg/resource/configuration_set/references.go @@ -0,0 +1,56 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package configuration_set + +import ( + "context" + "sigs.k8s.io/controller-runtime/pkg/client" + + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +// ClearResolvedReferences removes any reference values that were made +// concrete in the spec. It returns a copy of the input AWSResource which +// contains the original *Ref values, but none of their respective concrete +// values. +func (rm *resourceManager) ClearResolvedReferences(res acktypes.AWSResource) acktypes.AWSResource { + ko := rm.concreteResource(res).ko.DeepCopy() + + return &resource{ko} +} + +// ResolveReferences finds if there are any Reference field(s) present +// inside AWSResource passed in the parameter and attempts to resolve those +// reference field(s) into their respective target field(s). It returns a +// copy of the input AWSResource with resolved reference(s), a boolean which +// is set to true if the resource contains any references (regardless of if +// they are resolved successfully) and an error if the passed AWSResource's +// reference field(s) could not be resolved. +func (rm *resourceManager) ResolveReferences( + ctx context.Context, + apiReader client.Reader, + res acktypes.AWSResource, +) (acktypes.AWSResource, bool, error) { + return res, false, nil +} + +// validateReferenceFields validates the reference field and corresponding +// identifier field. +func validateReferenceFields(ko *svcapitypes.ConfigurationSet) error { + return nil +} diff --git a/pkg/resource/configuration_set/resource.go b/pkg/resource/configuration_set/resource.go new file mode 100644 index 0000000..5e25fc7 --- /dev/null +++ b/pkg/resource/configuration_set/resource.go @@ -0,0 +1,100 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package configuration_set + +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/ses-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.ConfigurationSet +} + +// Identifiers returns an AWSResourceIdentifiers object containing various +// identifying information, including the AWS account ID that owns the +// resource, the resource's AWS Resource Name (ARN) +func (r *resource) Identifiers() acktypes.AWSResourceIdentifiers { + return &resourceIdentifiers{r.ko.Status.ACKResourceMetadata} +} + +// IsBeingDeleted returns true if the Kubernetes resource has a non-zero +// deletion timestamp +func (r *resource) IsBeingDeleted() bool { + return !r.ko.DeletionTimestamp.IsZero() +} + +// RuntimeObject returns the Kubernetes apimachinery/runtime representation of +// the AWSResource +func (r *resource) RuntimeObject() rtclient.Object { + return r.ko +} + +// MetaObject returns the Kubernetes apimachinery/apis/meta/v1.Object +// representation of the AWSResource +func (r *resource) MetaObject() metav1.Object { + return r.ko.GetObjectMeta() +} + +// Conditions returns the ACK Conditions collection for the AWSResource +func (r *resource) Conditions() []*ackv1alpha1.Condition { + return r.ko.Status.Conditions +} + +// ReplaceConditions sets the Conditions status field for the resource +func (r *resource) ReplaceConditions(conditions []*ackv1alpha1.Condition) { + r.ko.Status.Conditions = conditions +} + +// SetObjectMeta sets the ObjectMeta field for the resource +func (r *resource) SetObjectMeta(meta metav1.ObjectMeta) { + r.ko.ObjectMeta = meta +} + +// SetStatus will set the Status field for the resource +func (r *resource) SetStatus(desired acktypes.AWSResource) { + r.ko.Status = desired.(*resource).ko.Status +} + +// SetIdentifiers sets the Spec or Status field that is referenced as the unique +// resource identifier +func (r *resource) SetIdentifiers(identifier *ackv1alpha1.AWSIdentifiers) error { + if identifier.NameOrID == "" { + return ackerrors.MissingNameIdentifier + } + r.ko.Spec.Name = &identifier.NameOrID + + return nil +} + +// DeepCopy will return a copy of the resource +func (r *resource) DeepCopy() acktypes.AWSResource { + koCopy := r.ko.DeepCopy() + return &resource{koCopy} +} diff --git a/pkg/resource/configuration_set/sdk.go b/pkg/resource/configuration_set/sdk.go new file mode 100644 index 0000000..2ee7924 --- /dev/null +++ b/pkg/resource/configuration_set/sdk.go @@ -0,0 +1,348 @@ +// 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 configuration_set + +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/ses" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +// Hack to avoid import errors during build... +var ( + _ = &metav1.Time{} + _ = strings.ToLower("") + _ = &aws.JSONValue{} + _ = &svcsdk.SES{} + _ = &svcapitypes.ConfigurationSet{} + _ = 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.requiredFieldsMissingFromReadManyInput(r) { + return nil, ackerr.NotFound + } + + input, err := rm.newListRequestPayload(r) + if err != nil { + return nil, err + } + var resp *svcsdk.ListConfigurationSetsOutput + resp, err = rm.sdkapi.ListConfigurationSetsWithContext(ctx, input) + rm.metrics.RecordAPICall("READ_MANY", "ListConfigurationSets", err) + if err != nil { + if awsErr, ok := ackerr.AWSError(err); ok && awsErr.Code() == "UNKNOWN" { + return nil, ackerr.NotFound + } + return nil, err + } + + // Merge in the information we read from the API call above to the copy of + // the original Kubernetes object we passed to the function + ko := r.ko.DeepCopy() + + found := false + for _, elem := range resp.ConfigurationSets { + if elem.Name != nil { + if ko.Spec.Name != nil { + if *elem.Name != *ko.Spec.Name { + continue + } + } + ko.Spec.Name = elem.Name + } else { + ko.Spec.Name = nil + } + found = true + break + } + if !found { + return nil, ackerr.NotFound + } + + rm.setStatusDefaults(ko) + return &resource{ko}, nil +} + +// requiredFieldsMissingFromReadManyInput returns true if there are any fields +// for the ReadMany Input shape that are required but not present in the +// resource's Spec or Status +func (rm *resourceManager) requiredFieldsMissingFromReadManyInput( + r *resource, +) bool { + return false +} + +// newListRequestPayload returns SDK-specific struct for the HTTP request +// payload of the List API call for the resource +func (rm *resourceManager) newListRequestPayload( + r *resource, +) (*svcsdk.ListConfigurationSetsInput, error) { + res := &svcsdk.ListConfigurationSetsInput{} + + return res, nil +} + +// sdkCreate creates the supplied resource in the backend AWS service API and +// returns a copy of the resource with resource fields (in both Spec and +// Status) filled in with values from the CREATE API operation's Output shape. +func (rm *resourceManager) sdkCreate( + ctx context.Context, + desired *resource, +) (created *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkCreate") + defer func() { + exit(err) + }() + input, err := rm.newCreateRequestPayload(ctx, desired) + if err != nil { + return nil, err + } + input.SetConfigurationSet(&svcsdk.ConfigurationSet{ + Name: desired.ko.Spec.Name, + }) + + var resp *svcsdk.CreateConfigurationSetOutput + _ = resp + resp, err = rm.sdkapi.CreateConfigurationSetWithContext(ctx, input) + rm.metrics.RecordAPICall("CREATE", "CreateConfigurationSet", 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() + + rm.setStatusDefaults(ko) + return &resource{ko}, nil +} + +// newCreateRequestPayload returns an SDK-specific struct for the HTTP request +// payload of the Create API call for the resource +func (rm *resourceManager) newCreateRequestPayload( + ctx context.Context, + r *resource, +) (*svcsdk.CreateConfigurationSetInput, error) { + res := &svcsdk.CreateConfigurationSetInput{} + + return res, nil +} + +// sdkUpdate patches the supplied resource in the backend AWS service API and +// returns a new resource with updated fields. +func (rm *resourceManager) sdkUpdate( + ctx context.Context, + desired *resource, + latest *resource, + delta *ackcompare.Delta, +) (*resource, error) { + return rm.customUpdate(ctx, desired, latest, delta) +} + +// sdkDelete deletes the supplied resource in the backend AWS service API +func (rm *resourceManager) sdkDelete( + ctx context.Context, + r *resource, +) (latest *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkDelete") + defer func() { + exit(err) + }() + input, err := rm.newDeleteRequestPayload(r) + if err != nil { + return nil, err + } + var resp *svcsdk.DeleteConfigurationSetOutput + _ = resp + resp, err = rm.sdkapi.DeleteConfigurationSetWithContext(ctx, input) + rm.metrics.RecordAPICall("DELETE", "DeleteConfigurationSet", 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.DeleteConfigurationSetInput, error) { + res := &svcsdk.DeleteConfigurationSetInput{} + + if r.ko.Spec.Name != nil { + res.SetConfigurationSetName(*r.ko.Spec.Name) + } + + return res, nil +} + +// setStatusDefaults sets default properties into supplied custom resource +func (rm *resourceManager) setStatusDefaults( + ko *svcapitypes.ConfigurationSet, +) { + 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 "ConfigurationSetAlreadyExists", + "InvalidConfigurationSet": + 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.Name") { + fields = append(fields, "Name") + } + + return fields +} diff --git a/pkg/resource/configuration_set_event_destination/delta.go b/pkg/resource/configuration_set_event_destination/delta.go new file mode 100644 index 0000000..6f2cd1d --- /dev/null +++ b/pkg/resource/configuration_set_event_destination/delta.go @@ -0,0 +1,123 @@ +// 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 configuration_set_event_destination + +import ( + "bytes" + "reflect" + + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" +) + +// Hack to avoid import errors during build... +var ( + _ = &bytes.Buffer{} + _ = &reflect.Method{} + _ = &acktags.Tags{} +) + +// newResourceDelta returns a new `ackcompare.Delta` used to compare two +// resources +func newResourceDelta( + a *resource, + b *resource, +) *ackcompare.Delta { + delta := ackcompare.NewDelta() + if (a == nil && b != nil) || + (a != nil && b == nil) { + delta.Add("", a, b) + return delta + } + + if ackcompare.HasNilDifference(a.ko.Spec.ConfigurationSetName, b.ko.Spec.ConfigurationSetName) { + delta.Add("Spec.ConfigurationSetName", a.ko.Spec.ConfigurationSetName, b.ko.Spec.ConfigurationSetName) + } else if a.ko.Spec.ConfigurationSetName != nil && b.ko.Spec.ConfigurationSetName != nil { + if *a.ko.Spec.ConfigurationSetName != *b.ko.Spec.ConfigurationSetName { + delta.Add("Spec.ConfigurationSetName", a.ko.Spec.ConfigurationSetName, b.ko.Spec.ConfigurationSetName) + } + } + if !reflect.DeepEqual(a.ko.Spec.ConfigurationSetRef, b.ko.Spec.ConfigurationSetRef) { + delta.Add("Spec.ConfigurationSetRef", a.ko.Spec.ConfigurationSetRef, b.ko.Spec.ConfigurationSetRef) + } + if ackcompare.HasNilDifference(a.ko.Spec.EventDestination, b.ko.Spec.EventDestination) { + delta.Add("Spec.EventDestination", a.ko.Spec.EventDestination, b.ko.Spec.EventDestination) + } else if a.ko.Spec.EventDestination != nil && b.ko.Spec.EventDestination != nil { + if ackcompare.HasNilDifference(a.ko.Spec.EventDestination.CloudWatchDestination, b.ko.Spec.EventDestination.CloudWatchDestination) { + delta.Add("Spec.EventDestination.CloudWatchDestination", a.ko.Spec.EventDestination.CloudWatchDestination, b.ko.Spec.EventDestination.CloudWatchDestination) + } else if a.ko.Spec.EventDestination.CloudWatchDestination != nil && b.ko.Spec.EventDestination.CloudWatchDestination != nil { + if len(a.ko.Spec.EventDestination.CloudWatchDestination.DimensionConfigurations) != len(b.ko.Spec.EventDestination.CloudWatchDestination.DimensionConfigurations) { + delta.Add("Spec.EventDestination.CloudWatchDestination.DimensionConfigurations", a.ko.Spec.EventDestination.CloudWatchDestination.DimensionConfigurations, b.ko.Spec.EventDestination.CloudWatchDestination.DimensionConfigurations) + } else if len(a.ko.Spec.EventDestination.CloudWatchDestination.DimensionConfigurations) > 0 { + if !reflect.DeepEqual(a.ko.Spec.EventDestination.CloudWatchDestination.DimensionConfigurations, b.ko.Spec.EventDestination.CloudWatchDestination.DimensionConfigurations) { + delta.Add("Spec.EventDestination.CloudWatchDestination.DimensionConfigurations", a.ko.Spec.EventDestination.CloudWatchDestination.DimensionConfigurations, b.ko.Spec.EventDestination.CloudWatchDestination.DimensionConfigurations) + } + } + } + if ackcompare.HasNilDifference(a.ko.Spec.EventDestination.Enabled, b.ko.Spec.EventDestination.Enabled) { + delta.Add("Spec.EventDestination.Enabled", a.ko.Spec.EventDestination.Enabled, b.ko.Spec.EventDestination.Enabled) + } else if a.ko.Spec.EventDestination.Enabled != nil && b.ko.Spec.EventDestination.Enabled != nil { + if *a.ko.Spec.EventDestination.Enabled != *b.ko.Spec.EventDestination.Enabled { + delta.Add("Spec.EventDestination.Enabled", a.ko.Spec.EventDestination.Enabled, b.ko.Spec.EventDestination.Enabled) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.EventDestination.KinesisFirehoseDestination, b.ko.Spec.EventDestination.KinesisFirehoseDestination) { + delta.Add("Spec.EventDestination.KinesisFirehoseDestination", a.ko.Spec.EventDestination.KinesisFirehoseDestination, b.ko.Spec.EventDestination.KinesisFirehoseDestination) + } else if a.ko.Spec.EventDestination.KinesisFirehoseDestination != nil && b.ko.Spec.EventDestination.KinesisFirehoseDestination != nil { + if ackcompare.HasNilDifference(a.ko.Spec.EventDestination.KinesisFirehoseDestination.DeliveryStreamARN, b.ko.Spec.EventDestination.KinesisFirehoseDestination.DeliveryStreamARN) { + delta.Add("Spec.EventDestination.KinesisFirehoseDestination.DeliveryStreamARN", a.ko.Spec.EventDestination.KinesisFirehoseDestination.DeliveryStreamARN, b.ko.Spec.EventDestination.KinesisFirehoseDestination.DeliveryStreamARN) + } else if a.ko.Spec.EventDestination.KinesisFirehoseDestination.DeliveryStreamARN != nil && b.ko.Spec.EventDestination.KinesisFirehoseDestination.DeliveryStreamARN != nil { + if *a.ko.Spec.EventDestination.KinesisFirehoseDestination.DeliveryStreamARN != *b.ko.Spec.EventDestination.KinesisFirehoseDestination.DeliveryStreamARN { + delta.Add("Spec.EventDestination.KinesisFirehoseDestination.DeliveryStreamARN", a.ko.Spec.EventDestination.KinesisFirehoseDestination.DeliveryStreamARN, b.ko.Spec.EventDestination.KinesisFirehoseDestination.DeliveryStreamARN) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.EventDestination.KinesisFirehoseDestination.IAMRoleARN, b.ko.Spec.EventDestination.KinesisFirehoseDestination.IAMRoleARN) { + delta.Add("Spec.EventDestination.KinesisFirehoseDestination.IAMRoleARN", a.ko.Spec.EventDestination.KinesisFirehoseDestination.IAMRoleARN, b.ko.Spec.EventDestination.KinesisFirehoseDestination.IAMRoleARN) + } else if a.ko.Spec.EventDestination.KinesisFirehoseDestination.IAMRoleARN != nil && b.ko.Spec.EventDestination.KinesisFirehoseDestination.IAMRoleARN != nil { + if *a.ko.Spec.EventDestination.KinesisFirehoseDestination.IAMRoleARN != *b.ko.Spec.EventDestination.KinesisFirehoseDestination.IAMRoleARN { + delta.Add("Spec.EventDestination.KinesisFirehoseDestination.IAMRoleARN", a.ko.Spec.EventDestination.KinesisFirehoseDestination.IAMRoleARN, b.ko.Spec.EventDestination.KinesisFirehoseDestination.IAMRoleARN) + } + } + } + if len(a.ko.Spec.EventDestination.MatchingEventTypes) != len(b.ko.Spec.EventDestination.MatchingEventTypes) { + delta.Add("Spec.EventDestination.MatchingEventTypes", a.ko.Spec.EventDestination.MatchingEventTypes, b.ko.Spec.EventDestination.MatchingEventTypes) + } else if len(a.ko.Spec.EventDestination.MatchingEventTypes) > 0 { + if !ackcompare.SliceStringPEqual(a.ko.Spec.EventDestination.MatchingEventTypes, b.ko.Spec.EventDestination.MatchingEventTypes) { + delta.Add("Spec.EventDestination.MatchingEventTypes", a.ko.Spec.EventDestination.MatchingEventTypes, b.ko.Spec.EventDestination.MatchingEventTypes) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.EventDestination.Name, b.ko.Spec.EventDestination.Name) { + delta.Add("Spec.EventDestination.Name", a.ko.Spec.EventDestination.Name, b.ko.Spec.EventDestination.Name) + } else if a.ko.Spec.EventDestination.Name != nil && b.ko.Spec.EventDestination.Name != nil { + if *a.ko.Spec.EventDestination.Name != *b.ko.Spec.EventDestination.Name { + delta.Add("Spec.EventDestination.Name", a.ko.Spec.EventDestination.Name, b.ko.Spec.EventDestination.Name) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.EventDestination.SNSDestination, b.ko.Spec.EventDestination.SNSDestination) { + delta.Add("Spec.EventDestination.SNSDestination", a.ko.Spec.EventDestination.SNSDestination, b.ko.Spec.EventDestination.SNSDestination) + } else if a.ko.Spec.EventDestination.SNSDestination != nil && b.ko.Spec.EventDestination.SNSDestination != nil { + if ackcompare.HasNilDifference(a.ko.Spec.EventDestination.SNSDestination.TopicARN, b.ko.Spec.EventDestination.SNSDestination.TopicARN) { + delta.Add("Spec.EventDestination.SNSDestination.TopicARN", a.ko.Spec.EventDestination.SNSDestination.TopicARN, b.ko.Spec.EventDestination.SNSDestination.TopicARN) + } else if a.ko.Spec.EventDestination.SNSDestination.TopicARN != nil && b.ko.Spec.EventDestination.SNSDestination.TopicARN != nil { + if *a.ko.Spec.EventDestination.SNSDestination.TopicARN != *b.ko.Spec.EventDestination.SNSDestination.TopicARN { + delta.Add("Spec.EventDestination.SNSDestination.TopicARN", a.ko.Spec.EventDestination.SNSDestination.TopicARN, b.ko.Spec.EventDestination.SNSDestination.TopicARN) + } + } + } + } + + return delta +} diff --git a/pkg/resource/configuration_set_event_destination/descriptor.go b/pkg/resource/configuration_set_event_destination/descriptor.go new file mode 100644 index 0000000..065d92b --- /dev/null +++ b/pkg/resource/configuration_set_event_destination/descriptor.go @@ -0,0 +1,155 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package configuration_set_event_destination + +import ( + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + rtclient "sigs.k8s.io/controller-runtime/pkg/client" + k8sctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +const ( + FinalizerString = "finalizers.ses.services.k8s.aws/ConfigurationSetEventDestination" +) + +var ( + GroupVersionResource = svcapitypes.GroupVersion.WithResource("configurationseteventdestinations") + GroupKind = metav1.GroupKind{ + Group: "ses.services.k8s.aws", + Kind: "ConfigurationSetEventDestination", + } +) + +// resourceDescriptor implements the +// `aws-service-operator-k8s/pkg/types.AWSResourceDescriptor` interface +type resourceDescriptor struct { +} + +// GroupVersionKind returns a Kubernetes schema.GroupVersionKind struct that +// describes the API Group, Version and Kind of CRs described by the descriptor +func (d *resourceDescriptor) GroupVersionKind() schema.GroupVersionKind { + return svcapitypes.GroupVersion.WithKind(GroupKind.Kind) +} + +// EmptyRuntimeObject returns an empty object prototype that may be used in +// apimachinery and k8s client operations +func (d *resourceDescriptor) EmptyRuntimeObject() rtclient.Object { + return &svcapitypes.ConfigurationSetEventDestination{} +} + +// 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.ConfigurationSetEventDestination), + } +} + +// 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/configuration_set_event_destination/hooks.go b/pkg/resource/configuration_set_event_destination/hooks.go new file mode 100644 index 0000000..e3fada2 --- /dev/null +++ b/pkg/resource/configuration_set_event_destination/hooks.go @@ -0,0 +1,29 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package configuration_set_event_destination + +import ( + svcsdk "github.com/aws/aws-sdk-go/service/ses" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +func getEventDestination(ko *svcapitypes.ConfigurationSetEventDestination, resp *svcsdk.DescribeConfigurationSetOutput) *svcapitypes.EventDestination { + for _, eventDestination := range resp.EventDestinations { + if *eventDestination.Name == *ko.Spec.EventDestination.Name { + return setResourceEventDestination(resp.EventDestinations[0]) + } + } + return nil +} diff --git a/pkg/resource/configuration_set_event_destination/identifiers.go b/pkg/resource/configuration_set_event_destination/identifiers.go new file mode 100644 index 0000000..a12ed1b --- /dev/null +++ b/pkg/resource/configuration_set_event_destination/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 configuration_set_event_destination + +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/configuration_set_event_destination/manager.go b/pkg/resource/configuration_set_event_destination/manager.go new file mode 100644 index 0000000..cde70d9 --- /dev/null +++ b/pkg/resource/configuration_set_event_destination/manager.go @@ -0,0 +1,350 @@ +// 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 configuration_set_event_destination + +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/ses" + svcsdkapi "github.com/aws/aws-sdk-go/service/ses/sesiface" + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +var ( + _ = ackutil.InStrings + _ = acktags.NewTags() + _ = ackrt.MissingImageTagValue + _ = svcapitypes.ConfigurationSetEventDestination{} +) + +// +kubebuilder:rbac:groups=ses.services.k8s.aws,resources=configurationseteventdestinations,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=ses.services.k8s.aws,resources=configurationseteventdestinations/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.SESAPI +} + +// 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:ses:%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 { + + 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/configuration_set_event_destination/manager_factory.go b/pkg/resource/configuration_set_event_destination/manager_factory.go new file mode 100644 index 0000000..69d4e3c --- /dev/null +++ b/pkg/resource/configuration_set_event_destination/manager_factory.go @@ -0,0 +1,100 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package configuration_set_event_destination + +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/ses-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, + roleARN ackv1alpha1.AWSResourceName, +) (acktypes.AWSResourceManager, error) { + // We use the account ID, region, and role ARN to uniquely identify a + // resource manager. This helps us to avoid creating multiple resource + // managers for the same account/region/roleARN combination. + rmId := fmt.Sprintf("%s/%s/%s", id, region, roleARN) + f.RLock() + rm, found := f.rmCache[rmId] + f.RUnlock() + + if found { + return rm, nil + } + + f.Lock() + defer f.Unlock() + + rm, err := newResourceManager(cfg, 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/configuration_set_event_destination/references.go b/pkg/resource/configuration_set_event_destination/references.go new file mode 100644 index 0000000..5306330 --- /dev/null +++ b/pkg/resource/configuration_set_event_destination/references.go @@ -0,0 +1,282 @@ +// 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 configuration_set_event_destination + +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" + ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + snsapitypes "github.com/aws-controllers-k8s/sns-controller/apis/v1alpha1" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +// +kubebuilder:rbac:groups=sns.services.k8s.aws,resources=topics,verbs=get;list +// +kubebuilder:rbac:groups=sns.services.k8s.aws,resources=topics/status,verbs=get;list + +// ClearResolvedReferences removes any reference values that were made +// concrete in the spec. It returns a copy of the input AWSResource which +// contains the original *Ref values, but none of their respective concrete +// values. +func (rm *resourceManager) ClearResolvedReferences(res acktypes.AWSResource) acktypes.AWSResource { + ko := rm.concreteResource(res).ko.DeepCopy() + + if ko.Spec.ConfigurationSetRef != nil { + ko.Spec.ConfigurationSetName = nil + } + + if ko.Spec.EventDestination != nil { + if ko.Spec.EventDestination.SNSDestination != nil { + if ko.Spec.EventDestination.SNSDestination.TopicRef != nil { + ko.Spec.EventDestination.SNSDestination.TopicARN = nil + } + } + } + + return &resource{ko} +} + +// ResolveReferences finds if there are any Reference field(s) present +// inside AWSResource passed in the parameter and attempts to resolve those +// reference field(s) into their respective target field(s). It returns a +// copy of the input AWSResource with resolved reference(s), a boolean which +// is set to true if the resource contains any references (regardless of if +// they are resolved successfully) and an error if the passed AWSResource's +// reference field(s) could not be resolved. +func (rm *resourceManager) ResolveReferences( + ctx context.Context, + apiReader client.Reader, + res acktypes.AWSResource, +) (acktypes.AWSResource, bool, error) { + ko := rm.concreteResource(res).ko + + resourceHasReferences := false + err := validateReferenceFields(ko) + if fieldHasReferences, err := rm.resolveReferenceForConfigurationSetName(ctx, apiReader, ko); err != nil { + return &resource{ko}, (resourceHasReferences || fieldHasReferences), err + } else { + resourceHasReferences = resourceHasReferences || fieldHasReferences + } + + if fieldHasReferences, err := rm.resolveReferenceForEventDestination_SNSDestination_TopicARN(ctx, apiReader, ko); err != nil { + return &resource{ko}, (resourceHasReferences || fieldHasReferences), err + } else { + resourceHasReferences = resourceHasReferences || fieldHasReferences + } + + return &resource{ko}, resourceHasReferences, err +} + +// validateReferenceFields validates the reference field and corresponding +// identifier field. +func validateReferenceFields(ko *svcapitypes.ConfigurationSetEventDestination) error { + + if ko.Spec.ConfigurationSetRef != nil && ko.Spec.ConfigurationSetName != nil { + return ackerr.ResourceReferenceAndIDNotSupportedFor("ConfigurationSetName", "ConfigurationSetRef") + } + if ko.Spec.ConfigurationSetRef == nil && ko.Spec.ConfigurationSetName == nil { + return ackerr.ResourceReferenceOrIDRequiredFor("ConfigurationSetName", "ConfigurationSetRef") + } + + if ko.Spec.EventDestination != nil { + if ko.Spec.EventDestination.SNSDestination != nil { + if ko.Spec.EventDestination.SNSDestination.TopicRef != nil && ko.Spec.EventDestination.SNSDestination.TopicARN != nil { + return ackerr.ResourceReferenceAndIDNotSupportedFor("EventDestination.SNSDestination.TopicARN", "EventDestination.SNSDestination.TopicRef") + } + if ko.Spec.EventDestination.SNSDestination.TopicRef == nil && ko.Spec.EventDestination.SNSDestination.TopicARN == nil { + return ackerr.ResourceReferenceOrIDRequiredFor("TopicARN", "TopicRef") + } + } + } + return nil +} + +// resolveReferenceForConfigurationSetName reads the resource referenced +// from ConfigurationSetRef field and sets the ConfigurationSetName +// from referenced resource. Returns a boolean indicating whether a reference +// contains references, or an error +func (rm *resourceManager) resolveReferenceForConfigurationSetName( + ctx context.Context, + apiReader client.Reader, + ko *svcapitypes.ConfigurationSetEventDestination, +) (hasReferences bool, err error) { + if ko.Spec.ConfigurationSetRef != nil && ko.Spec.ConfigurationSetRef.From != nil { + hasReferences = true + arr := ko.Spec.ConfigurationSetRef.From + if arr.Name == nil || *arr.Name == "" { + return hasReferences, fmt.Errorf("provided resource reference is nil or empty: ConfigurationSetRef") + } + namespace := ko.ObjectMeta.GetNamespace() + if arr.Namespace != nil && *arr.Namespace != "" { + namespace = *arr.Namespace + } + obj := &svcapitypes.ConfigurationSet{} + if err := getReferencedResourceState_ConfigurationSet(ctx, apiReader, obj, *arr.Name, namespace); err != nil { + return hasReferences, err + } + ko.Spec.ConfigurationSetName = (*string)(obj.Spec.Name) + } + + return hasReferences, nil +} + +// getReferencedResourceState_ConfigurationSet looks up whether a referenced resource +// exists and is in a ACK.ResourceSynced=True state. If the referenced resource does exist and is +// in a Synced state, returns nil, otherwise returns `ackerr.ResourceReferenceTerminalFor` or +// `ResourceReferenceNotSyncedFor` depending on if the resource is in a Terminal state. +func getReferencedResourceState_ConfigurationSet( + ctx context.Context, + apiReader client.Reader, + obj *svcapitypes.ConfigurationSet, + name string, // the Kubernetes name of the referenced resource + namespace string, // the Kubernetes namespace of the referenced resource +) error { + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + err := apiReader.Get(ctx, namespacedName, obj) + if err != nil { + return err + } + var refResourceTerminal bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeTerminal && + cond.Status == corev1.ConditionTrue { + return ackerr.ResourceReferenceTerminalFor( + "ConfigurationSet", + namespace, name) + } + } + if refResourceTerminal { + return ackerr.ResourceReferenceTerminalFor( + "ConfigurationSet", + namespace, name) + } + var refResourceSynced bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeResourceSynced && + cond.Status == corev1.ConditionTrue { + refResourceSynced = true + } + } + if !refResourceSynced { + return ackerr.ResourceReferenceNotSyncedFor( + "ConfigurationSet", + namespace, name) + } + if obj.Spec.Name == nil { + return ackerr.ResourceReferenceMissingTargetFieldFor( + "ConfigurationSet", + namespace, name, + "Spec.Name") + } + return nil +} + +// resolveReferenceForEventDestination_SNSDestination_TopicARN reads the resource referenced +// from EventDestination.SNSDestination.TopicRef field and sets the EventDestination.SNSDestination.TopicARN +// from referenced resource. Returns a boolean indicating whether a reference +// contains references, or an error +func (rm *resourceManager) resolveReferenceForEventDestination_SNSDestination_TopicARN( + ctx context.Context, + apiReader client.Reader, + ko *svcapitypes.ConfigurationSetEventDestination, +) (hasReferences bool, err error) { + if ko.Spec.EventDestination != nil { + if ko.Spec.EventDestination.SNSDestination != nil { + if ko.Spec.EventDestination.SNSDestination.TopicRef != nil && ko.Spec.EventDestination.SNSDestination.TopicRef.From != nil { + hasReferences = true + arr := ko.Spec.EventDestination.SNSDestination.TopicRef.From + if arr.Name == nil || *arr.Name == "" { + return hasReferences, fmt.Errorf("provided resource reference is nil or empty: EventDestination.SNSDestination.TopicRef") + } + namespace := ko.ObjectMeta.GetNamespace() + if arr.Namespace != nil && *arr.Namespace != "" { + namespace = *arr.Namespace + } + obj := &snsapitypes.Topic{} + if err := getReferencedResourceState_Topic(ctx, apiReader, obj, *arr.Name, namespace); err != nil { + return hasReferences, err + } + ko.Spec.EventDestination.SNSDestination.TopicARN = (*string)(obj.Status.TopicARN) + } + } + } + + return hasReferences, nil +} + +// getReferencedResourceState_Topic looks up whether a referenced resource +// exists and is in a ACK.ResourceSynced=True state. If the referenced resource does exist and is +// in a Synced state, returns nil, otherwise returns `ackerr.ResourceReferenceTerminalFor` or +// `ResourceReferenceNotSyncedFor` depending on if the resource is in a Terminal state. +func getReferencedResourceState_Topic( + ctx context.Context, + apiReader client.Reader, + obj *snsapitypes.Topic, + name string, // the Kubernetes name of the referenced resource + namespace string, // the Kubernetes namespace of the referenced resource +) error { + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + err := apiReader.Get(ctx, namespacedName, obj) + if err != nil { + return err + } + var refResourceTerminal bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeTerminal && + cond.Status == corev1.ConditionTrue { + return ackerr.ResourceReferenceTerminalFor( + "Topic", + namespace, name) + } + } + if refResourceTerminal { + return ackerr.ResourceReferenceTerminalFor( + "Topic", + namespace, name) + } + var refResourceSynced bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeResourceSynced && + cond.Status == corev1.ConditionTrue { + refResourceSynced = true + } + } + if !refResourceSynced { + return ackerr.ResourceReferenceNotSyncedFor( + "Topic", + namespace, name) + } + if obj.Status.TopicARN == nil { + return ackerr.ResourceReferenceMissingTargetFieldFor( + "Topic", + namespace, name, + "Status.TopicARN") + } + return nil +} diff --git a/pkg/resource/configuration_set_event_destination/resource.go b/pkg/resource/configuration_set_event_destination/resource.go new file mode 100644 index 0000000..86a0335 --- /dev/null +++ b/pkg/resource/configuration_set_event_destination/resource.go @@ -0,0 +1,100 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package configuration_set_event_destination + +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/ses-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.ConfigurationSetEventDestination +} + +// Identifiers returns an AWSResourceIdentifiers object containing various +// identifying information, including the AWS account ID that owns the +// resource, the resource's AWS Resource Name (ARN) +func (r *resource) Identifiers() acktypes.AWSResourceIdentifiers { + return &resourceIdentifiers{r.ko.Status.ACKResourceMetadata} +} + +// IsBeingDeleted returns true if the Kubernetes resource has a non-zero +// deletion timestamp +func (r *resource) IsBeingDeleted() bool { + return !r.ko.DeletionTimestamp.IsZero() +} + +// RuntimeObject returns the Kubernetes apimachinery/runtime representation of +// the AWSResource +func (r *resource) RuntimeObject() rtclient.Object { + return r.ko +} + +// MetaObject returns the Kubernetes apimachinery/apis/meta/v1.Object +// representation of the AWSResource +func (r *resource) MetaObject() metav1.Object { + return r.ko.GetObjectMeta() +} + +// Conditions returns the ACK Conditions collection for the AWSResource +func (r *resource) Conditions() []*ackv1alpha1.Condition { + return r.ko.Status.Conditions +} + +// ReplaceConditions sets the Conditions status field for the resource +func (r *resource) ReplaceConditions(conditions []*ackv1alpha1.Condition) { + r.ko.Status.Conditions = conditions +} + +// SetObjectMeta sets the ObjectMeta field for the resource +func (r *resource) SetObjectMeta(meta metav1.ObjectMeta) { + r.ko.ObjectMeta = meta +} + +// SetStatus will set the Status field for the resource +func (r *resource) SetStatus(desired acktypes.AWSResource) { + r.ko.Status = desired.(*resource).ko.Status +} + +// SetIdentifiers sets the Spec or Status field that is referenced as the unique +// resource identifier +func (r *resource) SetIdentifiers(identifier *ackv1alpha1.AWSIdentifiers) error { + if identifier.NameOrID == "" { + return ackerrors.MissingNameIdentifier + } + r.ko.Spec.ConfigurationSetName = &identifier.NameOrID + + return nil +} + +// DeepCopy will return a copy of the resource +func (r *resource) DeepCopy() acktypes.AWSResource { + koCopy := r.ko.DeepCopy() + return &resource{koCopy} +} diff --git a/pkg/resource/configuration_set_event_destination/sdk.go b/pkg/resource/configuration_set_event_destination/sdk.go new file mode 100644 index 0000000..38fe94f --- /dev/null +++ b/pkg/resource/configuration_set_event_destination/sdk.go @@ -0,0 +1,589 @@ +// 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 configuration_set_event_destination + +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/ses" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +// Hack to avoid import errors during build... +var ( + _ = &metav1.Time{} + _ = strings.ToLower("") + _ = &aws.JSONValue{} + _ = &svcsdk.SES{} + _ = &svcapitypes.ConfigurationSetEventDestination{} + _ = ackv1alpha1.AWSAccountID("") + _ = &ackerr.NotFound + _ = &ackcondition.NotManagedMessage + _ = &reflect.Value{} + _ = fmt.Sprintf("") + _ = &ackrequeue.NoRequeue{} +) + +// sdkFind returns SDK-specific information about a supplied resource +func (rm *resourceManager) sdkFind( + ctx context.Context, + r *resource, +) (latest *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkFind") + defer func() { + exit(err) + }() + if r.ko.Spec.EventDestination == nil || r.ko.Spec.EventDestination.Name == nil { + return nil, ackerr.NotFound + } + + // If any required fields in the input shape are missing, AWS resource is + // not created yet. Return NotFound here to indicate to callers that the + // resource isn't yet created. + if rm.requiredFieldsMissingFromReadOneInput(r) { + return nil, ackerr.NotFound + } + + input, err := rm.newDescribeRequestPayload(r) + if err != nil { + return nil, err + } + input.SetConfigurationSetAttributeNames(aws.StringSlice([]string{svcsdk.ConfigurationSetAttributeEventDestinations})) + + var resp *svcsdk.DescribeConfigurationSetOutput + resp, err = rm.sdkapi.DescribeConfigurationSetWithContext(ctx, input) + _ = resp + if err != nil { + if awsErr, ok := ackerr.AWSError(err); ok && awsErr.Code() == svcsdk.ErrCodeConfigurationSetDoesNotExistException { + rm.metrics.RecordAPICall("READ_ONE", "DescribeConfigurationSet", err) + return nil, ackerr.NotFound + } + } + + rm.metrics.RecordAPICall("READ_ONE", "DescribeConfigurationSet", err) + if err != nil { + if reqErr, ok := ackerr.AWSRequestFailure(err); ok && reqErr.StatusCode() == 404 { + return nil, ackerr.NotFound + } + if awsErr, ok := ackerr.AWSError(err); ok && awsErr.Code() == "UNKNOWN" { + return nil, ackerr.NotFound + } + return nil, err + } + + // Merge in the information we read from the API call above to the copy of + // the original Kubernetes object we passed to the function + ko := r.ko.DeepCopy() + + rm.setStatusDefaults(ko) + eventDestination := getEventDestination(ko, resp) + if eventDestination == nil { + return nil, ackerr.NotFound + } + ko.Spec.EventDestination = eventDestination + + 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.ConfigurationSetName == 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.DescribeConfigurationSetInput, error) { + res := &svcsdk.DescribeConfigurationSetInput{} + + if r.ko.Spec.ConfigurationSetName != nil { + res.SetConfigurationSetName(*r.ko.Spec.ConfigurationSetName) + } + + return res, nil +} + +// sdkCreate creates the supplied resource in the backend AWS service API and +// returns a copy of the resource with resource fields (in both Spec and +// Status) filled in with values from the CREATE API operation's Output shape. +func (rm *resourceManager) sdkCreate( + ctx context.Context, + desired *resource, +) (created *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkCreate") + defer func() { + exit(err) + }() + input, err := rm.newCreateRequestPayload(ctx, desired) + if err != nil { + return nil, err + } + + var resp *svcsdk.CreateConfigurationSetEventDestinationOutput + _ = resp + resp, err = rm.sdkapi.CreateConfigurationSetEventDestinationWithContext(ctx, input) + rm.metrics.RecordAPICall("CREATE", "CreateConfigurationSetEventDestination", 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() + + rm.setStatusDefaults(ko) + return &resource{ko}, nil +} + +// newCreateRequestPayload returns an SDK-specific struct for the HTTP request +// payload of the Create API call for the resource +func (rm *resourceManager) newCreateRequestPayload( + ctx context.Context, + r *resource, +) (*svcsdk.CreateConfigurationSetEventDestinationInput, error) { + res := &svcsdk.CreateConfigurationSetEventDestinationInput{} + + if r.ko.Spec.ConfigurationSetName != nil { + res.SetConfigurationSetName(*r.ko.Spec.ConfigurationSetName) + } + if r.ko.Spec.EventDestination != nil { + f1 := &svcsdk.EventDestination{} + if r.ko.Spec.EventDestination.CloudWatchDestination != nil { + f1f0 := &svcsdk.CloudWatchDestination{} + if r.ko.Spec.EventDestination.CloudWatchDestination.DimensionConfigurations != nil { + f1f0f0 := []*svcsdk.CloudWatchDimensionConfiguration{} + for _, f1f0f0iter := range r.ko.Spec.EventDestination.CloudWatchDestination.DimensionConfigurations { + f1f0f0elem := &svcsdk.CloudWatchDimensionConfiguration{} + if f1f0f0iter.DefaultDimensionValue != nil { + f1f0f0elem.SetDefaultDimensionValue(*f1f0f0iter.DefaultDimensionValue) + } + if f1f0f0iter.DimensionName != nil { + f1f0f0elem.SetDimensionName(*f1f0f0iter.DimensionName) + } + if f1f0f0iter.DimensionValueSource != nil { + f1f0f0elem.SetDimensionValueSource(*f1f0f0iter.DimensionValueSource) + } + f1f0f0 = append(f1f0f0, f1f0f0elem) + } + f1f0.SetDimensionConfigurations(f1f0f0) + } + f1.SetCloudWatchDestination(f1f0) + } + if r.ko.Spec.EventDestination.Enabled != nil { + f1.SetEnabled(*r.ko.Spec.EventDestination.Enabled) + } + if r.ko.Spec.EventDestination.KinesisFirehoseDestination != nil { + f1f2 := &svcsdk.KinesisFirehoseDestination{} + if r.ko.Spec.EventDestination.KinesisFirehoseDestination.DeliveryStreamARN != nil { + f1f2.SetDeliveryStreamARN(*r.ko.Spec.EventDestination.KinesisFirehoseDestination.DeliveryStreamARN) + } + if r.ko.Spec.EventDestination.KinesisFirehoseDestination.IAMRoleARN != nil { + f1f2.SetIAMRoleARN(*r.ko.Spec.EventDestination.KinesisFirehoseDestination.IAMRoleARN) + } + f1.SetKinesisFirehoseDestination(f1f2) + } + if r.ko.Spec.EventDestination.MatchingEventTypes != nil { + f1f3 := []*string{} + for _, f1f3iter := range r.ko.Spec.EventDestination.MatchingEventTypes { + var f1f3elem string + f1f3elem = *f1f3iter + f1f3 = append(f1f3, &f1f3elem) + } + f1.SetMatchingEventTypes(f1f3) + } + if r.ko.Spec.EventDestination.Name != nil { + f1.SetName(*r.ko.Spec.EventDestination.Name) + } + if r.ko.Spec.EventDestination.SNSDestination != nil { + f1f5 := &svcsdk.SNSDestination{} + if r.ko.Spec.EventDestination.SNSDestination.TopicARN != nil { + f1f5.SetTopicARN(*r.ko.Spec.EventDestination.SNSDestination.TopicARN) + } + f1.SetSNSDestination(f1f5) + } + res.SetEventDestination(f1) + } + + 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)) + } + input, err := rm.newUpdateRequestPayload(ctx, desired, delta) + if err != nil { + return nil, err + } + + var resp *svcsdk.UpdateConfigurationSetEventDestinationOutput + _ = resp + resp, err = rm.sdkapi.UpdateConfigurationSetEventDestinationWithContext(ctx, input) + rm.metrics.RecordAPICall("UPDATE", "UpdateConfigurationSetEventDestination", 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() + + 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, + delta *ackcompare.Delta, +) (*svcsdk.UpdateConfigurationSetEventDestinationInput, error) { + res := &svcsdk.UpdateConfigurationSetEventDestinationInput{} + + if r.ko.Spec.ConfigurationSetName != nil { + res.SetConfigurationSetName(*r.ko.Spec.ConfigurationSetName) + } + if r.ko.Spec.EventDestination != nil { + f1 := &svcsdk.EventDestination{} + if r.ko.Spec.EventDestination.CloudWatchDestination != nil { + f1f0 := &svcsdk.CloudWatchDestination{} + if r.ko.Spec.EventDestination.CloudWatchDestination.DimensionConfigurations != nil { + f1f0f0 := []*svcsdk.CloudWatchDimensionConfiguration{} + for _, f1f0f0iter := range r.ko.Spec.EventDestination.CloudWatchDestination.DimensionConfigurations { + f1f0f0elem := &svcsdk.CloudWatchDimensionConfiguration{} + if f1f0f0iter.DefaultDimensionValue != nil { + f1f0f0elem.SetDefaultDimensionValue(*f1f0f0iter.DefaultDimensionValue) + } + if f1f0f0iter.DimensionName != nil { + f1f0f0elem.SetDimensionName(*f1f0f0iter.DimensionName) + } + if f1f0f0iter.DimensionValueSource != nil { + f1f0f0elem.SetDimensionValueSource(*f1f0f0iter.DimensionValueSource) + } + f1f0f0 = append(f1f0f0, f1f0f0elem) + } + f1f0.SetDimensionConfigurations(f1f0f0) + } + f1.SetCloudWatchDestination(f1f0) + } + if r.ko.Spec.EventDestination.Enabled != nil { + f1.SetEnabled(*r.ko.Spec.EventDestination.Enabled) + } + if r.ko.Spec.EventDestination.KinesisFirehoseDestination != nil { + f1f2 := &svcsdk.KinesisFirehoseDestination{} + if r.ko.Spec.EventDestination.KinesisFirehoseDestination.DeliveryStreamARN != nil { + f1f2.SetDeliveryStreamARN(*r.ko.Spec.EventDestination.KinesisFirehoseDestination.DeliveryStreamARN) + } + if r.ko.Spec.EventDestination.KinesisFirehoseDestination.IAMRoleARN != nil { + f1f2.SetIAMRoleARN(*r.ko.Spec.EventDestination.KinesisFirehoseDestination.IAMRoleARN) + } + f1.SetKinesisFirehoseDestination(f1f2) + } + if r.ko.Spec.EventDestination.MatchingEventTypes != nil { + f1f3 := []*string{} + for _, f1f3iter := range r.ko.Spec.EventDestination.MatchingEventTypes { + var f1f3elem string + f1f3elem = *f1f3iter + f1f3 = append(f1f3, &f1f3elem) + } + f1.SetMatchingEventTypes(f1f3) + } + if r.ko.Spec.EventDestination.Name != nil { + f1.SetName(*r.ko.Spec.EventDestination.Name) + } + if r.ko.Spec.EventDestination.SNSDestination != nil { + f1f5 := &svcsdk.SNSDestination{} + if r.ko.Spec.EventDestination.SNSDestination.TopicARN != nil { + f1f5.SetTopicARN(*r.ko.Spec.EventDestination.SNSDestination.TopicARN) + } + f1.SetSNSDestination(f1f5) + } + res.SetEventDestination(f1) + } + + 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) + }() + input, err := rm.newDeleteRequestPayload(r) + if err != nil { + return nil, err + } + if eventDestination := r.ko.Spec.EventDestination; eventDestination != nil { + input.EventDestinationName = eventDestination.Name + } + + var resp *svcsdk.DeleteConfigurationSetEventDestinationOutput + _ = resp + resp, err = rm.sdkapi.DeleteConfigurationSetEventDestinationWithContext(ctx, input) + rm.metrics.RecordAPICall("DELETE", "DeleteConfigurationSetEventDestination", 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.DeleteConfigurationSetEventDestinationInput, error) { + res := &svcsdk.DeleteConfigurationSetEventDestinationInput{} + + if r.ko.Spec.ConfigurationSetName != nil { + res.SetConfigurationSetName(*r.ko.Spec.ConfigurationSetName) + } + + return res, nil +} + +// setStatusDefaults sets default properties into supplied custom resource +func (rm *resourceManager) setStatusDefaults( + ko *svcapitypes.ConfigurationSetEventDestination, +) { + 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 "ConfigurationSetDoesNotExist", + "EventDestinationAlreadyExists", + "InvalidCloudWatchDestination", + "InvalidFirehoseDestination", + "InvalidSNSDestination", + "LimitExceeded": + 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.ConfigurationSetName") { + fields = append(fields, "ConfigurationSetName") + } + if delta.DifferentAt("Spec.EventDestination.Name") { + fields = append(fields, "EventDestination.Name") + } + + return fields +} + +// setEventDestination sets a resource EventDestination type +// given the SDK type. +func setResourceEventDestination( + resp *svcsdk.EventDestination, +) *svcapitypes.EventDestination { + res := &svcapitypes.EventDestination{} + + if resp.CloudWatchDestination != nil { + resf0 := &svcapitypes.CloudWatchDestination{} + if resp.CloudWatchDestination.DimensionConfigurations != nil { + resf0f0 := []*svcapitypes.CloudWatchDimensionConfiguration{} + for _, resf0f0iter := range resp.CloudWatchDestination.DimensionConfigurations { + resf0f0elem := &svcapitypes.CloudWatchDimensionConfiguration{} + if resf0f0iter.DefaultDimensionValue != nil { + resf0f0elem.DefaultDimensionValue = resf0f0iter.DefaultDimensionValue + } + if resf0f0iter.DimensionName != nil { + resf0f0elem.DimensionName = resf0f0iter.DimensionName + } + if resf0f0iter.DimensionValueSource != nil { + resf0f0elem.DimensionValueSource = resf0f0iter.DimensionValueSource + } + resf0f0 = append(resf0f0, resf0f0elem) + } + resf0.DimensionConfigurations = resf0f0 + } + res.CloudWatchDestination = resf0 + } + if resp.Enabled != nil { + res.Enabled = resp.Enabled + } + if resp.KinesisFirehoseDestination != nil { + resf2 := &svcapitypes.KinesisFirehoseDestination{} + if resp.KinesisFirehoseDestination.DeliveryStreamARN != nil { + resf2.DeliveryStreamARN = resp.KinesisFirehoseDestination.DeliveryStreamARN + } + if resp.KinesisFirehoseDestination.IAMRoleARN != nil { + resf2.IAMRoleARN = resp.KinesisFirehoseDestination.IAMRoleARN + } + res.KinesisFirehoseDestination = resf2 + } + if resp.MatchingEventTypes != nil { + resf3 := []*string{} + for _, resf3iter := range resp.MatchingEventTypes { + var resf3elem string + resf3elem = *resf3iter + resf3 = append(resf3, &resf3elem) + } + res.MatchingEventTypes = resf3 + } + if resp.Name != nil { + res.Name = resp.Name + } + if resp.SNSDestination != nil { + resf5 := &svcapitypes.SNSDestination{} + if resp.SNSDestination.TopicARN != nil { + resf5.TopicARN = resp.SNSDestination.TopicARN + } + res.SNSDestination = resf5 + } + + return res +} diff --git a/pkg/resource/custom_verification_email_template/delta.go b/pkg/resource/custom_verification_email_template/delta.go new file mode 100644 index 0000000..fb45dd1 --- /dev/null +++ b/pkg/resource/custom_verification_email_template/delta.go @@ -0,0 +1,93 @@ +// 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 custom_verification_email_template + +import ( + "bytes" + "reflect" + + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" +) + +// Hack to avoid import errors during build... +var ( + _ = &bytes.Buffer{} + _ = &reflect.Method{} + _ = &acktags.Tags{} +) + +// newResourceDelta returns a new `ackcompare.Delta` used to compare two +// resources +func newResourceDelta( + a *resource, + b *resource, +) *ackcompare.Delta { + delta := ackcompare.NewDelta() + if (a == nil && b != nil) || + (a != nil && b == nil) { + delta.Add("", a, b) + return delta + } + + if ackcompare.HasNilDifference(a.ko.Spec.FailureRedirectionURL, b.ko.Spec.FailureRedirectionURL) { + delta.Add("Spec.FailureRedirectionURL", a.ko.Spec.FailureRedirectionURL, b.ko.Spec.FailureRedirectionURL) + } else if a.ko.Spec.FailureRedirectionURL != nil && b.ko.Spec.FailureRedirectionURL != nil { + if *a.ko.Spec.FailureRedirectionURL != *b.ko.Spec.FailureRedirectionURL { + delta.Add("Spec.FailureRedirectionURL", a.ko.Spec.FailureRedirectionURL, b.ko.Spec.FailureRedirectionURL) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.FromEmailAddress, b.ko.Spec.FromEmailAddress) { + delta.Add("Spec.FromEmailAddress", a.ko.Spec.FromEmailAddress, b.ko.Spec.FromEmailAddress) + } else if a.ko.Spec.FromEmailAddress != nil && b.ko.Spec.FromEmailAddress != nil { + if *a.ko.Spec.FromEmailAddress != *b.ko.Spec.FromEmailAddress { + delta.Add("Spec.FromEmailAddress", a.ko.Spec.FromEmailAddress, b.ko.Spec.FromEmailAddress) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.SuccessRedirectionURL, b.ko.Spec.SuccessRedirectionURL) { + delta.Add("Spec.SuccessRedirectionURL", a.ko.Spec.SuccessRedirectionURL, b.ko.Spec.SuccessRedirectionURL) + } else if a.ko.Spec.SuccessRedirectionURL != nil && b.ko.Spec.SuccessRedirectionURL != nil { + if *a.ko.Spec.SuccessRedirectionURL != *b.ko.Spec.SuccessRedirectionURL { + delta.Add("Spec.SuccessRedirectionURL", a.ko.Spec.SuccessRedirectionURL, b.ko.Spec.SuccessRedirectionURL) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.TemplateContent, b.ko.Spec.TemplateContent) { + delta.Add("Spec.TemplateContent", a.ko.Spec.TemplateContent, b.ko.Spec.TemplateContent) + } else if a.ko.Spec.TemplateContent != nil && b.ko.Spec.TemplateContent != nil { + if *a.ko.Spec.TemplateContent != *b.ko.Spec.TemplateContent { + delta.Add("Spec.TemplateContent", a.ko.Spec.TemplateContent, b.ko.Spec.TemplateContent) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.TemplateName, b.ko.Spec.TemplateName) { + delta.Add("Spec.TemplateName", a.ko.Spec.TemplateName, b.ko.Spec.TemplateName) + } else if a.ko.Spec.TemplateName != nil && b.ko.Spec.TemplateName != nil { + if *a.ko.Spec.TemplateName != *b.ko.Spec.TemplateName { + delta.Add("Spec.TemplateName", a.ko.Spec.TemplateName, b.ko.Spec.TemplateName) + } + } + if !reflect.DeepEqual(a.ko.Spec.TemplateRef, b.ko.Spec.TemplateRef) { + delta.Add("Spec.TemplateRef", a.ko.Spec.TemplateRef, b.ko.Spec.TemplateRef) + } + if ackcompare.HasNilDifference(a.ko.Spec.TemplateSubject, b.ko.Spec.TemplateSubject) { + delta.Add("Spec.TemplateSubject", a.ko.Spec.TemplateSubject, b.ko.Spec.TemplateSubject) + } else if a.ko.Spec.TemplateSubject != nil && b.ko.Spec.TemplateSubject != nil { + if *a.ko.Spec.TemplateSubject != *b.ko.Spec.TemplateSubject { + delta.Add("Spec.TemplateSubject", a.ko.Spec.TemplateSubject, b.ko.Spec.TemplateSubject) + } + } + + return delta +} diff --git a/pkg/resource/custom_verification_email_template/descriptor.go b/pkg/resource/custom_verification_email_template/descriptor.go new file mode 100644 index 0000000..71503fa --- /dev/null +++ b/pkg/resource/custom_verification_email_template/descriptor.go @@ -0,0 +1,155 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package custom_verification_email_template + +import ( + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + rtclient "sigs.k8s.io/controller-runtime/pkg/client" + k8sctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +const ( + FinalizerString = "finalizers.ses.services.k8s.aws/CustomVerificationEmailTemplate" +) + +var ( + GroupVersionResource = svcapitypes.GroupVersion.WithResource("customverificationemailtemplates") + GroupKind = metav1.GroupKind{ + Group: "ses.services.k8s.aws", + Kind: "CustomVerificationEmailTemplate", + } +) + +// resourceDescriptor implements the +// `aws-service-operator-k8s/pkg/types.AWSResourceDescriptor` interface +type resourceDescriptor struct { +} + +// GroupVersionKind returns a Kubernetes schema.GroupVersionKind struct that +// describes the API Group, Version and Kind of CRs described by the descriptor +func (d *resourceDescriptor) GroupVersionKind() schema.GroupVersionKind { + return svcapitypes.GroupVersion.WithKind(GroupKind.Kind) +} + +// EmptyRuntimeObject returns an empty object prototype that may be used in +// apimachinery and k8s client operations +func (d *resourceDescriptor) EmptyRuntimeObject() rtclient.Object { + return &svcapitypes.CustomVerificationEmailTemplate{} +} + +// 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.CustomVerificationEmailTemplate), + } +} + +// 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/custom_verification_email_template/identifiers.go b/pkg/resource/custom_verification_email_template/identifiers.go new file mode 100644 index 0000000..ad73a48 --- /dev/null +++ b/pkg/resource/custom_verification_email_template/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 custom_verification_email_template + +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/custom_verification_email_template/manager.go b/pkg/resource/custom_verification_email_template/manager.go new file mode 100644 index 0000000..cb1a8f8 --- /dev/null +++ b/pkg/resource/custom_verification_email_template/manager.go @@ -0,0 +1,350 @@ +// 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 custom_verification_email_template + +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/ses" + svcsdkapi "github.com/aws/aws-sdk-go/service/ses/sesiface" + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +var ( + _ = ackutil.InStrings + _ = acktags.NewTags() + _ = ackrt.MissingImageTagValue + _ = svcapitypes.CustomVerificationEmailTemplate{} +) + +// +kubebuilder:rbac:groups=ses.services.k8s.aws,resources=customverificationemailtemplates,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=ses.services.k8s.aws,resources=customverificationemailtemplates/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.SESAPI +} + +// 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:ses:%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 { + + 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/custom_verification_email_template/manager_factory.go b/pkg/resource/custom_verification_email_template/manager_factory.go new file mode 100644 index 0000000..cb7f343 --- /dev/null +++ b/pkg/resource/custom_verification_email_template/manager_factory.go @@ -0,0 +1,100 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package custom_verification_email_template + +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/ses-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, + roleARN ackv1alpha1.AWSResourceName, +) (acktypes.AWSResourceManager, error) { + // We use the account ID, region, and role ARN to uniquely identify a + // resource manager. This helps us to avoid creating multiple resource + // managers for the same account/region/roleARN combination. + rmId := fmt.Sprintf("%s/%s/%s", id, region, roleARN) + f.RLock() + rm, found := f.rmCache[rmId] + f.RUnlock() + + if found { + return rm, nil + } + + f.Lock() + defer f.Unlock() + + rm, err := newResourceManager(cfg, 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/custom_verification_email_template/references.go b/pkg/resource/custom_verification_email_template/references.go new file mode 100644 index 0000000..ed8d6fa --- /dev/null +++ b/pkg/resource/custom_verification_email_template/references.go @@ -0,0 +1,166 @@ +// 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 custom_verification_email_template + +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" + ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +// ClearResolvedReferences removes any reference values that were made +// concrete in the spec. It returns a copy of the input AWSResource which +// contains the original *Ref values, but none of their respective concrete +// values. +func (rm *resourceManager) ClearResolvedReferences(res acktypes.AWSResource) acktypes.AWSResource { + ko := rm.concreteResource(res).ko.DeepCopy() + + if ko.Spec.TemplateRef != nil { + ko.Spec.TemplateName = nil + } + + return &resource{ko} +} + +// ResolveReferences finds if there are any Reference field(s) present +// inside AWSResource passed in the parameter and attempts to resolve those +// reference field(s) into their respective target field(s). It returns a +// copy of the input AWSResource with resolved reference(s), a boolean which +// is set to true if the resource contains any references (regardless of if +// they are resolved successfully) and an error if the passed AWSResource's +// reference field(s) could not be resolved. +func (rm *resourceManager) ResolveReferences( + ctx context.Context, + apiReader client.Reader, + res acktypes.AWSResource, +) (acktypes.AWSResource, bool, error) { + ko := rm.concreteResource(res).ko + + resourceHasReferences := false + err := validateReferenceFields(ko) + if fieldHasReferences, err := rm.resolveReferenceForTemplateName(ctx, apiReader, ko); err != nil { + return &resource{ko}, (resourceHasReferences || fieldHasReferences), err + } else { + resourceHasReferences = resourceHasReferences || fieldHasReferences + } + + return &resource{ko}, resourceHasReferences, err +} + +// validateReferenceFields validates the reference field and corresponding +// identifier field. +func validateReferenceFields(ko *svcapitypes.CustomVerificationEmailTemplate) error { + + if ko.Spec.TemplateRef != nil && ko.Spec.TemplateName != nil { + return ackerr.ResourceReferenceAndIDNotSupportedFor("TemplateName", "TemplateRef") + } + if ko.Spec.TemplateRef == nil && ko.Spec.TemplateName == nil { + return ackerr.ResourceReferenceOrIDRequiredFor("TemplateName", "TemplateRef") + } + return nil +} + +// resolveReferenceForTemplateName reads the resource referenced +// from TemplateRef field and sets the TemplateName +// from referenced resource. Returns a boolean indicating whether a reference +// contains references, or an error +func (rm *resourceManager) resolveReferenceForTemplateName( + ctx context.Context, + apiReader client.Reader, + ko *svcapitypes.CustomVerificationEmailTemplate, +) (hasReferences bool, err error) { + if ko.Spec.TemplateRef != nil && ko.Spec.TemplateRef.From != nil { + hasReferences = true + arr := ko.Spec.TemplateRef.From + if arr.Name == nil || *arr.Name == "" { + return hasReferences, fmt.Errorf("provided resource reference is nil or empty: TemplateRef") + } + namespace := ko.ObjectMeta.GetNamespace() + if arr.Namespace != nil && *arr.Namespace != "" { + namespace = *arr.Namespace + } + obj := &svcapitypes.Template{} + if err := getReferencedResourceState_Template(ctx, apiReader, obj, *arr.Name, namespace); err != nil { + return hasReferences, err + } + ko.Spec.TemplateName = (*string)(obj.Spec.Name) + } + + return hasReferences, nil +} + +// getReferencedResourceState_Template looks up whether a referenced resource +// exists and is in a ACK.ResourceSynced=True state. If the referenced resource does exist and is +// in a Synced state, returns nil, otherwise returns `ackerr.ResourceReferenceTerminalFor` or +// `ResourceReferenceNotSyncedFor` depending on if the resource is in a Terminal state. +func getReferencedResourceState_Template( + ctx context.Context, + apiReader client.Reader, + obj *svcapitypes.Template, + name string, // the Kubernetes name of the referenced resource + namespace string, // the Kubernetes namespace of the referenced resource +) error { + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + err := apiReader.Get(ctx, namespacedName, obj) + if err != nil { + return err + } + var refResourceTerminal bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeTerminal && + cond.Status == corev1.ConditionTrue { + return ackerr.ResourceReferenceTerminalFor( + "Template", + namespace, name) + } + } + if refResourceTerminal { + return ackerr.ResourceReferenceTerminalFor( + "Template", + namespace, name) + } + var refResourceSynced bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeResourceSynced && + cond.Status == corev1.ConditionTrue { + refResourceSynced = true + } + } + if !refResourceSynced { + return ackerr.ResourceReferenceNotSyncedFor( + "Template", + namespace, name) + } + if obj.Spec.Name == nil { + return ackerr.ResourceReferenceMissingTargetFieldFor( + "Template", + namespace, name, + "Spec.Name") + } + return nil +} diff --git a/pkg/resource/custom_verification_email_template/resource.go b/pkg/resource/custom_verification_email_template/resource.go new file mode 100644 index 0000000..42da9e4 --- /dev/null +++ b/pkg/resource/custom_verification_email_template/resource.go @@ -0,0 +1,100 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package custom_verification_email_template + +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/ses-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.CustomVerificationEmailTemplate +} + +// Identifiers returns an AWSResourceIdentifiers object containing various +// identifying information, including the AWS account ID that owns the +// resource, the resource's AWS Resource Name (ARN) +func (r *resource) Identifiers() acktypes.AWSResourceIdentifiers { + return &resourceIdentifiers{r.ko.Status.ACKResourceMetadata} +} + +// IsBeingDeleted returns true if the Kubernetes resource has a non-zero +// deletion timestamp +func (r *resource) IsBeingDeleted() bool { + return !r.ko.DeletionTimestamp.IsZero() +} + +// RuntimeObject returns the Kubernetes apimachinery/runtime representation of +// the AWSResource +func (r *resource) RuntimeObject() rtclient.Object { + return r.ko +} + +// MetaObject returns the Kubernetes apimachinery/apis/meta/v1.Object +// representation of the AWSResource +func (r *resource) MetaObject() metav1.Object { + return r.ko.GetObjectMeta() +} + +// Conditions returns the ACK Conditions collection for the AWSResource +func (r *resource) Conditions() []*ackv1alpha1.Condition { + return r.ko.Status.Conditions +} + +// ReplaceConditions sets the Conditions status field for the resource +func (r *resource) ReplaceConditions(conditions []*ackv1alpha1.Condition) { + r.ko.Status.Conditions = conditions +} + +// SetObjectMeta sets the ObjectMeta field for the resource +func (r *resource) SetObjectMeta(meta metav1.ObjectMeta) { + r.ko.ObjectMeta = meta +} + +// SetStatus will set the Status field for the resource +func (r *resource) SetStatus(desired acktypes.AWSResource) { + r.ko.Status = desired.(*resource).ko.Status +} + +// SetIdentifiers sets the Spec or Status field that is referenced as the unique +// resource identifier +func (r *resource) SetIdentifiers(identifier *ackv1alpha1.AWSIdentifiers) error { + if identifier.NameOrID == "" { + return ackerrors.MissingNameIdentifier + } + r.ko.Spec.TemplateName = &identifier.NameOrID + + return nil +} + +// DeepCopy will return a copy of the resource +func (r *resource) DeepCopy() acktypes.AWSResource { + koCopy := r.ko.DeepCopy() + return &resource{koCopy} +} diff --git a/pkg/resource/custom_verification_email_template/sdk.go b/pkg/resource/custom_verification_email_template/sdk.go new file mode 100644 index 0000000..d9f62dc --- /dev/null +++ b/pkg/resource/custom_verification_email_template/sdk.go @@ -0,0 +1,449 @@ +// 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 custom_verification_email_template + +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/ses" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +// Hack to avoid import errors during build... +var ( + _ = &metav1.Time{} + _ = strings.ToLower("") + _ = &aws.JSONValue{} + _ = &svcsdk.SES{} + _ = &svcapitypes.CustomVerificationEmailTemplate{} + _ = 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.GetCustomVerificationEmailTemplateOutput + resp, err = rm.sdkapi.GetCustomVerificationEmailTemplateWithContext(ctx, input) + if err != nil { + if awsErr, ok := ackerr.AWSError(err); ok && awsErr.Code() == svcsdk.ErrCodeCustomVerificationEmailTemplateDoesNotExistException { + rm.metrics.RecordAPICall("READ_ONE", "GetCustomVerificationEmailTemplate", err) + return nil, ackerr.NotFound + } + } + + rm.metrics.RecordAPICall("READ_ONE", "GetCustomVerificationEmailTemplate", err) + if err != nil { + if reqErr, ok := ackerr.AWSRequestFailure(err); ok && reqErr.StatusCode() == 404 { + return nil, ackerr.NotFound + } + if awsErr, ok := ackerr.AWSError(err); ok && awsErr.Code() == "UNKNOWN" { + return nil, ackerr.NotFound + } + return nil, err + } + + // Merge in the information we read from the API call above to the copy of + // the original Kubernetes object we passed to the function + ko := r.ko.DeepCopy() + + if resp.FailureRedirectionURL != nil { + ko.Spec.FailureRedirectionURL = resp.FailureRedirectionURL + } else { + ko.Spec.FailureRedirectionURL = nil + } + if resp.FromEmailAddress != nil { + ko.Spec.FromEmailAddress = resp.FromEmailAddress + } else { + ko.Spec.FromEmailAddress = nil + } + if resp.SuccessRedirectionURL != nil { + ko.Spec.SuccessRedirectionURL = resp.SuccessRedirectionURL + } else { + ko.Spec.SuccessRedirectionURL = nil + } + if resp.TemplateContent != nil { + ko.Spec.TemplateContent = resp.TemplateContent + } else { + ko.Spec.TemplateContent = nil + } + if resp.TemplateName != nil { + ko.Spec.TemplateName = resp.TemplateName + } else { + ko.Spec.TemplateName = nil + } + if resp.TemplateSubject != nil { + ko.Spec.TemplateSubject = resp.TemplateSubject + } else { + ko.Spec.TemplateSubject = nil + } + + rm.setStatusDefaults(ko) + 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.TemplateName == 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.GetCustomVerificationEmailTemplateInput, error) { + res := &svcsdk.GetCustomVerificationEmailTemplateInput{} + + if r.ko.Spec.TemplateName != nil { + res.SetTemplateName(*r.ko.Spec.TemplateName) + } + + return res, nil +} + +// sdkCreate creates the supplied resource in the backend AWS service API and +// returns a copy of the resource with resource fields (in both Spec and +// Status) filled in with values from the CREATE API operation's Output shape. +func (rm *resourceManager) sdkCreate( + ctx context.Context, + desired *resource, +) (created *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkCreate") + defer func() { + exit(err) + }() + input, err := rm.newCreateRequestPayload(ctx, desired) + if err != nil { + return nil, err + } + + var resp *svcsdk.CreateCustomVerificationEmailTemplateOutput + _ = resp + resp, err = rm.sdkapi.CreateCustomVerificationEmailTemplateWithContext(ctx, input) + rm.metrics.RecordAPICall("CREATE", "CreateCustomVerificationEmailTemplate", 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() + + rm.setStatusDefaults(ko) + return &resource{ko}, nil +} + +// newCreateRequestPayload returns an SDK-specific struct for the HTTP request +// payload of the Create API call for the resource +func (rm *resourceManager) newCreateRequestPayload( + ctx context.Context, + r *resource, +) (*svcsdk.CreateCustomVerificationEmailTemplateInput, error) { + res := &svcsdk.CreateCustomVerificationEmailTemplateInput{} + + if r.ko.Spec.FailureRedirectionURL != nil { + res.SetFailureRedirectionURL(*r.ko.Spec.FailureRedirectionURL) + } + if r.ko.Spec.FromEmailAddress != nil { + res.SetFromEmailAddress(*r.ko.Spec.FromEmailAddress) + } + if r.ko.Spec.SuccessRedirectionURL != nil { + res.SetSuccessRedirectionURL(*r.ko.Spec.SuccessRedirectionURL) + } + if r.ko.Spec.TemplateContent != nil { + res.SetTemplateContent(*r.ko.Spec.TemplateContent) + } + if r.ko.Spec.TemplateName != nil { + res.SetTemplateName(*r.ko.Spec.TemplateName) + } + if r.ko.Spec.TemplateSubject != nil { + res.SetTemplateSubject(*r.ko.Spec.TemplateSubject) + } + + 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)) + } + input, err := rm.newUpdateRequestPayload(ctx, desired, delta) + if err != nil { + return nil, err + } + + var resp *svcsdk.UpdateCustomVerificationEmailTemplateOutput + _ = resp + resp, err = rm.sdkapi.UpdateCustomVerificationEmailTemplateWithContext(ctx, input) + rm.metrics.RecordAPICall("UPDATE", "UpdateCustomVerificationEmailTemplate", 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() + + 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, + delta *ackcompare.Delta, +) (*svcsdk.UpdateCustomVerificationEmailTemplateInput, error) { + res := &svcsdk.UpdateCustomVerificationEmailTemplateInput{} + + if r.ko.Spec.FailureRedirectionURL != nil { + res.SetFailureRedirectionURL(*r.ko.Spec.FailureRedirectionURL) + } + if r.ko.Spec.FromEmailAddress != nil { + res.SetFromEmailAddress(*r.ko.Spec.FromEmailAddress) + } + if r.ko.Spec.SuccessRedirectionURL != nil { + res.SetSuccessRedirectionURL(*r.ko.Spec.SuccessRedirectionURL) + } + if r.ko.Spec.TemplateContent != nil { + res.SetTemplateContent(*r.ko.Spec.TemplateContent) + } + if r.ko.Spec.TemplateName != nil { + res.SetTemplateName(*r.ko.Spec.TemplateName) + } + if r.ko.Spec.TemplateSubject != nil { + res.SetTemplateSubject(*r.ko.Spec.TemplateSubject) + } + + 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) + }() + input, err := rm.newDeleteRequestPayload(r) + if err != nil { + return nil, err + } + var resp *svcsdk.DeleteCustomVerificationEmailTemplateOutput + _ = resp + resp, err = rm.sdkapi.DeleteCustomVerificationEmailTemplateWithContext(ctx, input) + rm.metrics.RecordAPICall("DELETE", "DeleteCustomVerificationEmailTemplate", 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.DeleteCustomVerificationEmailTemplateInput, error) { + res := &svcsdk.DeleteCustomVerificationEmailTemplateInput{} + + if r.ko.Spec.TemplateName != nil { + res.SetTemplateName(*r.ko.Spec.TemplateName) + } + + return res, nil +} + +// setStatusDefaults sets default properties into supplied custom resource +func (rm *resourceManager) setStatusDefaults( + ko *svcapitypes.CustomVerificationEmailTemplate, +) { + 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 "CustomVerificationEmailInvalidContent", + "CustomVerificationEmailTemplateAlreadyExists": + 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.TemplateName") { + fields = append(fields, "TemplateName") + } + + return fields +} diff --git a/pkg/resource/receipt_filter/delta.go b/pkg/resource/receipt_filter/delta.go new file mode 100644 index 0000000..b07bea2 --- /dev/null +++ b/pkg/resource/receipt_filter/delta.go @@ -0,0 +1,77 @@ +// 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 receipt_filter + +import ( + "bytes" + "reflect" + + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" +) + +// Hack to avoid import errors during build... +var ( + _ = &bytes.Buffer{} + _ = &reflect.Method{} + _ = &acktags.Tags{} +) + +// newResourceDelta returns a new `ackcompare.Delta` used to compare two +// resources +func newResourceDelta( + a *resource, + b *resource, +) *ackcompare.Delta { + delta := ackcompare.NewDelta() + if (a == nil && b != nil) || + (a != nil && b == nil) { + delta.Add("", a, b) + return delta + } + + if ackcompare.HasNilDifference(a.ko.Spec.Filter, b.ko.Spec.Filter) { + delta.Add("Spec.Filter", a.ko.Spec.Filter, b.ko.Spec.Filter) + } else if a.ko.Spec.Filter != nil && b.ko.Spec.Filter != nil { + if ackcompare.HasNilDifference(a.ko.Spec.Filter.IPFilter, b.ko.Spec.Filter.IPFilter) { + delta.Add("Spec.Filter.IPFilter", a.ko.Spec.Filter.IPFilter, b.ko.Spec.Filter.IPFilter) + } else if a.ko.Spec.Filter.IPFilter != nil && b.ko.Spec.Filter.IPFilter != nil { + if ackcompare.HasNilDifference(a.ko.Spec.Filter.IPFilter.CIDR, b.ko.Spec.Filter.IPFilter.CIDR) { + delta.Add("Spec.Filter.IPFilter.CIDR", a.ko.Spec.Filter.IPFilter.CIDR, b.ko.Spec.Filter.IPFilter.CIDR) + } else if a.ko.Spec.Filter.IPFilter.CIDR != nil && b.ko.Spec.Filter.IPFilter.CIDR != nil { + if *a.ko.Spec.Filter.IPFilter.CIDR != *b.ko.Spec.Filter.IPFilter.CIDR { + delta.Add("Spec.Filter.IPFilter.CIDR", a.ko.Spec.Filter.IPFilter.CIDR, b.ko.Spec.Filter.IPFilter.CIDR) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.Filter.IPFilter.Policy, b.ko.Spec.Filter.IPFilter.Policy) { + delta.Add("Spec.Filter.IPFilter.Policy", a.ko.Spec.Filter.IPFilter.Policy, b.ko.Spec.Filter.IPFilter.Policy) + } else if a.ko.Spec.Filter.IPFilter.Policy != nil && b.ko.Spec.Filter.IPFilter.Policy != nil { + if *a.ko.Spec.Filter.IPFilter.Policy != *b.ko.Spec.Filter.IPFilter.Policy { + delta.Add("Spec.Filter.IPFilter.Policy", a.ko.Spec.Filter.IPFilter.Policy, b.ko.Spec.Filter.IPFilter.Policy) + } + } + } + } + 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) + } + } + + return delta +} diff --git a/pkg/resource/receipt_filter/descriptor.go b/pkg/resource/receipt_filter/descriptor.go new file mode 100644 index 0000000..e535bc7 --- /dev/null +++ b/pkg/resource/receipt_filter/descriptor.go @@ -0,0 +1,155 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package receipt_filter + +import ( + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + rtclient "sigs.k8s.io/controller-runtime/pkg/client" + k8sctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +const ( + FinalizerString = "finalizers.ses.services.k8s.aws/ReceiptFilter" +) + +var ( + GroupVersionResource = svcapitypes.GroupVersion.WithResource("receiptfilters") + GroupKind = metav1.GroupKind{ + Group: "ses.services.k8s.aws", + Kind: "ReceiptFilter", + } +) + +// resourceDescriptor implements the +// `aws-service-operator-k8s/pkg/types.AWSResourceDescriptor` interface +type resourceDescriptor struct { +} + +// GroupVersionKind returns a Kubernetes schema.GroupVersionKind struct that +// describes the API Group, Version and Kind of CRs described by the descriptor +func (d *resourceDescriptor) GroupVersionKind() schema.GroupVersionKind { + return svcapitypes.GroupVersion.WithKind(GroupKind.Kind) +} + +// EmptyRuntimeObject returns an empty object prototype that may be used in +// apimachinery and k8s client operations +func (d *resourceDescriptor) EmptyRuntimeObject() rtclient.Object { + return &svcapitypes.ReceiptFilter{} +} + +// 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.ReceiptFilter), + } +} + +// 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/receipt_filter/hooks.go b/pkg/resource/receipt_filter/hooks.go new file mode 100644 index 0000000..7bf6ff0 --- /dev/null +++ b/pkg/resource/receipt_filter/hooks.go @@ -0,0 +1,75 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package receipt_filter + +import ( + "context" + + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" + ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" + svcsdk "github.com/aws/aws-sdk-go/service/ses" + + "github.com/aws-controllers-k8s/ses-controller/pkg/util" +) + +func (rm *resourceManager) customFind( + 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 r.ko.Spec.Name == nil { + return nil, ackerr.NotFound + } + + var resp *svcsdk.ListReceiptFiltersOutput + resp, err = rm.sdkapi.ListReceiptFiltersWithContext(ctx, &svcsdk.ListReceiptFiltersInput{}) + + rm.metrics.RecordAPICall("READ_MANY", "ListReceiptFilters", 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 + var receiptFilter *svcsdk.ReceiptFilter + for _, filter := range resp.Filters { + if *filter.Name == *r.ko.Spec.Name { + receiptFilter = filter + break + } + } + if receiptFilter == nil { + return nil, ackerr.NotFound + } + ko := r.ko.DeepCopy() + rm.setStatusDefaults(ko) + ko.Spec.Filter = setResourceReceiptFilter(receiptFilter) + return &resource{ko}, nil +} + +func (rm *resourceManager) customUpdate( + ctx context.Context, + desired *resource, + _ *resource, + delta *ackcompare.Delta, +) (updated *resource, err error) { + return util.ValidateImmutableResource(ctx, rm.getImmutableFieldChanges(delta), desired) +} diff --git a/pkg/resource/receipt_filter/identifiers.go b/pkg/resource/receipt_filter/identifiers.go new file mode 100644 index 0000000..2ee6730 --- /dev/null +++ b/pkg/resource/receipt_filter/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 receipt_filter + +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/receipt_filter/manager.go b/pkg/resource/receipt_filter/manager.go new file mode 100644 index 0000000..b6444d0 --- /dev/null +++ b/pkg/resource/receipt_filter/manager.go @@ -0,0 +1,350 @@ +// 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 receipt_filter + +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/ses" + svcsdkapi "github.com/aws/aws-sdk-go/service/ses/sesiface" + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +var ( + _ = ackutil.InStrings + _ = acktags.NewTags() + _ = ackrt.MissingImageTagValue + _ = svcapitypes.ReceiptFilter{} +) + +// +kubebuilder:rbac:groups=ses.services.k8s.aws,resources=receiptfilters,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=ses.services.k8s.aws,resources=receiptfilters/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.SESAPI +} + +// 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:ses:%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 { + + 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/receipt_filter/manager_factory.go b/pkg/resource/receipt_filter/manager_factory.go new file mode 100644 index 0000000..f631cb9 --- /dev/null +++ b/pkg/resource/receipt_filter/manager_factory.go @@ -0,0 +1,100 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package receipt_filter + +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/ses-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, + roleARN ackv1alpha1.AWSResourceName, +) (acktypes.AWSResourceManager, error) { + // We use the account ID, region, and role ARN to uniquely identify a + // resource manager. This helps us to avoid creating multiple resource + // managers for the same account/region/roleARN combination. + rmId := fmt.Sprintf("%s/%s/%s", id, region, roleARN) + f.RLock() + rm, found := f.rmCache[rmId] + f.RUnlock() + + if found { + return rm, nil + } + + f.Lock() + defer f.Unlock() + + rm, err := newResourceManager(cfg, 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/receipt_filter/references.go b/pkg/resource/receipt_filter/references.go new file mode 100644 index 0000000..9dbc924 --- /dev/null +++ b/pkg/resource/receipt_filter/references.go @@ -0,0 +1,56 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package receipt_filter + +import ( + "context" + "sigs.k8s.io/controller-runtime/pkg/client" + + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +// ClearResolvedReferences removes any reference values that were made +// concrete in the spec. It returns a copy of the input AWSResource which +// contains the original *Ref values, but none of their respective concrete +// values. +func (rm *resourceManager) ClearResolvedReferences(res acktypes.AWSResource) acktypes.AWSResource { + ko := rm.concreteResource(res).ko.DeepCopy() + + return &resource{ko} +} + +// ResolveReferences finds if there are any Reference field(s) present +// inside AWSResource passed in the parameter and attempts to resolve those +// reference field(s) into their respective target field(s). It returns a +// copy of the input AWSResource with resolved reference(s), a boolean which +// is set to true if the resource contains any references (regardless of if +// they are resolved successfully) and an error if the passed AWSResource's +// reference field(s) could not be resolved. +func (rm *resourceManager) ResolveReferences( + ctx context.Context, + apiReader client.Reader, + res acktypes.AWSResource, +) (acktypes.AWSResource, bool, error) { + return res, false, nil +} + +// validateReferenceFields validates the reference field and corresponding +// identifier field. +func validateReferenceFields(ko *svcapitypes.ReceiptFilter) error { + return nil +} diff --git a/pkg/resource/receipt_filter/resource.go b/pkg/resource/receipt_filter/resource.go new file mode 100644 index 0000000..022cedc --- /dev/null +++ b/pkg/resource/receipt_filter/resource.go @@ -0,0 +1,100 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package receipt_filter + +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/ses-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.ReceiptFilter +} + +// Identifiers returns an AWSResourceIdentifiers object containing various +// identifying information, including the AWS account ID that owns the +// resource, the resource's AWS Resource Name (ARN) +func (r *resource) Identifiers() acktypes.AWSResourceIdentifiers { + return &resourceIdentifiers{r.ko.Status.ACKResourceMetadata} +} + +// IsBeingDeleted returns true if the Kubernetes resource has a non-zero +// deletion timestamp +func (r *resource) IsBeingDeleted() bool { + return !r.ko.DeletionTimestamp.IsZero() +} + +// RuntimeObject returns the Kubernetes apimachinery/runtime representation of +// the AWSResource +func (r *resource) RuntimeObject() rtclient.Object { + return r.ko +} + +// MetaObject returns the Kubernetes apimachinery/apis/meta/v1.Object +// representation of the AWSResource +func (r *resource) MetaObject() metav1.Object { + return r.ko.GetObjectMeta() +} + +// Conditions returns the ACK Conditions collection for the AWSResource +func (r *resource) Conditions() []*ackv1alpha1.Condition { + return r.ko.Status.Conditions +} + +// ReplaceConditions sets the Conditions status field for the resource +func (r *resource) ReplaceConditions(conditions []*ackv1alpha1.Condition) { + r.ko.Status.Conditions = conditions +} + +// SetObjectMeta sets the ObjectMeta field for the resource +func (r *resource) SetObjectMeta(meta metav1.ObjectMeta) { + r.ko.ObjectMeta = meta +} + +// SetStatus will set the Status field for the resource +func (r *resource) SetStatus(desired acktypes.AWSResource) { + r.ko.Status = desired.(*resource).ko.Status +} + +// SetIdentifiers sets the Spec or Status field that is referenced as the unique +// resource identifier +func (r *resource) SetIdentifiers(identifier *ackv1alpha1.AWSIdentifiers) error { + if identifier.NameOrID == "" { + return ackerrors.MissingNameIdentifier + } + r.ko.Spec.Name = &identifier.NameOrID + + return nil +} + +// DeepCopy will return a copy of the resource +func (r *resource) DeepCopy() acktypes.AWSResource { + koCopy := r.ko.DeepCopy() + return &resource{koCopy} +} diff --git a/pkg/resource/receipt_filter/sdk.go b/pkg/resource/receipt_filter/sdk.go new file mode 100644 index 0000000..b9a49ec --- /dev/null +++ b/pkg/resource/receipt_filter/sdk.go @@ -0,0 +1,324 @@ +// 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 receipt_filter + +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/ses" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +// Hack to avoid import errors during build... +var ( + _ = &metav1.Time{} + _ = strings.ToLower("") + _ = &aws.JSONValue{} + _ = &svcsdk.SES{} + _ = &svcapitypes.ReceiptFilter{} + _ = 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, +) (*resource, error) { + return rm.customFind(ctx, r) +} + +// sdkCreate creates the supplied resource in the backend AWS service API and +// returns a copy of the resource with resource fields (in both Spec and +// Status) filled in with values from the CREATE API operation's Output shape. +func (rm *resourceManager) sdkCreate( + ctx context.Context, + desired *resource, +) (created *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkCreate") + defer func() { + exit(err) + }() + input, err := rm.newCreateRequestPayload(ctx, desired) + if err != nil { + return nil, err + } + if input.Filter != nil { + input.Filter.Name = desired.ko.Spec.Name + } else { + input.Filter = &svcsdk.ReceiptFilter{ + Name: desired.ko.Spec.Name, + } + } + + var resp *svcsdk.CreateReceiptFilterOutput + _ = resp + resp, err = rm.sdkapi.CreateReceiptFilterWithContext(ctx, input) + rm.metrics.RecordAPICall("CREATE", "CreateReceiptFilter", 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() + + rm.setStatusDefaults(ko) + return &resource{ko}, nil +} + +// newCreateRequestPayload returns an SDK-specific struct for the HTTP request +// payload of the Create API call for the resource +func (rm *resourceManager) newCreateRequestPayload( + ctx context.Context, + r *resource, +) (*svcsdk.CreateReceiptFilterInput, error) { + res := &svcsdk.CreateReceiptFilterInput{} + + if r.ko.Spec.Filter != nil { + f0 := &svcsdk.ReceiptFilter{} + if r.ko.Spec.Filter.IPFilter != nil { + f0f0 := &svcsdk.ReceiptIpFilter{} + if r.ko.Spec.Filter.IPFilter.CIDR != nil { + f0f0.SetCidr(*r.ko.Spec.Filter.IPFilter.CIDR) + } + if r.ko.Spec.Filter.IPFilter.Policy != nil { + f0f0.SetPolicy(*r.ko.Spec.Filter.IPFilter.Policy) + } + f0.SetIpFilter(f0f0) + } + res.SetFilter(f0) + } + + return res, nil +} + +// sdkUpdate patches the supplied resource in the backend AWS service API and +// returns a new resource with updated fields. +func (rm *resourceManager) sdkUpdate( + ctx context.Context, + desired *resource, + latest *resource, + delta *ackcompare.Delta, +) (*resource, error) { + return rm.customUpdate(ctx, desired, latest, delta) +} + +// sdkDelete deletes the supplied resource in the backend AWS service API +func (rm *resourceManager) sdkDelete( + ctx context.Context, + r *resource, +) (latest *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkDelete") + defer func() { + exit(err) + }() + input, err := rm.newDeleteRequestPayload(r) + if err != nil { + return nil, err + } + var resp *svcsdk.DeleteReceiptFilterOutput + _ = resp + resp, err = rm.sdkapi.DeleteReceiptFilterWithContext(ctx, input) + rm.metrics.RecordAPICall("DELETE", "DeleteReceiptFilter", 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.DeleteReceiptFilterInput, error) { + res := &svcsdk.DeleteReceiptFilterInput{} + + if r.ko.Spec.Name != nil { + res.SetFilterName(*r.ko.Spec.Name) + } + + return res, nil +} + +// setStatusDefaults sets default properties into supplied custom resource +func (rm *resourceManager) setStatusDefaults( + ko *svcapitypes.ReceiptFilter, +) { + 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 "AlreadyExists": + 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.Filter.IPFilter.CIDR") { + fields = append(fields, "Filter.IPFilter.CIDR") + } + if delta.DifferentAt("Spec.Filter.IPFilter.Policy") { + fields = append(fields, "Filter.IPFilter.Policy") + } + if delta.DifferentAt("Spec.Name") { + fields = append(fields, "Name") + } + + return fields +} + +// setReceiptFilter sets a resource ReceiptFilter type +// given the SDK type. +func setResourceReceiptFilter( + resp *svcsdk.ReceiptFilter, +) *svcapitypes.ReceiptFilter_SDK { + res := &svcapitypes.ReceiptFilter_SDK{} + + if resp.IpFilter != nil { + resf0 := &svcapitypes.ReceiptIPFilter{} + if resp.IpFilter.Cidr != nil { + resf0.CIDR = resp.IpFilter.Cidr + } + if resp.IpFilter.Policy != nil { + resf0.Policy = resp.IpFilter.Policy + } + res.IPFilter = resf0 + } + + return res +} diff --git a/pkg/resource/receipt_rule/delta.go b/pkg/resource/receipt_rule/delta.go new file mode 100644 index 0000000..80fc09d --- /dev/null +++ b/pkg/resource/receipt_rule/delta.go @@ -0,0 +1,111 @@ +// 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 receipt_rule + +import ( + "bytes" + "reflect" + + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" +) + +// Hack to avoid import errors during build... +var ( + _ = &bytes.Buffer{} + _ = &reflect.Method{} + _ = &acktags.Tags{} +) + +// newResourceDelta returns a new `ackcompare.Delta` used to compare two +// resources +func newResourceDelta( + a *resource, + b *resource, +) *ackcompare.Delta { + delta := ackcompare.NewDelta() + if (a == nil && b != nil) || + (a != nil && b == nil) { + delta.Add("", a, b) + return delta + } + + if ackcompare.HasNilDifference(a.ko.Spec.After, b.ko.Spec.After) { + delta.Add("Spec.After", a.ko.Spec.After, b.ko.Spec.After) + } else if a.ko.Spec.After != nil && b.ko.Spec.After != nil { + if *a.ko.Spec.After != *b.ko.Spec.After { + delta.Add("Spec.After", a.ko.Spec.After, b.ko.Spec.After) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.Rule, b.ko.Spec.Rule) { + delta.Add("Spec.Rule", a.ko.Spec.Rule, b.ko.Spec.Rule) + } else if a.ko.Spec.Rule != nil && b.ko.Spec.Rule != nil { + if len(a.ko.Spec.Rule.Actions) != len(b.ko.Spec.Rule.Actions) { + delta.Add("Spec.Rule.Actions", a.ko.Spec.Rule.Actions, b.ko.Spec.Rule.Actions) + } else if len(a.ko.Spec.Rule.Actions) > 0 { + if !reflect.DeepEqual(a.ko.Spec.Rule.Actions, b.ko.Spec.Rule.Actions) { + delta.Add("Spec.Rule.Actions", a.ko.Spec.Rule.Actions, b.ko.Spec.Rule.Actions) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.Rule.Enabled, b.ko.Spec.Rule.Enabled) { + delta.Add("Spec.Rule.Enabled", a.ko.Spec.Rule.Enabled, b.ko.Spec.Rule.Enabled) + } else if a.ko.Spec.Rule.Enabled != nil && b.ko.Spec.Rule.Enabled != nil { + if *a.ko.Spec.Rule.Enabled != *b.ko.Spec.Rule.Enabled { + delta.Add("Spec.Rule.Enabled", a.ko.Spec.Rule.Enabled, b.ko.Spec.Rule.Enabled) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.Rule.Name, b.ko.Spec.Rule.Name) { + delta.Add("Spec.Rule.Name", a.ko.Spec.Rule.Name, b.ko.Spec.Rule.Name) + } else if a.ko.Spec.Rule.Name != nil && b.ko.Spec.Rule.Name != nil { + if *a.ko.Spec.Rule.Name != *b.ko.Spec.Rule.Name { + delta.Add("Spec.Rule.Name", a.ko.Spec.Rule.Name, b.ko.Spec.Rule.Name) + } + } + if len(a.ko.Spec.Rule.Recipients) != len(b.ko.Spec.Rule.Recipients) { + delta.Add("Spec.Rule.Recipients", a.ko.Spec.Rule.Recipients, b.ko.Spec.Rule.Recipients) + } else if len(a.ko.Spec.Rule.Recipients) > 0 { + if !ackcompare.SliceStringPEqual(a.ko.Spec.Rule.Recipients, b.ko.Spec.Rule.Recipients) { + delta.Add("Spec.Rule.Recipients", a.ko.Spec.Rule.Recipients, b.ko.Spec.Rule.Recipients) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.Rule.ScanEnabled, b.ko.Spec.Rule.ScanEnabled) { + delta.Add("Spec.Rule.ScanEnabled", a.ko.Spec.Rule.ScanEnabled, b.ko.Spec.Rule.ScanEnabled) + } else if a.ko.Spec.Rule.ScanEnabled != nil && b.ko.Spec.Rule.ScanEnabled != nil { + if *a.ko.Spec.Rule.ScanEnabled != *b.ko.Spec.Rule.ScanEnabled { + delta.Add("Spec.Rule.ScanEnabled", a.ko.Spec.Rule.ScanEnabled, b.ko.Spec.Rule.ScanEnabled) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.Rule.TLSPolicy, b.ko.Spec.Rule.TLSPolicy) { + delta.Add("Spec.Rule.TLSPolicy", a.ko.Spec.Rule.TLSPolicy, b.ko.Spec.Rule.TLSPolicy) + } else if a.ko.Spec.Rule.TLSPolicy != nil && b.ko.Spec.Rule.TLSPolicy != nil { + if *a.ko.Spec.Rule.TLSPolicy != *b.ko.Spec.Rule.TLSPolicy { + delta.Add("Spec.Rule.TLSPolicy", a.ko.Spec.Rule.TLSPolicy, b.ko.Spec.Rule.TLSPolicy) + } + } + } + if ackcompare.HasNilDifference(a.ko.Spec.RuleSetName, b.ko.Spec.RuleSetName) { + delta.Add("Spec.RuleSetName", a.ko.Spec.RuleSetName, b.ko.Spec.RuleSetName) + } else if a.ko.Spec.RuleSetName != nil && b.ko.Spec.RuleSetName != nil { + if *a.ko.Spec.RuleSetName != *b.ko.Spec.RuleSetName { + delta.Add("Spec.RuleSetName", a.ko.Spec.RuleSetName, b.ko.Spec.RuleSetName) + } + } + if !reflect.DeepEqual(a.ko.Spec.RuleSetRef, b.ko.Spec.RuleSetRef) { + delta.Add("Spec.RuleSetRef", a.ko.Spec.RuleSetRef, b.ko.Spec.RuleSetRef) + } + + return delta +} diff --git a/pkg/resource/receipt_rule/descriptor.go b/pkg/resource/receipt_rule/descriptor.go new file mode 100644 index 0000000..1e130e7 --- /dev/null +++ b/pkg/resource/receipt_rule/descriptor.go @@ -0,0 +1,155 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package receipt_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" + "k8s.io/apimachinery/pkg/runtime/schema" + rtclient "sigs.k8s.io/controller-runtime/pkg/client" + k8sctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +const ( + FinalizerString = "finalizers.ses.services.k8s.aws/ReceiptRule" +) + +var ( + GroupVersionResource = svcapitypes.GroupVersion.WithResource("receiptrules") + GroupKind = metav1.GroupKind{ + Group: "ses.services.k8s.aws", + Kind: "ReceiptRule", + } +) + +// resourceDescriptor implements the +// `aws-service-operator-k8s/pkg/types.AWSResourceDescriptor` interface +type resourceDescriptor struct { +} + +// GroupVersionKind returns a Kubernetes schema.GroupVersionKind struct that +// describes the API Group, Version and Kind of CRs described by the descriptor +func (d *resourceDescriptor) GroupVersionKind() schema.GroupVersionKind { + return svcapitypes.GroupVersion.WithKind(GroupKind.Kind) +} + +// EmptyRuntimeObject returns an empty object prototype that may be used in +// apimachinery and k8s client operations +func (d *resourceDescriptor) EmptyRuntimeObject() rtclient.Object { + return &svcapitypes.ReceiptRule{} +} + +// 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.ReceiptRule), + } +} + +// 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/receipt_rule/hooks.go b/pkg/resource/receipt_rule/hooks.go new file mode 100644 index 0000000..c0c8aa2 --- /dev/null +++ b/pkg/resource/receipt_rule/hooks.go @@ -0,0 +1,75 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package receipt_rule + +import ( + "context" + + ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" + ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" + svcsdk "github.com/aws/aws-sdk-go/service/ses" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +func setReceiptRule(ko *svcapitypes.ReceiptRule, resp *svcsdk.DescribeReceiptRuleOutput) { + ko.Spec.Rule = setResourceReceiptRule(resp.Rule) +} + +func (rm *resourceManager) customFind( + 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 r.ko.Spec.Rule == nil || r.ko.Spec.Rule.Name == nil || r.ko.Spec.RuleSetName == nil { + return nil, ackerr.NotFound + } + + input := &svcsdk.DescribeReceiptRuleInput{ + RuleSetName: r.ko.Spec.RuleSetName, + } + if rule := r.ko.Spec.Rule; rule != nil { + input.RuleName = rule.Name + } + + var resp *svcsdk.DescribeReceiptRuleOutput + resp, err = rm.sdkapi.DescribeReceiptRuleWithContext(ctx, input) + + rm.metrics.RecordAPICall("READ_ONE", "DescribeReceiptRule", err) + if err != nil { + if awsErr, ok := ackerr.AWSError(err); ok && awsErr.Code() == svcsdk.ErrCodeRuleDoesNotExistException { + return nil, ackerr.NotFound + } + if reqErr, ok := ackerr.AWSRequestFailure(err); ok && reqErr.StatusCode() == 404 { + 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() + + rm.setStatusDefaults(ko) + setReceiptRule(ko, resp) + + return &resource{ko}, nil +} diff --git a/pkg/resource/receipt_rule/identifiers.go b/pkg/resource/receipt_rule/identifiers.go new file mode 100644 index 0000000..f5d1ee9 --- /dev/null +++ b/pkg/resource/receipt_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 receipt_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/receipt_rule/manager.go b/pkg/resource/receipt_rule/manager.go new file mode 100644 index 0000000..c52f0ea --- /dev/null +++ b/pkg/resource/receipt_rule/manager.go @@ -0,0 +1,350 @@ +// 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 receipt_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/ses" + svcsdkapi "github.com/aws/aws-sdk-go/service/ses/sesiface" + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +var ( + _ = ackutil.InStrings + _ = acktags.NewTags() + _ = ackrt.MissingImageTagValue + _ = svcapitypes.ReceiptRule{} +) + +// +kubebuilder:rbac:groups=ses.services.k8s.aws,resources=receiptrules,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=ses.services.k8s.aws,resources=receiptrules/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.SESAPI +} + +// 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:ses:%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 { + + 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/receipt_rule/manager_factory.go b/pkg/resource/receipt_rule/manager_factory.go new file mode 100644 index 0000000..2646556 --- /dev/null +++ b/pkg/resource/receipt_rule/manager_factory.go @@ -0,0 +1,100 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package receipt_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/ses-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, + roleARN ackv1alpha1.AWSResourceName, +) (acktypes.AWSResourceManager, error) { + // We use the account ID, region, and role ARN to uniquely identify a + // resource manager. This helps us to avoid creating multiple resource + // managers for the same account/region/roleARN combination. + rmId := fmt.Sprintf("%s/%s/%s", id, region, roleARN) + f.RLock() + rm, found := f.rmCache[rmId] + f.RUnlock() + + if found { + return rm, nil + } + + f.Lock() + defer f.Unlock() + + rm, err := newResourceManager(cfg, 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/receipt_rule/references.go b/pkg/resource/receipt_rule/references.go new file mode 100644 index 0000000..ee70fce --- /dev/null +++ b/pkg/resource/receipt_rule/references.go @@ -0,0 +1,166 @@ +// 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 receipt_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" + ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +// ClearResolvedReferences removes any reference values that were made +// concrete in the spec. It returns a copy of the input AWSResource which +// contains the original *Ref values, but none of their respective concrete +// values. +func (rm *resourceManager) ClearResolvedReferences(res acktypes.AWSResource) acktypes.AWSResource { + ko := rm.concreteResource(res).ko.DeepCopy() + + if ko.Spec.RuleSetRef != nil { + ko.Spec.RuleSetName = nil + } + + return &resource{ko} +} + +// ResolveReferences finds if there are any Reference field(s) present +// inside AWSResource passed in the parameter and attempts to resolve those +// reference field(s) into their respective target field(s). It returns a +// copy of the input AWSResource with resolved reference(s), a boolean which +// is set to true if the resource contains any references (regardless of if +// they are resolved successfully) and an error if the passed AWSResource's +// reference field(s) could not be resolved. +func (rm *resourceManager) ResolveReferences( + ctx context.Context, + apiReader client.Reader, + res acktypes.AWSResource, +) (acktypes.AWSResource, bool, error) { + ko := rm.concreteResource(res).ko + + resourceHasReferences := false + err := validateReferenceFields(ko) + if fieldHasReferences, err := rm.resolveReferenceForRuleSetName(ctx, apiReader, ko); err != nil { + return &resource{ko}, (resourceHasReferences || fieldHasReferences), err + } else { + resourceHasReferences = resourceHasReferences || fieldHasReferences + } + + return &resource{ko}, resourceHasReferences, err +} + +// validateReferenceFields validates the reference field and corresponding +// identifier field. +func validateReferenceFields(ko *svcapitypes.ReceiptRule) error { + + if ko.Spec.RuleSetRef != nil && ko.Spec.RuleSetName != nil { + return ackerr.ResourceReferenceAndIDNotSupportedFor("RuleSetName", "RuleSetRef") + } + if ko.Spec.RuleSetRef == nil && ko.Spec.RuleSetName == nil { + return ackerr.ResourceReferenceOrIDRequiredFor("RuleSetName", "RuleSetRef") + } + return nil +} + +// resolveReferenceForRuleSetName reads the resource referenced +// from RuleSetRef field and sets the RuleSetName +// from referenced resource. Returns a boolean indicating whether a reference +// contains references, or an error +func (rm *resourceManager) resolveReferenceForRuleSetName( + ctx context.Context, + apiReader client.Reader, + ko *svcapitypes.ReceiptRule, +) (hasReferences bool, err error) { + if ko.Spec.RuleSetRef != nil && ko.Spec.RuleSetRef.From != nil { + hasReferences = true + arr := ko.Spec.RuleSetRef.From + if arr.Name == nil || *arr.Name == "" { + return hasReferences, fmt.Errorf("provided resource reference is nil or empty: RuleSetRef") + } + namespace := ko.ObjectMeta.GetNamespace() + if arr.Namespace != nil && *arr.Namespace != "" { + namespace = *arr.Namespace + } + obj := &svcapitypes.ReceiptRuleSet{} + if err := getReferencedResourceState_ReceiptRuleSet(ctx, apiReader, obj, *arr.Name, namespace); err != nil { + return hasReferences, err + } + ko.Spec.RuleSetName = (*string)(obj.Spec.RuleSetName) + } + + return hasReferences, nil +} + +// getReferencedResourceState_ReceiptRuleSet looks up whether a referenced resource +// exists and is in a ACK.ResourceSynced=True state. If the referenced resource does exist and is +// in a Synced state, returns nil, otherwise returns `ackerr.ResourceReferenceTerminalFor` or +// `ResourceReferenceNotSyncedFor` depending on if the resource is in a Terminal state. +func getReferencedResourceState_ReceiptRuleSet( + ctx context.Context, + apiReader client.Reader, + obj *svcapitypes.ReceiptRuleSet, + name string, // the Kubernetes name of the referenced resource + namespace string, // the Kubernetes namespace of the referenced resource +) error { + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + err := apiReader.Get(ctx, namespacedName, obj) + if err != nil { + return err + } + var refResourceTerminal bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeTerminal && + cond.Status == corev1.ConditionTrue { + return ackerr.ResourceReferenceTerminalFor( + "ReceiptRuleSet", + namespace, name) + } + } + if refResourceTerminal { + return ackerr.ResourceReferenceTerminalFor( + "ReceiptRuleSet", + namespace, name) + } + var refResourceSynced bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeResourceSynced && + cond.Status == corev1.ConditionTrue { + refResourceSynced = true + } + } + if !refResourceSynced { + return ackerr.ResourceReferenceNotSyncedFor( + "ReceiptRuleSet", + namespace, name) + } + if obj.Spec.RuleSetName == nil { + return ackerr.ResourceReferenceMissingTargetFieldFor( + "ReceiptRuleSet", + namespace, name, + "Spec.RuleSetName") + } + return nil +} diff --git a/pkg/resource/receipt_rule/resource.go b/pkg/resource/receipt_rule/resource.go new file mode 100644 index 0000000..04482fc --- /dev/null +++ b/pkg/resource/receipt_rule/resource.go @@ -0,0 +1,100 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package receipt_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/ses-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.ReceiptRule +} + +// Identifiers returns an AWSResourceIdentifiers object containing various +// identifying information, including the AWS account ID that owns the +// resource, the resource's AWS Resource Name (ARN) +func (r *resource) Identifiers() acktypes.AWSResourceIdentifiers { + return &resourceIdentifiers{r.ko.Status.ACKResourceMetadata} +} + +// IsBeingDeleted returns true if the Kubernetes resource has a non-zero +// deletion timestamp +func (r *resource) IsBeingDeleted() bool { + return !r.ko.DeletionTimestamp.IsZero() +} + +// RuntimeObject returns the Kubernetes apimachinery/runtime representation of +// the AWSResource +func (r *resource) RuntimeObject() rtclient.Object { + return r.ko +} + +// MetaObject returns the Kubernetes apimachinery/apis/meta/v1.Object +// representation of the AWSResource +func (r *resource) MetaObject() metav1.Object { + return r.ko.GetObjectMeta() +} + +// Conditions returns the ACK Conditions collection for the AWSResource +func (r *resource) Conditions() []*ackv1alpha1.Condition { + return r.ko.Status.Conditions +} + +// ReplaceConditions sets the Conditions status field for the resource +func (r *resource) ReplaceConditions(conditions []*ackv1alpha1.Condition) { + r.ko.Status.Conditions = conditions +} + +// SetObjectMeta sets the ObjectMeta field for the resource +func (r *resource) SetObjectMeta(meta metav1.ObjectMeta) { + r.ko.ObjectMeta = meta +} + +// SetStatus will set the Status field for the resource +func (r *resource) SetStatus(desired acktypes.AWSResource) { + r.ko.Status = desired.(*resource).ko.Status +} + +// SetIdentifiers sets the Spec or Status field that is referenced as the unique +// resource identifier +func (r *resource) SetIdentifiers(identifier *ackv1alpha1.AWSIdentifiers) error { + if identifier.NameOrID == "" { + return ackerrors.MissingNameIdentifier + } + r.ko.Spec.RuleSetName = &identifier.NameOrID + + return nil +} + +// DeepCopy will return a copy of the resource +func (r *resource) DeepCopy() acktypes.AWSResource { + koCopy := r.ko.DeepCopy() + return &resource{koCopy} +} diff --git a/pkg/resource/receipt_rule/sdk.go b/pkg/resource/receipt_rule/sdk.go new file mode 100644 index 0000000..10fa563 --- /dev/null +++ b/pkg/resource/receipt_rule/sdk.go @@ -0,0 +1,705 @@ +// 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 receipt_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/ses" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +// Hack to avoid import errors during build... +var ( + _ = &metav1.Time{} + _ = strings.ToLower("") + _ = &aws.JSONValue{} + _ = &svcsdk.SES{} + _ = &svcapitypes.ReceiptRule{} + _ = 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, +) (*resource, error) { + return rm.customFind(ctx, r) +} + +// sdkCreate creates the supplied resource in the backend AWS service API and +// returns a copy of the resource with resource fields (in both Spec and +// Status) filled in with values from the CREATE API operation's Output shape. +func (rm *resourceManager) sdkCreate( + ctx context.Context, + desired *resource, +) (created *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkCreate") + defer func() { + exit(err) + }() + input, err := rm.newCreateRequestPayload(ctx, desired) + if err != nil { + return nil, err + } + + var resp *svcsdk.CreateReceiptRuleOutput + _ = resp + resp, err = rm.sdkapi.CreateReceiptRuleWithContext(ctx, input) + rm.metrics.RecordAPICall("CREATE", "CreateReceiptRule", 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() + + rm.setStatusDefaults(ko) + return &resource{ko}, nil +} + +// newCreateRequestPayload returns an SDK-specific struct for the HTTP request +// payload of the Create API call for the resource +func (rm *resourceManager) newCreateRequestPayload( + ctx context.Context, + r *resource, +) (*svcsdk.CreateReceiptRuleInput, error) { + res := &svcsdk.CreateReceiptRuleInput{} + + if r.ko.Spec.After != nil { + res.SetAfter(*r.ko.Spec.After) + } + if r.ko.Spec.Rule != nil { + f1 := &svcsdk.ReceiptRule{} + if r.ko.Spec.Rule.Actions != nil { + f1f0 := []*svcsdk.ReceiptAction{} + for _, f1f0iter := range r.ko.Spec.Rule.Actions { + f1f0elem := &svcsdk.ReceiptAction{} + if f1f0iter.AddHeaderAction != nil { + f1f0elemf0 := &svcsdk.AddHeaderAction{} + if f1f0iter.AddHeaderAction.HeaderName != nil { + f1f0elemf0.SetHeaderName(*f1f0iter.AddHeaderAction.HeaderName) + } + if f1f0iter.AddHeaderAction.HeaderValue != nil { + f1f0elemf0.SetHeaderValue(*f1f0iter.AddHeaderAction.HeaderValue) + } + f1f0elem.SetAddHeaderAction(f1f0elemf0) + } + if f1f0iter.BounceAction != nil { + f1f0elemf1 := &svcsdk.BounceAction{} + if f1f0iter.BounceAction.Message != nil { + f1f0elemf1.SetMessage(*f1f0iter.BounceAction.Message) + } + if f1f0iter.BounceAction.Sender != nil { + f1f0elemf1.SetSender(*f1f0iter.BounceAction.Sender) + } + if f1f0iter.BounceAction.SmtpReplyCode != nil { + f1f0elemf1.SetSmtpReplyCode(*f1f0iter.BounceAction.SmtpReplyCode) + } + if f1f0iter.BounceAction.StatusCode != nil { + f1f0elemf1.SetStatusCode(*f1f0iter.BounceAction.StatusCode) + } + if f1f0iter.BounceAction.TopicARN != nil { + f1f0elemf1.SetTopicArn(*f1f0iter.BounceAction.TopicARN) + } + f1f0elem.SetBounceAction(f1f0elemf1) + } + if f1f0iter.LambdaAction != nil { + f1f0elemf2 := &svcsdk.LambdaAction{} + if f1f0iter.LambdaAction.FunctionARN != nil { + f1f0elemf2.SetFunctionArn(*f1f0iter.LambdaAction.FunctionARN) + } + if f1f0iter.LambdaAction.InvocationType != nil { + f1f0elemf2.SetInvocationType(*f1f0iter.LambdaAction.InvocationType) + } + if f1f0iter.LambdaAction.TopicARN != nil { + f1f0elemf2.SetTopicArn(*f1f0iter.LambdaAction.TopicARN) + } + f1f0elem.SetLambdaAction(f1f0elemf2) + } + if f1f0iter.S3Action != nil { + f1f0elemf3 := &svcsdk.S3Action{} + if f1f0iter.S3Action.BucketName != nil { + f1f0elemf3.SetBucketName(*f1f0iter.S3Action.BucketName) + } + if f1f0iter.S3Action.KMSKeyARN != nil { + f1f0elemf3.SetKmsKeyArn(*f1f0iter.S3Action.KMSKeyARN) + } + if f1f0iter.S3Action.ObjectKeyPrefix != nil { + f1f0elemf3.SetObjectKeyPrefix(*f1f0iter.S3Action.ObjectKeyPrefix) + } + if f1f0iter.S3Action.TopicARN != nil { + f1f0elemf3.SetTopicArn(*f1f0iter.S3Action.TopicARN) + } + f1f0elem.SetS3Action(f1f0elemf3) + } + if f1f0iter.SNSAction != nil { + f1f0elemf4 := &svcsdk.SNSAction{} + if f1f0iter.SNSAction.Encoding != nil { + f1f0elemf4.SetEncoding(*f1f0iter.SNSAction.Encoding) + } + if f1f0iter.SNSAction.TopicARN != nil { + f1f0elemf4.SetTopicArn(*f1f0iter.SNSAction.TopicARN) + } + f1f0elem.SetSNSAction(f1f0elemf4) + } + if f1f0iter.StopAction != nil { + f1f0elemf5 := &svcsdk.StopAction{} + if f1f0iter.StopAction.Scope != nil { + f1f0elemf5.SetScope(*f1f0iter.StopAction.Scope) + } + if f1f0iter.StopAction.TopicARN != nil { + f1f0elemf5.SetTopicArn(*f1f0iter.StopAction.TopicARN) + } + f1f0elem.SetStopAction(f1f0elemf5) + } + if f1f0iter.WorkmailAction != nil { + f1f0elemf6 := &svcsdk.WorkmailAction{} + if f1f0iter.WorkmailAction.OrganizationARN != nil { + f1f0elemf6.SetOrganizationArn(*f1f0iter.WorkmailAction.OrganizationARN) + } + if f1f0iter.WorkmailAction.TopicARN != nil { + f1f0elemf6.SetTopicArn(*f1f0iter.WorkmailAction.TopicARN) + } + f1f0elem.SetWorkmailAction(f1f0elemf6) + } + f1f0 = append(f1f0, f1f0elem) + } + f1.SetActions(f1f0) + } + if r.ko.Spec.Rule.Enabled != nil { + f1.SetEnabled(*r.ko.Spec.Rule.Enabled) + } + if r.ko.Spec.Rule.Name != nil { + f1.SetName(*r.ko.Spec.Rule.Name) + } + if r.ko.Spec.Rule.Recipients != nil { + f1f3 := []*string{} + for _, f1f3iter := range r.ko.Spec.Rule.Recipients { + var f1f3elem string + f1f3elem = *f1f3iter + f1f3 = append(f1f3, &f1f3elem) + } + f1.SetRecipients(f1f3) + } + if r.ko.Spec.Rule.ScanEnabled != nil { + f1.SetScanEnabled(*r.ko.Spec.Rule.ScanEnabled) + } + if r.ko.Spec.Rule.TLSPolicy != nil { + f1.SetTlsPolicy(*r.ko.Spec.Rule.TLSPolicy) + } + res.SetRule(f1) + } + if r.ko.Spec.RuleSetName != nil { + res.SetRuleSetName(*r.ko.Spec.RuleSetName) + } + + 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)) + } + input, err := rm.newUpdateRequestPayload(ctx, desired, delta) + if err != nil { + return nil, err + } + + var resp *svcsdk.UpdateReceiptRuleOutput + _ = resp + resp, err = rm.sdkapi.UpdateReceiptRuleWithContext(ctx, input) + rm.metrics.RecordAPICall("UPDATE", "UpdateReceiptRule", 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() + + 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, + delta *ackcompare.Delta, +) (*svcsdk.UpdateReceiptRuleInput, error) { + res := &svcsdk.UpdateReceiptRuleInput{} + + if r.ko.Spec.Rule != nil { + f0 := &svcsdk.ReceiptRule{} + if r.ko.Spec.Rule.Actions != nil { + f0f0 := []*svcsdk.ReceiptAction{} + for _, f0f0iter := range r.ko.Spec.Rule.Actions { + f0f0elem := &svcsdk.ReceiptAction{} + if f0f0iter.AddHeaderAction != nil { + f0f0elemf0 := &svcsdk.AddHeaderAction{} + if f0f0iter.AddHeaderAction.HeaderName != nil { + f0f0elemf0.SetHeaderName(*f0f0iter.AddHeaderAction.HeaderName) + } + if f0f0iter.AddHeaderAction.HeaderValue != nil { + f0f0elemf0.SetHeaderValue(*f0f0iter.AddHeaderAction.HeaderValue) + } + f0f0elem.SetAddHeaderAction(f0f0elemf0) + } + if f0f0iter.BounceAction != nil { + f0f0elemf1 := &svcsdk.BounceAction{} + if f0f0iter.BounceAction.Message != nil { + f0f0elemf1.SetMessage(*f0f0iter.BounceAction.Message) + } + if f0f0iter.BounceAction.Sender != nil { + f0f0elemf1.SetSender(*f0f0iter.BounceAction.Sender) + } + if f0f0iter.BounceAction.SmtpReplyCode != nil { + f0f0elemf1.SetSmtpReplyCode(*f0f0iter.BounceAction.SmtpReplyCode) + } + if f0f0iter.BounceAction.StatusCode != nil { + f0f0elemf1.SetStatusCode(*f0f0iter.BounceAction.StatusCode) + } + if f0f0iter.BounceAction.TopicARN != nil { + f0f0elemf1.SetTopicArn(*f0f0iter.BounceAction.TopicARN) + } + f0f0elem.SetBounceAction(f0f0elemf1) + } + if f0f0iter.LambdaAction != nil { + f0f0elemf2 := &svcsdk.LambdaAction{} + if f0f0iter.LambdaAction.FunctionARN != nil { + f0f0elemf2.SetFunctionArn(*f0f0iter.LambdaAction.FunctionARN) + } + if f0f0iter.LambdaAction.InvocationType != nil { + f0f0elemf2.SetInvocationType(*f0f0iter.LambdaAction.InvocationType) + } + if f0f0iter.LambdaAction.TopicARN != nil { + f0f0elemf2.SetTopicArn(*f0f0iter.LambdaAction.TopicARN) + } + f0f0elem.SetLambdaAction(f0f0elemf2) + } + if f0f0iter.S3Action != nil { + f0f0elemf3 := &svcsdk.S3Action{} + if f0f0iter.S3Action.BucketName != nil { + f0f0elemf3.SetBucketName(*f0f0iter.S3Action.BucketName) + } + if f0f0iter.S3Action.KMSKeyARN != nil { + f0f0elemf3.SetKmsKeyArn(*f0f0iter.S3Action.KMSKeyARN) + } + if f0f0iter.S3Action.ObjectKeyPrefix != nil { + f0f0elemf3.SetObjectKeyPrefix(*f0f0iter.S3Action.ObjectKeyPrefix) + } + if f0f0iter.S3Action.TopicARN != nil { + f0f0elemf3.SetTopicArn(*f0f0iter.S3Action.TopicARN) + } + f0f0elem.SetS3Action(f0f0elemf3) + } + if f0f0iter.SNSAction != nil { + f0f0elemf4 := &svcsdk.SNSAction{} + if f0f0iter.SNSAction.Encoding != nil { + f0f0elemf4.SetEncoding(*f0f0iter.SNSAction.Encoding) + } + if f0f0iter.SNSAction.TopicARN != nil { + f0f0elemf4.SetTopicArn(*f0f0iter.SNSAction.TopicARN) + } + f0f0elem.SetSNSAction(f0f0elemf4) + } + if f0f0iter.StopAction != nil { + f0f0elemf5 := &svcsdk.StopAction{} + if f0f0iter.StopAction.Scope != nil { + f0f0elemf5.SetScope(*f0f0iter.StopAction.Scope) + } + if f0f0iter.StopAction.TopicARN != nil { + f0f0elemf5.SetTopicArn(*f0f0iter.StopAction.TopicARN) + } + f0f0elem.SetStopAction(f0f0elemf5) + } + if f0f0iter.WorkmailAction != nil { + f0f0elemf6 := &svcsdk.WorkmailAction{} + if f0f0iter.WorkmailAction.OrganizationARN != nil { + f0f0elemf6.SetOrganizationArn(*f0f0iter.WorkmailAction.OrganizationARN) + } + if f0f0iter.WorkmailAction.TopicARN != nil { + f0f0elemf6.SetTopicArn(*f0f0iter.WorkmailAction.TopicARN) + } + f0f0elem.SetWorkmailAction(f0f0elemf6) + } + f0f0 = append(f0f0, f0f0elem) + } + f0.SetActions(f0f0) + } + if r.ko.Spec.Rule.Enabled != nil { + f0.SetEnabled(*r.ko.Spec.Rule.Enabled) + } + if r.ko.Spec.Rule.Name != nil { + f0.SetName(*r.ko.Spec.Rule.Name) + } + if r.ko.Spec.Rule.Recipients != nil { + f0f3 := []*string{} + for _, f0f3iter := range r.ko.Spec.Rule.Recipients { + var f0f3elem string + f0f3elem = *f0f3iter + f0f3 = append(f0f3, &f0f3elem) + } + f0.SetRecipients(f0f3) + } + if r.ko.Spec.Rule.ScanEnabled != nil { + f0.SetScanEnabled(*r.ko.Spec.Rule.ScanEnabled) + } + if r.ko.Spec.Rule.TLSPolicy != nil { + f0.SetTlsPolicy(*r.ko.Spec.Rule.TLSPolicy) + } + res.SetRule(f0) + } + if r.ko.Spec.RuleSetName != nil { + res.SetRuleSetName(*r.ko.Spec.RuleSetName) + } + + 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) + }() + input, err := rm.newDeleteRequestPayload(r) + if err != nil { + return nil, err + } + if rule := r.ko.Spec.Rule; rule != nil { + input.RuleName = rule.Name + } + + var resp *svcsdk.DeleteReceiptRuleOutput + _ = resp + resp, err = rm.sdkapi.DeleteReceiptRuleWithContext(ctx, input) + rm.metrics.RecordAPICall("DELETE", "DeleteReceiptRule", 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.DeleteReceiptRuleInput, error) { + res := &svcsdk.DeleteReceiptRuleInput{} + + if r.ko.Spec.RuleSetName != nil { + res.SetRuleSetName(*r.ko.Spec.RuleSetName) + } + + return res, nil +} + +// setStatusDefaults sets default properties into supplied custom resource +func (rm *resourceManager) setStatusDefaults( + ko *svcapitypes.ReceiptRule, +) { + 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 "AlreadyExists", + "InvalidLambdaFunction", + "InvalidS3Configuration", + "InvalidSnsTopic", + "RuleSetDoesNotExist": + 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.Rule.Name") { + fields = append(fields, "Rule.Name") + } + if delta.DifferentAt("Spec.RuleSetName") { + fields = append(fields, "RuleSetName") + } + + return fields +} + +// setReceiptRule sets a resource ReceiptRule type +// given the SDK type. +func setResourceReceiptRule( + resp *svcsdk.ReceiptRule, +) *svcapitypes.ReceiptRule_SDK { + res := &svcapitypes.ReceiptRule_SDK{} + + if resp.Actions != nil { + resf0 := []*svcapitypes.ReceiptAction{} + for _, resf0iter := range resp.Actions { + resf0elem := &svcapitypes.ReceiptAction{} + if resf0iter.AddHeaderAction != nil { + resf0elemf0 := &svcapitypes.AddHeaderAction{} + if resf0iter.AddHeaderAction.HeaderName != nil { + resf0elemf0.HeaderName = resf0iter.AddHeaderAction.HeaderName + } + if resf0iter.AddHeaderAction.HeaderValue != nil { + resf0elemf0.HeaderValue = resf0iter.AddHeaderAction.HeaderValue + } + resf0elem.AddHeaderAction = resf0elemf0 + } + if resf0iter.BounceAction != nil { + resf0elemf1 := &svcapitypes.BounceAction{} + if resf0iter.BounceAction.Message != nil { + resf0elemf1.Message = resf0iter.BounceAction.Message + } + if resf0iter.BounceAction.Sender != nil { + resf0elemf1.Sender = resf0iter.BounceAction.Sender + } + if resf0iter.BounceAction.SmtpReplyCode != nil { + resf0elemf1.SmtpReplyCode = resf0iter.BounceAction.SmtpReplyCode + } + if resf0iter.BounceAction.StatusCode != nil { + resf0elemf1.StatusCode = resf0iter.BounceAction.StatusCode + } + if resf0iter.BounceAction.TopicArn != nil { + resf0elemf1.TopicARN = resf0iter.BounceAction.TopicArn + } + resf0elem.BounceAction = resf0elemf1 + } + if resf0iter.LambdaAction != nil { + resf0elemf2 := &svcapitypes.LambdaAction{} + if resf0iter.LambdaAction.FunctionArn != nil { + resf0elemf2.FunctionARN = resf0iter.LambdaAction.FunctionArn + } + if resf0iter.LambdaAction.InvocationType != nil { + resf0elemf2.InvocationType = resf0iter.LambdaAction.InvocationType + } + if resf0iter.LambdaAction.TopicArn != nil { + resf0elemf2.TopicARN = resf0iter.LambdaAction.TopicArn + } + resf0elem.LambdaAction = resf0elemf2 + } + if resf0iter.S3Action != nil { + resf0elemf3 := &svcapitypes.S3Action{} + if resf0iter.S3Action.BucketName != nil { + resf0elemf3.BucketName = resf0iter.S3Action.BucketName + } + if resf0iter.S3Action.KmsKeyArn != nil { + resf0elemf3.KMSKeyARN = resf0iter.S3Action.KmsKeyArn + } + if resf0iter.S3Action.ObjectKeyPrefix != nil { + resf0elemf3.ObjectKeyPrefix = resf0iter.S3Action.ObjectKeyPrefix + } + if resf0iter.S3Action.TopicArn != nil { + resf0elemf3.TopicARN = resf0iter.S3Action.TopicArn + } + resf0elem.S3Action = resf0elemf3 + } + if resf0iter.SNSAction != nil { + resf0elemf4 := &svcapitypes.SNSAction{} + if resf0iter.SNSAction.Encoding != nil { + resf0elemf4.Encoding = resf0iter.SNSAction.Encoding + } + if resf0iter.SNSAction.TopicArn != nil { + resf0elemf4.TopicARN = resf0iter.SNSAction.TopicArn + } + resf0elem.SNSAction = resf0elemf4 + } + if resf0iter.StopAction != nil { + resf0elemf5 := &svcapitypes.StopAction{} + if resf0iter.StopAction.Scope != nil { + resf0elemf5.Scope = resf0iter.StopAction.Scope + } + if resf0iter.StopAction.TopicArn != nil { + resf0elemf5.TopicARN = resf0iter.StopAction.TopicArn + } + resf0elem.StopAction = resf0elemf5 + } + if resf0iter.WorkmailAction != nil { + resf0elemf6 := &svcapitypes.WorkmailAction{} + if resf0iter.WorkmailAction.OrganizationArn != nil { + resf0elemf6.OrganizationARN = resf0iter.WorkmailAction.OrganizationArn + } + if resf0iter.WorkmailAction.TopicArn != nil { + resf0elemf6.TopicARN = resf0iter.WorkmailAction.TopicArn + } + resf0elem.WorkmailAction = resf0elemf6 + } + resf0 = append(resf0, resf0elem) + } + res.Actions = resf0 + } + if resp.Enabled != nil { + res.Enabled = resp.Enabled + } + if resp.Name != nil { + res.Name = resp.Name + } + if resp.Recipients != nil { + resf3 := []*string{} + for _, resf3iter := range resp.Recipients { + var resf3elem string + resf3elem = *resf3iter + resf3 = append(resf3, &resf3elem) + } + res.Recipients = resf3 + } + if resp.ScanEnabled != nil { + res.ScanEnabled = resp.ScanEnabled + } + if resp.TlsPolicy != nil { + res.TLSPolicy = resp.TlsPolicy + } + + return res +} diff --git a/pkg/resource/receipt_rule_set/delta.go b/pkg/resource/receipt_rule_set/delta.go new file mode 100644 index 0000000..615ceac --- /dev/null +++ b/pkg/resource/receipt_rule_set/delta.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 receipt_rule_set + +import ( + "bytes" + "reflect" + + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" +) + +// Hack to avoid import errors during build... +var ( + _ = &bytes.Buffer{} + _ = &reflect.Method{} + _ = &acktags.Tags{} +) + +// newResourceDelta returns a new `ackcompare.Delta` used to compare two +// resources +func newResourceDelta( + a *resource, + b *resource, +) *ackcompare.Delta { + delta := ackcompare.NewDelta() + if (a == nil && b != nil) || + (a != nil && b == nil) { + delta.Add("", a, b) + return delta + } + + if ackcompare.HasNilDifference(a.ko.Spec.RuleSetName, b.ko.Spec.RuleSetName) { + delta.Add("Spec.RuleSetName", a.ko.Spec.RuleSetName, b.ko.Spec.RuleSetName) + } else if a.ko.Spec.RuleSetName != nil && b.ko.Spec.RuleSetName != nil { + if *a.ko.Spec.RuleSetName != *b.ko.Spec.RuleSetName { + delta.Add("Spec.RuleSetName", a.ko.Spec.RuleSetName, b.ko.Spec.RuleSetName) + } + } + + return delta +} diff --git a/pkg/resource/receipt_rule_set/descriptor.go b/pkg/resource/receipt_rule_set/descriptor.go new file mode 100644 index 0000000..2440348 --- /dev/null +++ b/pkg/resource/receipt_rule_set/descriptor.go @@ -0,0 +1,155 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package receipt_rule_set + +import ( + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + rtclient "sigs.k8s.io/controller-runtime/pkg/client" + k8sctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +const ( + FinalizerString = "finalizers.ses.services.k8s.aws/ReceiptRuleSet" +) + +var ( + GroupVersionResource = svcapitypes.GroupVersion.WithResource("receiptrulesets") + GroupKind = metav1.GroupKind{ + Group: "ses.services.k8s.aws", + Kind: "ReceiptRuleSet", + } +) + +// resourceDescriptor implements the +// `aws-service-operator-k8s/pkg/types.AWSResourceDescriptor` interface +type resourceDescriptor struct { +} + +// GroupVersionKind returns a Kubernetes schema.GroupVersionKind struct that +// describes the API Group, Version and Kind of CRs described by the descriptor +func (d *resourceDescriptor) GroupVersionKind() schema.GroupVersionKind { + return svcapitypes.GroupVersion.WithKind(GroupKind.Kind) +} + +// EmptyRuntimeObject returns an empty object prototype that may be used in +// apimachinery and k8s client operations +func (d *resourceDescriptor) EmptyRuntimeObject() rtclient.Object { + return &svcapitypes.ReceiptRuleSet{} +} + +// 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.ReceiptRuleSet), + } +} + +// 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/receipt_rule_set/hooks.go b/pkg/resource/receipt_rule_set/hooks.go new file mode 100644 index 0000000..0428b23 --- /dev/null +++ b/pkg/resource/receipt_rule_set/hooks.go @@ -0,0 +1,31 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package receipt_rule_set + +import ( + "context" + + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + + "github.com/aws-controllers-k8s/ses-controller/pkg/util" +) + +func (rm *resourceManager) customUpdate( + ctx context.Context, + desired *resource, + _ *resource, + delta *ackcompare.Delta, +) (updated *resource, err error) { + return util.ValidateImmutableResource(ctx, rm.getImmutableFieldChanges(delta), desired) +} diff --git a/pkg/resource/receipt_rule_set/identifiers.go b/pkg/resource/receipt_rule_set/identifiers.go new file mode 100644 index 0000000..2f4f59a --- /dev/null +++ b/pkg/resource/receipt_rule_set/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 receipt_rule_set + +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/receipt_rule_set/manager.go b/pkg/resource/receipt_rule_set/manager.go new file mode 100644 index 0000000..501a326 --- /dev/null +++ b/pkg/resource/receipt_rule_set/manager.go @@ -0,0 +1,350 @@ +// 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 receipt_rule_set + +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/ses" + svcsdkapi "github.com/aws/aws-sdk-go/service/ses/sesiface" + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +var ( + _ = ackutil.InStrings + _ = acktags.NewTags() + _ = ackrt.MissingImageTagValue + _ = svcapitypes.ReceiptRuleSet{} +) + +// +kubebuilder:rbac:groups=ses.services.k8s.aws,resources=receiptrulesets,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=ses.services.k8s.aws,resources=receiptrulesets/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.SESAPI +} + +// 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:ses:%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 { + + 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/receipt_rule_set/manager_factory.go b/pkg/resource/receipt_rule_set/manager_factory.go new file mode 100644 index 0000000..af81fc5 --- /dev/null +++ b/pkg/resource/receipt_rule_set/manager_factory.go @@ -0,0 +1,100 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package receipt_rule_set + +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/ses-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, + roleARN ackv1alpha1.AWSResourceName, +) (acktypes.AWSResourceManager, error) { + // We use the account ID, region, and role ARN to uniquely identify a + // resource manager. This helps us to avoid creating multiple resource + // managers for the same account/region/roleARN combination. + rmId := fmt.Sprintf("%s/%s/%s", id, region, roleARN) + f.RLock() + rm, found := f.rmCache[rmId] + f.RUnlock() + + if found { + return rm, nil + } + + f.Lock() + defer f.Unlock() + + rm, err := newResourceManager(cfg, 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/receipt_rule_set/references.go b/pkg/resource/receipt_rule_set/references.go new file mode 100644 index 0000000..97f5f5d --- /dev/null +++ b/pkg/resource/receipt_rule_set/references.go @@ -0,0 +1,56 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package receipt_rule_set + +import ( + "context" + "sigs.k8s.io/controller-runtime/pkg/client" + + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +// ClearResolvedReferences removes any reference values that were made +// concrete in the spec. It returns a copy of the input AWSResource which +// contains the original *Ref values, but none of their respective concrete +// values. +func (rm *resourceManager) ClearResolvedReferences(res acktypes.AWSResource) acktypes.AWSResource { + ko := rm.concreteResource(res).ko.DeepCopy() + + return &resource{ko} +} + +// ResolveReferences finds if there are any Reference field(s) present +// inside AWSResource passed in the parameter and attempts to resolve those +// reference field(s) into their respective target field(s). It returns a +// copy of the input AWSResource with resolved reference(s), a boolean which +// is set to true if the resource contains any references (regardless of if +// they are resolved successfully) and an error if the passed AWSResource's +// reference field(s) could not be resolved. +func (rm *resourceManager) ResolveReferences( + ctx context.Context, + apiReader client.Reader, + res acktypes.AWSResource, +) (acktypes.AWSResource, bool, error) { + return res, false, nil +} + +// validateReferenceFields validates the reference field and corresponding +// identifier field. +func validateReferenceFields(ko *svcapitypes.ReceiptRuleSet) error { + return nil +} diff --git a/pkg/resource/receipt_rule_set/resource.go b/pkg/resource/receipt_rule_set/resource.go new file mode 100644 index 0000000..46a9b2c --- /dev/null +++ b/pkg/resource/receipt_rule_set/resource.go @@ -0,0 +1,100 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package receipt_rule_set + +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/ses-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.ReceiptRuleSet +} + +// Identifiers returns an AWSResourceIdentifiers object containing various +// identifying information, including the AWS account ID that owns the +// resource, the resource's AWS Resource Name (ARN) +func (r *resource) Identifiers() acktypes.AWSResourceIdentifiers { + return &resourceIdentifiers{r.ko.Status.ACKResourceMetadata} +} + +// IsBeingDeleted returns true if the Kubernetes resource has a non-zero +// deletion timestamp +func (r *resource) IsBeingDeleted() bool { + return !r.ko.DeletionTimestamp.IsZero() +} + +// RuntimeObject returns the Kubernetes apimachinery/runtime representation of +// the AWSResource +func (r *resource) RuntimeObject() rtclient.Object { + return r.ko +} + +// MetaObject returns the Kubernetes apimachinery/apis/meta/v1.Object +// representation of the AWSResource +func (r *resource) MetaObject() metav1.Object { + return r.ko.GetObjectMeta() +} + +// Conditions returns the ACK Conditions collection for the AWSResource +func (r *resource) Conditions() []*ackv1alpha1.Condition { + return r.ko.Status.Conditions +} + +// ReplaceConditions sets the Conditions status field for the resource +func (r *resource) ReplaceConditions(conditions []*ackv1alpha1.Condition) { + r.ko.Status.Conditions = conditions +} + +// SetObjectMeta sets the ObjectMeta field for the resource +func (r *resource) SetObjectMeta(meta metav1.ObjectMeta) { + r.ko.ObjectMeta = meta +} + +// SetStatus will set the Status field for the resource +func (r *resource) SetStatus(desired acktypes.AWSResource) { + r.ko.Status = desired.(*resource).ko.Status +} + +// SetIdentifiers sets the Spec or Status field that is referenced as the unique +// resource identifier +func (r *resource) SetIdentifiers(identifier *ackv1alpha1.AWSIdentifiers) error { + if identifier.NameOrID == "" { + return ackerrors.MissingNameIdentifier + } + r.ko.Spec.RuleSetName = &identifier.NameOrID + + return nil +} + +// DeepCopy will return a copy of the resource +func (r *resource) DeepCopy() acktypes.AWSResource { + koCopy := r.ko.DeepCopy() + return &resource{koCopy} +} diff --git a/pkg/resource/receipt_rule_set/sdk.go b/pkg/resource/receipt_rule_set/sdk.go new file mode 100644 index 0000000..45aba52 --- /dev/null +++ b/pkg/resource/receipt_rule_set/sdk.go @@ -0,0 +1,346 @@ +// 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 receipt_rule_set + +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/ses" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +// Hack to avoid import errors during build... +var ( + _ = &metav1.Time{} + _ = strings.ToLower("") + _ = &aws.JSONValue{} + _ = &svcsdk.SES{} + _ = &svcapitypes.ReceiptRuleSet{} + _ = 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.DescribeReceiptRuleSetOutput + resp, err = rm.sdkapi.DescribeReceiptRuleSetWithContext(ctx, input) + _ = resp + if err != nil { + if awsErr, ok := ackerr.AWSError(err); ok && awsErr.Code() == svcsdk.ErrCodeRuleSetDoesNotExistException { + rm.metrics.RecordAPICall("READ_ONE", "DescribeReceiptRuleSet", err) + return nil, ackerr.NotFound + } + } + + rm.metrics.RecordAPICall("READ_ONE", "DescribeReceiptRuleSet", err) + if err != nil { + if reqErr, ok := ackerr.AWSRequestFailure(err); ok && reqErr.StatusCode() == 404 { + return nil, ackerr.NotFound + } + if awsErr, ok := ackerr.AWSError(err); ok && awsErr.Code() == "UNKNOWN" { + return nil, ackerr.NotFound + } + return nil, err + } + + // Merge in the information we read from the API call above to the copy of + // the original Kubernetes object we passed to the function + ko := r.ko.DeepCopy() + + rm.setStatusDefaults(ko) + 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.RuleSetName == 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.DescribeReceiptRuleSetInput, error) { + res := &svcsdk.DescribeReceiptRuleSetInput{} + + if r.ko.Spec.RuleSetName != nil { + res.SetRuleSetName(*r.ko.Spec.RuleSetName) + } + + return res, nil +} + +// sdkCreate creates the supplied resource in the backend AWS service API and +// returns a copy of the resource with resource fields (in both Spec and +// Status) filled in with values from the CREATE API operation's Output shape. +func (rm *resourceManager) sdkCreate( + ctx context.Context, + desired *resource, +) (created *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkCreate") + defer func() { + exit(err) + }() + input, err := rm.newCreateRequestPayload(ctx, desired) + if err != nil { + return nil, err + } + + var resp *svcsdk.CreateReceiptRuleSetOutput + _ = resp + resp, err = rm.sdkapi.CreateReceiptRuleSetWithContext(ctx, input) + rm.metrics.RecordAPICall("CREATE", "CreateReceiptRuleSet", 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() + + rm.setStatusDefaults(ko) + return &resource{ko}, nil +} + +// newCreateRequestPayload returns an SDK-specific struct for the HTTP request +// payload of the Create API call for the resource +func (rm *resourceManager) newCreateRequestPayload( + ctx context.Context, + r *resource, +) (*svcsdk.CreateReceiptRuleSetInput, error) { + res := &svcsdk.CreateReceiptRuleSetInput{} + + if r.ko.Spec.RuleSetName != nil { + res.SetRuleSetName(*r.ko.Spec.RuleSetName) + } + + return res, nil +} + +// sdkUpdate patches the supplied resource in the backend AWS service API and +// returns a new resource with updated fields. +func (rm *resourceManager) sdkUpdate( + ctx context.Context, + desired *resource, + latest *resource, + delta *ackcompare.Delta, +) (*resource, error) { + return rm.customUpdate(ctx, desired, latest, delta) +} + +// sdkDelete deletes the supplied resource in the backend AWS service API +func (rm *resourceManager) sdkDelete( + ctx context.Context, + r *resource, +) (latest *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkDelete") + defer func() { + exit(err) + }() + input, err := rm.newDeleteRequestPayload(r) + if err != nil { + return nil, err + } + var resp *svcsdk.DeleteReceiptRuleSetOutput + _ = resp + resp, err = rm.sdkapi.DeleteReceiptRuleSetWithContext(ctx, input) + rm.metrics.RecordAPICall("DELETE", "DeleteReceiptRuleSet", 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.DeleteReceiptRuleSetInput, error) { + res := &svcsdk.DeleteReceiptRuleSetInput{} + + if r.ko.Spec.RuleSetName != nil { + res.SetRuleSetName(*r.ko.Spec.RuleSetName) + } + + return res, nil +} + +// setStatusDefaults sets default properties into supplied custom resource +func (rm *resourceManager) setStatusDefaults( + ko *svcapitypes.ReceiptRuleSet, +) { + 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 "AlreadyExists": + 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.RuleSetName") { + fields = append(fields, "RuleSetName") + } + + return fields +} diff --git a/pkg/resource/template/delta.go b/pkg/resource/template/delta.go new file mode 100644 index 0000000..bcfffb1 --- /dev/null +++ b/pkg/resource/template/delta.go @@ -0,0 +1,76 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package template + +import ( + "bytes" + "reflect" + + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" +) + +// Hack to avoid import errors during build... +var ( + _ = &bytes.Buffer{} + _ = &reflect.Method{} + _ = &acktags.Tags{} +) + +// newResourceDelta returns a new `ackcompare.Delta` used to compare two +// resources +func newResourceDelta( + a *resource, + b *resource, +) *ackcompare.Delta { + delta := ackcompare.NewDelta() + if (a == nil && b != nil) || + (a != nil && b == nil) { + delta.Add("", a, b) + return delta + } + + if ackcompare.HasNilDifference(a.ko.Spec.HTMLPart, b.ko.Spec.HTMLPart) { + delta.Add("Spec.HTMLPart", a.ko.Spec.HTMLPart, b.ko.Spec.HTMLPart) + } else if a.ko.Spec.HTMLPart != nil && b.ko.Spec.HTMLPart != nil { + if *a.ko.Spec.HTMLPart != *b.ko.Spec.HTMLPart { + delta.Add("Spec.HTMLPart", a.ko.Spec.HTMLPart, b.ko.Spec.HTMLPart) + } + } + 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.SubjectPart, b.ko.Spec.SubjectPart) { + delta.Add("Spec.SubjectPart", a.ko.Spec.SubjectPart, b.ko.Spec.SubjectPart) + } else if a.ko.Spec.SubjectPart != nil && b.ko.Spec.SubjectPart != nil { + if *a.ko.Spec.SubjectPart != *b.ko.Spec.SubjectPart { + delta.Add("Spec.SubjectPart", a.ko.Spec.SubjectPart, b.ko.Spec.SubjectPart) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.TextPart, b.ko.Spec.TextPart) { + delta.Add("Spec.TextPart", a.ko.Spec.TextPart, b.ko.Spec.TextPart) + } else if a.ko.Spec.TextPart != nil && b.ko.Spec.TextPart != nil { + if *a.ko.Spec.TextPart != *b.ko.Spec.TextPart { + delta.Add("Spec.TextPart", a.ko.Spec.TextPart, b.ko.Spec.TextPart) + } + } + + return delta +} diff --git a/pkg/resource/template/descriptor.go b/pkg/resource/template/descriptor.go new file mode 100644 index 0000000..9e7cf5c --- /dev/null +++ b/pkg/resource/template/descriptor.go @@ -0,0 +1,155 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package template + +import ( + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + rtclient "sigs.k8s.io/controller-runtime/pkg/client" + k8sctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +const ( + FinalizerString = "finalizers.ses.services.k8s.aws/Template" +) + +var ( + GroupVersionResource = svcapitypes.GroupVersion.WithResource("templates") + GroupKind = metav1.GroupKind{ + Group: "ses.services.k8s.aws", + Kind: "Template", + } +) + +// resourceDescriptor implements the +// `aws-service-operator-k8s/pkg/types.AWSResourceDescriptor` interface +type resourceDescriptor struct { +} + +// GroupVersionKind returns a Kubernetes schema.GroupVersionKind struct that +// describes the API Group, Version and Kind of CRs described by the descriptor +func (d *resourceDescriptor) GroupVersionKind() schema.GroupVersionKind { + return svcapitypes.GroupVersion.WithKind(GroupKind.Kind) +} + +// EmptyRuntimeObject returns an empty object prototype that may be used in +// apimachinery and k8s client operations +func (d *resourceDescriptor) EmptyRuntimeObject() rtclient.Object { + return &svcapitypes.Template{} +} + +// 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.Template), + } +} + +// 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/template/identifiers.go b/pkg/resource/template/identifiers.go new file mode 100644 index 0000000..915b93a --- /dev/null +++ b/pkg/resource/template/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 template + +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/template/manager.go b/pkg/resource/template/manager.go new file mode 100644 index 0000000..f09366e --- /dev/null +++ b/pkg/resource/template/manager.go @@ -0,0 +1,350 @@ +// 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 template + +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/ses" + svcsdkapi "github.com/aws/aws-sdk-go/service/ses/sesiface" + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +var ( + _ = ackutil.InStrings + _ = acktags.NewTags() + _ = ackrt.MissingImageTagValue + _ = svcapitypes.Template{} +) + +// +kubebuilder:rbac:groups=ses.services.k8s.aws,resources=templates,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=ses.services.k8s.aws,resources=templates/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.SESAPI +} + +// 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:ses:%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 { + + 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/template/manager_factory.go b/pkg/resource/template/manager_factory.go new file mode 100644 index 0000000..0b44279 --- /dev/null +++ b/pkg/resource/template/manager_factory.go @@ -0,0 +1,100 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package template + +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/ses-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, + roleARN ackv1alpha1.AWSResourceName, +) (acktypes.AWSResourceManager, error) { + // We use the account ID, region, and role ARN to uniquely identify a + // resource manager. This helps us to avoid creating multiple resource + // managers for the same account/region/roleARN combination. + rmId := fmt.Sprintf("%s/%s/%s", id, region, roleARN) + f.RLock() + rm, found := f.rmCache[rmId] + f.RUnlock() + + if found { + return rm, nil + } + + f.Lock() + defer f.Unlock() + + rm, err := newResourceManager(cfg, 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/template/references.go b/pkg/resource/template/references.go new file mode 100644 index 0000000..81692fa --- /dev/null +++ b/pkg/resource/template/references.go @@ -0,0 +1,56 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package template + +import ( + "context" + "sigs.k8s.io/controller-runtime/pkg/client" + + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +// ClearResolvedReferences removes any reference values that were made +// concrete in the spec. It returns a copy of the input AWSResource which +// contains the original *Ref values, but none of their respective concrete +// values. +func (rm *resourceManager) ClearResolvedReferences(res acktypes.AWSResource) acktypes.AWSResource { + ko := rm.concreteResource(res).ko.DeepCopy() + + return &resource{ko} +} + +// ResolveReferences finds if there are any Reference field(s) present +// inside AWSResource passed in the parameter and attempts to resolve those +// reference field(s) into their respective target field(s). It returns a +// copy of the input AWSResource with resolved reference(s), a boolean which +// is set to true if the resource contains any references (regardless of if +// they are resolved successfully) and an error if the passed AWSResource's +// reference field(s) could not be resolved. +func (rm *resourceManager) ResolveReferences( + ctx context.Context, + apiReader client.Reader, + res acktypes.AWSResource, +) (acktypes.AWSResource, bool, error) { + return res, false, nil +} + +// validateReferenceFields validates the reference field and corresponding +// identifier field. +func validateReferenceFields(ko *svcapitypes.Template) error { + return nil +} diff --git a/pkg/resource/template/resource.go b/pkg/resource/template/resource.go new file mode 100644 index 0000000..0b908d9 --- /dev/null +++ b/pkg/resource/template/resource.go @@ -0,0 +1,100 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package template + +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/ses-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.Template +} + +// Identifiers returns an AWSResourceIdentifiers object containing various +// identifying information, including the AWS account ID that owns the +// resource, the resource's AWS Resource Name (ARN) +func (r *resource) Identifiers() acktypes.AWSResourceIdentifiers { + return &resourceIdentifiers{r.ko.Status.ACKResourceMetadata} +} + +// IsBeingDeleted returns true if the Kubernetes resource has a non-zero +// deletion timestamp +func (r *resource) IsBeingDeleted() bool { + return !r.ko.DeletionTimestamp.IsZero() +} + +// RuntimeObject returns the Kubernetes apimachinery/runtime representation of +// the AWSResource +func (r *resource) RuntimeObject() rtclient.Object { + return r.ko +} + +// MetaObject returns the Kubernetes apimachinery/apis/meta/v1.Object +// representation of the AWSResource +func (r *resource) MetaObject() metav1.Object { + return r.ko.GetObjectMeta() +} + +// Conditions returns the ACK Conditions collection for the AWSResource +func (r *resource) Conditions() []*ackv1alpha1.Condition { + return r.ko.Status.Conditions +} + +// ReplaceConditions sets the Conditions status field for the resource +func (r *resource) ReplaceConditions(conditions []*ackv1alpha1.Condition) { + r.ko.Status.Conditions = conditions +} + +// SetObjectMeta sets the ObjectMeta field for the resource +func (r *resource) SetObjectMeta(meta metav1.ObjectMeta) { + r.ko.ObjectMeta = meta +} + +// SetStatus will set the Status field for the resource +func (r *resource) SetStatus(desired acktypes.AWSResource) { + r.ko.Status = desired.(*resource).ko.Status +} + +// SetIdentifiers sets the Spec or Status field that is referenced as the unique +// resource identifier +func (r *resource) SetIdentifiers(identifier *ackv1alpha1.AWSIdentifiers) error { + if identifier.NameOrID == "" { + return ackerrors.MissingNameIdentifier + } + r.ko.Spec.Name = &identifier.NameOrID + + return nil +} + +// DeepCopy will return a copy of the resource +func (r *resource) DeepCopy() acktypes.AWSResource { + koCopy := r.ko.DeepCopy() + return &resource{koCopy} +} diff --git a/pkg/resource/template/sdk.go b/pkg/resource/template/sdk.go new file mode 100644 index 0000000..78d7b70 --- /dev/null +++ b/pkg/resource/template/sdk.go @@ -0,0 +1,413 @@ +// 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 template + +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/ses" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + svcapitypes "github.com/aws-controllers-k8s/ses-controller/apis/v1alpha1" +) + +// Hack to avoid import errors during build... +var ( + _ = &metav1.Time{} + _ = strings.ToLower("") + _ = &aws.JSONValue{} + _ = &svcsdk.SES{} + _ = &svcapitypes.Template{} + _ = 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.GetTemplateOutput + resp, err = rm.sdkapi.GetTemplateWithContext(ctx, input) + if err != nil { + if awsErr, ok := ackerr.AWSError(err); ok && awsErr.Code() == svcsdk.ErrCodeTemplateDoesNotExistException { + rm.metrics.RecordAPICall("READ_ONE", "GetTemplate", err) + return nil, ackerr.NotFound + } + } + + rm.metrics.RecordAPICall("READ_ONE", "GetTemplate", err) + if err != nil { + if reqErr, ok := ackerr.AWSRequestFailure(err); ok && reqErr.StatusCode() == 404 { + return nil, ackerr.NotFound + } + if awsErr, ok := ackerr.AWSError(err); ok && awsErr.Code() == "UNKNOWN" { + return nil, ackerr.NotFound + } + return nil, err + } + + // Merge in the information we read from the API call above to the copy of + // the original Kubernetes object we passed to the function + ko := r.ko.DeepCopy() + + if resp.Template.HtmlPart != nil { + ko.Spec.HTMLPart = resp.Template.HtmlPart + } else { + ko.Spec.HTMLPart = nil + } + if resp.Template.SubjectPart != nil { + ko.Spec.SubjectPart = resp.Template.SubjectPart + } else { + ko.Spec.SubjectPart = nil + } + if resp.Template.TemplateName != nil { + ko.Spec.Name = resp.Template.TemplateName + } else { + ko.Spec.Name = nil + } + if resp.Template.TextPart != nil { + ko.Spec.TextPart = resp.Template.TextPart + } else { + ko.Spec.TextPart = nil + } + + rm.setStatusDefaults(ko) + 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.GetTemplateInput, error) { + res := &svcsdk.GetTemplateInput{} + + if r.ko.Spec.Name != nil { + res.SetTemplateName(*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) + }() + input, err := rm.newCreateRequestPayload(ctx, desired) + if err != nil { + return nil, err + } + input.SetTemplate(&svcsdk.Template{ + TemplateName: desired.ko.Spec.Name, + HtmlPart: desired.ko.Spec.HTMLPart, + TextPart: desired.ko.Spec.TextPart, + SubjectPart: desired.ko.Spec.SubjectPart, + }) + + var resp *svcsdk.CreateTemplateOutput + _ = resp + resp, err = rm.sdkapi.CreateTemplateWithContext(ctx, input) + rm.metrics.RecordAPICall("CREATE", "CreateTemplate", 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() + + rm.setStatusDefaults(ko) + return &resource{ko}, nil +} + +// newCreateRequestPayload returns an SDK-specific struct for the HTTP request +// payload of the Create API call for the resource +func (rm *resourceManager) newCreateRequestPayload( + ctx context.Context, + r *resource, +) (*svcsdk.CreateTemplateInput, error) { + res := &svcsdk.CreateTemplateInput{} + + 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)) + } + input, err := rm.newUpdateRequestPayload(ctx, desired, delta) + if err != nil { + return nil, err + } + input.SetTemplate(&svcsdk.Template{ + TemplateName: desired.ko.Spec.Name, + HtmlPart: desired.ko.Spec.HTMLPart, + TextPart: desired.ko.Spec.TextPart, + SubjectPart: desired.ko.Spec.SubjectPart, + }) + + var resp *svcsdk.UpdateTemplateOutput + _ = resp + resp, err = rm.sdkapi.UpdateTemplateWithContext(ctx, input) + rm.metrics.RecordAPICall("UPDATE", "UpdateTemplate", 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() + + 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, + delta *ackcompare.Delta, +) (*svcsdk.UpdateTemplateInput, error) { + res := &svcsdk.UpdateTemplateInput{} + + 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) + }() + input, err := rm.newDeleteRequestPayload(r) + if err != nil { + return nil, err + } + var resp *svcsdk.DeleteTemplateOutput + _ = resp + resp, err = rm.sdkapi.DeleteTemplateWithContext(ctx, input) + rm.metrics.RecordAPICall("DELETE", "DeleteTemplate", 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.DeleteTemplateInput, error) { + res := &svcsdk.DeleteTemplateInput{} + + if r.ko.Spec.Name != nil { + res.SetTemplateName(*r.ko.Spec.Name) + } + + return res, nil +} + +// setStatusDefaults sets default properties into supplied custom resource +func (rm *resourceManager) setStatusDefaults( + ko *svcapitypes.Template, +) { + 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 "AlreadyExists", + "InvalidTemplate": + 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.Name") { + fields = append(fields, "Name") + } + + return fields +} diff --git a/pkg/util/immutable_resource.go b/pkg/util/immutable_resource.go new file mode 100644 index 0000000..f908134 --- /dev/null +++ b/pkg/util/immutable_resource.go @@ -0,0 +1,38 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package util + +import ( + "context" + "errors" + "fmt" + "strings" + + ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" + ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" + rtclient "sigs.k8s.io/controller-runtime/pkg/client" +) + +func ValidateImmutableResource[T interface{ RuntimeObject() rtclient.Object }](ctx context.Context, immutableFieldChanges []string, desired T) (t T, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkUpdate") + defer func() { + exit(err) + }() + if len(immutableFieldChanges) > 0 { + msg := fmt.Sprintf("Immutable Spec fields have been modified: %s", strings.Join(immutableFieldChanges, ",")) + return t, ackerr.NewTerminalError(errors.New(msg)) + } + return desired, nil +} diff --git a/templates/hooks/configuration_set/sdk_create_post_build_request.go.tpl b/templates/hooks/configuration_set/sdk_create_post_build_request.go.tpl new file mode 100644 index 0000000..f433032 --- /dev/null +++ b/templates/hooks/configuration_set/sdk_create_post_build_request.go.tpl @@ -0,0 +1,3 @@ + input.SetConfigurationSet(&svcsdk.ConfigurationSet{ + Name: desired.ko.Spec.Name, + }) diff --git a/templates/hooks/configuration_set/sdk_delete_post_build_request.go.tpl b/templates/hooks/configuration_set/sdk_delete_post_build_request.go.tpl new file mode 100644 index 0000000..f433032 --- /dev/null +++ b/templates/hooks/configuration_set/sdk_delete_post_build_request.go.tpl @@ -0,0 +1,3 @@ + input.SetConfigurationSet(&svcsdk.ConfigurationSet{ + Name: desired.ko.Spec.Name, + }) diff --git a/templates/hooks/configuration_set/sdk_read_one_post_request.go.tpl b/templates/hooks/configuration_set/sdk_read_one_post_request.go.tpl new file mode 100644 index 0000000..76967da --- /dev/null +++ b/templates/hooks/configuration_set/sdk_read_one_post_request.go.tpl @@ -0,0 +1,7 @@ + _ = resp + if err != nil { + if awsErr, ok := ackerr.AWSError(err); ok && awsErr.Code() == svcsdk.ErrCodeConfigurationSetDoesNotExistException { + rm.metrics.RecordAPICall("READ_ONE", "DescribeConfigurationSet", err) + return nil, ackerr.NotFound + } + } diff --git a/templates/hooks/configuration_set_event_destination/sdk_delete_post_build_request.go.tpl b/templates/hooks/configuration_set_event_destination/sdk_delete_post_build_request.go.tpl new file mode 100644 index 0000000..b7d00e1 --- /dev/null +++ b/templates/hooks/configuration_set_event_destination/sdk_delete_post_build_request.go.tpl @@ -0,0 +1,3 @@ + if eventDestination := r.ko.Spec.EventDestination; eventDestination != nil { + input.EventDestinationName = eventDestination.Name + } diff --git a/templates/hooks/configuration_set_event_destination/sdk_file_end.go.tpl b/templates/hooks/configuration_set_event_destination/sdk_file_end.go.tpl new file mode 100644 index 0000000..9b8c332 --- /dev/null +++ b/templates/hooks/configuration_set_event_destination/sdk_file_end.go.tpl @@ -0,0 +1,13 @@ +{{- $eventDestinationRef := (index .SDKAPI.API.Shapes "EventDestinations").MemberRef }} +{{- $eventDestinationName := "EventDestination" }} + +// set{{ $eventDestinationName }} sets a resource {{ $eventDestinationName }} type +// given the SDK type. +func setResource{{ $eventDestinationName }}( + resp *svcsdk.{{ $eventDestinationName }}, +) *svcapitypes.{{ $eventDestinationName }} { + res := &svcapitypes.{{ $eventDestinationName }}{} + +{{ GoCodeSetResourceForStruct .CRD "EventDestination" "res" $eventDestinationRef "resp" $eventDestinationRef 1 }} + return res +} diff --git a/templates/hooks/configuration_set_event_destination/sdk_read_one_post_build_request.go.tpl b/templates/hooks/configuration_set_event_destination/sdk_read_one_post_build_request.go.tpl new file mode 100644 index 0000000..3078192 --- /dev/null +++ b/templates/hooks/configuration_set_event_destination/sdk_read_one_post_build_request.go.tpl @@ -0,0 +1 @@ + input.SetConfigurationSetAttributeNames(aws.StringSlice([]string{svcsdk.ConfigurationSetAttributeEventDestinations})) \ No newline at end of file diff --git a/templates/hooks/configuration_set_event_destination/sdk_read_one_post_request.go.tpl b/templates/hooks/configuration_set_event_destination/sdk_read_one_post_request.go.tpl new file mode 100644 index 0000000..0d162fa --- /dev/null +++ b/templates/hooks/configuration_set_event_destination/sdk_read_one_post_request.go.tpl @@ -0,0 +1,7 @@ + _ = resp + if err != nil { + if awsErr, ok := ackerr.AWSError(err); ok && awsErr.Code() == svcsdk.ErrCodeConfigurationSetDoesNotExistException { + rm.metrics.RecordAPICall("READ_ONE", "DescribeConfigurationSet", err) + return nil, ackerr.NotFound + } + } diff --git a/templates/hooks/configuration_set_event_destination/sdk_read_one_post_set_output.go.tpl b/templates/hooks/configuration_set_event_destination/sdk_read_one_post_set_output.go.tpl new file mode 100644 index 0000000..87444fc --- /dev/null +++ b/templates/hooks/configuration_set_event_destination/sdk_read_one_post_set_output.go.tpl @@ -0,0 +1,5 @@ + eventDestination := getEventDestination(ko, resp) + if eventDestination == nil { + return nil, ackerr.NotFound + } + ko.Spec.EventDestination = eventDestination diff --git a/templates/hooks/configuration_set_event_destination/sdk_read_one_pre_build_request.go.tpl b/templates/hooks/configuration_set_event_destination/sdk_read_one_pre_build_request.go.tpl new file mode 100644 index 0000000..a95657a --- /dev/null +++ b/templates/hooks/configuration_set_event_destination/sdk_read_one_pre_build_request.go.tpl @@ -0,0 +1,3 @@ + if r.ko.Spec.EventDestination == nil || r.ko.Spec.EventDestination.Name == nil { + return nil, ackerr.NotFound + } diff --git a/templates/hooks/custom_verification_email_template/sdk_read_one_post_request.go.tpl b/templates/hooks/custom_verification_email_template/sdk_read_one_post_request.go.tpl new file mode 100644 index 0000000..d829fe3 --- /dev/null +++ b/templates/hooks/custom_verification_email_template/sdk_read_one_post_request.go.tpl @@ -0,0 +1,6 @@ + if err != nil { + if awsErr, ok := ackerr.AWSError(err); ok && awsErr.Code() == svcsdk.ErrCodeCustomVerificationEmailTemplateDoesNotExistException { + rm.metrics.RecordAPICall("READ_ONE", "GetCustomVerificationEmailTemplate", err) + return nil, ackerr.NotFound + } + } diff --git a/templates/hooks/receipt_filter/sdk_create_post_build_request.go.tpl b/templates/hooks/receipt_filter/sdk_create_post_build_request.go.tpl new file mode 100644 index 0000000..b06ad89 --- /dev/null +++ b/templates/hooks/receipt_filter/sdk_create_post_build_request.go.tpl @@ -0,0 +1,7 @@ + if input.Filter != nil { + input.Filter.Name = desired.ko.Spec.Name + } else { + input.Filter = &svcsdk.ReceiptFilter{ + Name: desired.ko.Spec.Name, + } + } diff --git a/templates/hooks/receipt_filter/sdk_file_end.go.tpl b/templates/hooks/receipt_filter/sdk_file_end.go.tpl new file mode 100644 index 0000000..04201ed --- /dev/null +++ b/templates/hooks/receipt_filter/sdk_file_end.go.tpl @@ -0,0 +1,13 @@ +{{- $receiptFilterRef := (index .SDKAPI.API.Shapes "ReceiptFilterList").MemberRef }} +{{- $receiptFilterName := "ReceiptFilter" }} + +// set{{ $receiptFilterName }} sets a resource {{ $receiptFilterName }} type +// given the SDK type. +func setResource{{ $receiptFilterName }}( + resp *svcsdk.{{ $receiptFilterName }}, +) *svcapitypes.{{ $receiptFilterName }}_SDK { + res := &svcapitypes.{{ $receiptFilterName }}_SDK{} + +{{ GoCodeSetResourceForStruct .CRD "Rule" "res" $receiptFilterRef "resp" $receiptFilterRef 1 }} + return res +} diff --git a/templates/hooks/receipt_filter/sdk_read_many_post_request.go.tpl b/templates/hooks/receipt_filter/sdk_read_many_post_request.go.tpl new file mode 100644 index 0000000..e69de29 diff --git a/templates/hooks/receipt_rule/sdk_delete_post_build_request.go.tpl b/templates/hooks/receipt_rule/sdk_delete_post_build_request.go.tpl new file mode 100644 index 0000000..9b019d6 --- /dev/null +++ b/templates/hooks/receipt_rule/sdk_delete_post_build_request.go.tpl @@ -0,0 +1,3 @@ + if rule := r.ko.Spec.Rule; rule != nil { + input.RuleName = rule.Name + } diff --git a/templates/hooks/receipt_rule/sdk_file_end.go.tpl b/templates/hooks/receipt_rule/sdk_file_end.go.tpl new file mode 100644 index 0000000..92f07b3 --- /dev/null +++ b/templates/hooks/receipt_rule/sdk_file_end.go.tpl @@ -0,0 +1,13 @@ +{{- $receiptRuleRef := (index .SDKAPI.API.Shapes "ReceiptRulesList").MemberRef }} +{{- $receiptRuleName := "ReceiptRule" }} + +// set{{ $receiptRuleName }} sets a resource {{ $receiptRuleName }} type +// given the SDK type. +func setResource{{ $receiptRuleName }}( + resp *svcsdk.{{ $receiptRuleName }}, +) *svcapitypes.{{ $receiptRuleName }}_SDK { + res := &svcapitypes.{{ $receiptRuleName }}_SDK{} + +{{ GoCodeSetResourceForStruct .CRD "Rule" "res" $receiptRuleRef "resp" $receiptRuleRef 1 }} + return res +} diff --git a/templates/hooks/receipt_rule/sdk_read_one_post_request.go.tpl b/templates/hooks/receipt_rule/sdk_read_one_post_request.go.tpl new file mode 100644 index 0000000..a8d00f1 --- /dev/null +++ b/templates/hooks/receipt_rule/sdk_read_one_post_request.go.tpl @@ -0,0 +1,7 @@ + _ = resp + if err != nil { + if awsErr, ok := ackerr.AWSError(err); ok && awsErr.Code() == svcsdk.ErrCodeConfigurationSetDoesNotExistException { + rm.metrics.RecordAPICall("READ_ONE", "DescribeReceiptRule", err) + return nil, ackerr.NotFound + } + } diff --git a/templates/hooks/receipt_rule/sdk_read_one_post_set_output.go.tpl b/templates/hooks/receipt_rule/sdk_read_one_post_set_output.go.tpl new file mode 100644 index 0000000..8b076e5 --- /dev/null +++ b/templates/hooks/receipt_rule/sdk_read_one_post_set_output.go.tpl @@ -0,0 +1 @@ + setReceiptRule(ko, resp) diff --git a/templates/hooks/receipt_rule_set/sdk_read_one_post_request.go.tpl b/templates/hooks/receipt_rule_set/sdk_read_one_post_request.go.tpl new file mode 100644 index 0000000..1ec8b03 --- /dev/null +++ b/templates/hooks/receipt_rule_set/sdk_read_one_post_request.go.tpl @@ -0,0 +1,7 @@ + _ = resp + if err != nil { + if awsErr, ok := ackerr.AWSError(err); ok && awsErr.Code() == svcsdk.ErrCodeRuleSetDoesNotExistException { + rm.metrics.RecordAPICall("READ_ONE", "DescribeReceiptRuleSet", err) + return nil, ackerr.NotFound + } + } diff --git a/templates/hooks/template/sdk_create_post_build_request.go.tpl b/templates/hooks/template/sdk_create_post_build_request.go.tpl new file mode 100644 index 0000000..26846cd --- /dev/null +++ b/templates/hooks/template/sdk_create_post_build_request.go.tpl @@ -0,0 +1,6 @@ + input.SetTemplate(&svcsdk.Template{ + TemplateName: desired.ko.Spec.Name, + HtmlPart: desired.ko.Spec.HTMLPart, + TextPart: desired.ko.Spec.TextPart, + SubjectPart: desired.ko.Spec.SubjectPart, + }) diff --git a/templates/hooks/template/sdk_read_one_post_request.go.tpl b/templates/hooks/template/sdk_read_one_post_request.go.tpl new file mode 100644 index 0000000..bd9a525 --- /dev/null +++ b/templates/hooks/template/sdk_read_one_post_request.go.tpl @@ -0,0 +1,6 @@ + if err != nil { + if awsErr, ok := ackerr.AWSError(err); ok && awsErr.Code() == svcsdk.ErrCodeTemplateDoesNotExistException { + rm.metrics.RecordAPICall("READ_ONE", "GetTemplate", err) + return nil, ackerr.NotFound + } + } diff --git a/templates/hooks/template/sdk_update_post_build_request.go.tpl b/templates/hooks/template/sdk_update_post_build_request.go.tpl new file mode 100644 index 0000000..26846cd --- /dev/null +++ b/templates/hooks/template/sdk_update_post_build_request.go.tpl @@ -0,0 +1,6 @@ + input.SetTemplate(&svcsdk.Template{ + TemplateName: desired.ko.Spec.Name, + HtmlPart: desired.ko.Spec.HTMLPart, + TextPart: desired.ko.Spec.TextPart, + SubjectPart: desired.ko.Spec.SubjectPart, + }) diff --git a/test/e2e/bootstrap_resources.py b/test/e2e/bootstrap_resources.py index 450a769..5eb30fe 100644 --- a/test/e2e/bootstrap_resources.py +++ b/test/e2e/bootstrap_resources.py @@ -18,10 +18,11 @@ from dataclasses import dataclass from acktest.bootstrapping import Resources from e2e import bootstrap_directory +from acktest.bootstrapping.sns import Topic @dataclass class BootstrapResources(Resources): - pass + SNSTopic: Topic _bootstrap_resources = None diff --git a/test/e2e/common/__init__.py b/test/e2e/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/e2e/common/waiter.py b/test/e2e/common/waiter.py new file mode 100644 index 0000000..c49a836 --- /dev/null +++ b/test/e2e/common/waiter.py @@ -0,0 +1,40 @@ +"""Utilities for working with SES resources""" + +import datetime +import time +import typing + +import pytest + +DEFAULT_WAIT_UNTIL_TIMEOUT_SECONDS = 30 +DEFAULT_WAIT_UNTIL_INTERVAL_SECONDS = 15 +MAX_WAIT_FOR_SYNCED_MINUTES = 1 + +GetResourceFunc = typing.NewType( + 'GetResourceFunc', + typing.Callable[[], dict], +) + + +def wait_until_deleted( + get_resource: GetResourceFunc, + timeout_seconds: int = DEFAULT_WAIT_UNTIL_TIMEOUT_SECONDS, + interval_seconds: int = DEFAULT_WAIT_UNTIL_INTERVAL_SECONDS, +) -> None: + """Waits until a resource is deleted from the SES API + + Usage: + from e2e.common.waiter import wait_until_deleted + + wait_until_deleted(partial(ses_client.describe_configuration_set, **resource_query)) + + Raises: + pytest.fail upon timeout + """ + now = datetime.datetime.now() + timeout = now + datetime.timedelta(seconds=timeout_seconds) + + while get_resource() is not None: + if datetime.datetime.now() >= timeout: + pytest.fail('Timed out waiting for resource to be deleted in SES') + time.sleep(interval_seconds) diff --git a/test/e2e/resources/configuration_set_simple.yaml b/test/e2e/resources/configuration_set_simple.yaml new file mode 100644 index 0000000..d336d6f --- /dev/null +++ b/test/e2e/resources/configuration_set_simple.yaml @@ -0,0 +1,6 @@ +apiVersion: ses.services.k8s.aws/v1alpha1 +kind: ConfigurationSet +metadata: + name: $CONFIGURATION_SET_NAME +spec: + name: $CONFIGURATION_SET_NAME diff --git a/test/e2e/resources/custom_verification_email_template_simple.yaml b/test/e2e/resources/custom_verification_email_template_simple.yaml new file mode 100644 index 0000000..f7c5c73 --- /dev/null +++ b/test/e2e/resources/custom_verification_email_template_simple.yaml @@ -0,0 +1,13 @@ +apiVersion: ses.services.k8s.aws/v1alpha1 +kind: CustomVerificationEmailTemplate +metadata: + name: $CUSTOM_VERIFICATION_EMAIL_TEMPLATE_NAME +spec: + templateRef: + from: + name: $TEMPLATE_REF_NAME + templateSubject: template-subject + failureRedirectionURL: https://example.com + fromEmailAddress: todo@todo.com + successRedirectionURL: https://example.com/test + templateContent: "Initial template content" diff --git a/test/e2e/resources/event_destination_simple.yaml b/test/e2e/resources/event_destination_simple.yaml new file mode 100644 index 0000000..10dc583 --- /dev/null +++ b/test/e2e/resources/event_destination_simple.yaml @@ -0,0 +1,16 @@ +apiVersion: ses.services.k8s.aws/v1alpha1 +kind: ConfigurationSetEventDestination +metadata: + name: $EVENT_DESTINATION_NAME +spec: + configurationSetRef: + from: + name: $CONFIGURATION_SET_REF_NAME + eventDestination: + name: $EVENT_DESTINATION_NAME + matchingEventTypes: + - send + - reject + enabled: false + snsDestination: + topicARN: $SNS_TOPIC_ARN diff --git a/test/e2e/resources/receipt_filter_simple.yaml b/test/e2e/resources/receipt_filter_simple.yaml new file mode 100644 index 0000000..1c66628 --- /dev/null +++ b/test/e2e/resources/receipt_filter_simple.yaml @@ -0,0 +1,10 @@ +apiVersion: ses.services.k8s.aws/v1alpha1 +kind: ReceiptFilter +metadata: + name: $RECEIPT_FILTER_NAME +spec: + name: $RECEIPT_FILTER_NAME + filter: + ipFilter: + cidr: "192.168.0.1/32" + policy: Allow diff --git a/test/e2e/resources/receipt_rule_set_simple.yaml b/test/e2e/resources/receipt_rule_set_simple.yaml new file mode 100644 index 0000000..489126f --- /dev/null +++ b/test/e2e/resources/receipt_rule_set_simple.yaml @@ -0,0 +1,6 @@ +apiVersion: ses.services.k8s.aws/v1alpha1 +kind: ReceiptRuleSet +metadata: + name: $RECEIPT_RULE_SET_NAME +spec: + ruleSetName: $RECEIPT_RULE_SET_NAME diff --git a/test/e2e/resources/receipt_rule_simple.yaml b/test/e2e/resources/receipt_rule_simple.yaml new file mode 100644 index 0000000..3d860cb --- /dev/null +++ b/test/e2e/resources/receipt_rule_simple.yaml @@ -0,0 +1,16 @@ +apiVersion: ses.services.k8s.aws/v1alpha1 +kind: ReceiptRule +metadata: + name: $RECEIPT_RULE_NAME +spec: + ruleSetRef: + from: + name: $RULE_SET_REF_NAME + rule: + enabled: true + name: $RECEIPT_RULE_NAME + recipients: + - example@example.com + - example2@example.com + scanEnabled: false + tlsPolicy: Require diff --git a/test/e2e/resources/template_simple.yaml b/test/e2e/resources/template_simple.yaml new file mode 100644 index 0000000..21416f0 --- /dev/null +++ b/test/e2e/resources/template_simple.yaml @@ -0,0 +1,9 @@ +apiVersion: ses.services.k8s.aws/v1alpha1 +kind: Template +metadata: + name: $TEMPLATE_NAME +spec: + name: $TEMPLATE_NAME + textPart: initial-text + subjectPart: initial-subject + htmlPart: "

Initial HTML

" diff --git a/test/e2e/service_bootstrap.py b/test/e2e/service_bootstrap.py index ba035d5..d5edfdc 100644 --- a/test/e2e/service_bootstrap.py +++ b/test/e2e/service_bootstrap.py @@ -18,12 +18,13 @@ from e2e import bootstrap_directory from e2e.bootstrap_resources import BootstrapResources +from acktest.bootstrapping.sns import Topic def service_bootstrap() -> Resources: logging.getLogger().setLevel(logging.INFO) resources = BootstrapResources( - # TODO: Add bootstrapping when you have defined the resources + SNSTopic=Topic(name_prefix='event-destination-test') ) try: diff --git a/test/e2e/tests/configuration_set_event_destination_test.py b/test/e2e/tests/configuration_set_event_destination_test.py new file mode 100644 index 0000000..9a43591 --- /dev/null +++ b/test/e2e/tests/configuration_set_event_destination_test.py @@ -0,0 +1,127 @@ +# 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 ConfigurationSetEventDestination resource +""" + +import boto3 +import logging +import time + +import pytest +from functools import partial + +from typing import Dict, Tuple +from acktest.k8s import resource as k8s +from acktest.resources import random_suffix_name +from acktest.k8s import condition +from e2e import service_marker, CRD_GROUP, CRD_VERSION, SERVICE_NAME, load_ses_resource +from e2e.common.waiter import wait_until_deleted, MAX_WAIT_FOR_SYNCED_MINUTES +from e2e.replacement_values import REPLACEMENT_VALUES +from e2e.bootstrap_resources import get_bootstrap_resources + +from .configuration_set_test import simple_configuration_set + + +EVENT_DESTINATION_RESOURCE_PLURAL = "configurationseteventdestinations" + +@pytest.fixture(scope='module') +def ses_client(): + return boto3.client(SERVICE_NAME) + +def get_event_destination(ses_client, configuration_set_name, event_destination_name): + try: + res = ses_client.describe_configuration_set(ConfigurationSetName=configuration_set_name, ConfigurationSetAttributeNames=['eventDestinations']) + event_destinations = [event_destination for event_destination in res['EventDestinations'] if event_destination['Name'] == event_destination_name] + return event_destinations[0] if event_destinations else None + except ses_client.exceptions.ConfigurationSetDoesNotExistException: + return None + +@pytest.fixture +def simple_event_destination(simple_configuration_set, ses_client) -> Tuple[k8s.CustomResourceReference, Dict]: + event_destination_name = random_suffix_name('simple-event-destination', 32) + + replacements = REPLACEMENT_VALUES.copy() + replacements['EVENT_DESTINATION_NAME'] = event_destination_name + replacements['SNS_TOPIC_ARN'] = get_bootstrap_resources().SNSTopic.arn + + (ref, configuration_set_cr) = simple_configuration_set + replacements['CONFIGURATION_SET_REF_NAME'] = configuration_set_cr['spec']['name'] + + resource_data = load_ses_resource( + 'event_destination_simple', + additional_replacements=replacements, + ) + logging.debug(resource_data) + + ref = k8s.CustomResourceReference( + CRD_GROUP, CRD_VERSION, EVENT_DESTINATION_RESOURCE_PLURAL, + event_destination_name, namespace='default', + ) + k8s.create_custom_resource(ref, resource_data) + cr = k8s.wait_resource_consumed_by_controller(ref, wait_periods=10) + + assert cr is not None + assert cr['status'] is not None + assert k8s.get_resource_exists(ref) + + yield ref, cr + + _, deleted = k8s.delete_custom_resource(ref, 3, 10) + assert deleted + wait_until_deleted(partial(get_event_destination, ses_client, configuration_set_cr['spec']['name'], event_destination_name)) + + +@service_marker +@pytest.mark.canary +class TestEventDestination: + def test_create_update_event_destination(self, simple_event_destination, ses_client): + (ref, cr) = simple_event_destination + assert k8s.wait_on_condition( + ref, + condition.CONDITION_TYPE_RESOURCE_SYNCED, + 'True', + wait_periods=MAX_WAIT_FOR_SYNCED_MINUTES, + ) + configuration_set_name = cr['spec']['configurationSetRef']['from']['name'] + event_destination_name = cr['spec']['eventDestination']['name'] + get_current_event_destination = partial(get_event_destination, ses_client, configuration_set_name, event_destination_name) + assert get_current_event_destination() is not None + + updates = { + 'spec': { + 'eventDestination': { + 'matchingEventTypes': ['send', 'reject', 'bounce', 'complaint'], + 'enabled': True, + } + } + } + k8s.patch_custom_resource(ref, updates) + time.sleep(10) + assert k8s.wait_on_condition( + ref, + condition.CONDITION_TYPE_RESOURCE_SYNCED, + 'True', + wait_periods=MAX_WAIT_FOR_SYNCED_MINUTES, + ) + + event_destination = get_current_event_destination() + updated_event_destination = updates['spec']['eventDestination'] + assert event_destination == { + 'Name': event_destination_name, + 'MatchingEventTypes': updated_event_destination['matchingEventTypes'], + 'Enabled': updated_event_destination['enabled'], + 'SNSDestination': { + 'TopicARN': cr['eventDestination']['snsDestination']['topicARN'], + }, + } diff --git a/test/e2e/tests/configuration_set_test.py b/test/e2e/tests/configuration_set_test.py new file mode 100644 index 0000000..6f21cf1 --- /dev/null +++ b/test/e2e/tests/configuration_set_test.py @@ -0,0 +1,87 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You may +# not use this file except in compliance with the License. A copy of the +# License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is distributed +# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +# express or implied. See the License for the specific language governing +# permissions and limitations under the License. + +"""Integration tests for the ConfigurationSet resource +""" + +import boto3 +import logging + +import pytest +from functools import partial + +from typing import Dict, Tuple +from acktest.k8s import resource as k8s +from acktest.resources import random_suffix_name +from acktest.k8s import condition +from e2e import service_marker, CRD_GROUP, CRD_VERSION, SERVICE_NAME, load_ses_resource +from e2e.common.waiter import wait_until_deleted, MAX_WAIT_FOR_SYNCED_MINUTES +from e2e.replacement_values import REPLACEMENT_VALUES + + +CONFIGURATION_SET_RESOURCE_PLURAL = "configurationsets" + +@pytest.fixture(scope='module') +def ses_client(): + return boto3.client(SERVICE_NAME) + +def get_configuration_set(ses_client, configuration_set_name): + try: + return ses_client.describe_configuration_set(ConfigurationSetName=configuration_set_name) + except ses_client.exceptions.ConfigurationSetDoesNotExistException: + return None + +@pytest.fixture +def simple_configuration_set(ses_client) -> Tuple[k8s.CustomResourceReference, Dict]: + configuration_set_name = random_suffix_name('simple-configuration-set', 32) + + replacements = REPLACEMENT_VALUES.copy() + replacements['CONFIGURATION_SET_NAME'] = configuration_set_name + + resource_data = load_ses_resource( + 'configuration_set_simple', + additional_replacements=replacements, + ) + logging.debug(resource_data) + + ref = k8s.CustomResourceReference( + CRD_GROUP, CRD_VERSION, CONFIGURATION_SET_RESOURCE_PLURAL, + configuration_set_name, namespace='default', + ) + k8s.create_custom_resource(ref, resource_data) + cr = k8s.wait_resource_consumed_by_controller(ref, wait_periods=10) + + assert cr is not None + assert cr['status'] is not None + assert k8s.get_resource_exists(ref) + + yield ref, cr + + _, deleted = k8s.delete_custom_resource(ref, 3, 10) + assert deleted + wait_until_deleted(partial(get_configuration_set, ses_client, configuration_set_name)) + + +@service_marker +@pytest.mark.canary +# @pytest.mark.skip +class TestConfigurationSet: + def test_create_configuration_set(self, simple_configuration_set, ses_client): + (ref, cr) = simple_configuration_set + assert k8s.wait_on_condition( + ref, + condition.CONDITION_TYPE_RESOURCE_SYNCED, + 'True', + wait_periods=MAX_WAIT_FOR_SYNCED_MINUTES, + ) + assert get_configuration_set(ses_client, cr['spec']['name']) is not None diff --git a/test/e2e/tests/custom_verification_email_template_test.py b/test/e2e/tests/custom_verification_email_template_test.py new file mode 100644 index 0000000..c128c56 --- /dev/null +++ b/test/e2e/tests/custom_verification_email_template_test.py @@ -0,0 +1,123 @@ +# 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 CustomVerificationEmailTemplate resource +""" + +import boto3 +import logging +import time + +import pytest +from functools import partial + +from typing import Dict, Tuple +from acktest.k8s import resource as k8s +from acktest.resources import random_suffix_name +from acktest.k8s import condition +from e2e import service_marker, CRD_GROUP, CRD_VERSION, SERVICE_NAME, load_ses_resource +from e2e.common.waiter import wait_until_deleted, MAX_WAIT_FOR_SYNCED_MINUTES +from e2e.replacement_values import REPLACEMENT_VALUES + +from .template_test import simple_template + + +CUSTOM_VERIFICATION_EMAIL_TEMPLATE_RESOURCE_PLURAL = "customverificationemailtemplates" + +@pytest.fixture(scope='module') +def ses_client(): + return boto3.client(SERVICE_NAME) + +def get_custom_verification_email_template(ses_client, template_name): + try: + return ses_client.get_custom_verification_email_template(TemplateName=template_name) + except ses_client.exceptions.CustomVerificationEmailTemplateDoesNotExistException: + return None + +@pytest.fixture +def simple_custom_verification_email_template(simple_template, ses_client) -> Tuple[k8s.CustomResourceReference, Dict]: + custom_verification_email_template_name = random_suffix_name('simple-verification-template', 32) + + replacements = REPLACEMENT_VALUES.copy() + replacements['CUSTOM_VERIFICATION_EMAIL_TEMPLATE_NAME'] = custom_verification_email_template_name + + (ref, template_cr) = simple_template + replacements['TEMPLATE_REF_NAME'] = template_cr['spec']['name'] + + resource_data = load_ses_resource( + 'custom_verification_email_template_simple', + additional_replacements=replacements, + ) + logging.debug(resource_data) + + ref = k8s.CustomResourceReference( + CRD_GROUP, CRD_VERSION, CUSTOM_VERIFICATION_EMAIL_TEMPLATE_RESOURCE_PLURAL, + custom_verification_email_template_name, namespace='default', + ) + k8s.create_custom_resource(ref, resource_data) + cr = k8s.wait_resource_consumed_by_controller(ref, wait_periods=10) + + assert cr is not None + assert cr['status'] is not None + assert k8s.get_resource_exists(ref) + + yield ref, cr + + _, deleted = k8s.delete_custom_resource(ref, 3, 10) + assert deleted + wait_until_deleted(partial(get_custom_verification_email_template, ses_client, custom_verification_email_template_name)) + + +@service_marker +@pytest.mark.canary +class TestCustomVerificationEmailTemplate: + def test_create_update_custom_verification_email_template(self, simple_custom_verification_email_template, ses_client): + (ref, cr) = simple_custom_verification_email_template + assert k8s.wait_on_condition( + ref, + condition.CONDITION_TYPE_RESOURCE_SYNCED, + 'True', + wait_periods=MAX_WAIT_FOR_SYNCED_MINUTES, + ) + template_name = cr['spec']['templateRef']['from']['name'] + get_current_template = partial(get_custom_verification_email_template, ses_client, template_name) + assert get_current_template() is not None + + updates = { + 'spec': { + 'templateSubject': 'updated-subject', + 'failureRedirectionURL': 'https://example.com/updated', + 'successRedirectionURL': 'https://example.com/updated', + 'templateContent': 'updated template content', + } + } + k8s.patch_custom_resource(ref, updates) + time.sleep(10) + assert k8s.wait_on_condition( + ref, + condition.CONDITION_TYPE_RESOURCE_SYNCED, + 'True', + wait_periods=MAX_WAIT_FOR_SYNCED_MINUTES, + ) + + template = get_current_template() + template.pop('ResponseMetadata', None) + updated_template = updates['spec'] + assert template == { + 'TemplateName': template_name, + 'TemplateSubject': updated_template['templateSubject'], + 'FromEmailAddress': cr['spec']['fromEmailAddress'], + 'FailureRedirectionURL': updated_template['failureRedirectionURL'], + 'SuccessRedirectionURL': updated_template['successRedirectionURL'], + 'TemplateContent': updated_template['templateContent'], + } diff --git a/test/e2e/tests/receipt_filter_test.py b/test/e2e/tests/receipt_filter_test.py new file mode 100644 index 0000000..9437da7 --- /dev/null +++ b/test/e2e/tests/receipt_filter_test.py @@ -0,0 +1,104 @@ +# 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 ReceiptFilter resource +""" + +import boto3 +import logging +import time + +import pytest +from functools import partial + +from typing import Dict, Tuple +from acktest.k8s import resource as k8s +from acktest.resources import random_suffix_name +from acktest.k8s import condition +from e2e import service_marker, CRD_GROUP, CRD_VERSION, SERVICE_NAME, load_ses_resource +from e2e.common.waiter import wait_until_deleted, MAX_WAIT_FOR_SYNCED_MINUTES +from e2e.replacement_values import REPLACEMENT_VALUES + + +RECEIPT_FILTER_RESOURCE_PLURAL = "receiptfilters" + +@pytest.fixture(scope='module') +def ses_client(): + return boto3.client(SERVICE_NAME) + +def get_receipt_filter(ses_client, receipt_filter_name): + receipt_filters = [receipt_filter for receipt_filter in ses_client.list_receipt_filters()['Filters'] if receipt_filter['Name'] == receipt_filter_name] + return receipt_filters[0] if receipt_filters else None + +@pytest.fixture +def simple_receipt_rule_set(ses_client) -> Tuple[k8s.CustomResourceReference, Dict]: + receipt_filter_name = random_suffix_name('simple-receipt-rule-set', 32) + + replacements = REPLACEMENT_VALUES.copy() + replacements['RECEIPT_FILTER_NAME'] = receipt_filter_name + + resource_data = load_ses_resource( + 'receipt_filter_simple', + additional_replacements=replacements, + ) + logging.debug(resource_data) + + ref = k8s.CustomResourceReference( + CRD_GROUP, CRD_VERSION, RECEIPT_FILTER_RESOURCE_PLURAL, + receipt_filter_name, namespace='default', + ) + k8s.create_custom_resource(ref, resource_data) + cr = k8s.wait_resource_consumed_by_controller(ref, wait_periods=10) + + assert cr is not None + assert cr['status'] is not None + assert k8s.get_resource_exists(ref) + + yield ref, cr + + _, deleted = k8s.delete_custom_resource(ref, 3, 10) + assert deleted + wait_until_deleted(partial(get_receipt_filter, ses_client, receipt_filter_name)) + + +@service_marker +@pytest.mark.canary +class TestReceiptFilter: + def test_create_update_receipt_rule_set(self, simple_receipt_rule_set, ses_client): + (ref, cr) = simple_receipt_rule_set + assert k8s.wait_on_condition( + ref, + condition.CONDITION_TYPE_RESOURCE_SYNCED, + 'True', + wait_periods=MAX_WAIT_FOR_SYNCED_MINUTES, + ) + assert get_receipt_filter(ses_client, cr['spec']['name']) is not None + + updates = { + 'spec': { + 'filter': { + 'ipFilter': { + 'cidr': '192.168.0.2/32', + 'policy': 'Block', + } + } + } + } + k8s.patch_custom_resource(ref, updates) + time.sleep(10) + assert k8s.wait_on_condition( + ref, + condition.CONDITION_TYPE_TERMINAL, + 'True', + wait_periods=MAX_WAIT_FOR_SYNCED_MINUTES, + ) diff --git a/test/e2e/tests/receipt_rule_set_test.py b/test/e2e/tests/receipt_rule_set_test.py new file mode 100644 index 0000000..1cf0ccf --- /dev/null +++ b/test/e2e/tests/receipt_rule_set_test.py @@ -0,0 +1,84 @@ +# 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 ReceiptRuleSet resource +""" + +import boto3 +import logging + +import pytest +from functools import partial + +from typing import Dict, Tuple +from acktest.k8s import resource as k8s +from acktest.resources import random_suffix_name +from acktest.k8s import condition +from e2e import service_marker, CRD_GROUP, CRD_VERSION, SERVICE_NAME, load_ses_resource +from e2e.common.waiter import wait_until_deleted, MAX_WAIT_FOR_SYNCED_MINUTES +from e2e.replacement_values import REPLACEMENT_VALUES + + +RECEIPT_RULE_SET_RESOURCE_PLURAL = "receiptrulesets" + +@pytest.fixture(scope='module') +def ses_client(): + return boto3.client(SERVICE_NAME) + +def get_receipt_rule_set(ses_client, receipt_rule_set_name): + receipt_rule_sets = [rule_set for rule_set in ses_client.list_receipt_rule_sets()['RuleSets'] if rule_set['Name'] == receipt_rule_set_name] + return receipt_rule_sets[0] if receipt_rule_sets else None + +@pytest.fixture +def simple_receipt_rule_set(ses_client) -> Tuple[k8s.CustomResourceReference, Dict]: + receipt_rule_set_name = random_suffix_name('simple-receipt-rule-set', 32) + + replacements = REPLACEMENT_VALUES.copy() + replacements['RECEIPT_RULE_SET_NAME'] = receipt_rule_set_name + + resource_data = load_ses_resource( + 'receipt_rule_set_simple', + additional_replacements=replacements, + ) + logging.debug(resource_data) + + ref = k8s.CustomResourceReference( + CRD_GROUP, CRD_VERSION, RECEIPT_RULE_SET_RESOURCE_PLURAL, + receipt_rule_set_name, namespace='default', + ) + k8s.create_custom_resource(ref, resource_data) + cr = k8s.wait_resource_consumed_by_controller(ref, wait_periods=10) + + assert cr is not None + assert cr['status'] is not None + assert k8s.get_resource_exists(ref) + + yield ref, cr + + _, deleted = k8s.delete_custom_resource(ref, 3, 10) + assert deleted + wait_until_deleted(partial(get_receipt_rule_set, ses_client, receipt_rule_set_name)) + + +@service_marker +@pytest.mark.canary +class TestReceiptRuleSet: + def test_create_receipt_rule_set(self, simple_receipt_rule_set, ses_client): + (ref, cr) = simple_receipt_rule_set + assert k8s.wait_on_condition( + ref, + condition.CONDITION_TYPE_RESOURCE_SYNCED, + 'True', + wait_periods=MAX_WAIT_FOR_SYNCED_MINUTES, + ) + assert get_receipt_rule_set(ses_client, cr['spec']['ruleSetName']) is not None diff --git a/test/e2e/tests/receipt_rule_test.py b/test/e2e/tests/receipt_rule_test.py new file mode 100644 index 0000000..6faef42 --- /dev/null +++ b/test/e2e/tests/receipt_rule_test.py @@ -0,0 +1,122 @@ +# 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 ReceiptRule resource +""" + +import boto3 +import logging +import time + +import pytest +from functools import partial + +from typing import Dict, Tuple +from acktest.k8s import resource as k8s +from acktest.resources import random_suffix_name +from acktest.k8s import condition +from e2e import service_marker, CRD_GROUP, CRD_VERSION, SERVICE_NAME, load_ses_resource +from e2e.common.waiter import wait_until_deleted, MAX_WAIT_FOR_SYNCED_MINUTES +from e2e.replacement_values import REPLACEMENT_VALUES + +from .receipt_rule_set_test import simple_receipt_rule_set + +RECEIPT_RULE_RESOURCE_PLURAL = "receiptrules" + +@pytest.fixture(scope='module') +def ses_client(): + return boto3.client(SERVICE_NAME) + +def get_receipt_rule(ses_client, rule_set_name, rule_name): + try: + return ses_client.describe_receipt_rule(RuleSetName=rule_set_name, RuleName=rule_name) + except ses_client.exceptions.RuleDoesNotExistException: + return None + +@pytest.fixture +def simple_receipt_rule(simple_receipt_rule_set, ses_client) -> Tuple[k8s.CustomResourceReference, Dict]: + receipt_rule_name = random_suffix_name('simple-receipt-rule', 32) + + replacements = REPLACEMENT_VALUES.copy() + replacements['RECEIPT_RULE_NAME'] = receipt_rule_name + + (ref, receipt_rule_set_cr) = simple_receipt_rule_set + replacements['RULE_SET_REF_NAME'] = receipt_rule_set_cr['spec']['ruleSetName'] + + resource_data = load_ses_resource( + 'receipt_rule_simple', + additional_replacements=replacements, + ) + logging.debug(resource_data) + + ref = k8s.CustomResourceReference( + CRD_GROUP, CRD_VERSION, RECEIPT_RULE_RESOURCE_PLURAL, + receipt_rule_name, namespace='default', + ) + k8s.create_custom_resource(ref, resource_data) + cr = k8s.wait_resource_consumed_by_controller(ref, wait_periods=10) + + assert cr is not None + assert cr['status'] is not None + assert k8s.get_resource_exists(ref) + + yield ref, cr + + _, deleted = k8s.delete_custom_resource(ref, 3, 10) + assert deleted + wait_until_deleted(partial(get_receipt_rule, ses_client, receipt_rule_set_cr['spec']['ruleSetName'], receipt_rule_name)) + + +@service_marker +@pytest.mark.canary +class TestReceiptRule: + def test_create_update_receipt_rule(self, simple_receipt_rule, ses_client): + (ref, cr) = simple_receipt_rule + assert k8s.wait_on_condition( + ref, + condition.CONDITION_TYPE_RESOURCE_SYNCED, + 'True', + wait_periods=MAX_WAIT_FOR_SYNCED_MINUTES, + ) + get_current_receipt_rule = partial(get_receipt_rule, ses_client, cr['spec']['ruleSetRef']['from']['name'], cr['spec']['rule']['name']) + assert get_current_receipt_rule() is not None + + updates = { + 'spec': { + 'rule': { + 'enabled': False, + 'recipients': ['updated2@example.com', 'updated@example.com'], + 'scanEnabled': True, + 'tlsPolicy': 'Require', + }, + } + } + k8s.patch_custom_resource(ref, updates) + time.sleep(10) + assert k8s.wait_on_condition( + ref, + condition.CONDITION_TYPE_RESOURCE_SYNCED, + 'True', + wait_periods=MAX_WAIT_FOR_SYNCED_MINUTES, + ) + + receipt_rule = get_current_receipt_rule() + updated_receipt_rule = updates['spec']['rule'] + assert receipt_rule['Rule'] == { + 'Name': cr['spec']['rule']['name'], + 'Enabled': updated_receipt_rule['enabled'], + 'Recipients': updated_receipt_rule['recipients'], + 'ScanEnabled': updated_receipt_rule['scanEnabled'], + 'TlsPolicy': updated_receipt_rule['tlsPolicy'], + 'Actions': [], + } diff --git a/test/e2e/tests/template_test.py b/test/e2e/tests/template_test.py new file mode 100644 index 0000000..371e309 --- /dev/null +++ b/test/e2e/tests/template_test.py @@ -0,0 +1,112 @@ +# 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 Template resource +""" + +import boto3 +import logging +import time + +import pytest +from functools import partial + +from typing import Dict, Tuple +from acktest.k8s import resource as k8s +from acktest.resources import random_suffix_name +from acktest.k8s import condition +from e2e import service_marker, CRD_GROUP, CRD_VERSION, SERVICE_NAME, load_ses_resource +from e2e.common.waiter import wait_until_deleted, MAX_WAIT_FOR_SYNCED_MINUTES +from e2e.replacement_values import REPLACEMENT_VALUES + + +TEMPLATE_RESOURCE_PLURAL = "templates" + +@pytest.fixture(scope='module') +def ses_client(): + return boto3.client(SERVICE_NAME) + +def get_template(ses_client, template_name): + try: + return ses_client.get_template(TemplateName=template_name) + except ses_client.exceptions.TemplateDoesNotExistException: + return None + +@pytest.fixture +def simple_template(ses_client) -> Tuple[k8s.CustomResourceReference, Dict]: + template_name = random_suffix_name('simple-template', 32) + + replacements = REPLACEMENT_VALUES.copy() + replacements['TEMPLATE_NAME'] = template_name + + resource_data = load_ses_resource( + 'template_simple', + additional_replacements=replacements, + ) + logging.debug(resource_data) + + ref = k8s.CustomResourceReference( + CRD_GROUP, CRD_VERSION, TEMPLATE_RESOURCE_PLURAL, + template_name, namespace='default', + ) + k8s.create_custom_resource(ref, resource_data) + cr = k8s.wait_resource_consumed_by_controller(ref, wait_periods=10) + + assert cr is not None + assert cr['status'] is not None + assert k8s.get_resource_exists(ref) + + yield ref, cr + + _, deleted = k8s.delete_custom_resource(ref, 3, 10) + assert deleted + wait_until_deleted(partial(get_template, ses_client, template_name)) + + +@service_marker +@pytest.mark.canary +class TestTemplate: + def test_create_update_template(self, simple_template, ses_client): + (ref, cr) = simple_template + assert k8s.wait_on_condition( + ref, + condition.CONDITION_TYPE_RESOURCE_SYNCED, + 'True', + wait_periods=MAX_WAIT_FOR_SYNCED_MINUTES, + ) + assert get_template(ses_client, cr['spec']['name']) is not None + + updates = { + 'spec': { + 'textPart': 'updated-text', + 'subjectPart': 'updated-subject', + 'htmlPart': '
Updated HTML
', + } + } + k8s.patch_custom_resource(ref, updates) + time.sleep(10) + assert k8s.wait_on_condition( + ref, + condition.CONDITION_TYPE_RESOURCE_SYNCED, + 'True', + wait_periods=MAX_WAIT_FOR_SYNCED_MINUTES, + ) + + template = get_template(ses_client, cr['spec']['name']) + updated_template = updates['spec'] + assert template['Template'] == { + 'TemplateName': cr['spec']['name'], + 'TextPart': updated_template['textPart'], + 'SubjectPart': updated_template['subjectPart'], + 'HtmlPart': updated_template['htmlPart'], + }