From eccae599d6bee2bd4f0fd8a26d95cf8418f14c78 Mon Sep 17 00:00:00 2001 From: Stephane Selim Date: Wed, 14 Aug 2024 10:34:51 +0200 Subject: [PATCH] add support for ResourcePolicy in cloudwatchlogs service Signed-off-by: Stephane Selim --- apis/cloudwatchlogs/generator-config.yaml | 23 ++ apis/cloudwatchlogs/v1alpha1/custom_types.go | 3 + .../v1alpha1/zz_generated.deepcopy.go | 175 ++++++++- .../v1alpha1/zz_generated.managed.go | 60 +++ .../v1alpha1/zz_generated.managedlist.go | 9 + .../v1alpha1/zz_resource_policy.go | 116 ++++++ apis/cloudwatchlogs/v1alpha1/zz_types.go | 8 +- examples/cloudwatchlogs/resourcepolicy.yaml | 28 ++ ...gs.aws.crossplane.io_resourcepolicies.yaml | 358 ++++++++++++++++++ .../cloudwatchlogs/resourcepolicy/setup.go | 124 ++++++ .../resourcepolicy/setup_test.go | 303 +++++++++++++++ .../resourcepolicy/zz_controller.go | 237 ++++++++++++ .../resourcepolicy/zz_conversions.go | 92 +++++ pkg/controller/cloudwatchlogs/setup.go | 2 + 14 files changed, 1534 insertions(+), 4 deletions(-) create mode 100644 apis/cloudwatchlogs/v1alpha1/zz_resource_policy.go create mode 100644 examples/cloudwatchlogs/resourcepolicy.yaml create mode 100644 package/crds/cloudwatchlogs.aws.crossplane.io_resourcepolicies.yaml create mode 100644 pkg/controller/cloudwatchlogs/resourcepolicy/setup.go create mode 100644 pkg/controller/cloudwatchlogs/resourcepolicy/setup_test.go create mode 100644 pkg/controller/cloudwatchlogs/resourcepolicy/zz_controller.go create mode 100644 pkg/controller/cloudwatchlogs/resourcepolicy/zz_conversions.go diff --git a/apis/cloudwatchlogs/generator-config.yaml b/apis/cloudwatchlogs/generator-config.yaml index b81f1c52a7..c86a1037db 100644 --- a/apis/cloudwatchlogs/generator-config.yaml +++ b/apis/cloudwatchlogs/generator-config.yaml @@ -4,6 +4,21 @@ ignore: - ExportTask field_paths: - CreateLogGroupInput.KmsKeyId + - PutResourcePolicyInput.PolicyName + - DeleteResourcePolicyInput.PolicyName +operations: + PutResourcePolicy: + operation_type: + - Create + resource_name: ResourcePolicy + DescribeResourcePolicies: + operation_type: + - Read + resource_name: ResourcePolicy + DeleteResourcePolicy: + operation_type: + - Delete + resource_name: ResourcePolicy resources: LogGroup: fields: @@ -42,3 +57,11 @@ resources: from: operation: DescribeLogGroups path: LogGroups.StoredBytes + ResourcePolicy: + fields: + policyDocument: + is_required: true + exceptions: + errors: + 404: + code: ResourceNotFoundException diff --git a/apis/cloudwatchlogs/v1alpha1/custom_types.go b/apis/cloudwatchlogs/v1alpha1/custom_types.go index ae507a3f49..9e7c711c2e 100644 --- a/apis/cloudwatchlogs/v1alpha1/custom_types.go +++ b/apis/cloudwatchlogs/v1alpha1/custom_types.go @@ -44,3 +44,6 @@ type CustomLogGroupParameters struct { // +optional KMSKeyIDSelector *xpv1.Selector `json:"kmsKeyIDSelector,omitempty"` } + +// CustomResourcePolicyParameters includes the custom fields of ResourcePolicy. +type CustomResourcePolicyParameters struct{} diff --git a/apis/cloudwatchlogs/v1alpha1/zz_generated.deepcopy.go b/apis/cloudwatchlogs/v1alpha1/zz_generated.deepcopy.go index 592da19f7c..6259c05221 100644 --- a/apis/cloudwatchlogs/v1alpha1/zz_generated.deepcopy.go +++ b/apis/cloudwatchlogs/v1alpha1/zz_generated.deepcopy.go @@ -38,6 +38,11 @@ func (in *AccountPolicy) DeepCopyInto(out *AccountPolicy) { *out = new(int64) **out = **in } + if in.PolicyName != nil { + in, out := &in.PolicyName, &out.PolicyName + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccountPolicy. @@ -85,6 +90,21 @@ func (in *CustomLogGroupParameters) DeepCopy() *CustomLogGroupParameters { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CustomResourcePolicyParameters) DeepCopyInto(out *CustomResourcePolicyParameters) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourcePolicyParameters. +func (in *CustomResourcePolicyParameters) DeepCopy() *CustomResourcePolicyParameters { + if in == nil { + return nil + } + out := new(CustomResourcePolicyParameters) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Destination) DeepCopyInto(out *Destination) { *out = *in @@ -598,20 +618,169 @@ func (in *QueryInfo) DeepCopy() *QueryInfo { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResourcePolicy) DeepCopyInto(out *ResourcePolicy) { + *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 ResourcePolicy. +func (in *ResourcePolicy) DeepCopy() *ResourcePolicy { + if in == nil { + return nil + } + out := new(ResourcePolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ResourcePolicy) 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 *ResourcePolicyList) DeepCopyInto(out *ResourcePolicyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ResourcePolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourcePolicyList. +func (in *ResourcePolicyList) DeepCopy() *ResourcePolicyList { + if in == nil { + return nil + } + out := new(ResourcePolicyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ResourcePolicyList) 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 *ResourcePolicyObservation) DeepCopyInto(out *ResourcePolicyObservation) { *out = *in if in.LastUpdatedTime != nil { in, out := &in.LastUpdatedTime, &out.LastUpdatedTime *out = new(int64) **out = **in } + if in.PolicyName != nil { + in, out := &in.PolicyName, &out.PolicyName + *out = new(string) + **out = **in + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourcePolicy. -func (in *ResourcePolicy) DeepCopy() *ResourcePolicy { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourcePolicyObservation. +func (in *ResourcePolicyObservation) DeepCopy() *ResourcePolicyObservation { if in == nil { return nil } - out := new(ResourcePolicy) + out := new(ResourcePolicyObservation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourcePolicyParameters) DeepCopyInto(out *ResourcePolicyParameters) { + *out = *in + if in.PolicyDocument != nil { + in, out := &in.PolicyDocument, &out.PolicyDocument + *out = new(string) + **out = **in + } + out.CustomResourcePolicyParameters = in.CustomResourcePolicyParameters +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourcePolicyParameters. +func (in *ResourcePolicyParameters) DeepCopy() *ResourcePolicyParameters { + if in == nil { + return nil + } + out := new(ResourcePolicyParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourcePolicySpec) DeepCopyInto(out *ResourcePolicySpec) { + *out = *in + in.ResourceSpec.DeepCopyInto(&out.ResourceSpec) + in.ForProvider.DeepCopyInto(&out.ForProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourcePolicySpec. +func (in *ResourcePolicySpec) DeepCopy() *ResourcePolicySpec { + if in == nil { + return nil + } + out := new(ResourcePolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourcePolicyStatus) DeepCopyInto(out *ResourcePolicyStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) + in.AtProvider.DeepCopyInto(&out.AtProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourcePolicyStatus. +func (in *ResourcePolicyStatus) DeepCopy() *ResourcePolicyStatus { + if in == nil { + return nil + } + out := new(ResourcePolicyStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourcePolicy_SDK) DeepCopyInto(out *ResourcePolicy_SDK) { + *out = *in + if in.LastUpdatedTime != nil { + in, out := &in.LastUpdatedTime, &out.LastUpdatedTime + *out = new(int64) + **out = **in + } + if in.PolicyDocument != nil { + in, out := &in.PolicyDocument, &out.PolicyDocument + *out = new(string) + **out = **in + } + if in.PolicyName != nil { + in, out := &in.PolicyName, &out.PolicyName + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourcePolicy_SDK. +func (in *ResourcePolicy_SDK) DeepCopy() *ResourcePolicy_SDK { + if in == nil { + return nil + } + out := new(ResourcePolicy_SDK) in.DeepCopyInto(out) return out } diff --git a/apis/cloudwatchlogs/v1alpha1/zz_generated.managed.go b/apis/cloudwatchlogs/v1alpha1/zz_generated.managed.go index 0f3cfd1d46..c343284bef 100644 --- a/apis/cloudwatchlogs/v1alpha1/zz_generated.managed.go +++ b/apis/cloudwatchlogs/v1alpha1/zz_generated.managed.go @@ -79,3 +79,63 @@ func (mg *LogGroup) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetai func (mg *LogGroup) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { mg.Spec.WriteConnectionSecretToReference = r } + +// GetCondition of this ResourcePolicy. +func (mg *ResourcePolicy) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetDeletionPolicy of this ResourcePolicy. +func (mg *ResourcePolicy) GetDeletionPolicy() xpv1.DeletionPolicy { + return mg.Spec.DeletionPolicy +} + +// GetManagementPolicies of this ResourcePolicy. +func (mg *ResourcePolicy) GetManagementPolicies() xpv1.ManagementPolicies { + return mg.Spec.ManagementPolicies +} + +// GetProviderConfigReference of this ResourcePolicy. +func (mg *ResourcePolicy) GetProviderConfigReference() *xpv1.Reference { + return mg.Spec.ProviderConfigReference +} + +// GetPublishConnectionDetailsTo of this ResourcePolicy. +func (mg *ResourcePolicy) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo { + return mg.Spec.PublishConnectionDetailsTo +} + +// GetWriteConnectionSecretToReference of this ResourcePolicy. +func (mg *ResourcePolicy) GetWriteConnectionSecretToReference() *xpv1.SecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this ResourcePolicy. +func (mg *ResourcePolicy) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetDeletionPolicy of this ResourcePolicy. +func (mg *ResourcePolicy) SetDeletionPolicy(r xpv1.DeletionPolicy) { + mg.Spec.DeletionPolicy = r +} + +// SetManagementPolicies of this ResourcePolicy. +func (mg *ResourcePolicy) SetManagementPolicies(r xpv1.ManagementPolicies) { + mg.Spec.ManagementPolicies = r +} + +// SetProviderConfigReference of this ResourcePolicy. +func (mg *ResourcePolicy) SetProviderConfigReference(r *xpv1.Reference) { + mg.Spec.ProviderConfigReference = r +} + +// SetPublishConnectionDetailsTo of this ResourcePolicy. +func (mg *ResourcePolicy) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetailsTo) { + mg.Spec.PublishConnectionDetailsTo = r +} + +// SetWriteConnectionSecretToReference of this ResourcePolicy. +func (mg *ResourcePolicy) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} diff --git a/apis/cloudwatchlogs/v1alpha1/zz_generated.managedlist.go b/apis/cloudwatchlogs/v1alpha1/zz_generated.managedlist.go index 564b6f2602..dbc94e3932 100644 --- a/apis/cloudwatchlogs/v1alpha1/zz_generated.managedlist.go +++ b/apis/cloudwatchlogs/v1alpha1/zz_generated.managedlist.go @@ -28,3 +28,12 @@ func (l *LogGroupList) GetItems() []resource.Managed { } return items } + +// GetItems of this ResourcePolicyList. +func (l *ResourcePolicyList) GetItems() []resource.Managed { + items := make([]resource.Managed, len(l.Items)) + for i := range l.Items { + items[i] = &l.Items[i] + } + return items +} diff --git a/apis/cloudwatchlogs/v1alpha1/zz_resource_policy.go b/apis/cloudwatchlogs/v1alpha1/zz_resource_policy.go new file mode 100644 index 0000000000..ae68b06bca --- /dev/null +++ b/apis/cloudwatchlogs/v1alpha1/zz_resource_policy.go @@ -0,0 +1,116 @@ +/* +Copyright 2021 The Crossplane Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License 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 ( + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// ResourcePolicyParameters defines the desired state of ResourcePolicy +type ResourcePolicyParameters struct { + // Region is which region the ResourcePolicy will be created. + // +kubebuilder:validation:Required + Region string `json:"region"` + // Details of the new policy, including the identity of the principal that is + // enabled to put logs to this account. This is formatted as a JSON string. + // This parameter is required. + // + // The following example creates a resource policy enabling the Route 53 service + // to put DNS query logs in to the specified log group. Replace "logArn" with + // the ARN of your CloudWatch Logs resource, such as a log group or log stream. + // + // CloudWatch Logs also supports aws:SourceArn (https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-sourcearn) + // and aws:SourceAccount (https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-sourceaccount) + // condition context keys. + // + // In the example resource policy, you would replace the value of SourceArn + // with the resource making the call from Route 53 to CloudWatch Logs. You would + // also replace the value of SourceAccount with the Amazon Web Services account + // ID making that call. + // + // { "Version": "2012-10-17", "Statement": [ { "Sid": "Route53LogsToCloudWatchLogs", + // "Effect": "Allow", "Principal": { "Service": [ "route53.amazonaws.com" ] + // }, "Action": "logs:PutLogEvents", "Resource": "logArn", "Condition": { "ArnLike": + // { "aws:SourceArn": "myRoute53ResourceArn" }, "StringEquals": { "aws:SourceAccount": + // "myAwsAccountId" } } } ] } + // +kubebuilder:validation:Required + PolicyDocument *string `json:"policyDocument"` + CustomResourcePolicyParameters `json:",inline"` +} + +// ResourcePolicySpec defines the desired state of ResourcePolicy +type ResourcePolicySpec struct { + xpv1.ResourceSpec `json:",inline"` + ForProvider ResourcePolicyParameters `json:"forProvider"` +} + +// ResourcePolicyObservation defines the observed state of ResourcePolicy +type ResourcePolicyObservation struct { + // Timestamp showing when this policy was last updated, expressed as the number + // of milliseconds after Jan 1, 1970 00:00:00 UTC. + LastUpdatedTime *int64 `json:"lastUpdatedTime,omitempty"` + // The name of the resource policy. + PolicyName *string `json:"policyName,omitempty"` +} + +// ResourcePolicyStatus defines the observed state of ResourcePolicy. +type ResourcePolicyStatus struct { + xpv1.ResourceStatus `json:",inline"` + AtProvider ResourcePolicyObservation `json:"atProvider,omitempty"` +} + +// +kubebuilder:object:root=true + +// ResourcePolicy is the Schema for the ResourcePolicies API +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="EXTERNAL-NAME",type="string",JSONPath=".metadata.annotations.crossplane\\.io/external-name" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:subresource:status +// +kubebuilder:storageversion +// +kubebuilder:resource:scope=Cluster,categories={crossplane,managed,aws} +type ResourcePolicy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec ResourcePolicySpec `json:"spec"` + Status ResourcePolicyStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ResourcePolicyList contains a list of ResourcePolicies +type ResourcePolicyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ResourcePolicy `json:"items"` +} + +// Repository type metadata. +var ( + ResourcePolicyKind = "ResourcePolicy" + ResourcePolicyGroupKind = schema.GroupKind{Group: CRDGroup, Kind: ResourcePolicyKind}.String() + ResourcePolicyKindAPIVersion = ResourcePolicyKind + "." + GroupVersion.String() + ResourcePolicyGroupVersionKind = GroupVersion.WithKind(ResourcePolicyKind) +) + +func init() { + SchemeBuilder.Register(&ResourcePolicy{}, &ResourcePolicyList{}) +} diff --git a/apis/cloudwatchlogs/v1alpha1/zz_types.go b/apis/cloudwatchlogs/v1alpha1/zz_types.go index bb21b061a6..7eaa41c25b 100644 --- a/apis/cloudwatchlogs/v1alpha1/zz_types.go +++ b/apis/cloudwatchlogs/v1alpha1/zz_types.go @@ -32,6 +32,8 @@ type AccountPolicy struct { AccountID *string `json:"accountID,omitempty"` LastUpdatedTime *int64 `json:"lastUpdatedTime,omitempty"` + + PolicyName *string `json:"policyName,omitempty"` } // +kubebuilder:skipversion @@ -137,8 +139,12 @@ type QueryInfo struct { } // +kubebuilder:skipversion -type ResourcePolicy struct { +type ResourcePolicy_SDK struct { LastUpdatedTime *int64 `json:"lastUpdatedTime,omitempty"` + + PolicyDocument *string `json:"policyDocument,omitempty"` + + PolicyName *string `json:"policyName,omitempty"` } // +kubebuilder:skipversion diff --git a/examples/cloudwatchlogs/resourcepolicy.yaml b/examples/cloudwatchlogs/resourcepolicy.yaml new file mode 100644 index 0000000000..50ff2dc0be --- /dev/null +++ b/examples/cloudwatchlogs/resourcepolicy.yaml @@ -0,0 +1,28 @@ +apiVersion: cloudwatchlogs.aws.crossplane.io/v1alpha1 +kind: ResourcePolicy +metadata: + name: my-example-resource-policy +spec: + forProvider: + region: eu-central-1 + policyDocument: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "", + "Effect": "Allow", + "Principal": { + "Service": "es.amazonaws.com" + }, + "Action": [ + "logs:PutLogEvents", + "logs:PutLogEventsBatch", + "logs:CreateLogStream" + ], + "Resource": "arn:aws:logs:eu-central-1:123456789012:destination:testDestination" + } + ] + } + providerConfigRef: + name: providerconfig-aws diff --git a/package/crds/cloudwatchlogs.aws.crossplane.io_resourcepolicies.yaml b/package/crds/cloudwatchlogs.aws.crossplane.io_resourcepolicies.yaml new file mode 100644 index 0000000000..2acbdddc07 --- /dev/null +++ b/package/crds/cloudwatchlogs.aws.crossplane.io_resourcepolicies.yaml @@ -0,0 +1,358 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: resourcepolicies.cloudwatchlogs.aws.crossplane.io +spec: + group: cloudwatchlogs.aws.crossplane.io + names: + categories: + - crossplane + - managed + - aws + kind: ResourcePolicy + listKind: ResourcePolicyList + plural: resourcepolicies + singular: resourcepolicy + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + - jsonPath: .status.conditions[?(@.type=='Synced')].status + name: SYNCED + type: string + - jsonPath: .metadata.annotations.crossplane\.io/external-name + name: EXTERNAL-NAME + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: ResourcePolicy is the Schema for the ResourcePolicies 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: ResourcePolicySpec defines the desired state of ResourcePolicy + properties: + deletionPolicy: + default: Delete + description: |- + DeletionPolicy specifies what will happen to the underlying external + when this managed resource is deleted - either "Delete" or "Orphan" the + external resource. + This field is planned to be deprecated in favor of the ManagementPolicies + field in a future release. Currently, both could be set independently and + non-default values would be honored if the feature flag is enabled. + See the design doc for more information: https://github.com/crossplane/crossplane/blob/499895a25d1a1a0ba1604944ef98ac7a1a71f197/design/design-doc-observe-only-resources.md?plain=1#L223 + enum: + - Orphan + - Delete + type: string + forProvider: + description: ResourcePolicyParameters defines the desired state of + ResourcePolicy + properties: + policyDocument: + description: |- + Details of the new policy, including the identity of the principal that is + enabled to put logs to this account. This is formatted as a JSON string. + This parameter is required. + + + The following example creates a resource policy enabling the Route 53 service + to put DNS query logs in to the specified log group. Replace "logArn" with + the ARN of your CloudWatch Logs resource, such as a log group or log stream. + + + CloudWatch Logs also supports aws:SourceArn (https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-sourcearn) + and aws:SourceAccount (https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-sourceaccount) + condition context keys. + + + In the example resource policy, you would replace the value of SourceArn + with the resource making the call from Route 53 to CloudWatch Logs. You would + also replace the value of SourceAccount with the Amazon Web Services account + ID making that call. + + + { "Version": "2012-10-17", "Statement": [ { "Sid": "Route53LogsToCloudWatchLogs", + "Effect": "Allow", "Principal": { "Service": [ "route53.amazonaws.com" ] + }, "Action": "logs:PutLogEvents", "Resource": "logArn", "Condition": { "ArnLike": + { "aws:SourceArn": "myRoute53ResourceArn" }, "StringEquals": { "aws:SourceAccount": + "myAwsAccountId" } } } ] } + type: string + region: + description: Region is which region the ResourcePolicy will be + created. + type: string + required: + - policyDocument + - region + type: object + managementPolicies: + default: + - '*' + description: |- + THIS IS A BETA FIELD. It is on by default but can be opted out + through a Crossplane feature flag. + ManagementPolicies specify the array of actions Crossplane is allowed to + take on the managed and external resources. + This field is planned to replace the DeletionPolicy field in a future + release. Currently, both could be set independently and non-default + values would be honored if the feature flag is enabled. If both are + custom, the DeletionPolicy field will be ignored. + See the design doc for more information: https://github.com/crossplane/crossplane/blob/499895a25d1a1a0ba1604944ef98ac7a1a71f197/design/design-doc-observe-only-resources.md?plain=1#L223 + and this one: https://github.com/crossplane/crossplane/blob/444267e84783136daa93568b364a5f01228cacbe/design/one-pager-ignore-changes.md + items: + description: |- + A ManagementAction represents an action that the Crossplane controllers + can take on an external resource. + enum: + - Observe + - Create + - Update + - Delete + - LateInitialize + - '*' + type: string + type: array + providerConfigRef: + default: + name: default + description: |- + ProviderConfigReference specifies how the provider that will be used to + create, observe, update, and delete this managed resource should be + configured. + properties: + name: + description: Name of the referenced object. + type: string + policy: + description: Policies for referencing. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + required: + - name + type: object + publishConnectionDetailsTo: + description: |- + PublishConnectionDetailsTo specifies the connection secret config which + contains a name, metadata and a reference to secret store config to + which any connection details for this managed resource should be written. + Connection details frequently include the endpoint, username, + and password required to connect to the managed resource. + properties: + configRef: + default: + name: default + description: |- + SecretStoreConfigRef specifies which secret store config should be used + for this ConnectionSecret. + properties: + name: + description: Name of the referenced object. + type: string + policy: + description: Policies for referencing. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + required: + - name + type: object + metadata: + description: Metadata is the metadata for connection secret. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations are the annotations to be added to connection secret. + - For Kubernetes secrets, this will be used as "metadata.annotations". + - It is up to Secret Store implementation for others store types. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels are the labels/tags to be added to connection secret. + - For Kubernetes secrets, this will be used as "metadata.labels". + - It is up to Secret Store implementation for others store types. + type: object + type: + description: |- + Type is the SecretType for the connection secret. + - Only valid for Kubernetes Secret Stores. + type: string + type: object + name: + description: Name is the name of the connection secret. + type: string + required: + - name + type: object + writeConnectionSecretToRef: + description: |- + WriteConnectionSecretToReference specifies the namespace and name of a + Secret to which any connection details for this managed resource should + be written. Connection details frequently include the endpoint, username, + and password required to connect to the managed resource. + This field is planned to be replaced in a future release in favor of + PublishConnectionDetailsTo. Currently, both could be set independently + and connection details would be published to both without affecting + each other. + properties: + name: + description: Name of the secret. + type: string + namespace: + description: Namespace of the secret. + type: string + required: + - name + - namespace + type: object + required: + - forProvider + type: object + status: + description: ResourcePolicyStatus defines the observed state of ResourcePolicy. + properties: + atProvider: + description: ResourcePolicyObservation defines the observed state + of ResourcePolicy + properties: + lastUpdatedTime: + description: |- + Timestamp showing when this policy was last updated, expressed as the number + of milliseconds after Jan 1, 1970 00:00:00 UTC. + format: int64 + type: integer + policyName: + description: The name of the resource policy. + type: string + type: object + conditions: + description: Conditions of the resource. + items: + description: A Condition that may apply to a resource. + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time this condition transitioned from one + status to another. + format: date-time + type: string + message: + description: |- + A Message containing details about this condition's last transition from + one status to another, if any. + type: string + observedGeneration: + description: |- + ObservedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + type: integer + reason: + description: A Reason for this condition's last transition from + one status to another. + type: string + status: + description: Status of this condition; is it currently True, + False, or Unknown? + type: string + type: + description: |- + Type of this condition. At most one of each condition type may apply to + a resource at any point in time. + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + observedGeneration: + description: |- + ObservedGeneration is the latest metadata.generation + which resulted in either a ready state, or stalled due to error + it can not recover from without human intervention. + format: int64 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/pkg/controller/cloudwatchlogs/resourcepolicy/setup.go b/pkg/controller/cloudwatchlogs/resourcepolicy/setup.go new file mode 100644 index 0000000000..2d5350dd7b --- /dev/null +++ b/pkg/controller/cloudwatchlogs/resourcepolicy/setup.go @@ -0,0 +1,124 @@ +package resourcepolicy + +import ( + "context" + + svcsdk "github.com/aws/aws-sdk-go/service/cloudwatchlogs" + svcsdkapi "github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface" + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + "github.com/crossplane/crossplane-runtime/pkg/connection" + "github.com/crossplane/crossplane-runtime/pkg/controller" + "github.com/crossplane/crossplane-runtime/pkg/event" + "github.com/crossplane/crossplane-runtime/pkg/meta" + "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/pkg/resource" + "github.com/pkg/errors" + ctrl "sigs.k8s.io/controller-runtime" + + svcapitypes "github.com/crossplane-contrib/provider-aws/apis/cloudwatchlogs/v1alpha1" + "github.com/crossplane-contrib/provider-aws/apis/v1alpha1" + "github.com/crossplane-contrib/provider-aws/pkg/clients/iam" + "github.com/crossplane-contrib/provider-aws/pkg/features" + errorutils "github.com/crossplane-contrib/provider-aws/pkg/utils/errors" + "github.com/crossplane-contrib/provider-aws/pkg/utils/pointer" +) + +// SetupResourcePolicy adds a controller that reconciles ResourcePolicy. +func SetupResourcePolicy(mgr ctrl.Manager, o controller.Options) error { + name := managed.ControllerName(svcapitypes.ResourcePolicyGroupKind) + + cps := []managed.ConnectionPublisher{managed.NewAPISecretPublisher(mgr.GetClient(), mgr.GetScheme())} + if o.Features.Enabled(features.EnableAlphaExternalSecretStores) { + cps = append(cps, connection.NewDetailsManager(mgr.GetClient(), v1alpha1.StoreConfigGroupVersionKind)) + } + + opts := []option{ + func(e *external) { + c := &custom{client: e.client} + e.filterList = filterList + e.preCreate = preCreate + e.preDelete = preDelete + e.postObserve = postObserve + e.isUpToDate = isUpToDate + e.update = c.update + }, + } + + return ctrl.NewControllerManagedBy(mgr). + Named(name). + WithOptions(o.ForControllerRuntime()). + WithEventFilter(resource.DesiredStateChanged()). + For(&svcapitypes.ResourcePolicy{}). + Complete(managed.NewReconciler(mgr, + resource.ManagedKind(svcapitypes.ResourcePolicyGroupVersionKind), + managed.WithExternalConnecter(&connector{kube: mgr.GetClient(), opts: opts}), + managed.WithPollInterval(o.PollInterval), + managed.WithLogger(o.Logger.WithValues("controller", name)), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), + managed.WithConnectionPublishers(cps...))) +} + +type custom struct { + client svcsdkapi.CloudWatchLogsAPI +} + +func preCreate(_ context.Context, cr *svcapitypes.ResourcePolicy, obj *svcsdk.PutResourcePolicyInput) error { + obj.PolicyName = pointer.ToOrNilIfZeroValue(meta.GetExternalName(cr)) + return nil +} + +func preDelete(_ context.Context, cr *svcapitypes.ResourcePolicy, obj *svcsdk.DeleteResourcePolicyInput) (bool, error) { + obj.PolicyName = pointer.ToOrNilIfZeroValue(meta.GetExternalName(cr)) + return false, nil +} + +func postObserve(_ context.Context, cr *svcapitypes.ResourcePolicy, _ *svcsdk.DescribeResourcePoliciesOutput, obs managed.ExternalObservation, err error) (managed.ExternalObservation, error) { + if err != nil { + return managed.ExternalObservation{}, err + } + cr.SetConditions(xpv1.Available()) + return obs, nil +} + +func isUpToDate(_ context.Context, cr *svcapitypes.ResourcePolicy, obj *svcsdk.DescribeResourcePoliciesOutput) (bool, string, error) { + // Check if the policy exists + for _, policy := range obj.ResourcePolicies { + if policy.PolicyName != nil && *policy.PolicyName == meta.GetExternalName(cr) { + // Use existing method from iam to compare policy documents + return iam.IsPolicyDocumentUpToDate(*cr.Spec.ForProvider.PolicyDocument, policy.PolicyDocument) + } + } + return false, "", nil +} + +func filterList(cr *svcapitypes.ResourcePolicy, obj *svcsdk.DescribeResourcePoliciesOutput) *svcsdk.DescribeResourcePoliciesOutput { + resourcePolicyIdentifier := pointer.ToOrNilIfZeroValue(meta.GetExternalName(cr)) + resp := &svcsdk.DescribeResourcePoliciesOutput{} + + for _, resourcePolicy := range obj.ResourcePolicies { + if pointer.StringValue(resourcePolicy.PolicyName) == pointer.StringValue(resourcePolicyIdentifier) { + resp.ResourcePolicies = append(resp.ResourcePolicies, resourcePolicy) + break + } + } + return resp +} + +func preUpdate(_ context.Context, cr *svcapitypes.ResourcePolicy, obj *svcsdk.PutResourcePolicyInput) { + obj.PolicyName = pointer.ToOrNilIfZeroValue(meta.GetExternalName(cr)) +} + +func postUpdate(_ context.Context, _ *svcapitypes.ResourcePolicy, _ *svcsdk.PutResourcePolicyOutput, upd managed.ExternalUpdate, err error) (managed.ExternalUpdate, error) { + return upd, err +} + +func (e *custom) update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { + cr, ok := mg.(*svcapitypes.ResourcePolicy) + if !ok { + return managed.ExternalUpdate{}, errors.New(errUnexpectedObject) + } + input := GeneratePutResourcePolicyInput(cr) + preUpdate(ctx, cr, input) + resp, err := e.client.PutResourcePolicyWithContext(ctx, input) + return postUpdate(ctx, cr, resp, managed.ExternalUpdate{}, errorutils.Wrap(err, errUpdate)) +} diff --git a/pkg/controller/cloudwatchlogs/resourcepolicy/setup_test.go b/pkg/controller/cloudwatchlogs/resourcepolicy/setup_test.go new file mode 100644 index 0000000000..a09b790992 --- /dev/null +++ b/pkg/controller/cloudwatchlogs/resourcepolicy/setup_test.go @@ -0,0 +1,303 @@ +/* +Copyright 2019 The Crossplane Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License 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 resourcepolicy + +import ( + "context" + "testing" + + "github.com/aws/aws-sdk-go/aws" + svcsdk "github.com/aws/aws-sdk-go/service/cloudwatchlogs" + "github.com/crossplane/crossplane-runtime/pkg/meta" + "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + svcapitypes "github.com/crossplane-contrib/provider-aws/apis/cloudwatchlogs/v1alpha1" +) + +const ( + testPolicyDocument = `{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Route53LogsToCloudWatchLogs", + "Effect": "Allow", + "Principal": { + "Service": "route53.amazonaws.com" + }, + "Action": "logs:PutLogEvents", + "Resource": "logArn" + } + ] + }` + testPolicyDocument2 = `{ + "Version" : "2012-10-17", + "Statement" : [ + { + "Sid" : "", + "Effect" : "Allow", + "Principal" : { + "Service" : "111122223333" + }, + "Action" : "logs:PutSubscriptionFilter", + "Resource" : "arn:aws:logs:us-east-1:123456789012:destination:testDestination" + } + ] + }` + policyName = "policyName" + otherPolicyName = "otherPolicyName" +) + +type args struct { + describeResourcePoliciesOutput *svcsdk.DescribeResourcePoliciesOutput + resourcePolicy *svcapitypes.ResourcePolicy +} + +func TestIsUpToDate(t *testing.T) { + type want struct { + result bool + _ string + err error + } + + cases := map[string]struct { + args args + want want + }{ + "SameFields": { + args: args{ + describeResourcePoliciesOutput: &svcsdk.DescribeResourcePoliciesOutput{ + ResourcePolicies: []*svcsdk.ResourcePolicy{ + { + PolicyName: aws.String(policyName), + PolicyDocument: aws.String(testPolicyDocument), + }, + { + PolicyName: aws.String(otherPolicyName), + PolicyDocument: aws.String(testPolicyDocument2), + }, + }, + }, + resourcePolicy: &svcapitypes.ResourcePolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: policyName, + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: policyName, + }, + }, + Spec: svcapitypes.ResourcePolicySpec{ + ForProvider: svcapitypes.ResourcePolicyParameters{ + PolicyDocument: aws.String(testPolicyDocument), + }, + }, + }, + }, + want: want{ + result: true, + err: nil, + }, + }, + "DifferentPolicyDocument": { + args: args{ + describeResourcePoliciesOutput: &svcsdk.DescribeResourcePoliciesOutput{ + ResourcePolicies: []*svcsdk.ResourcePolicy{ + { + PolicyName: aws.String(policyName), + PolicyDocument: aws.String(testPolicyDocument), + }, + }, + }, + resourcePolicy: &svcapitypes.ResourcePolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: policyName, + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: policyName, + }, + }, + Spec: svcapitypes.ResourcePolicySpec{ + ForProvider: svcapitypes.ResourcePolicyParameters{ + PolicyDocument: aws.String(testPolicyDocument2), + }, + }, + }, + }, + want: want{ + result: false, + err: nil, + }, + }, + "ResourcePolicyNotFound": { + args: args{ + describeResourcePoliciesOutput: &svcsdk.DescribeResourcePoliciesOutput{ + ResourcePolicies: []*svcsdk.ResourcePolicy{ + { + PolicyName: aws.String(otherPolicyName), + PolicyDocument: aws.String(testPolicyDocument2), + }, + }, + }, + resourcePolicy: &svcapitypes.ResourcePolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: policyName, + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: policyName, + }, + }, + Spec: svcapitypes.ResourcePolicySpec{ + ForProvider: svcapitypes.ResourcePolicyParameters{ + PolicyDocument: aws.String(testPolicyDocument), + }, + }, + }, + }, + want: want{ + result: false, + err: nil, + }, + }, + "ResourcePolicyNotFoundEmptyDescribeResourcePoliciesOutput": { + args: args{ + describeResourcePoliciesOutput: &svcsdk.DescribeResourcePoliciesOutput{ + ResourcePolicies: []*svcsdk.ResourcePolicy{}, + }, + resourcePolicy: &svcapitypes.ResourcePolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: policyName, + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: policyName, + }, + }, + Spec: svcapitypes.ResourcePolicySpec{ + ForProvider: svcapitypes.ResourcePolicyParameters{ + PolicyDocument: aws.String(testPolicyDocument), + }, + }, + }, + }, + want: want{ + result: false, + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + + got, _, err := isUpToDate(context.Background(), tc.args.resourcePolicy, tc.args.describeResourcePoliciesOutput) + + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Errorf("isUpToDate(...): -want error, +got error: %s", diff) + } + if diff := cmp.Diff(tc.want.result, got); diff != "" { + t.Errorf("isUpToDate(...): -want, +got: %s", diff) + } + }) + } +} + +func TestFilterList(t *testing.T) { + type want struct { + resp *svcsdk.DescribeResourcePoliciesOutput + } + cases := map[string]struct { + args args + want want + }{ + "ResourcePolicyFound": { + args: args{ + describeResourcePoliciesOutput: &svcsdk.DescribeResourcePoliciesOutput{ + ResourcePolicies: []*svcsdk.ResourcePolicy{ + { + PolicyName: aws.String(policyName), + }, + { + PolicyName: aws.String(otherPolicyName), + }, + }, + }, + resourcePolicy: &svcapitypes.ResourcePolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: policyName, + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: policyName, + }, + }, + }, + }, + want: want{ + resp: &svcsdk.DescribeResourcePoliciesOutput{ + ResourcePolicies: []*svcsdk.ResourcePolicy{ + { + PolicyName: aws.String(policyName), + }, + }, + }, + }, + }, + "ResourcePolicyNotFound": { + args: args{ + describeResourcePoliciesOutput: &svcsdk.DescribeResourcePoliciesOutput{ + ResourcePolicies: []*svcsdk.ResourcePolicy{ + { + PolicyName: aws.String(otherPolicyName), + }, + }, + }, + resourcePolicy: &svcapitypes.ResourcePolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: policyName, + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: policyName, + }, + }, + }, + }, + want: want{ + resp: &svcsdk.DescribeResourcePoliciesOutput{}, + }, + }, + "ResourcePolicyNotFoundEmptyDescribeResourcePoliciesOutput": { + args: args{ + describeResourcePoliciesOutput: &svcsdk.DescribeResourcePoliciesOutput{ + ResourcePolicies: []*svcsdk.ResourcePolicy{}, + }, + resourcePolicy: &svcapitypes.ResourcePolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: policyName, + Annotations: map[string]string{ + meta.AnnotationKeyExternalName: policyName, + }, + }, + }, + }, + want: want{ + resp: &svcsdk.DescribeResourcePoliciesOutput{}, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + resp := filterList(tc.args.resourcePolicy, tc.args.describeResourcePoliciesOutput) + if diff := cmp.Diff(tc.want.resp, resp); diff != "" { + t.Errorf("filterList(...): -want, +got: %s", diff) + } + }) + } +} diff --git a/pkg/controller/cloudwatchlogs/resourcepolicy/zz_controller.go b/pkg/controller/cloudwatchlogs/resourcepolicy/zz_controller.go new file mode 100644 index 0000000000..cf47bc6ab6 --- /dev/null +++ b/pkg/controller/cloudwatchlogs/resourcepolicy/zz_controller.go @@ -0,0 +1,237 @@ +/* +Copyright 2021 The Crossplane Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License 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 resourcepolicy + +import ( + "context" + + svcapi "github.com/aws/aws-sdk-go/service/cloudwatchlogs" + svcsdk "github.com/aws/aws-sdk-go/service/cloudwatchlogs" + svcsdkapi "github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface" + "github.com/google/go-cmp/cmp" + "github.com/pkg/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + "github.com/crossplane/crossplane-runtime/pkg/meta" + "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + cpresource "github.com/crossplane/crossplane-runtime/pkg/resource" + + svcapitypes "github.com/crossplane-contrib/provider-aws/apis/cloudwatchlogs/v1alpha1" + connectaws "github.com/crossplane-contrib/provider-aws/pkg/utils/connect/aws" + errorutils "github.com/crossplane-contrib/provider-aws/pkg/utils/errors" +) + +const ( + errUnexpectedObject = "managed resource is not an ResourcePolicy resource" + + errCreateSession = "cannot create a new session" + errCreate = "cannot create ResourcePolicy in AWS" + errUpdate = "cannot update ResourcePolicy in AWS" + errDescribe = "failed to describe ResourcePolicy" + errDelete = "failed to delete ResourcePolicy" +) + +type connector struct { + kube client.Client + opts []option +} + +func (c *connector) Connect(ctx context.Context, mg cpresource.Managed) (managed.ExternalClient, error) { + cr, ok := mg.(*svcapitypes.ResourcePolicy) + if !ok { + return nil, errors.New(errUnexpectedObject) + } + sess, err := connectaws.GetConfigV1(ctx, c.kube, mg, cr.Spec.ForProvider.Region) + if err != nil { + return nil, errors.Wrap(err, errCreateSession) + } + return newExternal(c.kube, svcapi.New(sess), c.opts), nil +} + +func (e *external) Observe(ctx context.Context, mg cpresource.Managed) (managed.ExternalObservation, error) { + cr, ok := mg.(*svcapitypes.ResourcePolicy) + if !ok { + return managed.ExternalObservation{}, errors.New(errUnexpectedObject) + } + if meta.GetExternalName(cr) == "" { + return managed.ExternalObservation{ + ResourceExists: false, + }, nil + } + input := GenerateDescribeResourcePoliciesInput(cr) + if err := e.preObserve(ctx, cr, input); err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, "pre-observe failed") + } + resp, err := e.client.DescribeResourcePoliciesWithContext(ctx, input) + if err != nil { + return managed.ExternalObservation{ResourceExists: false}, errorutils.Wrap(cpresource.Ignore(IsNotFound, err), errDescribe) + } + resp = e.filterList(cr, resp) + if len(resp.ResourcePolicies) == 0 { + return managed.ExternalObservation{ResourceExists: false}, nil + } + currentSpec := cr.Spec.ForProvider.DeepCopy() + if err := e.lateInitialize(&cr.Spec.ForProvider, resp); err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, "late-init failed") + } + GenerateResourcePolicy(resp).Status.AtProvider.DeepCopyInto(&cr.Status.AtProvider) + upToDate := true + diff := "" + if !meta.WasDeleted(cr) { // There is no need to run isUpToDate if the resource is deleted + upToDate, diff, err = e.isUpToDate(ctx, cr, resp) + if err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, "isUpToDate check failed") + } + } + return e.postObserve(ctx, cr, resp, managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: upToDate, + Diff: diff, + ResourceLateInitialized: !cmp.Equal(&cr.Spec.ForProvider, currentSpec), + }, nil) +} + +func (e *external) Create(ctx context.Context, mg cpresource.Managed) (managed.ExternalCreation, error) { + cr, ok := mg.(*svcapitypes.ResourcePolicy) + if !ok { + return managed.ExternalCreation{}, errors.New(errUnexpectedObject) + } + cr.Status.SetConditions(xpv1.Creating()) + input := GeneratePutResourcePolicyInput(cr) + if err := e.preCreate(ctx, cr, input); err != nil { + return managed.ExternalCreation{}, errors.Wrap(err, "pre-create failed") + } + resp, err := e.client.PutResourcePolicyWithContext(ctx, input) + if err != nil { + return managed.ExternalCreation{}, errorutils.Wrap(err, errCreate) + } + + if resp.ResourcePolicy.LastUpdatedTime != nil { + cr.Status.AtProvider.LastUpdatedTime = resp.ResourcePolicy.LastUpdatedTime + } else { + cr.Status.AtProvider.LastUpdatedTime = nil + } + if resp.ResourcePolicy.PolicyDocument != nil { + cr.Spec.ForProvider.PolicyDocument = resp.ResourcePolicy.PolicyDocument + } else { + cr.Spec.ForProvider.PolicyDocument = nil + } + if resp.ResourcePolicy.PolicyName != nil { + cr.Status.AtProvider.PolicyName = resp.ResourcePolicy.PolicyName + } else { + cr.Status.AtProvider.PolicyName = nil + } + + return e.postCreate(ctx, cr, resp, managed.ExternalCreation{}, err) +} + +func (e *external) Update(ctx context.Context, mg cpresource.Managed) (managed.ExternalUpdate, error) { + return e.update(ctx, mg) + +} + +func (e *external) Delete(ctx context.Context, mg cpresource.Managed) error { + cr, ok := mg.(*svcapitypes.ResourcePolicy) + if !ok { + return errors.New(errUnexpectedObject) + } + cr.Status.SetConditions(xpv1.Deleting()) + input := GenerateDeleteResourcePolicyInput(cr) + ignore, err := e.preDelete(ctx, cr, input) + if err != nil { + return errors.Wrap(err, "pre-delete failed") + } + if ignore { + return nil + } + resp, err := e.client.DeleteResourcePolicyWithContext(ctx, input) + return e.postDelete(ctx, cr, resp, errorutils.Wrap(cpresource.Ignore(IsNotFound, err), errDelete)) +} + +type option func(*external) + +func newExternal(kube client.Client, client svcsdkapi.CloudWatchLogsAPI, opts []option) *external { + e := &external{ + kube: kube, + client: client, + preObserve: nopPreObserve, + postObserve: nopPostObserve, + lateInitialize: nopLateInitialize, + isUpToDate: alwaysUpToDate, + filterList: nopFilterList, + preCreate: nopPreCreate, + postCreate: nopPostCreate, + preDelete: nopPreDelete, + postDelete: nopPostDelete, + update: nopUpdate, + } + for _, f := range opts { + f(e) + } + return e +} + +type external struct { + kube client.Client + client svcsdkapi.CloudWatchLogsAPI + preObserve func(context.Context, *svcapitypes.ResourcePolicy, *svcsdk.DescribeResourcePoliciesInput) error + postObserve func(context.Context, *svcapitypes.ResourcePolicy, *svcsdk.DescribeResourcePoliciesOutput, managed.ExternalObservation, error) (managed.ExternalObservation, error) + filterList func(*svcapitypes.ResourcePolicy, *svcsdk.DescribeResourcePoliciesOutput) *svcsdk.DescribeResourcePoliciesOutput + lateInitialize func(*svcapitypes.ResourcePolicyParameters, *svcsdk.DescribeResourcePoliciesOutput) error + isUpToDate func(context.Context, *svcapitypes.ResourcePolicy, *svcsdk.DescribeResourcePoliciesOutput) (bool, string, error) + preCreate func(context.Context, *svcapitypes.ResourcePolicy, *svcsdk.PutResourcePolicyInput) error + postCreate func(context.Context, *svcapitypes.ResourcePolicy, *svcsdk.PutResourcePolicyOutput, managed.ExternalCreation, error) (managed.ExternalCreation, error) + preDelete func(context.Context, *svcapitypes.ResourcePolicy, *svcsdk.DeleteResourcePolicyInput) (bool, error) + postDelete func(context.Context, *svcapitypes.ResourcePolicy, *svcsdk.DeleteResourcePolicyOutput, error) error + update func(context.Context, cpresource.Managed) (managed.ExternalUpdate, error) +} + +func nopPreObserve(context.Context, *svcapitypes.ResourcePolicy, *svcsdk.DescribeResourcePoliciesInput) error { + return nil +} +func nopPostObserve(_ context.Context, _ *svcapitypes.ResourcePolicy, _ *svcsdk.DescribeResourcePoliciesOutput, obs managed.ExternalObservation, err error) (managed.ExternalObservation, error) { + return obs, err +} +func nopFilterList(_ *svcapitypes.ResourcePolicy, list *svcsdk.DescribeResourcePoliciesOutput) *svcsdk.DescribeResourcePoliciesOutput { + return list +} + +func nopLateInitialize(*svcapitypes.ResourcePolicyParameters, *svcsdk.DescribeResourcePoliciesOutput) error { + return nil +} +func alwaysUpToDate(context.Context, *svcapitypes.ResourcePolicy, *svcsdk.DescribeResourcePoliciesOutput) (bool, string, error) { + return true, "", nil +} + +func nopPreCreate(context.Context, *svcapitypes.ResourcePolicy, *svcsdk.PutResourcePolicyInput) error { + return nil +} +func nopPostCreate(_ context.Context, _ *svcapitypes.ResourcePolicy, _ *svcsdk.PutResourcePolicyOutput, cre managed.ExternalCreation, err error) (managed.ExternalCreation, error) { + return cre, err +} +func nopPreDelete(context.Context, *svcapitypes.ResourcePolicy, *svcsdk.DeleteResourcePolicyInput) (bool, error) { + return false, nil +} +func nopPostDelete(_ context.Context, _ *svcapitypes.ResourcePolicy, _ *svcsdk.DeleteResourcePolicyOutput, err error) error { + return err +} +func nopUpdate(context.Context, cpresource.Managed) (managed.ExternalUpdate, error) { + return managed.ExternalUpdate{}, nil +} diff --git a/pkg/controller/cloudwatchlogs/resourcepolicy/zz_conversions.go b/pkg/controller/cloudwatchlogs/resourcepolicy/zz_conversions.go new file mode 100644 index 0000000000..d5123d7047 --- /dev/null +++ b/pkg/controller/cloudwatchlogs/resourcepolicy/zz_conversions.go @@ -0,0 +1,92 @@ +/* +Copyright 2021 The Crossplane Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License 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 resourcepolicy + +import ( + "github.com/aws/aws-sdk-go/aws/awserr" + svcsdk "github.com/aws/aws-sdk-go/service/cloudwatchlogs" + + svcapitypes "github.com/crossplane-contrib/provider-aws/apis/cloudwatchlogs/v1alpha1" +) + +// NOTE(muvaf): We return pointers in case the function needs to start with an +// empty object, hence need to return a new pointer. + +// GenerateDescribeResourcePoliciesInput returns input for read +// operation. +func GenerateDescribeResourcePoliciesInput(cr *svcapitypes.ResourcePolicy) *svcsdk.DescribeResourcePoliciesInput { + res := &svcsdk.DescribeResourcePoliciesInput{} + + return res +} + +// GenerateResourcePolicy returns the current state in the form of *svcapitypes.ResourcePolicy. +func GenerateResourcePolicy(resp *svcsdk.DescribeResourcePoliciesOutput) *svcapitypes.ResourcePolicy { + cr := &svcapitypes.ResourcePolicy{} + + found := false + for _, elem := range resp.ResourcePolicies { + if elem.LastUpdatedTime != nil { + cr.Status.AtProvider.LastUpdatedTime = elem.LastUpdatedTime + } else { + cr.Status.AtProvider.LastUpdatedTime = nil + } + if elem.PolicyDocument != nil { + cr.Spec.ForProvider.PolicyDocument = elem.PolicyDocument + } else { + cr.Spec.ForProvider.PolicyDocument = nil + } + if elem.PolicyName != nil { + cr.Status.AtProvider.PolicyName = elem.PolicyName + } else { + cr.Status.AtProvider.PolicyName = nil + } + found = true + break + } + if !found { + return cr + } + + return cr +} + +// GeneratePutResourcePolicyInput returns a create input. +func GeneratePutResourcePolicyInput(cr *svcapitypes.ResourcePolicy) *svcsdk.PutResourcePolicyInput { + res := &svcsdk.PutResourcePolicyInput{} + + if cr.Spec.ForProvider.PolicyDocument != nil { + res.SetPolicyDocument(*cr.Spec.ForProvider.PolicyDocument) + } + + return res +} + +// GenerateDeleteResourcePolicyInput returns a deletion input. +func GenerateDeleteResourcePolicyInput(cr *svcapitypes.ResourcePolicy) *svcsdk.DeleteResourcePolicyInput { + res := &svcsdk.DeleteResourcePolicyInput{} + + return res +} + +// IsNotFound returns whether the given error is of type NotFound or not. +func IsNotFound(err error) bool { + awsErr, ok := err.(awserr.Error) + return ok && awsErr.Code() == "ResourceNotFoundException" +} diff --git a/pkg/controller/cloudwatchlogs/setup.go b/pkg/controller/cloudwatchlogs/setup.go index 6591a0285a..2a4462ece8 100644 --- a/pkg/controller/cloudwatchlogs/setup.go +++ b/pkg/controller/cloudwatchlogs/setup.go @@ -21,6 +21,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "github.com/crossplane-contrib/provider-aws/pkg/controller/cloudwatchlogs/loggroup" + "github.com/crossplane-contrib/provider-aws/pkg/controller/cloudwatchlogs/resourcepolicy" "github.com/crossplane-contrib/provider-aws/pkg/utils/setup" ) @@ -29,5 +30,6 @@ func Setup(mgr ctrl.Manager, o controller.Options) error { return setup.SetupControllers( mgr, o, loggroup.SetupLogGroup, + resourcepolicy.SetupResourcePolicy, ) }