diff --git a/aws-redshiftserverless-endpointaccess/.gitignore b/aws-redshiftserverless-endpointaccess/.gitignore new file mode 100644 index 0000000..faa9259 --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/.gitignore @@ -0,0 +1,23 @@ +# macOS +.DS_Store +._* + +# Maven outputs +.classpath + +# IntelliJ +*.iml +.idea +out.java +out/ +.settings +.project + +# auto-generated files +target/ + +# our logs +rpdk.log* + +# contains credentials +sam-tests/ diff --git a/aws-redshiftserverless-endpointaccess/.rpdk-config b/aws-redshiftserverless-endpointaccess/.rpdk-config new file mode 100644 index 0000000..3ac602a --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/.rpdk-config @@ -0,0 +1,28 @@ +{ + "artifact_type": "RESOURCE", + "typeName": "AWS::RedshiftServerless::EndpointAccess", + "language": "java", + "runtime": "java8", + "entrypoint": "software.amazon.redshiftserverless.endpointaccess.HandlerWrapper::handleRequest", + "testEntrypoint": "software.amazon.redshiftserverless.endpointaccess.HandlerWrapper::testEntrypoint", + "settings": { + "version": false, + "subparser_name": null, + "verbose": 0, + "force": false, + "type_name": null, + "artifact_type": null, + "endpoint_url": null, + "region": null, + "target_schemas": [], + "namespace": [ + "software", + "amazon", + "redshiftserverless", + "endpointaccess" + ], + "codegen_template_path": "guided_aws", + "protocolVersion": "2.0.0" + }, + "executableEntrypoint": "software.amazon.redshiftserverless.endpointaccess.HandlerWrapperExecutable" +} diff --git a/aws-redshiftserverless-endpointaccess/README.md b/aws-redshiftserverless-endpointaccess/README.md new file mode 100644 index 0000000..df260a2 --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/README.md @@ -0,0 +1,12 @@ +# AWS::RedshiftServerless::EndpointAccess + +Congratulations on starting development! Next steps: + +1. Write the JSON schema describing your resource, `aws-redshiftserverless-endpointaccess.json` +1. Implement your resource handlers. + +The RPDK will automatically generate the correct resource model from the schema whenever the project is built via Maven. You can also do this manually with the following command: `cfn generate`. + +> Please don't modify files under `target/generated-sources/rpdk`, as they will be automatically overwritten. + +The code uses [Lombok](https://projectlombok.org/), and [you may have to install IDE integrations](https://projectlombok.org/setup/overview) to enable auto-complete for Lombok-annotated classes. diff --git a/aws-redshiftserverless-endpointaccess/aws-redshiftserverless-endpointaccess.json b/aws-redshiftserverless-endpointaccess/aws-redshiftserverless-endpointaccess.json new file mode 100644 index 0000000..f126f7c --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/aws-redshiftserverless-endpointaccess.json @@ -0,0 +1,221 @@ +{ + "typeName": "AWS::RedshiftServerless::EndpointAccess", + "description": "Resource schema for a Redshift Serverless managed VPC endpoint.", + "sourceUrl": "https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-redshift-serverless", + "definitions": { + "VpcSecurityGroupMembership": { + "description": "Describes the members of a VPC security group associated with the workgroup.", + "type": "object", + "properties": { + "VpcSecurityGroupId": { + "type": "string", + "description": "The identifier of the VPC security group." + }, + "Status": { + "type": "string", + "description": "The status of the VPC security group." + } + }, + "additionalProperties": false + }, + "NetworkInterface": { + "description": "Describes a network interface.", + "type": "object", + "properties": { + "NetworkInterfaceId": { + "type": "string", + "description": "The network interface identifier." + }, + "SubnetId": { + "type": "string", + "description": "The subnet identifier." + }, + "PrivateIpAddress": { + "type": "string", + "description": "The IPv4 address of the network interface within the subnet." + }, + "AvailabilityZone": { + "type": "string", + "description": "The Availability Zone." + } + }, + "additionalProperties": false + } + }, + "properties": { + "EndpointName": { + "description": "The name of the endpoint.", + "type": "string", + "pattern": "^(?=^[a-z][a-z0-9]*(-[a-z0-9]+)*$).{1,30}$" + }, + "EndpointStatus": { + "description": "The status of the VPC endpoint.", + "type": "string" + }, + "WorkgroupName": { + "description": "The name of the workgroup.", + "type": "string", + "pattern": "^(?=^[a-z0-9-]+$).{3,64}$", + "maxLength": 64, + "minLength": 3 + }, + "EndpointCreateTime": { + "description": "The time (UTC) that the endpoint was created.", + "type": "string" + }, + "Port": { + "description": "The port number on which Amazon Redshift Serverless accepts incoming connections.", + "type": "integer" + }, + "Address": { + "description": "The DNS address of the endpoint.", + "type": "string" + }, + "SubnetIds": { + "description": "The unique identifier of subnets where Amazon Redshift Serverless choose to deploy the VPC endpoint.", + "type": "array", + "items": { + "type": "string", + "pattern": "^sg-[0-9a-fA-F]{8,}$", + "maxLength": 255, + "minLength": 0 + }, + "insertionOrder": false + }, + "VpcSecurityGroups": { + "description": "A list of Virtual Private Cloud (VPC) security groups to be associated with the endpoint.", + "type": "array", + "items": { + "$ref": "#/definitions/VpcSecurityGroupMembership" + }, + "insertionOrder": false + }, + "VpcEndpoint": { + "description": "The connection endpoint for connecting to Amazon Redshift Serverless.", + "type": "object", + "properties": { + "VpcEndpointId": { + "type": "string", + "description": "The connection endpoint ID for connecting to Amazon Redshift Serverless." + }, + "VpcId": { + "type": "string", + "description": "The VPC identifier that the endpoint is associated with." + }, + "NetworkInterfaces": { + "type": "array", + "description": "One or more network interfaces of the endpoint. Also known as an interface endpoint.", + "items": { + "$ref": "#/definitions/NetworkInterface" + }, + "insertionOrder": false + } + }, + "additionalProperties": false + }, + "EndpointArn": { + "description": "The Amazon Resource Name (ARN) of the VPC endpoint.", + "type": "string" + }, + "OwnerAccount": { + "description": "Account Id of the resource owner", + "type": "string" + }, + "VpcSecurityGroupIds": { + "description": "A list of VPC security group IDs to associate with the workgroup.", + "type": "array", + "insertionOrder": false, + "items": { + "type": "string", + "pattern": "^sg-[0-9a-fA-F]{8,}$", + "maxLength": 255, + "minLength": 0 + } + }, + "VpcId": { + "type": "string" + } + }, + "tagging": { + "taggable": false + }, + "additionalProperties": false, + "required": [ + "EndpointName" + ], + "primaryIdentifier": [ + "/properties/EndpointName" + ], + "createOnlyProperties": [ + "/properties/EndpointName" + ], + "readOnlyProperties": [ + "/properties/Address", + "/properties/EndpointStatus", + "/properties/EndpointCreateTime", + "/properties/Port", + "/properties/VpcSecurityGroups", + "/properties/VpcEndpoint", + "/properties/EndpointArn" + ], + "handlers": { + "create": { + "permissions": [ + "redshift-serverless:CreateEndpointAccess", + "ec2:CreateClientVpnEndpoint", + "ec2:DescribeVpcAttribute", + "ec2:DescribeSecurityGroups", + "ec2:DescribeAddresses", + "ec2:DescribeInternetGateways", + "ec2:DescribeSubnets" + ], + "timeoutInMinutes": 60 + }, + "read": { + "permissions": [ + "redshift-serverless:GetEndpointAccess", + "ec2:DescribeClientVpnEndpoints", + "ec2:DescribeVpcAttribute", + "ec2:DescribeSecurityGroups", + "ec2:DescribeAddresses", + "ec2:DescribeInternetGateways", + "ec2:DescribeSubnets" + ] + }, + "update": { + "permissions": [ + "redshift-serverless:UpdateEndpointAccess", + "ec2:ModifyClientVpnEndpoint", + "ec2:DescribeVpcAttribute", + "ec2:DescribeSecurityGroups", + "ec2:DescribeAddresses", + "ec2:DescribeInternetGateways", + "ec2:DescribeSubnets" + ], + "timeoutInMinutes": 60 + }, + "delete": { + "permissions": [ + "redshift-serverless:DeleteEndpointAccess", + "ec2:DeleteClientVpnEndpoint", + "ec2:DescribeVpcAttribute", + "ec2:DescribeSecurityGroups", + "ec2:DescribeAddresses", + "ec2:DescribeInternetGateways", + "ec2:DescribeSubnets" + ], + "timeoutInMinutes": 60 + }, + "list": { + "permissions": [ + "redshift-serverless:ListEndpointAccess", + "ec2:DescribeClientVpnEndpoints", + "ec2:DescribeVpcAttribute", + "ec2:DescribeSecurityGroups", + "ec2:DescribeAddresses", + "ec2:DescribeInternetGateways", + "ec2:DescribeSubnets" + ] + } + } +} \ No newline at end of file diff --git a/aws-redshiftserverless-endpointaccess/docs/README.md b/aws-redshiftserverless-endpointaccess/docs/README.md new file mode 100644 index 0000000..4746262 --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/docs/README.md @@ -0,0 +1,147 @@ +# AWS::RedshiftServerless::EndpointAccess + +Resource schema for a Redshift Serverless managed VPC endpoint. + +## Syntax + +To declare this entity in your AWS CloudFormation template, use the following syntax: + +### JSON + +
+{
+    "Type" : "AWS::RedshiftServerless::EndpointAccess",
+    "Properties" : {
+        "EndpointName" : String,
+        "WorkgroupName" : String,
+        "SubnetIds" : [ String, ... ],
+        "OwnerAccount" : String,
+        "VpcSecurityGroupIds" : [ String, ... ],
+        "VpcId" : String
+    }
+}
+
+ +### YAML + +
+Type: AWS::RedshiftServerless::EndpointAccess
+Properties:
+    EndpointName: String
+    WorkgroupName: String
+    SubnetIds: 
+      - String
+    OwnerAccount: String
+    VpcSecurityGroupIds: 
+      - String
+    VpcId: String
+
+ +## Properties + +#### EndpointName + +The name of the endpoint. + +_Required_: Yes + +_Type_: String + +_Pattern_: ^(?=^[a-z][a-z0-9]*(-[a-z0-9]+)*$).{1,30}$ + +_Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement) + +#### WorkgroupName + +The name of the workgroup. + +_Required_: No + +_Type_: String + +_Minimum Length_: 3 + +_Maximum Length_: 64 + +_Pattern_: ^(?=^[a-z0-9-]+$).{3,64}$ + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### SubnetIds + +The unique identifier of subnets where Amazon Redshift Serverless choose to deploy the VPC endpoint. + +_Required_: No + +_Type_: List of String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### OwnerAccount + +Account Id of the resource owner + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### VpcSecurityGroupIds + +A list of VPC security group IDs to associate with the workgroup. + +_Required_: No + +_Type_: List of String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### VpcId + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +## Return Values + +### Ref + +When you pass the logical ID of this resource to the intrinsic `Ref` function, Ref returns the EndpointName. + +### Fn::GetAtt + +The `Fn::GetAtt` intrinsic function returns a value for a specified attribute of this type. The following are the available attributes and sample return values. + +For more information about using the `Fn::GetAtt` intrinsic function, see [Fn::GetAtt](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html). + +#### Address + +The DNS address of the endpoint. + +#### EndpointStatus + +The status of the VPC endpoint. + +#### EndpointCreateTime + +The time (UTC) that the endpoint was created. + +#### Port + +The port number on which Amazon Redshift Serverless accepts incoming connections. + +#### VpcSecurityGroups + +A list of Virtual Private Cloud (VPC) security groups to be associated with the endpoint. + +#### VpcEndpoint + +The connection endpoint for connecting to Amazon Redshift Serverless. + +#### EndpointArn + +The Amazon Resource Name (ARN) of the VPC endpoint. + diff --git a/aws-redshiftserverless-endpointaccess/docs/networkinterface.md b/aws-redshiftserverless-endpointaccess/docs/networkinterface.md new file mode 100644 index 0000000..324d6e7 --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/docs/networkinterface.md @@ -0,0 +1,70 @@ +# AWS::RedshiftServerless::EndpointAccess NetworkInterface + +Describes a network interface. + +## Syntax + +To declare this entity in your AWS CloudFormation template, use the following syntax: + +### JSON + +
+{
+    "NetworkInterfaceId" : String,
+    "SubnetId" : String,
+    "PrivateIpAddress" : String,
+    "AvailabilityZone" : String
+}
+
+ +### YAML + +
+NetworkInterfaceId: String
+SubnetId: String
+PrivateIpAddress: String
+AvailabilityZone: String
+
+ +## Properties + +#### NetworkInterfaceId + +The network interface identifier. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### SubnetId + +The subnet identifier. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### PrivateIpAddress + +The IPv4 address of the network interface within the subnet. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### AvailabilityZone + +The Availability Zone. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + diff --git a/aws-redshiftserverless-endpointaccess/docs/vpcendpoint.md b/aws-redshiftserverless-endpointaccess/docs/vpcendpoint.md new file mode 100644 index 0000000..3077eac --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/docs/vpcendpoint.md @@ -0,0 +1,59 @@ +# AWS::RedshiftServerless::EndpointAccess VpcEndpoint + +The connection endpoint for connecting to Amazon Redshift Serverless. + +## Syntax + +To declare this entity in your AWS CloudFormation template, use the following syntax: + +### JSON + +
+{
+    "VpcEndpointId" : String,
+    "VpcId" : String,
+    "NetworkInterfaces" : [ NetworkInterface, ... ]
+}
+
+ +### YAML + +
+VpcEndpointId: String
+VpcId: String
+NetworkInterfaces: 
+      - NetworkInterface
+
+ +## Properties + +#### VpcEndpointId + +The connection endpoint ID for connecting to Amazon Redshift Serverless. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### VpcId + +The VPC identifier that the endpoint is associated with. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### NetworkInterfaces + +One or more network interfaces of the endpoint. Also known as an interface endpoint. + +_Required_: No + +_Type_: List of NetworkInterface + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + diff --git a/aws-redshiftserverless-endpointaccess/docs/vpcsecuritygroupmembership.md b/aws-redshiftserverless-endpointaccess/docs/vpcsecuritygroupmembership.md new file mode 100644 index 0000000..642eb95 --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/docs/vpcsecuritygroupmembership.md @@ -0,0 +1,46 @@ +# AWS::RedshiftServerless::EndpointAccess VpcSecurityGroupMembership + +Describes the members of a VPC security group associated with the workgroup. + +## Syntax + +To declare this entity in your AWS CloudFormation template, use the following syntax: + +### JSON + +
+{
+    "VpcSecurityGroupId" : String,
+    "Status" : String
+}
+
+ +### YAML + +
+VpcSecurityGroupId: String
+Status: String
+
+ +## Properties + +#### VpcSecurityGroupId + +The identifier of the VPC security group. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### Status + +The status of the VPC security group. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + diff --git a/aws-redshiftserverless-endpointaccess/example_inputs/inputs_1_create.json b/aws-redshiftserverless-endpointaccess/example_inputs/inputs_1_create.json new file mode 100644 index 0000000..46ed7da --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/example_inputs/inputs_1_create.json @@ -0,0 +1,12 @@ +{ + "TPSCode": "...", + "Title": "...", + "CoverSheetIncluded": "...", + "DueDate": "...", + "ApprovalDate": "...", + "Memo": "...", + "SecondCopyOfMemo": "...", + "TestCode": "...", + "Authors": "...", + "Tags": "..." +} diff --git a/aws-redshiftserverless-endpointaccess/example_inputs/inputs_1_invalid.json b/aws-redshiftserverless-endpointaccess/example_inputs/inputs_1_invalid.json new file mode 100644 index 0000000..46ed7da --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/example_inputs/inputs_1_invalid.json @@ -0,0 +1,12 @@ +{ + "TPSCode": "...", + "Title": "...", + "CoverSheetIncluded": "...", + "DueDate": "...", + "ApprovalDate": "...", + "Memo": "...", + "SecondCopyOfMemo": "...", + "TestCode": "...", + "Authors": "...", + "Tags": "..." +} diff --git a/aws-redshiftserverless-endpointaccess/example_inputs/inputs_1_update.json b/aws-redshiftserverless-endpointaccess/example_inputs/inputs_1_update.json new file mode 100644 index 0000000..46ed7da --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/example_inputs/inputs_1_update.json @@ -0,0 +1,12 @@ +{ + "TPSCode": "...", + "Title": "...", + "CoverSheetIncluded": "...", + "DueDate": "...", + "ApprovalDate": "...", + "Memo": "...", + "SecondCopyOfMemo": "...", + "TestCode": "...", + "Authors": "...", + "Tags": "..." +} diff --git a/aws-redshiftserverless-endpointaccess/lombok.config b/aws-redshiftserverless-endpointaccess/lombok.config new file mode 100644 index 0000000..7a21e88 --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/lombok.config @@ -0,0 +1 @@ +lombok.addLombokGeneratedAnnotation = true diff --git a/aws-redshiftserverless-endpointaccess/overrides.json b/aws-redshiftserverless-endpointaccess/overrides.json new file mode 100644 index 0000000..9cc4668 --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/overrides.json @@ -0,0 +1,11 @@ +{ + "CREATE": { + "/OwnerAccount": null, + "/VpcSecurityGroupIds": null, + "/SubnetIds": null, + "/WorkgroupName": null + }, + "UPDATE": { + "/VpcSecurityGroupIds": null + } +} diff --git a/aws-redshiftserverless-endpointaccess/pom.xml b/aws-redshiftserverless-endpointaccess/pom.xml new file mode 100644 index 0000000..01e42c6 --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/pom.xml @@ -0,0 +1,261 @@ + + + 4.0.0 + + software.amazon.redshiftserverless.endpointaccess + aws-redshiftserverless-endpointaccess-handler + aws-redshiftserverless-endpointaccess-handler + 1.0-SNAPSHOT + jar + + + 1.8 + 1.8 + UTF-8 + UTF-8 + + + + + + + software.amazon.awssdk + redshiftserverless + 2.23.21 + + + + software.amazon.cloudformation + aws-cloudformation-rpdk-java-plugin + [2.0.0,3.0.0) + + + + org.projectlombok + lombok + 1.18.22 + provided + + + + org.apache.logging.log4j + log4j-api + 2.17.1 + + + + org.apache.logging.log4j + log4j-core + 2.17.1 + + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.13.3 + + + + + org.assertj + assertj-core + 3.12.2 + test + + + + org.junit.jupiter + junit-jupiter + 5.5.0-M1 + test + + + + org.mockito + mockito-core + 3.6.0 + test + + + + org.mockito + mockito-junit-jupiter + 3.6.0 + test + + + + com.google.code.gson + gson + 2.9.0 + + + + + + + software.amazon.awssdk + bom + 2.22.5 + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + -Xlint:all,-options,-processing + -Werror + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + false + + + *:* + + **/Log4j2Plugins.dat + + + + + + + package + + shade + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + + generate + generate-sources + + exec + + + cfn + generate ${cfn.generate.args} + ${project.basedir} + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.0.0 + + + add-source + generate-sources + + add-source + + + + ${project.basedir}/target/generated-sources/rpdk + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.4 + + + maven-surefire-plugin + 3.0.0-M3 + + + org.jacoco + jacoco-maven-plugin + 0.8.4 + + + **/BaseConfiguration* + **/BaseHandler* + **/HandlerWrapper* + **/ResourceModel* + + + + + + prepare-agent + + + + report + test + + report + + + + jacoco-check + + check + + + + + PACKAGE + + + BRANCH + COVEREDRATIO + 0.8 + + + INSTRUCTION + COVEREDRATIO + 0.8 + + + + + + + + + + + + ${project.basedir} + + aws-redshiftserverless-endpointaccess.json + + + + ${project.basedir}/target/loaded-target-schemas + + **/*.json + + + + + diff --git a/aws-redshiftserverless-endpointaccess/resource-role.yaml b/aws-redshiftserverless-endpointaccess/resource-role.yaml new file mode 100644 index 0000000..32b9580 --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/resource-role.yaml @@ -0,0 +1,51 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: > + This CloudFormation template creates a role assumed by CloudFormation + during CRUDL operations to mutate resources on behalf of the customer. + +Resources: + ExecutionRole: + Type: AWS::IAM::Role + Properties: + MaxSessionDuration: 8400 + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: resources.cloudformation.amazonaws.com + Action: sts:AssumeRole + Condition: + StringEquals: + aws:SourceAccount: + Ref: AWS::AccountId + StringLike: + aws:SourceArn: + Fn::Sub: arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:type/resource/AWS-RedshiftServerless-EndpointAccess/* + Path: "/" + Policies: + - PolicyName: ResourceTypePolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - "ec2:CreateClientVpnEndpoint" + - "ec2:DeleteClientVpnEndpoint" + - "ec2:DescribeAddresses" + - "ec2:DescribeClientVpnEndpoints" + - "ec2:DescribeInternetGateways" + - "ec2:DescribeSecurityGroups" + - "ec2:DescribeSubnets" + - "ec2:DescribeVpcAttribute" + - "ec2:ModifyClientVpnEndpoint" + - "redshift-serverless:CreateEndpointAccess" + - "redshift-serverless:DeleteEndpointAccess" + - "redshift-serverless:GetEndpointAccess" + - "redshift-serverless:ListEndpointAccess" + - "redshift-serverless:UpdateEndpointAccess" + Resource: "*" +Outputs: + ExecutionRoleArn: + Value: + Fn::GetAtt: ExecutionRole.Arn diff --git a/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/BaseHandlerStd.java b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/BaseHandlerStd.java new file mode 100644 index 0000000..5eef6aa --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/BaseHandlerStd.java @@ -0,0 +1,41 @@ +package software.amazon.redshiftserverless.endpointaccess; + +import software.amazon.awssdk.services.redshiftserverless.RedshiftServerlessClient; +import software.amazon.awssdk.services.redshiftserverless.model.RedshiftServerlessResponse; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.Logger; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; + +public abstract class BaseHandlerStd extends BaseHandler { + @Override + public final ProgressEvent handleRequest( + final AmazonWebServicesClientProxy proxy, + final ResourceHandlerRequest request, + final CallbackContext callbackContext, + final Logger logger) { + return handleRequest( + proxy, + request, + callbackContext != null ? callbackContext : new CallbackContext(), + proxy.newProxy(ClientBuilder::getClient), + logger + ); + } + + protected abstract ProgressEvent handleRequest( + final AmazonWebServicesClientProxy proxy, + final ResourceHandlerRequest request, + final CallbackContext callbackContext, + final ProxyClient proxyClient, + final Logger logger); + + public boolean isEndpointAccessRequest(final Object awsRequest, + final RedshiftServerlessResponse awsResponse, + final ProxyClient proxyClient, + final ResourceModel model, + final CallbackContext context) { + return true; + } +} diff --git a/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/CallbackContext.java b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/CallbackContext.java new file mode 100644 index 0000000..2019483 --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/CallbackContext.java @@ -0,0 +1,10 @@ +package software.amazon.redshiftserverless.endpointaccess; + +import software.amazon.cloudformation.proxy.StdCallbackContext; + +@lombok.Getter +@lombok.Setter +@lombok.ToString +@lombok.EqualsAndHashCode(callSuper = true) +public class CallbackContext extends StdCallbackContext { +} diff --git a/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/ClientBuilder.java b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/ClientBuilder.java new file mode 100644 index 0000000..3f47b34 --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/ClientBuilder.java @@ -0,0 +1,13 @@ +package software.amazon.redshiftserverless.endpointaccess; + +import software.amazon.awssdk.services.redshiftserverless.RedshiftServerlessClient; +import software.amazon.cloudformation.LambdaWrapper; + +public class ClientBuilder { + + public static RedshiftServerlessClient getClient() { + return RedshiftServerlessClient.builder() + .httpClient(LambdaWrapper.HTTP_CLIENT) + .build(); + } +} diff --git a/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/Configuration.java b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/Configuration.java new file mode 100644 index 0000000..716232a --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/Configuration.java @@ -0,0 +1,8 @@ +package software.amazon.redshiftserverless.endpointaccess; + +class Configuration extends BaseConfiguration { + + public Configuration() { + super("aws-redshiftserverless-endpointaccess.json"); + } +} diff --git a/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/CreateHandler.java b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/CreateHandler.java new file mode 100644 index 0000000..e5b172f --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/CreateHandler.java @@ -0,0 +1,87 @@ +package software.amazon.redshiftserverless.endpointaccess; + +import software.amazon.awssdk.awscore.AwsResponse; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.services.redshiftserverless.RedshiftServerlessClient; +import software.amazon.awssdk.services.redshiftserverless.model.AccessDeniedException; +import software.amazon.awssdk.services.redshiftserverless.model.ConflictException; +import software.amazon.awssdk.services.redshiftserverless.model.CreateEndpointAccessRequest; +import software.amazon.awssdk.services.redshiftserverless.model.CreateEndpointAccessResponse; +import software.amazon.awssdk.services.redshiftserverless.model.InsufficientCapacityException; +import software.amazon.awssdk.services.redshiftserverless.model.InternalServerException; +import software.amazon.awssdk.services.redshiftserverless.model.ResourceNotFoundException; +import software.amazon.awssdk.services.redshiftserverless.model.ServiceQuotaExceededException; +import software.amazon.awssdk.services.redshiftserverless.model.TooManyTagsException; +import software.amazon.awssdk.services.redshiftserverless.model.ValidationException; +import software.amazon.cloudformation.exceptions.CfnGeneralServiceException; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.HandlerErrorCode; +import software.amazon.cloudformation.proxy.Logger; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; + +import java.util.regex.Pattern; + + +public class CreateHandler extends BaseHandlerStd { + private Logger logger; + + protected ProgressEvent handleRequest( + final AmazonWebServicesClientProxy proxy, + final ResourceHandlerRequest request, + final CallbackContext callbackContext, + final ProxyClient proxyClient, + final Logger logger) { + + this.logger = logger; + final ResourceModel resourceModel = request.getDesiredResourceState(); + + return ProgressEvent.progress(resourceModel, callbackContext) + .then(progress -> + proxy.initiate("AWS-RedshiftServerless-EndpointAccess::Create", proxyClient, progress.getResourceModel(), progress.getCallbackContext()) + .translateToServiceRequest(Translator::translateToCreateEndpointAccessRequest) + .makeServiceCall(this::createEndpointAccess) + .stabilize(this::isEndpointAccessRequest) + .handleError(this::createEndpointAccessErrorHandler) + .done(awsResponse -> ProgressEvent.defaultSuccessHandler(Translator.translateFromEndpointAccessResponse(awsResponse.endpoint())))); + } + + private CreateEndpointAccessResponse createEndpointAccess(final CreateEndpointAccessRequest awsRequest, + final ProxyClient proxyClient) { + CreateEndpointAccessResponse awsResponse; + awsResponse = proxyClient.injectCredentialsAndInvokeV2(awsRequest, proxyClient.client()::createEndpointAccess); + + logger.log(String.format("%s successfully created.", ResourceModel.TYPE_NAME)); + return awsResponse; + } + + private ProgressEvent createEndpointAccessErrorHandler(final CreateEndpointAccessRequest awsRequest, + final Exception exception, + final ProxyClient client, + final ResourceModel model, + final CallbackContext context) { + if (exception instanceof ValidationException || + exception instanceof AccessDeniedException) { + return ProgressEvent.defaultFailureHandler(exception, HandlerErrorCode.InvalidRequest); + + } else if (exception instanceof ResourceNotFoundException) { + return ProgressEvent.defaultFailureHandler(exception, HandlerErrorCode.NotFound); + + } else if (exception instanceof InternalServerException) { + return ProgressEvent.defaultFailureHandler(exception, HandlerErrorCode.InternalFailure); + + } else if (exception instanceof ConflictException || + exception instanceof ServiceQuotaExceededException) { + Pattern pattern = Pattern.compile(".*already exists.*", Pattern.CASE_INSENSITIVE); + HandlerErrorCode handlerErrorCode = pattern.matcher(exception.getMessage()).matches() ? + HandlerErrorCode.AlreadyExists : + HandlerErrorCode.ResourceConflict; + + return ProgressEvent.defaultFailureHandler(exception, handlerErrorCode); + + } else { + return ProgressEvent.defaultFailureHandler(exception, HandlerErrorCode.GeneralServiceException); + } + } +} diff --git a/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/DeleteHandler.java b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/DeleteHandler.java new file mode 100644 index 0000000..6799150 --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/DeleteHandler.java @@ -0,0 +1,73 @@ +package software.amazon.redshiftserverless.endpointaccess; + +import software.amazon.awssdk.awscore.AwsResponse; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.services.redshiftserverless.RedshiftServerlessClient; +import software.amazon.awssdk.services.redshiftserverless.model.ConflictException; +import software.amazon.awssdk.services.redshiftserverless.model.DeleteEndpointAccessRequest; +import software.amazon.awssdk.services.redshiftserverless.model.DeleteEndpointAccessResponse; +import software.amazon.awssdk.services.redshiftserverless.model.InternalServerException; +import software.amazon.awssdk.services.redshiftserverless.model.ResourceNotFoundException; +import software.amazon.awssdk.services.redshiftserverless.model.ValidationException; +import software.amazon.cloudformation.exceptions.CfnGeneralServiceException; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.HandlerErrorCode; +import software.amazon.cloudformation.proxy.Logger; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; + +public class DeleteHandler extends BaseHandlerStd { + private Logger logger; + + protected ProgressEvent handleRequest( + final AmazonWebServicesClientProxy proxy, + final ResourceHandlerRequest request, + final CallbackContext callbackContext, + final ProxyClient proxyClient, + final Logger logger) { + + this.logger = logger; + final ResourceModel resourceModel = request.getDesiredResourceState(); + + return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext) + .then(progress -> + proxy.initiate("AWS-RedshiftServerless-EndpointAccess::Delete", proxyClient, progress.getResourceModel(), progress.getCallbackContext()) + .translateToServiceRequest(Translator::translateToDeleteEndpointAccessRequest) + .makeServiceCall(this::deleteEndpointAccess) + .stabilize(this::isEndpointAccessRequest) + .handleError(this::deleteEndpointAccessErrorHandler) + .done(awsResponse -> ProgressEvent.defaultSuccessHandler(Translator.translateFromEndpointAccessResponse(awsResponse.endpoint())))); + } + + private DeleteEndpointAccessResponse deleteEndpointAccess(final DeleteEndpointAccessRequest awsRequest, + final ProxyClient proxyClient) { + DeleteEndpointAccessResponse awsResponse; + awsResponse = proxyClient.injectCredentialsAndInvokeV2(awsRequest, proxyClient.client()::deleteEndpointAccess); + + logger.log(String.format("%s successfully deleted.", ResourceModel.TYPE_NAME)); + return awsResponse; + } + + private ProgressEvent deleteEndpointAccessErrorHandler(final DeleteEndpointAccessRequest awsRequest, + final Exception exception, + final ProxyClient client, + final ResourceModel model, + final CallbackContext context) { + if (exception instanceof ResourceNotFoundException) { + return ProgressEvent.defaultFailureHandler(exception, HandlerErrorCode.NotFound); + + } else if (exception instanceof InternalServerException) { + return ProgressEvent.defaultFailureHandler(exception, HandlerErrorCode.InternalFailure); + + } else if (exception instanceof ConflictException) { + return ProgressEvent.defaultFailureHandler(exception, HandlerErrorCode.ResourceConflict); + + } else if (exception instanceof ValidationException) { + return ProgressEvent.defaultFailureHandler(exception, HandlerErrorCode.InvalidRequest); + + } else { + return ProgressEvent.defaultFailureHandler(exception, HandlerErrorCode.GeneralServiceException); + } + } +} diff --git a/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/ListHandler.java b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/ListHandler.java new file mode 100644 index 0000000..77f68d4 --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/ListHandler.java @@ -0,0 +1,73 @@ +package software.amazon.redshiftserverless.endpointaccess; + +import software.amazon.awssdk.awscore.AwsRequest; +import software.amazon.awssdk.awscore.AwsResponse; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.services.redshiftserverless.model.InternalServerException; +import software.amazon.awssdk.services.redshiftserverless.model.ConflictException; +import software.amazon.awssdk.services.redshiftserverless.model.ListEndpointAccessRequest; +import software.amazon.awssdk.services.redshiftserverless.model.ListEndpointAccessResponse; +import software.amazon.awssdk.services.redshiftserverless.model.ResourceNotFoundException; +import software.amazon.awssdk.services.redshiftserverless.model.ValidationException; +import software.amazon.cloudformation.exceptions.CfnGeneralServiceException; +import software.amazon.cloudformation.exceptions.CfnInternalFailureException; +import software.amazon.cloudformation.exceptions.CfnInvalidRequestException; +import software.amazon.cloudformation.exceptions.CfnResourceConflictException; +import software.amazon.cloudformation.exceptions.CfnNotFoundException; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.Logger; +import software.amazon.cloudformation.proxy.OperationStatus; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; + +import java.util.ArrayList; +import java.util.List; + +public class ListHandler extends BaseHandler { + private Logger logger; + + @Override + public ProgressEvent handleRequest( + final AmazonWebServicesClientProxy proxy, + final ResourceHandlerRequest request, + final CallbackContext callbackContext, + final Logger logger) { + + this.logger = logger; + final ResourceModel resourceModel = request.getDesiredResourceState(); + + ListEndpointAccessRequest awsRequest = Translator.translateToListEndpointAccessRequest(resourceModel, request.getNextToken()); + ListEndpointAccessResponse awsResponse = listEndpointAccess(awsRequest, proxy); + List models = Translator.translateFromListEndpointAccessResponse(awsResponse); + + return ProgressEvent.builder() + .resourceModels(models) + .nextToken(awsResponse.nextToken()) + .status(OperationStatus.SUCCESS) + .build(); + } + + private ListEndpointAccessResponse listEndpointAccess(final ListEndpointAccessRequest awsRequest, + final AmazonWebServicesClientProxy proxy) { + ListEndpointAccessResponse awsResponse; + try { + awsResponse = proxy.injectCredentialsAndInvokeV2(awsRequest, ClientBuilder.getClient()::listEndpointAccess); + + } catch (final ValidationException e) { + throw new CfnInvalidRequestException(e); + + } catch (final InternalServerException e) { + throw new CfnInternalFailureException(e); + + } catch(final ConflictException e) { + throw new CfnResourceConflictException(e); + + } catch(final ResourceNotFoundException e) { + throw new CfnNotFoundException(e); + + } + + logger.log(String.format("%s has successfully been listed.", ResourceModel.TYPE_NAME)); + return awsResponse; + } +} diff --git a/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/ReadHandler.java b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/ReadHandler.java new file mode 100644 index 0000000..935c01c --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/ReadHandler.java @@ -0,0 +1,70 @@ +package software.amazon.redshiftserverless.endpointaccess; + +import software.amazon.awssdk.awscore.AwsResponse; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.services.redshiftserverless.RedshiftServerlessClient; +import software.amazon.awssdk.services.redshiftserverless.model.GetEndpointAccessRequest; +import software.amazon.awssdk.services.redshiftserverless.model.GetEndpointAccessResponse; +import software.amazon.awssdk.services.redshiftserverless.model.InternalServerException; +import software.amazon.awssdk.services.redshiftserverless.model.ResourceNotFoundException; +import software.amazon.awssdk.services.redshiftserverless.model.ValidationException; +import software.amazon.cloudformation.exceptions.CfnGeneralServiceException; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.HandlerErrorCode; +import software.amazon.cloudformation.proxy.Logger; +import software.amazon.cloudformation.proxy.OperationStatus; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; + +public class ReadHandler extends BaseHandlerStd { + private Logger logger; + + protected ProgressEvent handleRequest( + final AmazonWebServicesClientProxy proxy, + final ResourceHandlerRequest request, + final CallbackContext callbackContext, + final ProxyClient proxyClient, + final Logger logger) { + + this.logger = logger; + final ResourceModel model = request.getDesiredResourceState(); + + return ProgressEvent.progress(model, callbackContext) + .then(progress -> + proxy.initiate("AWS-RedshiftServerless-EndpointAccess::Read", proxyClient, progress.getResourceModel(), progress.getCallbackContext()) + .translateToServiceRequest(Translator::translateToGetEndpointAccessRequest) + .makeServiceCall(this::getEndpointAccess) + .stabilize(this::isEndpointAccessRequest) + .handleError(this::getEndpointAccessErrorHandler) + .done(awsResponse -> ProgressEvent.defaultSuccessHandler(Translator.translateFromEndpointAccessResponse(awsResponse.endpoint())))); + } + + private GetEndpointAccessResponse getEndpointAccess(final GetEndpointAccessRequest awsRequest, + final ProxyClient proxyClient) { + GetEndpointAccessResponse awsResponse; + awsResponse = proxyClient.injectCredentialsAndInvokeV2(awsRequest, proxyClient.client()::getEndpointAccess); + + logger.log(String.format("%s has successfully been read.", ResourceModel.TYPE_NAME)); + return awsResponse; + } + + private ProgressEvent getEndpointAccessErrorHandler(final GetEndpointAccessRequest awsRequest, + final Exception exception, + final ProxyClient client, + final ResourceModel model, + final CallbackContext context) { + if (exception instanceof ResourceNotFoundException) { + return ProgressEvent.defaultFailureHandler(exception, HandlerErrorCode.NotFound); + + } else if (exception instanceof ValidationException) { + return ProgressEvent.defaultFailureHandler(exception, HandlerErrorCode.InvalidRequest); + + } else if (exception instanceof InternalServerException) { + return ProgressEvent.defaultFailureHandler(exception, HandlerErrorCode.InternalFailure); + + } else { + return ProgressEvent.defaultFailureHandler(exception, HandlerErrorCode.GeneralServiceException); + } + } +} diff --git a/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/Translator.java b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/Translator.java new file mode 100644 index 0000000..3dd449c --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/Translator.java @@ -0,0 +1,147 @@ +package software.amazon.redshiftserverless.endpointaccess; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import software.amazon.awssdk.awscore.AwsRequest; +import software.amazon.awssdk.awscore.AwsResponse; +import software.amazon.awssdk.services.redshiftserverless.model.CreateEndpointAccessRequest; +import software.amazon.awssdk.services.redshiftserverless.model.DeleteEndpointAccessRequest; +import software.amazon.awssdk.services.redshiftserverless.model.EndpointAccess; +import software.amazon.awssdk.services.redshiftserverless.model.GetEndpointAccessRequest; +import software.amazon.awssdk.services.redshiftserverless.model.ListEndpointAccessRequest; +import software.amazon.awssdk.services.redshiftserverless.model.ListEndpointAccessResponse; +import software.amazon.awssdk.services.redshiftserverless.model.UpdateEndpointAccessRequest; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * This class is a centralized placeholder for + * - api request construction + * - object translation to/from aws sdk + * - resource model construction for read/list handlers + */ + +public class Translator { + private static final Gson GSON = new GsonBuilder().create(); + + /** + * Request to create endpoint access + * + * @param model resource model + * @return awsRequest the aws service request to create endpoint access + */ + static CreateEndpointAccessRequest translateToCreateEndpointAccessRequest(final ResourceModel model) { + return CreateEndpointAccessRequest.builder() + .endpointName(model.getEndpointName()) + .ownerAccount(model.getOwnerAccount()) + .vpcSecurityGroupIds(model.getVpcSecurityGroupIds()) + .subnetIds(model.getSubnetIds()) + .workgroupName(model.getWorkgroupName()) + .build(); + } + + /** + * Translates EndpointAccess resource object from sdk into a resource model + * + * @param endpointAccess the aws services resource response + * @return model resource model + */ + static ResourceModel translateFromEndpointAccessResponse(final EndpointAccess endpointAccess) { + return ResourceModel.builder() + .endpointName(endpointAccess.endpointName()) + .endpointStatus(endpointAccess.endpointStatus()) + .workgroupName(endpointAccess.workgroupName()) + .endpointCreateTime(endpointAccess.endpointCreateTime().toString()) + .port(endpointAccess.port()) + .address(endpointAccess.address()) + .subnetIds(endpointAccess.subnetIds()) + .vpcSecurityGroups(translateToModelVpcSecurityGroupMemberships(endpointAccess.vpcSecurityGroups())) + .vpcEndpoint(translateToModelVpcEndpoint(endpointAccess.vpcEndpoint())) + .endpointArn(endpointAccess.endpointArn()) + .build(); + } + + /** + * Request to get endpoint access + * + * @param model resource model + * @return awsRequest the aws service request to delete a resource + */ + static GetEndpointAccessRequest translateToGetEndpointAccessRequest(final ResourceModel model) { + return GetEndpointAccessRequest.builder() + .endpointName(model.getEndpointName()) + .build(); + } + + /** + * Request to delete endpoint access + * + * @param model resource model + * @return awsRequest the aws service request to delete a resource + */ + static DeleteEndpointAccessRequest translateToDeleteEndpointAccessRequest(final ResourceModel model) { + return DeleteEndpointAccessRequest.builder() + .endpointName(model.getEndpointName()) + .build(); + } + + /** + * Request to update properties of a previously created endpoint access + * + * @param model resource model + * @return awsRequest the aws service request to modify a resource + */ + static UpdateEndpointAccessRequest translateToUpdateEndpointAccessRequest(final ResourceModel model) { + return UpdateEndpointAccessRequest.builder() + .endpointName(model.getEndpointName()) + .vpcSecurityGroupIds(model.getVpcSecurityGroupIds()) + .build(); + } + + /** + * Request to list resources + * @param nextToken token passed to the aws service list resources request + * @return awsRequest the aws service request to list resources within aws account + */ + static ListEndpointAccessRequest translateToListEndpointAccessRequest(final ResourceModel resourceModel, final String nextToken) { + return ListEndpointAccessRequest.builder() + .workgroupName(resourceModel.getWorkgroupName()) + .ownerAccount(resourceModel.getOwnerAccount()) + .vpcId(resourceModel.getVpcId()) + .build(); + } + + /** + * Translates resource objects from sdk into a resource model (primary identifier only) + * + * @param awsResponse the aws service describe resource response + * @return list of resource models + */ + static List translateFromListEndpointAccessResponse(final ListEndpointAccessResponse awsResponse) { + return awsResponse.endpoints() + .stream() + .map(endpointAccess -> translateFromEndpointAccessResponse(endpointAccess)) + .collect(Collectors.toList()); + } + + + private static VpcEndpoint translateToModelVpcEndpoint(software.amazon.awssdk.services.redshiftserverless.model.VpcEndpoint vpcEndpoint) { + return GSON.fromJson(GSON.toJson(vpcEndpoint), VpcEndpoint.class); + } + + private static VpcSecurityGroupMembership translateToModelVpcSecurityGroupMembership(software.amazon.awssdk.services.redshiftserverless.model.VpcSecurityGroupMembership vpcSecurityGroupMembership) { + return GSON.fromJson(GSON.toJson(vpcSecurityGroupMembership), VpcSecurityGroupMembership.class); + } + + private static List translateToModelVpcSecurityGroupMemberships(Collection vpcSecurityGroupMemberships) { + return vpcSecurityGroupMemberships == null ? null : vpcSecurityGroupMemberships + .stream() + .map(Translator::translateToModelVpcSecurityGroupMembership) + .collect(Collectors.toList()); + } +} diff --git a/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/UpdateHandler.java b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/UpdateHandler.java new file mode 100644 index 0000000..a04102f --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/src/main/java/software/amazon/redshiftserverless/endpointaccess/UpdateHandler.java @@ -0,0 +1,78 @@ +package software.amazon.redshiftserverless.endpointaccess; + +import software.amazon.awssdk.awscore.AwsResponse; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.services.redshiftserverless.RedshiftServerlessClient; +import software.amazon.awssdk.services.redshiftserverless.model.AccessDeniedException; +import software.amazon.awssdk.services.redshiftserverless.model.ConflictException; +import software.amazon.awssdk.services.redshiftserverless.model.InsufficientCapacityException; +import software.amazon.awssdk.services.redshiftserverless.model.InternalServerException; +import software.amazon.awssdk.services.redshiftserverless.model.ResourceNotFoundException; +import software.amazon.awssdk.services.redshiftserverless.model.UpdateEndpointAccessRequest; +import software.amazon.awssdk.services.redshiftserverless.model.UpdateEndpointAccessResponse; +import software.amazon.awssdk.services.redshiftserverless.model.ValidationException; +import software.amazon.cloudformation.exceptions.CfnGeneralServiceException; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.HandlerErrorCode; +import software.amazon.cloudformation.proxy.Logger; +import software.amazon.cloudformation.proxy.OperationStatus; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; + +public class UpdateHandler extends BaseHandlerStd { + private Logger logger; + + protected ProgressEvent handleRequest( + final AmazonWebServicesClientProxy proxy, + final ResourceHandlerRequest request, + final CallbackContext callbackContext, + final ProxyClient proxyClient, + final Logger logger) { + + this.logger = logger; + final ResourceModel model = request.getDesiredResourceState(); + + return ProgressEvent.progress(model, callbackContext) + .then(progress -> + proxy.initiate("AWS-RedshiftServerless-EndpointAccess::Update", proxyClient, progress.getResourceModel(), progress.getCallbackContext()) + .translateToServiceRequest(Translator::translateToUpdateEndpointAccessRequest) + .makeServiceCall(this::updateEndpointAccess) + .stabilize(this::isEndpointAccessRequest) + .handleError(this::updateEndpointAccessErrorHandler) + .done(awsResponse -> ProgressEvent.defaultSuccessHandler(Translator.translateFromEndpointAccessResponse(awsResponse.endpoint())))); + } + + private UpdateEndpointAccessResponse updateEndpointAccess(final UpdateEndpointAccessRequest awsRequest, + final ProxyClient proxyClient) { + UpdateEndpointAccessResponse awsResponse; + awsResponse = proxyClient.injectCredentialsAndInvokeV2(awsRequest, proxyClient.client()::updateEndpointAccess); + + logger.log(String.format("%s has successfully been updated.", ResourceModel.TYPE_NAME)); + return awsResponse; + } + + private ProgressEvent updateEndpointAccessErrorHandler(final UpdateEndpointAccessRequest awsRequest, + final Exception exception, + final ProxyClient client, + final ResourceModel model, + final CallbackContext context) { + if (exception instanceof ResourceNotFoundException) { + return ProgressEvent.defaultFailureHandler(exception, HandlerErrorCode.NotFound); + + } else if (exception instanceof ValidationException || + exception instanceof AccessDeniedException) { + return ProgressEvent.defaultFailureHandler(exception, HandlerErrorCode.InvalidRequest); + + } else if (exception instanceof InternalServerException) { + return ProgressEvent.defaultFailureHandler(exception, HandlerErrorCode.InternalFailure); + + } else if (exception instanceof ConflictException || + exception instanceof InsufficientCapacityException) { + return ProgressEvent.defaultFailureHandler(exception, HandlerErrorCode.ResourceConflict); + + } else { + return ProgressEvent.defaultFailureHandler(exception, HandlerErrorCode.GeneralServiceException); + } + } +} diff --git a/aws-redshiftserverless-endpointaccess/src/resources/log4j2.xml b/aws-redshiftserverless-endpointaccess/src/resources/log4j2.xml new file mode 100644 index 0000000..5657daf --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/src/resources/log4j2.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/aws-redshiftserverless-endpointaccess/src/test/java/software/amazon/redshiftserverless/endpointaccess/AbstractTestBase.java b/aws-redshiftserverless-endpointaccess/src/test/java/software/amazon/redshiftserverless/endpointaccess/AbstractTestBase.java new file mode 100644 index 0000000..5a5cc4a --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/src/test/java/software/amazon/redshiftserverless/endpointaccess/AbstractTestBase.java @@ -0,0 +1,253 @@ +package software.amazon.redshiftserverless.endpointaccess; + +import java.time.Instant; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +import software.amazon.awssdk.awscore.AwsRequest; +import software.amazon.awssdk.awscore.AwsResponse; +import software.amazon.awssdk.services.redshiftserverless.RedshiftServerlessClient; +import software.amazon.awssdk.core.ResponseBytes; +import software.amazon.awssdk.core.ResponseInputStream; +import software.amazon.awssdk.core.pagination.sync.SdkIterable; +import software.amazon.awssdk.services.redshiftserverless.model.CreateEndpointAccessResponse; +import software.amazon.awssdk.services.redshiftserverless.model.DeleteEndpointAccessResponse; +import software.amazon.awssdk.services.redshiftserverless.model.GetEndpointAccessResponse; +import software.amazon.awssdk.services.redshiftserverless.model.ListEndpointAccessResponse; +import software.amazon.awssdk.services.redshiftserverless.model.UpdateEndpointAccessResponse; +import software.amazon.awssdk.services.redshiftserverless.model.EndpointAccess; +import software.amazon.awssdk.services.redshiftserverless.model.VpcEndpoint; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.Credentials; +import software.amazon.cloudformation.proxy.LoggerProxy; +import software.amazon.cloudformation.proxy.ProxyClient; + +public class AbstractTestBase { + protected static final Credentials MOCK_CREDENTIALS; + protected static final LoggerProxy logger; + protected static final String AWS_REGION; + private static final String ENDPOINT_NAME; + private static final String ENDPOINT_STATUS; + private static final String WORKGROUP_NAME; + private static final int DEFAULT_PORT; + private static final String ADDRESS; + private static final List SUBNET_IDS; + private static final List VPC_SECURITY_GROUPS; + private static final String ENDPOINT_ARN; + private static final String VPC_ENDPOINT_ID; + private static final String VPC_ID; + private static final Instant CREATION_DATE; + private static final String OWNER_ACCOUNT; + + static { + MOCK_CREDENTIALS = new Credentials("accessKey", "secretKey", "token"); + logger = new LoggerProxy(); + AWS_REGION = "us-east-1"; + ENDPOINT_NAME = "testendpoint"; + ENDPOINT_STATUS = "available"; + WORKGROUP_NAME = "testworkgroup"; + DEFAULT_PORT = 5439; + ADDRESS = "xyz"; + SUBNET_IDS = Collections.emptyList(); + VPC_SECURITY_GROUPS = Collections.emptyList(); + ENDPOINT_ARN = "abc"; + VPC_ENDPOINT_ID = "def"; + VPC_ID = "ghi"; + CREATION_DATE = Instant.now(); + OWNER_ACCOUNT = "abcd"; + } + static ProxyClient MOCK_PROXY( + final AmazonWebServicesClientProxy proxy, + final RedshiftServerlessClient sdkClient) { + return new ProxyClient() { + @Override + public ResponseT + injectCredentialsAndInvokeV2(RequestT request, Function requestFunction) { + return proxy.injectCredentialsAndInvokeV2(request, requestFunction); + } + + @Override + public + CompletableFuture + injectCredentialsAndInvokeV2Async(RequestT request, Function> requestFunction) { + throw new UnsupportedOperationException(); + } + + @Override + public > + IterableT + injectCredentialsAndInvokeIterableV2(RequestT request, Function requestFunction) { + return proxy.injectCredentialsAndInvokeIterableV2(request, requestFunction); + } + + @Override + public ResponseInputStream + injectCredentialsAndInvokeV2InputStream(RequestT requestT, Function> function) { + throw new UnsupportedOperationException(); + } + + @Override + public ResponseBytes + injectCredentialsAndInvokeV2Bytes(RequestT requestT, Function> function) { + throw new UnsupportedOperationException(); + } + + @Override + public RedshiftServerlessClient client() { + return sdkClient; + } + }; + } + + public static ResourceModel createEndpointAccessResourceModel() { + return ResourceModel.builder() + .endpointName(ENDPOINT_NAME) + .subnetIds(SUBNET_IDS) + .workgroupName(WORKGROUP_NAME) + .build(); + } + + public static CreateEndpointAccessResponse createEndpointAccessResponseSdk() { + return CreateEndpointAccessResponse.builder() + .endpoint(EndpointAccess.builder() + .endpointName(ENDPOINT_NAME) + .endpointArn(ENDPOINT_ARN) + .endpointCreateTime(CREATION_DATE) + .endpointStatus(ENDPOINT_STATUS) + .address(ADDRESS) + .port(DEFAULT_PORT) + .subnetIds(SUBNET_IDS) + .workgroupName(WORKGROUP_NAME) + .vpcSecurityGroups(VPC_SECURITY_GROUPS) + .vpcEndpoint(VpcEndpoint.builder().vpcEndpointId(VPC_ENDPOINT_ID).vpcId(VPC_ID).networkInterfaces(Collections.emptyList()).build()) + .build()) + .build(); + } + + public static ResourceModel deleteEndpointAccessResourceModel() { + return ResourceModel.builder() + .endpointName(ENDPOINT_NAME) + .build(); + } + + public static DeleteEndpointAccessResponse deleteEndpointAccessResponseSdk() { + return DeleteEndpointAccessResponse.builder() + .endpoint(EndpointAccess.builder() + .endpointName(ENDPOINT_NAME) + .endpointArn(ENDPOINT_ARN) + .endpointCreateTime(CREATION_DATE) + .endpointStatus(ENDPOINT_STATUS) + .address(ADDRESS) + .port(DEFAULT_PORT) + .subnetIds(SUBNET_IDS) + .workgroupName(WORKGROUP_NAME) + .vpcSecurityGroups(VPC_SECURITY_GROUPS) + .vpcEndpoint(VpcEndpoint.builder().vpcEndpointId(VPC_ENDPOINT_ID).vpcId(VPC_ID).networkInterfaces(Collections.emptyList()).build()) + .build()) + .build(); + } + + public static ResourceModel getEndpointAccessResourceModel() { + return ResourceModel.builder() + .endpointName(ENDPOINT_NAME) + .build(); + } + + public static GetEndpointAccessResponse getEndpointAccessResponseSdk() { + return GetEndpointAccessResponse.builder() + .endpoint(EndpointAccess.builder() + .endpointName(ENDPOINT_NAME) + .endpointArn(ENDPOINT_ARN) + .endpointCreateTime(CREATION_DATE) + .endpointStatus(ENDPOINT_STATUS) + .address(ADDRESS) + .port(DEFAULT_PORT) + .subnetIds(SUBNET_IDS) + .workgroupName(WORKGROUP_NAME) + .vpcSecurityGroups(VPC_SECURITY_GROUPS) + .vpcEndpoint(VpcEndpoint.builder().vpcEndpointId(VPC_ENDPOINT_ID).vpcId(VPC_ID).networkInterfaces(Collections.emptyList()).build()) + .build()) + .build(); + } + + public static ResourceModel listEndpointAccessResourceModel() { + return ResourceModel.builder() + .workgroupName(WORKGROUP_NAME) + .vpcId(VPC_ID) + .ownerAccount(OWNER_ACCOUNT) + .build(); + } + + public static ListEndpointAccessResponse getListEndpointAccessResponsesSdk() { + return ListEndpointAccessResponse.builder() + .endpoints(software.amazon.awssdk.services.redshiftserverless.model.EndpointAccess.builder() + .endpointName(ENDPOINT_NAME) + .endpointArn(ENDPOINT_ARN) + .endpointCreateTime(CREATION_DATE) + .endpointStatus(ENDPOINT_STATUS) + .address(ADDRESS) + .port(DEFAULT_PORT) + .subnetIds(SUBNET_IDS) + .workgroupName(WORKGROUP_NAME) + .vpcSecurityGroups(VPC_SECURITY_GROUPS) + .vpcEndpoint(VpcEndpoint.builder().vpcEndpointId(VPC_ENDPOINT_ID).vpcId(VPC_ID).networkInterfaces(Collections.emptyList()).build()) + .build()) + .build(); + } + + public static List getListResponsesResourceModel() { + return Collections.singletonList(ResourceModel.builder() + .endpointName(ENDPOINT_NAME) + .endpointArn(ENDPOINT_ARN) + .endpointCreateTime(CREATION_DATE.toString()) + .endpointStatus(ENDPOINT_STATUS) + .address(ADDRESS) + .port(DEFAULT_PORT) + .subnetIds(SUBNET_IDS) + .workgroupName(WORKGROUP_NAME) + .vpcSecurityGroups(Collections.emptyList()) + .vpcEndpoint(software.amazon.redshiftserverless.endpointaccess.VpcEndpoint.builder().vpcEndpointId(VPC_ENDPOINT_ID).vpcId(VPC_ID).networkInterfaces(Collections.emptyList()).build()) + .build()); + } + + public static ResourceModel updateEndpointAccessResourceModel() { + return ResourceModel.builder() + .endpointName(ENDPOINT_NAME) + .vpcSecurityGroups(Collections.emptyList()) + .build(); + } + + public static UpdateEndpointAccessResponse updateEndpointAccessResponseSdk() { + return UpdateEndpointAccessResponse.builder() + .endpoint(EndpointAccess.builder() + .endpointName(ENDPOINT_NAME) + .endpointArn(ENDPOINT_ARN) + .endpointCreateTime(CREATION_DATE) + .endpointStatus(ENDPOINT_STATUS) + .address(ADDRESS) + .port(DEFAULT_PORT) + .subnetIds(SUBNET_IDS) + .workgroupName(WORKGROUP_NAME) + .vpcSecurityGroups(VPC_SECURITY_GROUPS) + .vpcEndpoint(VpcEndpoint.builder().vpcEndpointId(VPC_ENDPOINT_ID).vpcId(VPC_ID).networkInterfaces(Collections.emptyList()).build()) + .build()) + .build(); + } + + public static ResourceModel getEndpointAccessResponseResourceModel() { + return ResourceModel.builder() + .endpointName(ENDPOINT_NAME) + .endpointArn(ENDPOINT_ARN) + .endpointCreateTime(CREATION_DATE.toString()) + .endpointStatus(ENDPOINT_STATUS) + .address(ADDRESS) + .port(DEFAULT_PORT) + .subnetIds(SUBNET_IDS) + .workgroupName(WORKGROUP_NAME) + .vpcSecurityGroups(Collections.emptyList()) + .vpcEndpoint(software.amazon.redshiftserverless.endpointaccess.VpcEndpoint.builder().vpcEndpointId(VPC_ENDPOINT_ID).vpcId(VPC_ID).networkInterfaces(Collections.emptyList()).build()) + .build(); + } +} diff --git a/aws-redshiftserverless-endpointaccess/src/test/java/software/amazon/redshiftserverless/endpointaccess/CreateHandlerTest.java b/aws-redshiftserverless-endpointaccess/src/test/java/software/amazon/redshiftserverless/endpointaccess/CreateHandlerTest.java new file mode 100644 index 0000000..8a1356e --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/src/test/java/software/amazon/redshiftserverless/endpointaccess/CreateHandlerTest.java @@ -0,0 +1,74 @@ +package software.amazon.redshiftserverless.endpointaccess; + +import java.time.Duration; +import software.amazon.awssdk.services.redshiftserverless.RedshiftServerlessClient; +import software.amazon.awssdk.services.redshiftserverless.model.CreateEndpointAccessRequest; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.OperationStatus; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class CreateHandlerTest extends AbstractTestBase { + + @Mock + private AmazonWebServicesClientProxy proxy; + + @Mock + private ProxyClient proxyClient; + + @Mock + RedshiftServerlessClient sdkClient; + + @BeforeEach + public void setup() { + proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis()); + sdkClient = mock(RedshiftServerlessClient.class); + proxyClient = MOCK_PROXY(proxy, sdkClient); + } + + @AfterEach + public void tear_down() { + verify(sdkClient, atLeastOnce()).serviceName(); + verifyNoMoreInteractions(sdkClient); + } + + @Test + public void handleRequest_SimpleSuccess() { + final CreateHandler handler = new CreateHandler(); + + final ResourceModel requestResourceModel = createEndpointAccessResourceModel(); + final ResourceModel responseResourceModel = getEndpointAccessResponseResourceModel(); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .desiredResourceState(requestResourceModel) + .build(); + + when(proxyClient.client().createEndpointAccess(any(CreateEndpointAccessRequest.class))).thenReturn(createEndpointAccessResponseSdk()); + + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModel()).isEqualTo(responseResourceModel); + assertThat(response.getResourceModels()).isNull(); + assertThat(response.getMessage()).isNull(); + assertThat(response.getErrorCode()).isNull(); + } +} diff --git a/aws-redshiftserverless-endpointaccess/src/test/java/software/amazon/redshiftserverless/endpointaccess/DeleteHandlerTest.java b/aws-redshiftserverless-endpointaccess/src/test/java/software/amazon/redshiftserverless/endpointaccess/DeleteHandlerTest.java new file mode 100644 index 0000000..4591bfc --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/src/test/java/software/amazon/redshiftserverless/endpointaccess/DeleteHandlerTest.java @@ -0,0 +1,74 @@ +package software.amazon.redshiftserverless.endpointaccess; + +import java.time.Duration; +import software.amazon.awssdk.services.redshiftserverless.model.DeleteEndpointAccessRequest; +import software.amazon.awssdk.services.redshiftserverless.RedshiftServerlessClient; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.OperationStatus; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class DeleteHandlerTest extends AbstractTestBase { + + @Mock + private AmazonWebServicesClientProxy proxy; + + @Mock + private ProxyClient proxyClient; + + @Mock + RedshiftServerlessClient sdkClient; + + @BeforeEach + public void setup() { + proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis()); + sdkClient = mock(RedshiftServerlessClient.class); + proxyClient = MOCK_PROXY(proxy, sdkClient); + } + + @AfterEach + public void tear_down() { + verify(sdkClient, atLeastOnce()).serviceName(); + verifyNoMoreInteractions(sdkClient); + } + + @Test + public void handleRequest_SimpleSuccess() { + final DeleteHandler handler = new DeleteHandler(); + + final ResourceModel requestResourceModel = deleteEndpointAccessResourceModel(); + final ResourceModel responseResourceModel = getEndpointAccessResponseResourceModel(); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .desiredResourceState(requestResourceModel) + .build(); + + when(proxyClient.client().deleteEndpointAccess(any(DeleteEndpointAccessRequest.class))).thenReturn(deleteEndpointAccessResponseSdk()); + + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModel()).isEqualTo(responseResourceModel); + assertThat(response.getResourceModels()).isNull(); + assertThat(response.getMessage()).isNull(); + assertThat(response.getErrorCode()).isNull(); + } +} diff --git a/aws-redshiftserverless-endpointaccess/src/test/java/software/amazon/redshiftserverless/endpointaccess/ListHandlerTest.java b/aws-redshiftserverless-endpointaccess/src/test/java/software/amazon/redshiftserverless/endpointaccess/ListHandlerTest.java new file mode 100644 index 0000000..1db32eb --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/src/test/java/software/amazon/redshiftserverless/endpointaccess/ListHandlerTest.java @@ -0,0 +1,62 @@ +package software.amazon.redshiftserverless.endpointaccess; + +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.Logger; +import software.amazon.cloudformation.proxy.OperationStatus; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.doReturn; + +@ExtendWith(MockitoExtension.class) +public class ListHandlerTest extends AbstractTestBase { + + @Mock + private AmazonWebServicesClientProxy proxy; + + @Mock + private Logger logger; + + @BeforeEach + public void setup() { + proxy = mock(AmazonWebServicesClientProxy.class); + logger = mock(Logger.class); + System.setProperty("aws.region", AWS_REGION); + } + + @Test + public void handleRequest_SimpleSuccess() { + final ListHandler handler = new ListHandler(); + + final ResourceModel requestResourceModel = listEndpointAccessResourceModel(); + final List responseResourceModel = getListResponsesResourceModel(); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .desiredResourceState(requestResourceModel) + .build(); + + doReturn(getListEndpointAccessResponsesSdk()).when(proxy).injectCredentialsAndInvokeV2(any(), any()); + + final ProgressEvent response = + handler.handleRequest(proxy, request, null, logger); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); + assertThat(response.getCallbackContext()).isNull(); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModel()).isNull(); + assertThat(response.getResourceModels()).isEqualTo(responseResourceModel); + assertThat(response.getMessage()).isNull(); + assertThat(response.getErrorCode()).isNull(); + } +} diff --git a/aws-redshiftserverless-endpointaccess/src/test/java/software/amazon/redshiftserverless/endpointaccess/ReadHandlerTest.java b/aws-redshiftserverless-endpointaccess/src/test/java/software/amazon/redshiftserverless/endpointaccess/ReadHandlerTest.java new file mode 100644 index 0000000..5f04f67 --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/src/test/java/software/amazon/redshiftserverless/endpointaccess/ReadHandlerTest.java @@ -0,0 +1,74 @@ +package software.amazon.redshiftserverless.endpointaccess; + +import java.time.Duration; +import software.amazon.awssdk.services.redshiftserverless.model.GetEndpointAccessRequest; +import software.amazon.awssdk.services.redshiftserverless.RedshiftServerlessClient; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.OperationStatus; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class ReadHandlerTest extends AbstractTestBase { + + @Mock + private AmazonWebServicesClientProxy proxy; + + @Mock + private ProxyClient proxyClient; + + @Mock + RedshiftServerlessClient sdkClient; + + @BeforeEach + public void setup() { + proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis()); + sdkClient = mock(RedshiftServerlessClient.class); + proxyClient = MOCK_PROXY(proxy, sdkClient); + } + + @AfterEach + public void tear_down() { + verify(sdkClient, atLeastOnce()).serviceName(); + verifyNoMoreInteractions(sdkClient); + } + + @Test + public void handleRequest_SimpleSuccess() { + final ReadHandler handler = new ReadHandler(); + + final ResourceModel requestResourceModel = getEndpointAccessResourceModel(); + final ResourceModel responseResourceModel = getEndpointAccessResponseResourceModel(); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .desiredResourceState(requestResourceModel) + .build(); + + when(proxyClient.client().getEndpointAccess(any(GetEndpointAccessRequest.class))).thenReturn(getEndpointAccessResponseSdk()); + + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModel()).isEqualTo(responseResourceModel); + assertThat(response.getResourceModels()).isNull(); + assertThat(response.getMessage()).isNull(); + assertThat(response.getErrorCode()).isNull(); + } +} diff --git a/aws-redshiftserverless-endpointaccess/src/test/java/software/amazon/redshiftserverless/endpointaccess/UpdateHandlerTest.java b/aws-redshiftserverless-endpointaccess/src/test/java/software/amazon/redshiftserverless/endpointaccess/UpdateHandlerTest.java new file mode 100644 index 0000000..eebac17 --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/src/test/java/software/amazon/redshiftserverless/endpointaccess/UpdateHandlerTest.java @@ -0,0 +1,74 @@ +package software.amazon.redshiftserverless.endpointaccess; + +import java.time.Duration; +import software.amazon.awssdk.services.redshiftserverless.model.UpdateEndpointAccessRequest; +import software.amazon.awssdk.services.redshiftserverless.RedshiftServerlessClient; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.OperationStatus; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class UpdateHandlerTest extends AbstractTestBase { + + @Mock + private AmazonWebServicesClientProxy proxy; + + @Mock + private ProxyClient proxyClient; + + @Mock + RedshiftServerlessClient sdkClient; + + @BeforeEach + public void setup() { + proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis()); + sdkClient = mock(RedshiftServerlessClient.class); + proxyClient = MOCK_PROXY(proxy, sdkClient); + } + + @AfterEach + public void tear_down() { + verify(sdkClient, atLeastOnce()).serviceName(); + verifyNoMoreInteractions(sdkClient); + } + + @Test + public void handleRequest_SimpleSuccess() { + final UpdateHandler handler = new UpdateHandler(); + + final ResourceModel requestResourceModel = updateEndpointAccessResourceModel(); + final ResourceModel responseResourceModel = getEndpointAccessResponseResourceModel(); + + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .desiredResourceState(requestResourceModel) + .build(); + + when(proxyClient.client().updateEndpointAccess(any(UpdateEndpointAccessRequest.class))).thenReturn(updateEndpointAccessResponseSdk()); + + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModel()).isEqualTo(responseResourceModel); + assertThat(response.getResourceModels()).isNull(); + assertThat(response.getMessage()).isNull(); + assertThat(response.getErrorCode()).isNull(); + } +} diff --git a/aws-redshiftserverless-endpointaccess/template.yml b/aws-redshiftserverless-endpointaccess/template.yml new file mode 100644 index 0000000..abdac4c --- /dev/null +++ b/aws-redshiftserverless-endpointaccess/template.yml @@ -0,0 +1,24 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: AWS SAM template for the AWS::RedshiftServerless::EndpointAccess resource type + +Globals: + Function: + Timeout: 180 # docker start-up times can be long for SAM CLI + MemorySize: 512 + +Resources: + TypeFunction: + Type: AWS::Serverless::Function + Properties: + Handler: software.amazon.redshiftserverless.endpointaccess.HandlerWrapper::handleRequest + Runtime: java8 + CodeUri: ./target/aws-redshiftserverless-endpointaccess-handler-1.0-SNAPSHOT.jar + + TestEntrypoint: + Type: AWS::Serverless::Function + Properties: + Handler: software.amazon.redshiftserverless.endpointaccess.HandlerWrapper::testEntrypoint + Runtime: java8 + CodeUri: ./target/aws-redshiftserverless-endpointaccess-handler-1.0-SNAPSHOT.jar +