From 87d42b0cf5317ee0324415208ca0e555c02cf1ed Mon Sep 17 00:00:00 2001 From: Arush Sharma Date: Tue, 22 Apr 2025 23:22:11 -0700 Subject: [PATCH 1/3] add authorizer resource support --- apis/v1alpha1/ack-generate-metadata.yaml | 6 +- apis/v1alpha1/authorizer.go | 134 +++++ apis/v1alpha1/generator.yaml | 36 +- apis/v1alpha1/types.go | 24 +- apis/v1alpha1/zz_generated.deepcopy.go | 193 +++++- cmd/controller/main.go | 1 + ...igateway.services.k8s.aws_authorizers.yaml | 227 +++++++ config/crd/kustomization.yaml | 1 + config/rbac/cluster-role-controller.yaml | 2 + config/rbac/role-reader.yaml | 1 + config/rbac/role-writer.yaml | 2 + generator.yaml | 36 +- ...igateway.services.k8s.aws_authorizers.yaml | 227 +++++++ helm/templates/_helpers.tpl | 2 + helm/templates/role-reader.yaml | 1 + helm/templates/role-writer.yaml | 2 + pkg/resource/authorizer/delta.go | 121 ++++ pkg/resource/authorizer/descriptor.go | 155 +++++ pkg/resource/authorizer/hooks.go | 114 ++++ pkg/resource/authorizer/identifiers.go | 55 ++ pkg/resource/authorizer/manager.go | 376 ++++++++++++ pkg/resource/authorizer/manager_factory.go | 100 ++++ pkg/resource/authorizer/references.go | 166 +++++ pkg/resource/authorizer/resource.go | 122 ++++ pkg/resource/authorizer/sdk.go | 565 ++++++++++++++++++ pkg/util/patch/patch.go | 12 + .../sdk_update_post_build_request.go.tpl | 1 + test/e2e/conftest.py | 4 + test/e2e/resources/authorizer_simple.yaml | 11 + test/e2e/tests/authorizer_test.py | 134 +++++ 30 files changed, 2812 insertions(+), 19 deletions(-) create mode 100644 apis/v1alpha1/authorizer.go create mode 100644 config/crd/bases/apigateway.services.k8s.aws_authorizers.yaml create mode 100644 helm/crds/apigateway.services.k8s.aws_authorizers.yaml create mode 100644 pkg/resource/authorizer/delta.go create mode 100644 pkg/resource/authorizer/descriptor.go create mode 100644 pkg/resource/authorizer/hooks.go create mode 100644 pkg/resource/authorizer/identifiers.go create mode 100644 pkg/resource/authorizer/manager.go create mode 100644 pkg/resource/authorizer/manager_factory.go create mode 100644 pkg/resource/authorizer/references.go create mode 100644 pkg/resource/authorizer/resource.go create mode 100644 pkg/resource/authorizer/sdk.go create mode 100644 templates/hooks/authorizer/sdk_update_post_build_request.go.tpl create mode 100644 test/e2e/resources/authorizer_simple.yaml create mode 100644 test/e2e/tests/authorizer_test.py diff --git a/apis/v1alpha1/ack-generate-metadata.yaml b/apis/v1alpha1/ack-generate-metadata.yaml index 8f7f6c1..b698e83 100644 --- a/apis/v1alpha1/ack-generate-metadata.yaml +++ b/apis/v1alpha1/ack-generate-metadata.yaml @@ -1,13 +1,13 @@ ack_generate_info: - build_date: "2025-04-25T18:19:58Z" + build_date: "2025-04-27T19:42:31Z" build_hash: 0909e7f0adb8ffe4120a8c20d5d58b991f2539e9 go_version: go1.24.1 version: v0.44.0-3-g0909e7f -api_directory_checksum: a1bb994937d5ff4cab2e5afbe5a2370afc13dab8 +api_directory_checksum: 94b9a1c92c36be454d70625764b603b5c2d5b554 api_version: v1alpha1 aws_sdk_go_version: v1.32.6 generator_config_info: - file_checksum: aa8189e95756917562438c32e1995bc1cc1f59b0 + file_checksum: 94f4e735b9f53f4992c3402f108ea847d3d438f2 original_file_name: generator.yaml last_modification: reason: API generation diff --git a/apis/v1alpha1/authorizer.go b/apis/v1alpha1/authorizer.go new file mode 100644 index 0000000..25516b3 --- /dev/null +++ b/apis/v1alpha1/authorizer.go @@ -0,0 +1,134 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package v1alpha1 + +import ( + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// AuthorizerSpec defines the desired state of Authorizer. +// +// Represents an authorization layer for methods. If enabled on a method, API +// Gateway will activate the authorizer when a client calls the method. +type AuthorizerSpec struct { + + // Optional customer-defined field, used in OpenAPI imports and exports without + // functional impact. + AuthType *string `json:"authType,omitempty"` + // Specifies the required credentials as an IAM role for API Gateway to invoke + // the authorizer. To specify an IAM role for API Gateway to assume, use the + // role's Amazon Resource Name (ARN). To use resource-based permissions on the + // Lambda function, specify null. + AuthorizerCredentials *string `json:"authorizerCredentials,omitempty"` + // The TTL in seconds of cached authorizer results. If it equals 0, authorization + // caching is disabled. If it is greater than 0, API Gateway will cache authorizer + // responses. If this field is not set, the default value is 300. The maximum + // value is 3600, or 1 hour. + AuthorizerResultTTLInSeconds *int64 `json:"authorizerResultTTLInSeconds,omitempty"` + // Specifies the authorizer's Uniform Resource Identifier (URI). For TOKEN or + // REQUEST authorizers, this must be a well-formed Lambda function URI, for + // example, arn:aws:apigateway:us-west-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-2:{account_id}:function:{lambda_function_name}/invocations. + // In general, the URI has this form arn:aws:apigateway:{region}:lambda:path/{service_api}, + // where {region} is the same as the region hosting the Lambda function, path + // indicates that the remaining substring in the URI should be treated as the + // path to the resource, including the initial /. For Lambda functions, this + // is usually of the form /2015-03-31/functions/[FunctionARN]/invocations. + AuthorizerURI *string `json:"authorizerURI,omitempty"` + // The identity source for which authorization is requested. For a TOKEN or + // COGNITO_USER_POOLS authorizer, this is required and specifies the request + // header mapping expression for the custom header holding the authorization + // token submitted by the client. For example, if the token header name is Auth, + // the header mapping expression is method.request.header.Auth. For the REQUEST + // authorizer, this is required when authorization caching is enabled. The value + // is a comma-separated string of one or more mapping expressions of the specified + // request parameters. For example, if an Auth header, a Name query string parameter + // are defined as identity sources, this value is method.request.header.Auth, + // method.request.querystring.Name. These parameters will be used to derive + // the authorization caching key and to perform runtime validation of the REQUEST + // authorizer by verifying all of the identity-related request parameters are + // present, not null and non-empty. Only when this is true does the authorizer + // invoke the authorizer Lambda function, otherwise, it returns a 401 Unauthorized + // response without calling the Lambda function. The valid value is a string + // of comma-separated mapping expressions of the specified request parameters. + // When the authorization caching is not enabled, this property is optional. + IdentitySource *string `json:"identitySource,omitempty"` + // A validation expression for the incoming identity token. For TOKEN authorizers, + // this value is a regular expression. For COGNITO_USER_POOLS authorizers, API + // Gateway will match the aud field of the incoming token from the client against + // the specified regular expression. It will invoke the authorizer's Lambda + // function when there is a match. Otherwise, it will return a 401 Unauthorized + // response without calling the Lambda function. The validation expression does + // not apply to the REQUEST authorizer. + IdentityValidationExpression *string `json:"identityValidationExpression,omitempty"` + // The name of the authorizer. + // +kubebuilder:validation:Required + Name *string `json:"name"` + // A list of the Amazon Cognito user pool ARNs for the COGNITO_USER_POOLS authorizer. + // Each element is of this format: arn:aws:cognito-idp:{region}:{account_id}:userpool/{user_pool_id}. + // For a TOKEN or REQUEST authorizer, this is not defined. + ProviderARNs []*string `json:"providerARNs,omitempty"` + // The string identifier of the associated RestApi. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable once set" + RestAPIID *string `json:"restAPIID,omitempty"` + RestAPIRef *ackv1alpha1.AWSResourceReferenceWrapper `json:"restAPIRef,omitempty"` + // The authorizer type. Valid values are TOKEN for a Lambda function using a + // single authorization token submitted in a custom header, REQUEST for a Lambda + // function using incoming request parameters, and COGNITO_USER_POOLS for using + // an Amazon Cognito user pool. + // +kubebuilder:validation:Required + Type *string `json:"type,omitempty"` +} + +// AuthorizerStatus defines the observed state of Authorizer +type AuthorizerStatus struct { + // All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + // that is used to contain resource sync state, account ownership, + // constructed ARN for the resource + // +kubebuilder:validation:Optional + ACKResourceMetadata *ackv1alpha1.ResourceMetadata `json:"ackResourceMetadata"` + // All CRs managed by ACK have a common `Status.Conditions` member that + // contains a collection of `ackv1alpha1.Condition` objects that describe + // the various terminal states of the CR and its backend AWS service API + // resource + // +kubebuilder:validation:Optional + Conditions []*ackv1alpha1.Condition `json:"conditions"` + // The identifier for the authorizer resource. + // +kubebuilder:validation:Optional + ID *string `json:"id,omitempty"` +} + +// Authorizer is the Schema for the Authorizers API +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type Authorizer struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec AuthorizerSpec `json:"spec,omitempty"` + Status AuthorizerStatus `json:"status,omitempty"` +} + +// AuthorizerList contains a list of Authorizer +// +kubebuilder:object:root=true +type AuthorizerList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Authorizer `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Authorizer{}, &AuthorizerList{}) +} diff --git a/apis/v1alpha1/generator.yaml b/apis/v1alpha1/generator.yaml index 79c51ef..ed13b03 100644 --- a/apis/v1alpha1/generator.yaml +++ b/apis/v1alpha1/generator.yaml @@ -44,7 +44,7 @@ operations: ignore: resource_names: # - ApiKey - - Authorizer + # - Authorizer - BasePathMapping # - Deployment - DocumentationPart @@ -394,4 +394,36 @@ resources: terminal_codes: - BadRequestException tags: - ignore: true \ No newline at end of file + ignore: true + Authorizer: + fields: + ID: + is_primary_key: true + RestAPIID: + references: + resource: RestAPI + path: Status.ID + is_required: true + is_immutable: true + Type: + go_tag: json:"type,omitempty" + renames: + operations: + GetAuthorizer: + input_fields: + AuthorizerId: Id + UpdateAuthorizer: + input_fields: + AuthorizerId: Id + DeleteAuthorizer: + input_fields: + AuthorizerId: Id + hooks: + sdk_update_post_build_request: + template_path: hooks/authorizer/sdk_update_post_build_request.go.tpl + tags: + ignore: true + exceptions: + terminal_codes: + - BadRequestException + - InvalidParameter \ No newline at end of file diff --git a/apis/v1alpha1/types.go b/apis/v1alpha1/types.go index c092008..4697a59 100644 --- a/apis/v1alpha1/types.go +++ b/apis/v1alpha1/types.go @@ -60,15 +60,21 @@ type AccessLogSettings struct { // Represents an authorization layer for methods. If enabled on a method, API // Gateway will activate the authorizer when a client calls the method. -type Authorizer struct { - AuthType *string `json:"authType,omitempty"` - AuthorizerCredentials *string `json:"authorizerCredentials,omitempty"` - AuthorizerResultTTLInSeconds *int64 `json:"authorizerResultTTLInSeconds,omitempty"` - AuthorizerURI *string `json:"authorizerURI,omitempty"` - ID *string `json:"id,omitempty"` - IdentitySource *string `json:"identitySource,omitempty"` - IdentityValidationExpression *string `json:"identityValidationExpression,omitempty"` - Name *string `json:"name,omitempty"` +type Authorizer_SDK struct { + AuthType *string `json:"authType,omitempty"` + AuthorizerCredentials *string `json:"authorizerCredentials,omitempty"` + AuthorizerResultTTLInSeconds *int64 `json:"authorizerResultTTLInSeconds,omitempty"` + AuthorizerURI *string `json:"authorizerURI,omitempty"` + ID *string `json:"id,omitempty"` + IdentitySource *string `json:"identitySource,omitempty"` + IdentityValidationExpression *string `json:"identityValidationExpression,omitempty"` + Name *string `json:"name,omitempty"` + ProviderARNs []*string `json:"providerARNs,omitempty"` + // The authorizer type. Valid values are TOKEN for a Lambda function using a + // single authorization token submitted in a custom header, REQUEST for a Lambda + // function using incoming request parameters, and COGNITO_USER_POOLS for using + // an Amazon Cognito user pool. + Type *string `json:"type_,omitempty"` } // Represents the base path that callers of the API must provide as part of diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index 768f234..a58a2c9 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -675,6 +675,177 @@ func (in *AccessLogSettings) DeepCopy() *AccessLogSettings { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Authorizer) DeepCopyInto(out *Authorizer) { + *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 Authorizer. +func (in *Authorizer) DeepCopy() *Authorizer { + if in == nil { + return nil + } + out := new(Authorizer) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Authorizer) 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 *AuthorizerList) DeepCopyInto(out *AuthorizerList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Authorizer, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizerList. +func (in *AuthorizerList) DeepCopy() *AuthorizerList { + if in == nil { + return nil + } + out := new(AuthorizerList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AuthorizerList) 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 *AuthorizerSpec) DeepCopyInto(out *AuthorizerSpec) { + *out = *in + if in.AuthType != nil { + in, out := &in.AuthType, &out.AuthType + *out = new(string) + **out = **in + } + if in.AuthorizerCredentials != nil { + in, out := &in.AuthorizerCredentials, &out.AuthorizerCredentials + *out = new(string) + **out = **in + } + if in.AuthorizerResultTTLInSeconds != nil { + in, out := &in.AuthorizerResultTTLInSeconds, &out.AuthorizerResultTTLInSeconds + *out = new(int64) + **out = **in + } + if in.AuthorizerURI != nil { + in, out := &in.AuthorizerURI, &out.AuthorizerURI + *out = new(string) + **out = **in + } + if in.IdentitySource != nil { + in, out := &in.IdentitySource, &out.IdentitySource + *out = new(string) + **out = **in + } + if in.IdentityValidationExpression != nil { + in, out := &in.IdentityValidationExpression, &out.IdentityValidationExpression + *out = new(string) + **out = **in + } + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + if in.ProviderARNs != nil { + in, out := &in.ProviderARNs, &out.ProviderARNs + *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.RestAPIID != nil { + in, out := &in.RestAPIID, &out.RestAPIID + *out = new(string) + **out = **in + } + if in.RestAPIRef != nil { + in, out := &in.RestAPIRef, &out.RestAPIRef + *out = new(corev1alpha1.AWSResourceReferenceWrapper) + (*in).DeepCopyInto(*out) + } + if in.Type != nil { + in, out := &in.Type, &out.Type + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizerSpec. +func (in *AuthorizerSpec) DeepCopy() *AuthorizerSpec { + if in == nil { + return nil + } + out := new(AuthorizerSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthorizerStatus) DeepCopyInto(out *AuthorizerStatus) { + *out = *in + if in.ACKResourceMetadata != nil { + in, out := &in.ACKResourceMetadata, &out.ACKResourceMetadata + *out = new(corev1alpha1.ResourceMetadata) + (*in).DeepCopyInto(*out) + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]*corev1alpha1.Condition, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(corev1alpha1.Condition) + (*in).DeepCopyInto(*out) + } + } + } + if in.ID != nil { + in, out := &in.ID, &out.ID + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizerStatus. +func (in *AuthorizerStatus) DeepCopy() *AuthorizerStatus { + if in == nil { + return nil + } + out := new(AuthorizerStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Authorizer_SDK) DeepCopyInto(out *Authorizer_SDK) { *out = *in if in.AuthType != nil { in, out := &in.AuthType, &out.AuthType @@ -716,14 +887,30 @@ func (in *Authorizer) DeepCopyInto(out *Authorizer) { *out = new(string) **out = **in } + if in.ProviderARNs != nil { + in, out := &in.ProviderARNs, &out.ProviderARNs + *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.Type != nil { + in, out := &in.Type, &out.Type + *out = new(string) + **out = **in + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Authorizer. -func (in *Authorizer) DeepCopy() *Authorizer { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Authorizer_SDK. +func (in *Authorizer_SDK) DeepCopy() *Authorizer_SDK { if in == nil { return nil } - out := new(Authorizer) + out := new(Authorizer_SDK) in.DeepCopyInto(out) return out } diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 82ff11d..e729874 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -43,6 +43,7 @@ import ( _ "github.com/aws-controllers-k8s/apigateway-controller/pkg/resource/api_integration_response" _ "github.com/aws-controllers-k8s/apigateway-controller/pkg/resource/api_key" _ "github.com/aws-controllers-k8s/apigateway-controller/pkg/resource/api_method_response" + _ "github.com/aws-controllers-k8s/apigateway-controller/pkg/resource/authorizer" _ "github.com/aws-controllers-k8s/apigateway-controller/pkg/resource/deployment" _ "github.com/aws-controllers-k8s/apigateway-controller/pkg/resource/integration" _ "github.com/aws-controllers-k8s/apigateway-controller/pkg/resource/method" diff --git a/config/crd/bases/apigateway.services.k8s.aws_authorizers.yaml b/config/crd/bases/apigateway.services.k8s.aws_authorizers.yaml new file mode 100644 index 0000000..f1e612b --- /dev/null +++ b/config/crd/bases/apigateway.services.k8s.aws_authorizers.yaml @@ -0,0 +1,227 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.2 + name: authorizers.apigateway.services.k8s.aws +spec: + group: apigateway.services.k8s.aws + names: + kind: Authorizer + listKind: AuthorizerList + plural: authorizers + singular: authorizer + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Authorizer is the Schema for the Authorizers 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: |- + AuthorizerSpec defines the desired state of Authorizer. + + Represents an authorization layer for methods. If enabled on a method, API + Gateway will activate the authorizer when a client calls the method. + properties: + authType: + description: |- + Optional customer-defined field, used in OpenAPI imports and exports without + functional impact. + type: string + authorizerCredentials: + description: |- + Specifies the required credentials as an IAM role for API Gateway to invoke + the authorizer. To specify an IAM role for API Gateway to assume, use the + role's Amazon Resource Name (ARN). To use resource-based permissions on the + Lambda function, specify null. + type: string + authorizerResultTTLInSeconds: + description: |- + The TTL in seconds of cached authorizer results. If it equals 0, authorization + caching is disabled. If it is greater than 0, API Gateway will cache authorizer + responses. If this field is not set, the default value is 300. The maximum + value is 3600, or 1 hour. + format: int64 + type: integer + authorizerURI: + description: |- + Specifies the authorizer's Uniform Resource Identifier (URI). For TOKEN or + REQUEST authorizers, this must be a well-formed Lambda function URI, for + example, arn:aws:apigateway:us-west-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-2:{account_id}:function:{lambda_function_name}/invocations. + In general, the URI has this form arn:aws:apigateway:{region}:lambda:path/{service_api}, + where {region} is the same as the region hosting the Lambda function, path + indicates that the remaining substring in the URI should be treated as the + path to the resource, including the initial /. For Lambda functions, this + is usually of the form /2015-03-31/functions/[FunctionARN]/invocations. + type: string + identitySource: + description: |- + The identity source for which authorization is requested. For a TOKEN or + COGNITO_USER_POOLS authorizer, this is required and specifies the request + header mapping expression for the custom header holding the authorization + token submitted by the client. For example, if the token header name is Auth, + the header mapping expression is method.request.header.Auth. For the REQUEST + authorizer, this is required when authorization caching is enabled. The value + is a comma-separated string of one or more mapping expressions of the specified + request parameters. For example, if an Auth header, a Name query string parameter + are defined as identity sources, this value is method.request.header.Auth, + method.request.querystring.Name. These parameters will be used to derive + the authorization caching key and to perform runtime validation of the REQUEST + authorizer by verifying all of the identity-related request parameters are + present, not null and non-empty. Only when this is true does the authorizer + invoke the authorizer Lambda function, otherwise, it returns a 401 Unauthorized + response without calling the Lambda function. The valid value is a string + of comma-separated mapping expressions of the specified request parameters. + When the authorization caching is not enabled, this property is optional. + type: string + identityValidationExpression: + description: |- + A validation expression for the incoming identity token. For TOKEN authorizers, + this value is a regular expression. For COGNITO_USER_POOLS authorizers, API + Gateway will match the aud field of the incoming token from the client against + the specified regular expression. It will invoke the authorizer's Lambda + function when there is a match. Otherwise, it will return a 401 Unauthorized + response without calling the Lambda function. The validation expression does + not apply to the REQUEST authorizer. + type: string + name: + description: The name of the authorizer. + type: string + providerARNs: + description: |- + A list of the Amazon Cognito user pool ARNs for the COGNITO_USER_POOLS authorizer. + Each element is of this format: arn:aws:cognito-idp:{region}:{account_id}:userpool/{user_pool_id}. + For a TOKEN or REQUEST authorizer, this is not defined. + items: + type: string + type: array + restAPIID: + description: The string identifier of the associated RestApi. + type: string + x-kubernetes-validations: + - message: Value is immutable once set + rule: self == oldSelf + restAPIRef: + 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 + type: + description: |- + The authorizer type. Valid values are TOKEN for a Lambda function using a + single authorization token submitted in a custom header, REQUEST for a Lambda + function using incoming request parameters, and COGNITO_USER_POOLS for using + an Amazon Cognito user pool. + type: string + required: + - name + - type + type: object + status: + description: AuthorizerStatus defines the observed state of Authorizer + 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 + id: + description: The identifier for the authorizer resource. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 5aa20fd..610b567 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -5,6 +5,7 @@ resources: - bases/apigateway.services.k8s.aws_apiintegrationresponses.yaml - bases/apigateway.services.k8s.aws_apikeys.yaml - bases/apigateway.services.k8s.aws_apimethodresponses.yaml + - bases/apigateway.services.k8s.aws_authorizers.yaml - bases/apigateway.services.k8s.aws_deployments.yaml - bases/apigateway.services.k8s.aws_integrations.yaml - bases/apigateway.services.k8s.aws_methods.yaml diff --git a/config/rbac/cluster-role-controller.yaml b/config/rbac/cluster-role-controller.yaml index e7723cd..415c5fe 100644 --- a/config/rbac/cluster-role-controller.yaml +++ b/config/rbac/cluster-role-controller.yaml @@ -28,6 +28,7 @@ rules: - apiintegrationresponses - apikeys - apimethodresponses + - authorizers - deployments - integrations - methods @@ -49,6 +50,7 @@ rules: - apiintegrationresponses/status - apikeys/status - apimethodresponses/status + - authorizers/status - deployments/status - integrations/status - methods/status diff --git a/config/rbac/role-reader.yaml b/config/rbac/role-reader.yaml index eee93a4..f84b820 100644 --- a/config/rbac/role-reader.yaml +++ b/config/rbac/role-reader.yaml @@ -12,6 +12,7 @@ rules: - apiintegrationresponses - apikeys - apimethodresponses + - authorizers - deployments - integrations - methods diff --git a/config/rbac/role-writer.yaml b/config/rbac/role-writer.yaml index 25416da..0f40871 100644 --- a/config/rbac/role-writer.yaml +++ b/config/rbac/role-writer.yaml @@ -12,6 +12,7 @@ rules: - apiintegrationresponses - apikeys - apimethodresponses + - authorizers - deployments - integrations - methods @@ -33,6 +34,7 @@ rules: - apiintegrationresponses - apikeys - apimethodresponses + - authorizers - deployments - integrations - methods diff --git a/generator.yaml b/generator.yaml index 79c51ef..ed13b03 100644 --- a/generator.yaml +++ b/generator.yaml @@ -44,7 +44,7 @@ operations: ignore: resource_names: # - ApiKey - - Authorizer + # - Authorizer - BasePathMapping # - Deployment - DocumentationPart @@ -394,4 +394,36 @@ resources: terminal_codes: - BadRequestException tags: - ignore: true \ No newline at end of file + ignore: true + Authorizer: + fields: + ID: + is_primary_key: true + RestAPIID: + references: + resource: RestAPI + path: Status.ID + is_required: true + is_immutable: true + Type: + go_tag: json:"type,omitempty" + renames: + operations: + GetAuthorizer: + input_fields: + AuthorizerId: Id + UpdateAuthorizer: + input_fields: + AuthorizerId: Id + DeleteAuthorizer: + input_fields: + AuthorizerId: Id + hooks: + sdk_update_post_build_request: + template_path: hooks/authorizer/sdk_update_post_build_request.go.tpl + tags: + ignore: true + exceptions: + terminal_codes: + - BadRequestException + - InvalidParameter \ No newline at end of file diff --git a/helm/crds/apigateway.services.k8s.aws_authorizers.yaml b/helm/crds/apigateway.services.k8s.aws_authorizers.yaml new file mode 100644 index 0000000..f1e612b --- /dev/null +++ b/helm/crds/apigateway.services.k8s.aws_authorizers.yaml @@ -0,0 +1,227 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.2 + name: authorizers.apigateway.services.k8s.aws +spec: + group: apigateway.services.k8s.aws + names: + kind: Authorizer + listKind: AuthorizerList + plural: authorizers + singular: authorizer + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Authorizer is the Schema for the Authorizers 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: |- + AuthorizerSpec defines the desired state of Authorizer. + + Represents an authorization layer for methods. If enabled on a method, API + Gateway will activate the authorizer when a client calls the method. + properties: + authType: + description: |- + Optional customer-defined field, used in OpenAPI imports and exports without + functional impact. + type: string + authorizerCredentials: + description: |- + Specifies the required credentials as an IAM role for API Gateway to invoke + the authorizer. To specify an IAM role for API Gateway to assume, use the + role's Amazon Resource Name (ARN). To use resource-based permissions on the + Lambda function, specify null. + type: string + authorizerResultTTLInSeconds: + description: |- + The TTL in seconds of cached authorizer results. If it equals 0, authorization + caching is disabled. If it is greater than 0, API Gateway will cache authorizer + responses. If this field is not set, the default value is 300. The maximum + value is 3600, or 1 hour. + format: int64 + type: integer + authorizerURI: + description: |- + Specifies the authorizer's Uniform Resource Identifier (URI). For TOKEN or + REQUEST authorizers, this must be a well-formed Lambda function URI, for + example, arn:aws:apigateway:us-west-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-2:{account_id}:function:{lambda_function_name}/invocations. + In general, the URI has this form arn:aws:apigateway:{region}:lambda:path/{service_api}, + where {region} is the same as the region hosting the Lambda function, path + indicates that the remaining substring in the URI should be treated as the + path to the resource, including the initial /. For Lambda functions, this + is usually of the form /2015-03-31/functions/[FunctionARN]/invocations. + type: string + identitySource: + description: |- + The identity source for which authorization is requested. For a TOKEN or + COGNITO_USER_POOLS authorizer, this is required and specifies the request + header mapping expression for the custom header holding the authorization + token submitted by the client. For example, if the token header name is Auth, + the header mapping expression is method.request.header.Auth. For the REQUEST + authorizer, this is required when authorization caching is enabled. The value + is a comma-separated string of one or more mapping expressions of the specified + request parameters. For example, if an Auth header, a Name query string parameter + are defined as identity sources, this value is method.request.header.Auth, + method.request.querystring.Name. These parameters will be used to derive + the authorization caching key and to perform runtime validation of the REQUEST + authorizer by verifying all of the identity-related request parameters are + present, not null and non-empty. Only when this is true does the authorizer + invoke the authorizer Lambda function, otherwise, it returns a 401 Unauthorized + response without calling the Lambda function. The valid value is a string + of comma-separated mapping expressions of the specified request parameters. + When the authorization caching is not enabled, this property is optional. + type: string + identityValidationExpression: + description: |- + A validation expression for the incoming identity token. For TOKEN authorizers, + this value is a regular expression. For COGNITO_USER_POOLS authorizers, API + Gateway will match the aud field of the incoming token from the client against + the specified regular expression. It will invoke the authorizer's Lambda + function when there is a match. Otherwise, it will return a 401 Unauthorized + response without calling the Lambda function. The validation expression does + not apply to the REQUEST authorizer. + type: string + name: + description: The name of the authorizer. + type: string + providerARNs: + description: |- + A list of the Amazon Cognito user pool ARNs for the COGNITO_USER_POOLS authorizer. + Each element is of this format: arn:aws:cognito-idp:{region}:{account_id}:userpool/{user_pool_id}. + For a TOKEN or REQUEST authorizer, this is not defined. + items: + type: string + type: array + restAPIID: + description: The string identifier of the associated RestApi. + type: string + x-kubernetes-validations: + - message: Value is immutable once set + rule: self == oldSelf + restAPIRef: + 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 + type: + description: |- + The authorizer type. Valid values are TOKEN for a Lambda function using a + single authorization token submitted in a custom header, REQUEST for a Lambda + function using incoming request parameters, and COGNITO_USER_POOLS for using + an Amazon Cognito user pool. + type: string + required: + - name + - type + type: object + status: + description: AuthorizerStatus defines the observed state of Authorizer + 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 + id: + description: The identifier for the authorizer resource. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl index baefe0e..d43477a 100644 --- a/helm/templates/_helpers.tpl +++ b/helm/templates/_helpers.tpl @@ -75,6 +75,7 @@ rules: - apiintegrationresponses - apikeys - apimethodresponses + - authorizers - deployments - integrations - methods @@ -96,6 +97,7 @@ rules: - apiintegrationresponses/status - apikeys/status - apimethodresponses/status + - authorizers/status - deployments/status - integrations/status - methods/status diff --git a/helm/templates/role-reader.yaml b/helm/templates/role-reader.yaml index 60d2260..26d8eb5 100644 --- a/helm/templates/role-reader.yaml +++ b/helm/templates/role-reader.yaml @@ -12,6 +12,7 @@ rules: - apiintegrationresponses - apikeys - apimethodresponses + - authorizers - deployments - integrations - methods diff --git a/helm/templates/role-writer.yaml b/helm/templates/role-writer.yaml index 2d73aaa..e2b738b 100644 --- a/helm/templates/role-writer.yaml +++ b/helm/templates/role-writer.yaml @@ -12,6 +12,7 @@ rules: - apiintegrationresponses - apikeys - apimethodresponses + - authorizers - deployments - integrations - methods @@ -33,6 +34,7 @@ rules: - apiintegrationresponses - apikeys - apimethodresponses + - authorizers - deployments - integrations - methods diff --git a/pkg/resource/authorizer/delta.go b/pkg/resource/authorizer/delta.go new file mode 100644 index 0000000..a42b97c --- /dev/null +++ b/pkg/resource/authorizer/delta.go @@ -0,0 +1,121 @@ +// 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 authorizer + +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.AuthType, b.ko.Spec.AuthType) { + delta.Add("Spec.AuthType", a.ko.Spec.AuthType, b.ko.Spec.AuthType) + } else if a.ko.Spec.AuthType != nil && b.ko.Spec.AuthType != nil { + if *a.ko.Spec.AuthType != *b.ko.Spec.AuthType { + delta.Add("Spec.AuthType", a.ko.Spec.AuthType, b.ko.Spec.AuthType) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.AuthorizerCredentials, b.ko.Spec.AuthorizerCredentials) { + delta.Add("Spec.AuthorizerCredentials", a.ko.Spec.AuthorizerCredentials, b.ko.Spec.AuthorizerCredentials) + } else if a.ko.Spec.AuthorizerCredentials != nil && b.ko.Spec.AuthorizerCredentials != nil { + if *a.ko.Spec.AuthorizerCredentials != *b.ko.Spec.AuthorizerCredentials { + delta.Add("Spec.AuthorizerCredentials", a.ko.Spec.AuthorizerCredentials, b.ko.Spec.AuthorizerCredentials) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.AuthorizerResultTTLInSeconds, b.ko.Spec.AuthorizerResultTTLInSeconds) { + delta.Add("Spec.AuthorizerResultTTLInSeconds", a.ko.Spec.AuthorizerResultTTLInSeconds, b.ko.Spec.AuthorizerResultTTLInSeconds) + } else if a.ko.Spec.AuthorizerResultTTLInSeconds != nil && b.ko.Spec.AuthorizerResultTTLInSeconds != nil { + if *a.ko.Spec.AuthorizerResultTTLInSeconds != *b.ko.Spec.AuthorizerResultTTLInSeconds { + delta.Add("Spec.AuthorizerResultTTLInSeconds", a.ko.Spec.AuthorizerResultTTLInSeconds, b.ko.Spec.AuthorizerResultTTLInSeconds) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.AuthorizerURI, b.ko.Spec.AuthorizerURI) { + delta.Add("Spec.AuthorizerURI", a.ko.Spec.AuthorizerURI, b.ko.Spec.AuthorizerURI) + } else if a.ko.Spec.AuthorizerURI != nil && b.ko.Spec.AuthorizerURI != nil { + if *a.ko.Spec.AuthorizerURI != *b.ko.Spec.AuthorizerURI { + delta.Add("Spec.AuthorizerURI", a.ko.Spec.AuthorizerURI, b.ko.Spec.AuthorizerURI) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.IdentitySource, b.ko.Spec.IdentitySource) { + delta.Add("Spec.IdentitySource", a.ko.Spec.IdentitySource, b.ko.Spec.IdentitySource) + } else if a.ko.Spec.IdentitySource != nil && b.ko.Spec.IdentitySource != nil { + if *a.ko.Spec.IdentitySource != *b.ko.Spec.IdentitySource { + delta.Add("Spec.IdentitySource", a.ko.Spec.IdentitySource, b.ko.Spec.IdentitySource) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.IdentityValidationExpression, b.ko.Spec.IdentityValidationExpression) { + delta.Add("Spec.IdentityValidationExpression", a.ko.Spec.IdentityValidationExpression, b.ko.Spec.IdentityValidationExpression) + } else if a.ko.Spec.IdentityValidationExpression != nil && b.ko.Spec.IdentityValidationExpression != nil { + if *a.ko.Spec.IdentityValidationExpression != *b.ko.Spec.IdentityValidationExpression { + delta.Add("Spec.IdentityValidationExpression", a.ko.Spec.IdentityValidationExpression, b.ko.Spec.IdentityValidationExpression) + } + } + 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 len(a.ko.Spec.ProviderARNs) != len(b.ko.Spec.ProviderARNs) { + delta.Add("Spec.ProviderARNs", a.ko.Spec.ProviderARNs, b.ko.Spec.ProviderARNs) + } else if len(a.ko.Spec.ProviderARNs) > 0 { + if !ackcompare.SliceStringPEqual(a.ko.Spec.ProviderARNs, b.ko.Spec.ProviderARNs) { + delta.Add("Spec.ProviderARNs", a.ko.Spec.ProviderARNs, b.ko.Spec.ProviderARNs) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.RestAPIID, b.ko.Spec.RestAPIID) { + delta.Add("Spec.RestAPIID", a.ko.Spec.RestAPIID, b.ko.Spec.RestAPIID) + } else if a.ko.Spec.RestAPIID != nil && b.ko.Spec.RestAPIID != nil { + if *a.ko.Spec.RestAPIID != *b.ko.Spec.RestAPIID { + delta.Add("Spec.RestAPIID", a.ko.Spec.RestAPIID, b.ko.Spec.RestAPIID) + } + } + if !reflect.DeepEqual(a.ko.Spec.RestAPIRef, b.ko.Spec.RestAPIRef) { + delta.Add("Spec.RestAPIRef", a.ko.Spec.RestAPIRef, b.ko.Spec.RestAPIRef) + } + if ackcompare.HasNilDifference(a.ko.Spec.Type, b.ko.Spec.Type) { + delta.Add("Spec.Type", a.ko.Spec.Type, b.ko.Spec.Type) + } else if a.ko.Spec.Type != nil && b.ko.Spec.Type != nil { + if *a.ko.Spec.Type != *b.ko.Spec.Type { + delta.Add("Spec.Type", a.ko.Spec.Type, b.ko.Spec.Type) + } + } + + return delta +} diff --git a/pkg/resource/authorizer/descriptor.go b/pkg/resource/authorizer/descriptor.go new file mode 100644 index 0000000..696a928 --- /dev/null +++ b/pkg/resource/authorizer/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 authorizer + +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/apigateway-controller/apis/v1alpha1" +) + +const ( + FinalizerString = "finalizers.apigateway.services.k8s.aws/Authorizer" +) + +var ( + GroupVersionResource = svcapitypes.GroupVersion.WithResource("authorizers") + GroupKind = metav1.GroupKind{ + Group: "apigateway.services.k8s.aws", + Kind: "Authorizer", + } +) + +// 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.Authorizer{} +} + +// 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.Authorizer), + } +} + +// 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/authorizer/hooks.go b/pkg/resource/authorizer/hooks.go new file mode 100644 index 0000000..0fa4335 --- /dev/null +++ b/pkg/resource/authorizer/hooks.go @@ -0,0 +1,114 @@ +// 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 authorizer + +import ( + "strconv" + + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + svcsdk "github.com/aws/aws-sdk-go-v2/service/apigateway" + "github.com/aws/aws-sdk-go/aws" + + "github.com/aws-controllers-k8s/apigateway-controller/pkg/util/patch" +) + +// updateAuthorizerInput patches the resource based on the delta between desired and latest state. +// It uses the patchSet utility to generate add/remove operations for list fields like providerARNs, +// as indicated by the UpdateAuthorizer API documentation. +func updateAuthorizerInput( + desired *resource, + latest *resource, + input *svcsdk.UpdateAuthorizerInput, + delta *ackcompare.Delta, +) { + desiredSpec := desired.ko.Spec + latestSpec := latest.ko.Spec + + var patchSet patch.Set + + if delta.DifferentAt("Spec.AuthorizerURI") { + patchSet.Replace("/authorizerUri", desiredSpec.AuthorizerURI) + } + + if delta.DifferentAt("Spec.AuthorizerCredentials") { + patchSet.Replace("/authorizerCredentials", desiredSpec.AuthorizerCredentials) + } + + if delta.DifferentAt("Spec.AuthorizerResultTTLInSeconds") { + val := aws.String(strconv.FormatInt(*desiredSpec.AuthorizerResultTTLInSeconds, 10)) + patchSet.Replace("/authorizerResultTtlInSeconds", val) + } + + if delta.DifferentAt("Spec.AuthType") { + patchSet.Replace("/authType", desiredSpec.AuthType) + } + + if delta.DifferentAt("Spec.IdentitySource") { + patchSet.Replace("/identitySource", desiredSpec.IdentitySource) + } + + if delta.DifferentAt("Spec.IdentityValidationExpression") { + patchSet.Replace("/identityValidationExpression", desiredSpec.IdentityValidationExpression) + } + + if delta.DifferentAt("Spec.Name") { + patchSet.Replace("/name", desiredSpec.Name) + } + + if delta.DifferentAt("Spec.ProviderARNs") { + updateProviderARNsPatches(&patchSet, latestSpec.ProviderARNs, desiredSpec.ProviderARNs) + } + + if delta.DifferentAt("Spec.Type") { + patchSet.Replace("/type", desiredSpec.Type) + } + + input.PatchOperations = patchSet.GetPatchOperations() +} + +// updateProviderARNsPatches generates patch operations for the providerARNs field +func updateProviderARNsPatches( + patchSet *patch.Set, + latestARNs, desiredARNs []*string, +) { + // Convert []*string to map[string]bool + latestSet := make(map[string]bool) + for _, arnPtr := range latestARNs { + if arnPtr != nil { + latestSet[*arnPtr] = true + } + } + + desiredSet := make(map[string]bool) + for _, arnPtr := range desiredARNs { + if arnPtr != nil { + desiredSet[*arnPtr] = true + } + } + + // ARNs to remove + for arn := range latestSet { + if !desiredSet[arn] { + // Use RemoveWithValue to generate: op=remove, path=/providerARNs, value=arn + patchSet.RemoveWithValue("/providerARNs", aws.String(arn)) + } + } + + // ARNs to add + for arn := range desiredSet { + if !latestSet[arn] { + patchSet.Add("/providerARNs", aws.String(arn)) + } + } +} diff --git a/pkg/resource/authorizer/identifiers.go b/pkg/resource/authorizer/identifiers.go new file mode 100644 index 0000000..cbbd81c --- /dev/null +++ b/pkg/resource/authorizer/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 authorizer + +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/authorizer/manager.go b/pkg/resource/authorizer/manager.go new file mode 100644 index 0000000..7004eaa --- /dev/null +++ b/pkg/resource/authorizer/manager.go @@ -0,0 +1,376 @@ +// 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 authorizer + +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-v2/aws" + svcsdk "github.com/aws/aws-sdk-go-v2/service/apigateway" + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + + svcapitypes "github.com/aws-controllers-k8s/apigateway-controller/apis/v1alpha1" +) + +var ( + _ = ackutil.InStrings + _ = acktags.NewTags() + _ = ackrt.MissingImageTagValue + _ = svcapitypes.Authorizer{} +) + +// +kubebuilder:rbac:groups=apigateway.services.k8s.aws,resources=authorizers,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=apigateway.services.k8s.aws,resources=authorizers/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 + // clientcfg is a copy of the client configuration passed on start of the + // service controller + clientcfg aws.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 + // sdk is a pointer to the AWS service API client exposed by the + // aws-sdk-go-v2/services/{alias} package. + sdkapi *svcsdk.Client +} + +// 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) + mirrorAWSTags(r, observed) + 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:apigateway:%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 +} + +// FilterAWSTags ignores tags that have keys that start with "aws:" +// is needed to ensure the controller does not attempt to remove +// tags set by AWS. This function needs to be called after each Read +// operation. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func (rm *resourceManager) FilterSystemTags(res acktypes.AWSResource) { + +} + +// mirrorAWSTags ensures that AWS tags are included in the desired resource +// if they are present in the latest resource. This will ensure that the +// aws tags are not present in a diff. The logic of the controller will +// ensure these tags aren't patched to the resource in the cluster, and +// will only be present to make sure we don't try to remove these tags. +// +// Although there are a lot of similarities between this function and +// EnsureTags, they are very much different. +// While EnsureTags tries to make sure the resource contains the controller +// tags, mirrowAWSTags tries to make sure tags injected by AWS are mirrored +// from the latest resoruce to the desired resource. +func mirrorAWSTags(a *resource, b *resource) { + +} + +// newResourceManager returns a new struct implementing +// acktypes.AWSResourceManager +// This is for AWS-SDK-GO-V2 - Created newResourceManager With AWS sdk-Go-ClientV2 +func newResourceManager( + cfg ackcfg.Config, + clientcfg aws.Config, + log logr.Logger, + metrics *ackmetrics.Metrics, + rr acktypes.Reconciler, + id ackv1alpha1.AWSAccountID, + region ackv1alpha1.AWSRegion, +) (*resourceManager, error) { + return &resourceManager{ + cfg: cfg, + clientcfg: clientcfg, + log: log, + metrics: metrics, + rr: rr, + awsAccountID: id, + awsRegion: region, + sdkapi: svcsdk.NewFromConfig(clientcfg), + }, 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/authorizer/manager_factory.go b/pkg/resource/authorizer/manager_factory.go new file mode 100644 index 0000000..39fab9e --- /dev/null +++ b/pkg/resource/authorizer/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 authorizer + +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-v2/aws" + "github.com/go-logr/logr" + + svcresource "github.com/aws-controllers-k8s/apigateway-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, + clientcfg aws.Config, + log logr.Logger, + metrics *ackmetrics.Metrics, + rr acktypes.Reconciler, + 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, clientcfg, log, metrics, rr, 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/authorizer/references.go b/pkg/resource/authorizer/references.go new file mode 100644 index 0000000..f637672 --- /dev/null +++ b/pkg/resource/authorizer/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 authorizer + +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/apigateway-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.RestAPIRef != nil { + ko.Spec.RestAPIID = 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.resolveReferenceForRestAPIID(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.Authorizer) error { + + if ko.Spec.RestAPIRef != nil && ko.Spec.RestAPIID != nil { + return ackerr.ResourceReferenceAndIDNotSupportedFor("RestAPIID", "RestAPIRef") + } + if ko.Spec.RestAPIRef == nil && ko.Spec.RestAPIID == nil { + return ackerr.ResourceReferenceOrIDRequiredFor("RestAPIID", "RestAPIRef") + } + return nil +} + +// resolveReferenceForRestAPIID reads the resource referenced +// from RestAPIRef field and sets the RestAPIID +// from referenced resource. Returns a boolean indicating whether a reference +// contains references, or an error +func (rm *resourceManager) resolveReferenceForRestAPIID( + ctx context.Context, + apiReader client.Reader, + ko *svcapitypes.Authorizer, +) (hasReferences bool, err error) { + if ko.Spec.RestAPIRef != nil && ko.Spec.RestAPIRef.From != nil { + hasReferences = true + arr := ko.Spec.RestAPIRef.From + if arr.Name == nil || *arr.Name == "" { + return hasReferences, fmt.Errorf("provided resource reference is nil or empty: RestAPIRef") + } + namespace := ko.ObjectMeta.GetNamespace() + if arr.Namespace != nil && *arr.Namespace != "" { + namespace = *arr.Namespace + } + obj := &svcapitypes.RestAPI{} + if err := getReferencedResourceState_RestAPI(ctx, apiReader, obj, *arr.Name, namespace); err != nil { + return hasReferences, err + } + ko.Spec.RestAPIID = (*string)(obj.Status.ID) + } + + return hasReferences, nil +} + +// getReferencedResourceState_RestAPI 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_RestAPI( + ctx context.Context, + apiReader client.Reader, + obj *svcapitypes.RestAPI, + 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( + "RestAPI", + namespace, name) + } + } + if refResourceTerminal { + return ackerr.ResourceReferenceTerminalFor( + "RestAPI", + 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( + "RestAPI", + namespace, name) + } + if obj.Status.ID == nil { + return ackerr.ResourceReferenceMissingTargetFieldFor( + "RestAPI", + namespace, name, + "Status.ID") + } + return nil +} diff --git a/pkg/resource/authorizer/resource.go b/pkg/resource/authorizer/resource.go new file mode 100644 index 0000000..8998748 --- /dev/null +++ b/pkg/resource/authorizer/resource.go @@ -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. + +// Code generated by ack-generate. DO NOT EDIT. + +package authorizer + +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" + "github.com/aws/aws-sdk-go-v2/aws" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + rtclient "sigs.k8s.io/controller-runtime/pkg/client" + + svcapitypes "github.com/aws-controllers-k8s/apigateway-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.Authorizer +} + +// Identifiers returns an AWSResourceIdentifiers object containing various +// identifying information, including the AWS account ID that owns the +// resource, the resource's AWS Resource Name (ARN) +func (r *resource) Identifiers() acktypes.AWSResourceIdentifiers { + return &resourceIdentifiers{r.ko.Status.ACKResourceMetadata} +} + +// IsBeingDeleted returns true if the Kubernetes resource has a non-zero +// deletion timestamp +func (r *resource) IsBeingDeleted() bool { + return !r.ko.DeletionTimestamp.IsZero() +} + +// RuntimeObject returns the Kubernetes apimachinery/runtime representation of +// the AWSResource +func (r *resource) RuntimeObject() rtclient.Object { + return r.ko +} + +// MetaObject returns the Kubernetes apimachinery/apis/meta/v1.Object +// representation of the AWSResource +func (r *resource) MetaObject() metav1.Object { + return r.ko.GetObjectMeta() +} + +// Conditions returns the ACK Conditions collection for the AWSResource +func (r *resource) Conditions() []*ackv1alpha1.Condition { + return r.ko.Status.Conditions +} + +// ReplaceConditions sets the Conditions status field for the resource +func (r *resource) ReplaceConditions(conditions []*ackv1alpha1.Condition) { + r.ko.Status.Conditions = conditions +} + +// SetObjectMeta sets the ObjectMeta field for the resource +func (r *resource) SetObjectMeta(meta metav1.ObjectMeta) { + r.ko.ObjectMeta = meta +} + +// SetStatus will set the Status field for the resource +func (r *resource) SetStatus(desired acktypes.AWSResource) { + r.ko.Status = desired.(*resource).ko.Status +} + +// SetIdentifiers sets the Spec or Status field that is referenced as the unique +// resource identifier +func (r *resource) SetIdentifiers(identifier *ackv1alpha1.AWSIdentifiers) error { + if identifier.NameOrID == "" { + return ackerrors.MissingNameIdentifier + } + r.ko.Status.ID = &identifier.NameOrID + + f1, f1ok := identifier.AdditionalKeys["restAPIID"] + if f1ok { + r.ko.Spec.RestAPIID = aws.String(f1) + } + + return nil +} + +// PopulateResourceFromAnnotation populates the fields passed from adoption annotation +func (r *resource) PopulateResourceFromAnnotation(fields map[string]string) error { + tmp, ok := fields["id"] + if !ok { + return ackerrors.MissingNameIdentifier + } + r.ko.Status.ID = &tmp + + f1, f1ok := fields["restAPIID"] + if f1ok { + r.ko.Spec.RestAPIID = aws.String(f1) + } + + 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/authorizer/sdk.go b/pkg/resource/authorizer/sdk.go new file mode 100644 index 0000000..3f6c117 --- /dev/null +++ b/pkg/resource/authorizer/sdk.go @@ -0,0 +1,565 @@ +// 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 authorizer + +import ( + "context" + "errors" + "fmt" + "math" + "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-v2/aws" + svcsdk "github.com/aws/aws-sdk-go-v2/service/apigateway" + svcsdktypes "github.com/aws/aws-sdk-go-v2/service/apigateway/types" + smithy "github.com/aws/smithy-go" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + svcapitypes "github.com/aws-controllers-k8s/apigateway-controller/apis/v1alpha1" +) + +// Hack to avoid import errors during build... +var ( + _ = &metav1.Time{} + _ = strings.ToLower("") + _ = &svcsdk.Client{} + _ = &svcapitypes.Authorizer{} + _ = ackv1alpha1.AWSAccountID("") + _ = &ackerr.NotFound + _ = &ackcondition.NotManagedMessage + _ = &reflect.Value{} + _ = fmt.Sprintf("") + _ = &ackrequeue.NoRequeue{} + _ = &aws.Config{} +) + +// 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.GetAuthorizerOutput + resp, err = rm.sdkapi.GetAuthorizer(ctx, input) + rm.metrics.RecordAPICall("READ_ONE", "GetAuthorizer", err) + if err != nil { + var awsErr smithy.APIError + if errors.As(err, &awsErr) && awsErr.ErrorCode() == "NotFoundException" { + 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.AuthType != nil { + ko.Spec.AuthType = resp.AuthType + } else { + ko.Spec.AuthType = nil + } + if resp.AuthorizerCredentials != nil { + ko.Spec.AuthorizerCredentials = resp.AuthorizerCredentials + } else { + ko.Spec.AuthorizerCredentials = nil + } + if resp.AuthorizerResultTtlInSeconds != nil { + authorizerResultTTLInSecondsCopy := int64(*resp.AuthorizerResultTtlInSeconds) + ko.Spec.AuthorizerResultTTLInSeconds = &authorizerResultTTLInSecondsCopy + } else { + ko.Spec.AuthorizerResultTTLInSeconds = nil + } + if resp.AuthorizerUri != nil { + ko.Spec.AuthorizerURI = resp.AuthorizerUri + } else { + ko.Spec.AuthorizerURI = nil + } + if resp.Id != nil { + ko.Status.ID = resp.Id + } else { + ko.Status.ID = nil + } + if resp.IdentitySource != nil { + ko.Spec.IdentitySource = resp.IdentitySource + } else { + ko.Spec.IdentitySource = nil + } + if resp.IdentityValidationExpression != nil { + ko.Spec.IdentityValidationExpression = resp.IdentityValidationExpression + } else { + ko.Spec.IdentityValidationExpression = nil + } + if resp.Name != nil { + ko.Spec.Name = resp.Name + } else { + ko.Spec.Name = nil + } + if resp.ProviderARNs != nil { + ko.Spec.ProviderARNs = aws.StringSlice(resp.ProviderARNs) + } else { + ko.Spec.ProviderARNs = nil + } + if resp.Type != "" { + ko.Spec.Type = aws.String(string(resp.Type)) + } else { + ko.Spec.Type = 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.Status.ID == nil || r.ko.Spec.RestAPIID == 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.GetAuthorizerInput, error) { + res := &svcsdk.GetAuthorizerInput{} + + if r.ko.Status.ID != nil { + res.AuthorizerId = r.ko.Status.ID + } + if r.ko.Spec.RestAPIID != nil { + res.RestApiId = r.ko.Spec.RestAPIID + } + + 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.CreateAuthorizerOutput + _ = resp + resp, err = rm.sdkapi.CreateAuthorizer(ctx, input) + rm.metrics.RecordAPICall("CREATE", "CreateAuthorizer", err) + if err != nil { + return nil, err + } + // Merge in the information we read from the API call above to the copy of + // the original Kubernetes object we passed to the function + ko := desired.ko.DeepCopy() + + if resp.AuthType != nil { + ko.Spec.AuthType = resp.AuthType + } else { + ko.Spec.AuthType = nil + } + if resp.AuthorizerCredentials != nil { + ko.Spec.AuthorizerCredentials = resp.AuthorizerCredentials + } else { + ko.Spec.AuthorizerCredentials = nil + } + if resp.AuthorizerResultTtlInSeconds != nil { + authorizerResultTTLInSecondsCopy := int64(*resp.AuthorizerResultTtlInSeconds) + ko.Spec.AuthorizerResultTTLInSeconds = &authorizerResultTTLInSecondsCopy + } else { + ko.Spec.AuthorizerResultTTLInSeconds = nil + } + if resp.AuthorizerUri != nil { + ko.Spec.AuthorizerURI = resp.AuthorizerUri + } else { + ko.Spec.AuthorizerURI = nil + } + if resp.Id != nil { + ko.Status.ID = resp.Id + } else { + ko.Status.ID = nil + } + if resp.IdentitySource != nil { + ko.Spec.IdentitySource = resp.IdentitySource + } else { + ko.Spec.IdentitySource = nil + } + if resp.IdentityValidationExpression != nil { + ko.Spec.IdentityValidationExpression = resp.IdentityValidationExpression + } else { + ko.Spec.IdentityValidationExpression = nil + } + if resp.Name != nil { + ko.Spec.Name = resp.Name + } else { + ko.Spec.Name = nil + } + if resp.ProviderARNs != nil { + ko.Spec.ProviderARNs = aws.StringSlice(resp.ProviderARNs) + } else { + ko.Spec.ProviderARNs = nil + } + if resp.Type != "" { + ko.Spec.Type = aws.String(string(resp.Type)) + } else { + ko.Spec.Type = nil + } + + rm.setStatusDefaults(ko) + return &resource{ko}, nil +} + +// newCreateRequestPayload returns an SDK-specific struct for the HTTP request +// payload of the Create API call for the resource +func (rm *resourceManager) newCreateRequestPayload( + ctx context.Context, + r *resource, +) (*svcsdk.CreateAuthorizerInput, error) { + res := &svcsdk.CreateAuthorizerInput{} + + if r.ko.Spec.AuthType != nil { + res.AuthType = r.ko.Spec.AuthType + } + if r.ko.Spec.AuthorizerCredentials != nil { + res.AuthorizerCredentials = r.ko.Spec.AuthorizerCredentials + } + if r.ko.Spec.AuthorizerResultTTLInSeconds != nil { + authorizerResultTTLInSecondsCopy0 := *r.ko.Spec.AuthorizerResultTTLInSeconds + if authorizerResultTTLInSecondsCopy0 > math.MaxInt32 || authorizerResultTTLInSecondsCopy0 < math.MinInt32 { + return nil, fmt.Errorf("error: field AuthorizerResultTtlInSeconds is of type int32") + } + authorizerResultTTLInSecondsCopy := int32(authorizerResultTTLInSecondsCopy0) + res.AuthorizerResultTtlInSeconds = &authorizerResultTTLInSecondsCopy + } + if r.ko.Spec.AuthorizerURI != nil { + res.AuthorizerUri = r.ko.Spec.AuthorizerURI + } + if r.ko.Spec.IdentitySource != nil { + res.IdentitySource = r.ko.Spec.IdentitySource + } + if r.ko.Spec.IdentityValidationExpression != nil { + res.IdentityValidationExpression = r.ko.Spec.IdentityValidationExpression + } + if r.ko.Spec.Name != nil { + res.Name = r.ko.Spec.Name + } + if r.ko.Spec.ProviderARNs != nil { + res.ProviderARNs = aws.ToStringSlice(r.ko.Spec.ProviderARNs) + } + if r.ko.Spec.RestAPIID != nil { + res.RestApiId = r.ko.Spec.RestAPIID + } + if r.ko.Spec.Type != nil { + res.Type = svcsdktypes.AuthorizerType(*r.ko.Spec.Type) + } + + 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) + }() + input, err := rm.newUpdateRequestPayload(ctx, desired, delta) + if err != nil { + return nil, err + } + updateAuthorizerInput(desired, latest, input, delta) + + var resp *svcsdk.UpdateAuthorizerOutput + _ = resp + resp, err = rm.sdkapi.UpdateAuthorizer(ctx, input) + rm.metrics.RecordAPICall("UPDATE", "UpdateAuthorizer", err) + if err != nil { + return nil, err + } + // Merge in the information we read from the API call above to the copy of + // the original Kubernetes object we passed to the function + ko := desired.ko.DeepCopy() + + if resp.AuthType != nil { + ko.Spec.AuthType = resp.AuthType + } else { + ko.Spec.AuthType = nil + } + if resp.AuthorizerCredentials != nil { + ko.Spec.AuthorizerCredentials = resp.AuthorizerCredentials + } else { + ko.Spec.AuthorizerCredentials = nil + } + if resp.AuthorizerResultTtlInSeconds != nil { + authorizerResultTTLInSecondsCopy := int64(*resp.AuthorizerResultTtlInSeconds) + ko.Spec.AuthorizerResultTTLInSeconds = &authorizerResultTTLInSecondsCopy + } else { + ko.Spec.AuthorizerResultTTLInSeconds = nil + } + if resp.AuthorizerUri != nil { + ko.Spec.AuthorizerURI = resp.AuthorizerUri + } else { + ko.Spec.AuthorizerURI = nil + } + if resp.Id != nil { + ko.Status.ID = resp.Id + } else { + ko.Status.ID = nil + } + if resp.IdentitySource != nil { + ko.Spec.IdentitySource = resp.IdentitySource + } else { + ko.Spec.IdentitySource = nil + } + if resp.IdentityValidationExpression != nil { + ko.Spec.IdentityValidationExpression = resp.IdentityValidationExpression + } else { + ko.Spec.IdentityValidationExpression = nil + } + if resp.Name != nil { + ko.Spec.Name = resp.Name + } else { + ko.Spec.Name = nil + } + if resp.ProviderARNs != nil { + ko.Spec.ProviderARNs = aws.StringSlice(resp.ProviderARNs) + } else { + ko.Spec.ProviderARNs = nil + } + if resp.Type != "" { + ko.Spec.Type = aws.String(string(resp.Type)) + } else { + ko.Spec.Type = nil + } + + 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.UpdateAuthorizerInput, error) { + res := &svcsdk.UpdateAuthorizerInput{} + + if r.ko.Status.ID != nil { + res.AuthorizerId = r.ko.Status.ID + } + if r.ko.Spec.RestAPIID != nil { + res.RestApiId = r.ko.Spec.RestAPIID + } + + 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.DeleteAuthorizerOutput + _ = resp + resp, err = rm.sdkapi.DeleteAuthorizer(ctx, input) + rm.metrics.RecordAPICall("DELETE", "DeleteAuthorizer", 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.DeleteAuthorizerInput, error) { + res := &svcsdk.DeleteAuthorizerInput{} + + if r.ko.Status.ID != nil { + res.AuthorizerId = r.ko.Status.ID + } + if r.ko.Spec.RestAPIID != nil { + res.RestApiId = r.ko.Spec.RestAPIID + } + + return res, nil +} + +// setStatusDefaults sets default properties into supplied custom resource +func (rm *resourceManager) setStatusDefaults( + ko *svcapitypes.Authorizer, +) { + 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 + } + + var terminalErr smithy.APIError + if !errors.As(err, &terminalErr) { + return false + } + switch terminalErr.ErrorCode() { + case "BadRequestException", + "InvalidParameter": + return true + default: + return false + } +} diff --git a/pkg/util/patch/patch.go b/pkg/util/patch/patch.go index b5e7b7a..ffde3ec 100644 --- a/pkg/util/patch/patch.go +++ b/pkg/util/patch/patch.go @@ -94,6 +94,18 @@ func (p *Set) Remove(path string) { }) } +// RemoveWithValue adds a patch operation to this set for removing a specific value +// from a list identified by the path. +// This generates op=remove, path=, value=. +// Useful for APIs like API Gateway UpdateAuthorizer for providerARNs. +func (p *Set) RemoveWithValue(path string, valueToRemove *string) { + p.patchOps = append(p.patchOps, &apigatewaytypes.PatchOperation{ + Op: apigatewaytypes.OpRemove, + Path: aws.String(path), + Value: valueToRemove, + }) +} + // GetPatchOperations returns the patch operations applied to this set. func (p *Set) GetPatchOperations() []apigatewaytypes.PatchOperation { var patchOps []apigatewaytypes.PatchOperation diff --git a/templates/hooks/authorizer/sdk_update_post_build_request.go.tpl b/templates/hooks/authorizer/sdk_update_post_build_request.go.tpl new file mode 100644 index 0000000..0872a9b --- /dev/null +++ b/templates/hooks/authorizer/sdk_update_post_build_request.go.tpl @@ -0,0 +1 @@ + updateAuthorizerInput(desired, latest, input, delta) diff --git a/test/e2e/conftest.py b/test/e2e/conftest.py index 9be9e24..a44d227 100644 --- a/test/e2e/conftest.py +++ b/test/e2e/conftest.py @@ -43,3 +43,7 @@ def k8s_client(): @pytest.fixture(scope='module') def apigateway_client(): return boto3.client('apigateway') + +@pytest.fixture(scope='module') +def cognito_client(): + return boto3.client('cognito-idp') diff --git a/test/e2e/resources/authorizer_simple.yaml b/test/e2e/resources/authorizer_simple.yaml new file mode 100644 index 0000000..f3c77ad --- /dev/null +++ b/test/e2e/resources/authorizer_simple.yaml @@ -0,0 +1,11 @@ +apiVersion: apigateway.services.k8s.aws/v1alpha1 +kind: Authorizer +metadata: + name: $AUTHORIZER_NAME +spec: + restAPIID: $REST_API_ID + name: $AUTHORIZER_NAME + type: COGNITO_USER_POOLS + identitySource: method.request.header.Authorization + providerARNs: + - "$USER_POOL_ARN_1" \ No newline at end of file diff --git a/test/e2e/tests/authorizer_test.py b/test/e2e/tests/authorizer_test.py new file mode 100644 index 0000000..bef6496 --- /dev/null +++ b/test/e2e/tests/authorizer_test.py @@ -0,0 +1,134 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You may +# not use this file except in compliance with the License. A copy of the +# License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is distributed +# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +# express or implied. See the License for the specific language governing +# permissions and limitations under the License. + +"""Integration tests for the Authorizer resource +""" + +import logging +import time +import pytest +import boto3 +from functools import partial + +from acktest.resources import random_suffix_name +from acktest.k8s import resource as k8s +from e2e import service_marker, CRD_GROUP, CRD_VERSION, load_apigateway_resource +from e2e.replacement_values import REPLACEMENT_VALUES +from .rest_api_test import simple_rest_api +from e2e.common.waiter import wait_until_deleted, safe_get + +RESOURCE_PLURAL = "authorizers" +DEFAULT_WAIT_SECS = 10 + + +@pytest.fixture(scope="function") +def authorizer_test_resources(cognito_client, simple_rest_api): + _, rest_api_cr = simple_rest_api + rest_api_id = rest_api_cr["status"].get("id") + assert rest_api_id is not None + + user_pool_name_1 = random_suffix_name("user-pool-1", 32) + user_pool_name_2 = random_suffix_name("user-pool-2", 32) + authorizer_name = random_suffix_name("authorizer", 32) + + pool1_resp = cognito_client.create_user_pool(PoolName=user_pool_name_1) + user_pool_id_1 = pool1_resp['UserPool']['Id'] + user_pool_arn_1 = pool1_resp['UserPool']['Arn'] + + pool2_resp = cognito_client.create_user_pool(PoolName=user_pool_name_2) + user_pool_id_2 = pool2_resp['UserPool']['Id'] + user_pool_arn_2 = pool2_resp['UserPool']['Arn'] + + # Create Authorizer CR using prerequisites + replacements_authorizer = REPLACEMENT_VALUES.copy() + replacements_authorizer["AUTHORIZER_NAME"] = authorizer_name + replacements_authorizer["REST_API_ID"] = rest_api_id + replacements_authorizer["USER_POOL_ARN_1"] = user_pool_arn_1 + resource_data_authorizer = load_apigateway_resource( + "authorizer_simple", + additional_replacements=replacements_authorizer, + ) + authorizer_ref = k8s.CustomResourceReference( + CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL, + authorizer_name, namespace='default', + ) + k8s.create_custom_resource(authorizer_ref, resource_data_authorizer) + authorizer_cr = k8s.wait_resource_consumed_by_controller(authorizer_ref) + assert authorizer_cr is not None + assert k8s.get_resource_exists(authorizer_ref) + authorizer_id = authorizer_cr["status"].get("id", None) + assert authorizer_id is not None + + yield (authorizer_ref, authorizer_cr, authorizer_name, user_pool_arn_1, user_pool_arn_2) + + _, deleted_auth = k8s.delete_custom_resource( + authorizer_ref, wait_periods=3, period_length=DEFAULT_WAIT_SECS) + assert deleted_auth + + cognito_client.delete_user_pool(UserPoolId=user_pool_id_1) + cognito_client.delete_user_pool(UserPoolId=user_pool_id_2) + + +@service_marker +@pytest.mark.canary +def test_authorizer_crud(authorizer_test_resources, apigateway_client): + (authorizer_ref, authorizer_cr, authorizer_name, + user_pool_arn_1, user_pool_arn_2) = authorizer_test_resources + + rest_api_id = authorizer_cr["spec"]["restAPIID"] + authorizer_id = authorizer_cr["status"].get("id", None) + assert authorizer_id is not None + + get_aws_authorizer = partial( + apigateway_client.get_authorizer, restApiId=rest_api_id, authorizerId=authorizer_id) + + assert authorizer_cr is not None + assert k8s.get_resource_exists(authorizer_ref) + assert authorizer_cr["spec"].get("name") == authorizer_name + assert authorizer_cr["spec"].get("type") == "COGNITO_USER_POOLS" + assert authorizer_cr["spec"].get( + "identitySource") == "method.request.header.Authorization" + assert authorizer_cr["spec"].get("providerARNs") == [user_pool_arn_1] + + aws_authorizer = safe_get(get_aws_authorizer) + assert aws_authorizer is not None, f"Authorizer {authorizer_id} not found in AWS API" + assert aws_authorizer["name"] == authorizer_name + assert aws_authorizer["type"] == "COGNITO_USER_POOLS" + assert aws_authorizer["identitySource"] == "method.request.header.Authorization" + assert aws_authorizer["providerARNs"] == [user_pool_arn_1] + + updated_authorizer_name = random_suffix_name("authorizer-updated", 32) + updates = { + "spec": { + "name": updated_authorizer_name, + "providerARNs": [user_pool_arn_1, user_pool_arn_2], + }, + } + k8s.patch_custom_resource(authorizer_ref, updates) + time.sleep(DEFAULT_WAIT_SECS * 2) + + # Verify updated state + updated_cr = k8s.get_resource(authorizer_ref) + assert updated_cr["spec"].get("name") == updated_authorizer_name + provider_arns_cr = updated_cr["spec"].get("providerARNs", []) + assert len(provider_arns_cr) == 2 + assert user_pool_arn_1 in provider_arns_cr + assert user_pool_arn_2 in provider_arns_cr + + aws_authorizer_updated = safe_get(get_aws_authorizer) + assert aws_authorizer_updated is not None, f"Updated Authorizer {authorizer_id} not found in AWS API" + assert aws_authorizer_updated["name"] == updated_authorizer_name + provider_arns_aws = aws_authorizer_updated.get("providerARNs", []) + assert len(provider_arns_aws) == 2 + assert user_pool_arn_1 in provider_arns_aws + assert user_pool_arn_2 in provider_arns_aws From 66e3b5f6f74e06a398dc0be85048a18a7bb0c029 Mon Sep 17 00:00:00 2001 From: arush sharma Date: Mon, 28 Apr 2025 12:40:55 -0700 Subject: [PATCH 2/3] move cognita userpool to test-infra bootstrap --- test/e2e/bootstrap_resources.py | 3 +++ test/e2e/requirements.txt | 2 +- test/e2e/service_bootstrap.py | 13 +++++++++++-- test/e2e/tests/authorizer_test.py | 20 ++++++-------------- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/test/e2e/bootstrap_resources.py b/test/e2e/bootstrap_resources.py index 16e0451..66e447b 100644 --- a/test/e2e/bootstrap_resources.py +++ b/test/e2e/bootstrap_resources.py @@ -19,11 +19,14 @@ from acktest.bootstrapping import Resources from e2e import bootstrap_directory from acktest.bootstrapping.elbv2 import NetworkLoadBalancer +from acktest.bootstrapping.cognito_identity import UserPool @dataclass class BootstrapResources(Resources): NetworkLoadBalancer: NetworkLoadBalancer + AuthorizerUserPool1: UserPool + AuthorizerUserPool2: UserPool _bootstrap_resources = None diff --git a/test/e2e/requirements.txt b/test/e2e/requirements.txt index 2df0111..11f5a53 100644 --- a/test/e2e/requirements.txt +++ b/test/e2e/requirements.txt @@ -1 +1 @@ -acktest @ git+https://github.com/aws-controllers-k8s/test-infra.git@3aedc6b0bf8bbcfdaf0bddb322d5c6adf04c329a +acktest @ git+https://github.com/aws-controllers-k8s/test-infra.git@55a6fdba4a637f0702cf164035f03c16c8d6b884 \ No newline at end of file diff --git a/test/e2e/service_bootstrap.py b/test/e2e/service_bootstrap.py index 0734611..410f6bf 100644 --- a/test/e2e/service_bootstrap.py +++ b/test/e2e/service_bootstrap.py @@ -4,7 +4,7 @@ # not use this file except in compliance with the License. A copy of the # License is located at # -# http://aws.amazon.com/apache2.0/ +# 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 @@ -13,19 +13,27 @@ """Bootstraps the resources required to run the API Gateway integration tests. """ import logging +import os from acktest.bootstrapping import Resources, BootstrapFailureException from e2e import bootstrap_directory from e2e.bootstrap_resources import BootstrapResources from acktest.bootstrapping.elbv2 import NetworkLoadBalancer +from acktest.bootstrapping.cognito_identity import UserPool def service_bootstrap() -> Resources: logging.getLogger().setLevel(logging.INFO) + user_pool_1 = UserPool(name_prefix="ack-apigw-auth-pool-1") + user_pool_2 = UserPool(name_prefix="ack-apigw-auth-pool-2") + resources = BootstrapResources( - NetworkLoadBalancer=NetworkLoadBalancer(name_prefix='vpc-link-test', scheme='internal') + NetworkLoadBalancer=NetworkLoadBalancer( + name_prefix='vpc-link-test', scheme='internal'), + AuthorizerUserPool1=user_pool_1, + AuthorizerUserPool2=user_pool_2, ) try: @@ -35,6 +43,7 @@ def service_bootstrap() -> Resources: return resources + if __name__ == "__main__": config = service_bootstrap() # Write config to current directory by default diff --git a/test/e2e/tests/authorizer_test.py b/test/e2e/tests/authorizer_test.py index bef6496..6d1d211 100644 --- a/test/e2e/tests/authorizer_test.py +++ b/test/e2e/tests/authorizer_test.py @@ -22,6 +22,7 @@ from acktest.resources import random_suffix_name from acktest.k8s import resource as k8s +from e2e.bootstrap_resources import get_bootstrap_resources from e2e import service_marker, CRD_GROUP, CRD_VERSION, load_apigateway_resource from e2e.replacement_values import REPLACEMENT_VALUES from .rest_api_test import simple_rest_api @@ -32,24 +33,18 @@ @pytest.fixture(scope="function") -def authorizer_test_resources(cognito_client, simple_rest_api): +def authorizer_test_resources(simple_rest_api): _, rest_api_cr = simple_rest_api rest_api_id = rest_api_cr["status"].get("id") assert rest_api_id is not None - user_pool_name_1 = random_suffix_name("user-pool-1", 32) - user_pool_name_2 = random_suffix_name("user-pool-2", 32) - authorizer_name = random_suffix_name("authorizer", 32) + bootstrap_resources = get_bootstrap_resources() - pool1_resp = cognito_client.create_user_pool(PoolName=user_pool_name_1) - user_pool_id_1 = pool1_resp['UserPool']['Id'] - user_pool_arn_1 = pool1_resp['UserPool']['Arn'] + user_pool_arn_1 = bootstrap_resources.AuthorizerUserPool1.user_pool_arn + user_pool_arn_2 = bootstrap_resources.AuthorizerUserPool2.user_pool_arn - pool2_resp = cognito_client.create_user_pool(PoolName=user_pool_name_2) - user_pool_id_2 = pool2_resp['UserPool']['Id'] - user_pool_arn_2 = pool2_resp['UserPool']['Arn'] + authorizer_name = random_suffix_name("authorizer", 32) - # Create Authorizer CR using prerequisites replacements_authorizer = REPLACEMENT_VALUES.copy() replacements_authorizer["AUTHORIZER_NAME"] = authorizer_name replacements_authorizer["REST_API_ID"] = rest_api_id @@ -75,9 +70,6 @@ def authorizer_test_resources(cognito_client, simple_rest_api): authorizer_ref, wait_periods=3, period_length=DEFAULT_WAIT_SECS) assert deleted_auth - cognito_client.delete_user_pool(UserPoolId=user_pool_id_1) - cognito_client.delete_user_pool(UserPoolId=user_pool_id_2) - @service_marker @pytest.mark.canary From 976d655260c7629baddb56edae38e8da0fdc64c1 Mon Sep 17 00:00:00 2001 From: arush sharma Date: Mon, 28 Apr 2025 15:12:18 -0700 Subject: [PATCH 3/3] combine remove and remove with value --- pkg/resource/api_key/hooks.go | 2 +- pkg/resource/api_method_response/hooks.go | 2 +- pkg/resource/authorizer/hooks.go | 2 +- pkg/resource/stage/hooks.go | 2 +- pkg/util/patch/patch.go | 24 +++++++++-------------- pkg/util/patch/patch_test.go | 6 +++--- test/e2e/conftest.py | 6 +----- test/e2e/service_bootstrap.py | 3 +-- 8 files changed, 18 insertions(+), 29 deletions(-) diff --git a/pkg/resource/api_key/hooks.go b/pkg/resource/api_key/hooks.go index 27dee21..046d0b1 100644 --- a/pkg/resource/api_key/hooks.go +++ b/pkg/resource/api_key/hooks.go @@ -96,7 +96,7 @@ func updateStageKeyPatches(patchSet *patch.Set, latest, desired []*svcapitypes.S key := fmt.Sprintf("%s/%s", *sk.RestAPIID, *sk.StageName) if !desiredMap[key] { encodedKey := strings.Replace(key, "/", "~1", -1) - patchSet.Remove(fmt.Sprintf("/stages/%s", encodedKey)) + patchSet.Remove(fmt.Sprintf("/stages/%s", encodedKey), nil) } } } diff --git a/pkg/resource/api_method_response/hooks.go b/pkg/resource/api_method_response/hooks.go index a172777..e173c5d 100644 --- a/pkg/resource/api_method_response/hooks.go +++ b/pkg/resource/api_method_response/hooks.go @@ -36,7 +36,7 @@ func updateMethodResponseInput(desired, latest *resource, input *svcsdk.UpdateMe // Handle boolean map patching for k := range latestSpec.ResponseParameters { if _, ok := desiredSpec.ResponseParameters[k]; !ok { - patchSet.Remove(fmt.Sprintf("/responseParameters/%s", patchKeyEncoder.Replace(k))) + patchSet.Remove(fmt.Sprintf("/responseParameters/%s", patchKeyEncoder.Replace(k)), nil) } } for k, v := range desiredSpec.ResponseParameters { diff --git a/pkg/resource/authorizer/hooks.go b/pkg/resource/authorizer/hooks.go index 0fa4335..10407cb 100644 --- a/pkg/resource/authorizer/hooks.go +++ b/pkg/resource/authorizer/hooks.go @@ -101,7 +101,7 @@ func updateProviderARNsPatches( for arn := range latestSet { if !desiredSet[arn] { // Use RemoveWithValue to generate: op=remove, path=/providerARNs, value=arn - patchSet.RemoveWithValue("/providerARNs", aws.String(arn)) + patchSet.Remove("/providerARNs", aws.String(arn)) } } diff --git a/pkg/resource/stage/hooks.go b/pkg/resource/stage/hooks.go index b09fb6c..971983a 100644 --- a/pkg/resource/stage/hooks.go +++ b/pkg/resource/stage/hooks.go @@ -78,7 +78,7 @@ func updateCanarySettings(delta *compare.Delta, desiredSpec, latestSpec svcapity const rootKey = "/canarySettings" canary := desiredSpec.CanarySettings if canary == nil { - patchSet.Remove(rootKey) + patchSet.Remove(rootKey, nil) return } diff --git a/pkg/util/patch/patch.go b/pkg/util/patch/patch.go index ffde3ec..6a4a61c 100644 --- a/pkg/util/patch/patch.go +++ b/pkg/util/patch/patch.go @@ -87,23 +87,17 @@ func (p *Set) Add(path string, desiredVal *string) { } // Remove adds a patch operation to this set for removing the specified path. -func (p *Set) Remove(path string) { - p.patchOps = append(p.patchOps, &apigatewaytypes.PatchOperation{ +// If valueToRemove is provided (not nil), it adds the value to the operation, +// generating op=remove, path=, value=. +func (p *Set) Remove(path string, valueToRemove *string) { + op := &apigatewaytypes.PatchOperation{ Op: apigatewaytypes.OpRemove, Path: aws.String(path), - }) -} - -// RemoveWithValue adds a patch operation to this set for removing a specific value -// from a list identified by the path. -// This generates op=remove, path=, value=. -// Useful for APIs like API Gateway UpdateAuthorizer for providerARNs. -func (p *Set) RemoveWithValue(path string, valueToRemove *string) { - p.patchOps = append(p.patchOps, &apigatewaytypes.PatchOperation{ - Op: apigatewaytypes.OpRemove, - Path: aws.String(path), - Value: valueToRemove, - }) + } + if valueToRemove != nil { + op.Value = valueToRemove + } + p.patchOps = append(p.patchOps, op) } // GetPatchOperations returns the patch operations applied to this set. diff --git a/pkg/util/patch/patch_test.go b/pkg/util/patch/patch_test.go index 5883f6b..3d23125 100644 --- a/pkg/util/patch/patch_test.go +++ b/pkg/util/patch/patch_test.go @@ -30,7 +30,7 @@ func TestPatchOperations(t *testing.T) { "k1": aws.String("v1"), "k3": aws.String("v3"), }, false) - patchSet.Remove("/removed") + patchSet.Remove("/removed", nil) }, expectedPatchOps: []apigatewaytypes.PatchOperation{ { @@ -84,7 +84,7 @@ func TestPatchOperations(t *testing.T) { "k1~": aws.String("v1~/"), "k3~/": aws.String("v3~/"), }, false) - patchSet.Remove("/removed") + patchSet.Remove("/removed", nil) }, expectedPatchOps: []apigatewaytypes.PatchOperation{ { @@ -138,7 +138,7 @@ func TestPatchOperations(t *testing.T) { "k1": aws.String("v1"), "k3": aws.String("v3"), }, true) - patchSet.Remove("/removed") + patchSet.Remove("/removed", nil) }, expectedPatchOps: []apigatewaytypes.PatchOperation{ { diff --git a/test/e2e/conftest.py b/test/e2e/conftest.py index a44d227..30b3ca5 100644 --- a/test/e2e/conftest.py +++ b/test/e2e/conftest.py @@ -42,8 +42,4 @@ def k8s_client(): @pytest.fixture(scope='module') def apigateway_client(): - return boto3.client('apigateway') - -@pytest.fixture(scope='module') -def cognito_client(): - return boto3.client('cognito-idp') + return boto3.client('apigateway') \ No newline at end of file diff --git a/test/e2e/service_bootstrap.py b/test/e2e/service_bootstrap.py index 410f6bf..8dae698 100644 --- a/test/e2e/service_bootstrap.py +++ b/test/e2e/service_bootstrap.py @@ -4,7 +4,7 @@ # not use this file except in compliance with the License. A copy of the # License is located at # -# http://aws.amazon.com/apache2.0/ +# 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 @@ -13,7 +13,6 @@ """Bootstraps the resources required to run the API Gateway integration tests. """ import logging -import os from acktest.bootstrapping import Resources, BootstrapFailureException