From 8c1d8237cb386fe69a7b271d180f91519d2cba71 Mon Sep 17 00:00:00 2001 From: Deepanjan Bhattacharyya Date: Thu, 12 Dec 2024 09:45:53 -0800 Subject: [PATCH] Add support for opensearch serverless --- .../opensearch-serverless/access-policy.gyro | 6 + .../opensearch-serverless/collection.gyro | 31 ++ .../data-access-policy.json | 13 + .../encryption-policy.json | 7 + .../lifecycle-policy.gyro | 6 + .../opensearch-serverless/network-policy.json | 7 + .../retention-policy.json | 7 + .../opensearch-serverless/saml-metadata.xml | 18 ++ .../security-config.gyro | 11 + .../security-policy.gyro | 6 + .../opensearch-serverless/vpc-endpoint.gyro | 55 ++++ ...penSearchServerlessAccessPolicyFinder.java | 108 +++++++ ...nSearchServerlessAccessPolicyResource.java | 200 ++++++++++++ .../OpenSearchServerlessCollectionFinder.java | 105 ++++++ ...penSearchServerlessCollectionResource.java | 299 ++++++++++++++++++ ...archServerlessIamIdentityCenterConfig.java | 144 +++++++++ ...SearchServerlessLifecyclePolicyFinder.java | 116 +++++++ ...archServerlessLifecyclePolicyResource.java | 210 ++++++++++++ .../OpenSearchServerlessSamlConfig.java | 117 +++++++ ...nSearchServerlessSecurityConfigFinder.java | 94 ++++++ ...earchServerlessSecurityConfigResource.java | 282 +++++++++++++++++ ...nSearchServerlessSecurityPolicyFinder.java | 107 +++++++ ...earchServerlessSecurityPolicyResource.java | 202 ++++++++++++ ...OpenSearchServerlessVpcEndpointFinder.java | 89 ++++++ ...enSearchServerlessVpcEndpointResource.java | 294 +++++++++++++++++ .../opensearchserverless/package-info.java | 20 ++ 26 files changed, 2554 insertions(+) create mode 100644 examples/opensearch-serverless/access-policy.gyro create mode 100644 examples/opensearch-serverless/collection.gyro create mode 100644 examples/opensearch-serverless/data-access-policy.json create mode 100644 examples/opensearch-serverless/encryption-policy.json create mode 100644 examples/opensearch-serverless/lifecycle-policy.gyro create mode 100644 examples/opensearch-serverless/network-policy.json create mode 100644 examples/opensearch-serverless/retention-policy.json create mode 100644 examples/opensearch-serverless/saml-metadata.xml create mode 100644 examples/opensearch-serverless/security-config.gyro create mode 100644 examples/opensearch-serverless/security-policy.gyro create mode 100644 examples/opensearch-serverless/vpc-endpoint.gyro create mode 100644 src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessAccessPolicyFinder.java create mode 100644 src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessAccessPolicyResource.java create mode 100644 src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessCollectionFinder.java create mode 100644 src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessCollectionResource.java create mode 100644 src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessIamIdentityCenterConfig.java create mode 100644 src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessLifecyclePolicyFinder.java create mode 100644 src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessLifecyclePolicyResource.java create mode 100644 src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSamlConfig.java create mode 100644 src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSecurityConfigFinder.java create mode 100644 src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSecurityConfigResource.java create mode 100644 src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSecurityPolicyFinder.java create mode 100644 src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSecurityPolicyResource.java create mode 100644 src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessVpcEndpointFinder.java create mode 100644 src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessVpcEndpointResource.java create mode 100644 src/main/java/gyro/aws/opensearchserverless/package-info.java diff --git a/examples/opensearch-serverless/access-policy.gyro b/examples/opensearch-serverless/access-policy.gyro new file mode 100644 index 000000000..cbb56d129 --- /dev/null +++ b/examples/opensearch-serverless/access-policy.gyro @@ -0,0 +1,6 @@ +aws::opensearch-serverless-access-policy access-policy + name: "example-data-access-policy" + description: "example data access policy" + type: "data" + policy: "data-access-policy.json" +end diff --git a/examples/opensearch-serverless/collection.gyro b/examples/opensearch-serverless/collection.gyro new file mode 100644 index 000000000..56546df83 --- /dev/null +++ b/examples/opensearch-serverless/collection.gyro @@ -0,0 +1,31 @@ +aws::opensearch-serverless-security-policy encryption-security-policy + name: "vs-coll-enc-policy" + description: "serverless-collection example encryption security policy" + type: "encryption" + policy: "encryption-policy.json" +end + +aws::opensearch-serverless-security-policy network-security-policy + name: "vs-coll-net-policy" + description: "serverless-collection example network security policy" + type: "network" + policy: "network-policy.json" +end + +aws::opensearch-serverless-access-policy data-access-policy + name: "vs-coll-dal-policy" + description: "serverless-collection example data access policy" + type: "data" + policy: "data-access-policy.json" +end + +aws::opensearch-serverless-collection collection + name: "vector-search-collection-example" + description: "vector search collection example" + type: "VECTORSEARCH" + standby-replicas: DISABLED + + tags: { + Name: "opensearch-vector-search-collection-example" + } +end diff --git a/examples/opensearch-serverless/data-access-policy.json b/examples/opensearch-serverless/data-access-policy.json new file mode 100644 index 000000000..6f3bd1373 --- /dev/null +++ b/examples/opensearch-serverless/data-access-policy.json @@ -0,0 +1,13 @@ +[ { + "Rules" : [ { + "Resource" : [ "collection/vector-search-collection-example" ], + "Permission" : [ "aoss:CreateCollectionItems", "aoss:DeleteCollectionItems", "aoss:UpdateCollectionItems", "aoss:DescribeCollectionItems" ], + "ResourceType" : "collection" + }, { + "Resource" : [ "index/vector-search-collection-example/*" ], + "Permission" : [ "aoss:CreateIndex", "aoss:DeleteIndex", "aoss:UpdateIndex", "aoss:DescribeIndex", "aoss:ReadDocument", "aoss:WriteDocument" ], + "ResourceType" : "index" + } ], + "Principal" : [ "arn:aws:iam::242040583208:role/ops-desk" ], + "Description" : "vector-search-collection-example data access policy" +} ] diff --git a/examples/opensearch-serverless/encryption-policy.json b/examples/opensearch-serverless/encryption-policy.json new file mode 100644 index 000000000..8d7705b7b --- /dev/null +++ b/examples/opensearch-serverless/encryption-policy.json @@ -0,0 +1,7 @@ +{ + "Rules" : [ { + "Resource" : [ "collection/vector-search-collection-example" ], + "ResourceType" : "collection" + } ], + "AWSOwnedKey" : true +} diff --git a/examples/opensearch-serverless/lifecycle-policy.gyro b/examples/opensearch-serverless/lifecycle-policy.gyro new file mode 100644 index 000000000..57695991e --- /dev/null +++ b/examples/opensearch-serverless/lifecycle-policy.gyro @@ -0,0 +1,6 @@ +aws::opensearch-serverless-lifecycle-policy lifecycle-policy + name: "example-lifecycle-policy" + description: "example lifecycle policy" + type: "retention" + policy: "retention-policy.json" +end diff --git a/examples/opensearch-serverless/network-policy.json b/examples/opensearch-serverless/network-policy.json new file mode 100644 index 000000000..fa6005a7d --- /dev/null +++ b/examples/opensearch-serverless/network-policy.json @@ -0,0 +1,7 @@ +[ { + "Rules" : [ { + "Resource" : [ "collection/vector-search-collection-example" ], + "ResourceType" : "collection" + } ], + "AllowFromPublic" : true +} ] diff --git a/examples/opensearch-serverless/retention-policy.json b/examples/opensearch-serverless/retention-policy.json new file mode 100644 index 000000000..dc1983b13 --- /dev/null +++ b/examples/opensearch-serverless/retention-policy.json @@ -0,0 +1,7 @@ +{ + "Rules" : [ { + "Resource" : [ "index/vector-search-collection-example/*" ], + "ResourceType" : "index", + "MinIndexRetention" : "30d" + } ] +} diff --git a/examples/opensearch-serverless/saml-metadata.xml b/examples/opensearch-serverless/saml-metadata.xml new file mode 100644 index 000000000..65b3d6d9b --- /dev/null +++ b/examples/opensearch-serverless/saml-metadata.xml @@ -0,0 +1,18 @@ + + + + + + + MII...CERT_CONTENT... + + + + + + + diff --git a/examples/opensearch-serverless/security-config.gyro b/examples/opensearch-serverless/security-config.gyro new file mode 100644 index 000000000..e8cd4b9d8 --- /dev/null +++ b/examples/opensearch-serverless/security-config.gyro @@ -0,0 +1,11 @@ +aws::opensearch-serverless-security-config security-config + name: "example-security-config" + description: "example security config" + type: "saml" + saml-config + group-attribute: "group" + user-attribute: "username" + session-timeout: 600 + metadata: "saml-metadata.xml" + end +end diff --git a/examples/opensearch-serverless/security-policy.gyro b/examples/opensearch-serverless/security-policy.gyro new file mode 100644 index 000000000..52c20df2e --- /dev/null +++ b/examples/opensearch-serverless/security-policy.gyro @@ -0,0 +1,6 @@ +aws::opensearch-serverless-security-policy security-policy + name: "example-security-policy" + description: "example security policy" + type: "encryption" + policy: "encryption-policy.json" +end diff --git a/examples/opensearch-serverless/vpc-endpoint.gyro b/examples/opensearch-serverless/vpc-endpoint.gyro new file mode 100644 index 000000000..90900dd12 --- /dev/null +++ b/examples/opensearch-serverless/vpc-endpoint.gyro @@ -0,0 +1,55 @@ +aws::vpc vpc-example + cidr-block: "10.0.0.0/16" + provide-ipv6-cidr-block: true + + tags: { + Name: "opensearch-collection-example" + } +end + +aws::subnet example-subnet-1 + vpc: $(aws::vpc vpc-example) + availability-zone: us-east-1a + cidr-block: "10.0.0.0/24" + + tags: { + Name: "opensearch-collection-example-1" + } +end + +aws::subnet example-subnet-2 + vpc: $(aws::vpc vpc-example) + availability-zone: us-east-1b + cidr-block: "10.0.1.0/24" + + tags: { + Name: "opensearch-collection-example-2" + } +end + +aws::subnet example-subnet-3 + vpc: $(aws::vpc vpc-example) + availability-zone: us-east-1c + cidr-block: "10.0.2.0/24" + + tags: { + Name: "opensearch-collection-example-3" + } +end + +aws::security-group example-security-group + name: "opensearch-collection-example" + vpc: $(aws::vpc vpc-example) + description: "security group example for es" +end + +aws::opensearch-serverless-vpc-endpoint vpc-endpoint-example + name: "os-coll-vpc-endpoint" + vpc: $(aws::vpc vpc-example) + security-groups: $(aws::security-group example-security-group) + subnets: [ + $(aws::subnet example-subnet-1), + $(aws::subnet example-subnet-2), + $(aws::subnet example-subnet-3) + ] +end diff --git a/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessAccessPolicyFinder.java b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessAccessPolicyFinder.java new file mode 100644 index 000000000..9421b8f2e --- /dev/null +++ b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessAccessPolicyFinder.java @@ -0,0 +1,108 @@ +/* + * Copyright 2024, Brightspot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gyro.aws.opensearchserverless; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import gyro.aws.AwsFinder; +import gyro.core.Type; +import software.amazon.awssdk.services.opensearchserverless.OpenSearchServerlessClient; +import software.amazon.awssdk.services.opensearchserverless.model.AccessPolicyDetail; +import software.amazon.awssdk.services.opensearchserverless.model.AccessPolicySummary; +import software.amazon.awssdk.services.opensearchserverless.model.AccessPolicyType; +import software.amazon.awssdk.services.opensearchserverless.model.ListAccessPoliciesResponse; +import software.amazon.awssdk.services.opensearchserverless.model.ResourceNotFoundException; + +/** + * Query OpenSearch Serverless access policy. + * + * Example + * ------- + * + * .. code-block:: gyro + * + * access-policy: $(external-query aws::opensearch-serverless-access-policy { name: ''}) + */ +@Type("opensearch-serverless-access-policy") +public class OpenSearchServerlessAccessPolicyFinder + extends AwsFinder { + + private String name; + private String type; + + /** + * The name of the access policy. + */ + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * The type of the access policy. + */ + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Override + protected List findAllAws(OpenSearchServerlessClient client) { + List accessPolicyDetails = new ArrayList<>(); + for (AccessPolicyType type : AccessPolicyType.knownValues()) { + ListAccessPoliciesResponse response = client.listAccessPolicies(r -> r.type(type)); + List collect = response.accessPolicySummaries() + .stream() + .map(AccessPolicySummary::name) + .collect(Collectors.toList()); + + for (String name : collect) { + accessPolicyDetails.add(client.getAccessPolicy(r -> r.name(name).type(type)).accessPolicyDetail()); + } + } + + return accessPolicyDetails; + } + + @Override + protected List findAws(OpenSearchServerlessClient client, Map filters) { + List accessPolicyDetails = new ArrayList<>(); + + if (filters.containsKey("name") && filters.containsKey("type")) { + try { + accessPolicyDetails.add(client.getAccessPolicy(r -> r + .name(filters.get("name")) + .type(AccessPolicyType.fromValue(filters.get("type"))).build()) + .accessPolicyDetail()); + } catch (ResourceNotFoundException ex) { + // Ignore + } + } + + return accessPolicyDetails; + } + +} diff --git a/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessAccessPolicyResource.java b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessAccessPolicyResource.java new file mode 100644 index 000000000..a2af40438 --- /dev/null +++ b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessAccessPolicyResource.java @@ -0,0 +1,200 @@ +/* + * Copyright 2024, Brightspot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gyro.aws.opensearchserverless; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Set; +import java.util.UUID; + +import gyro.aws.AwsResource; +import gyro.aws.Copyable; +import gyro.aws.iam.PolicyResource; +import gyro.core.GyroException; +import gyro.core.GyroUI; +import gyro.core.Type; +import gyro.core.resource.Id; +import gyro.core.resource.Resource; +import gyro.core.resource.Updatable; +import gyro.core.scope.State; +import gyro.core.validation.Required; +import gyro.core.validation.ValidStrings; +import software.amazon.awssdk.services.opensearchserverless.OpenSearchServerlessClient; +import software.amazon.awssdk.services.opensearchserverless.model.AccessPolicyDetail; +import software.amazon.awssdk.services.opensearchserverless.model.AccessPolicyType; +import software.amazon.awssdk.services.opensearchserverless.model.CreateAccessPolicyResponse; +import software.amazon.awssdk.services.opensearchserverless.model.GetAccessPolicyResponse; +import software.amazon.awssdk.services.opensearchserverless.model.ResourceNotFoundException; +import software.amazon.awssdk.utils.IoUtils; + +/** + * Create an OpenSearch Serverless access policy. + * + * Example + * ------- + * + * .. code-block:: gyro + * + * aws::opensearch-serverless-access-policy access-policy-example + * name: "access-policy-example" + * description: "Access policy example" + * type: "data" + * policy: "policy.json" + * end + */ +@Type("opensearch-serverless-access-policy") +public class OpenSearchServerlessAccessPolicyResource extends AwsResource implements Copyable { + + private String name; + private String description; + private AccessPolicyType type; + private String policy; + private String policyVersion; + + /** + * The name of the access policy. + */ + @Id + @Required + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * The description of the access policy. + */ + @Updatable + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + /** + * The type of the access policy. + */ + @Required + @ValidStrings("data") + public AccessPolicyType getType() { + return type; + } + + public void setType(AccessPolicyType type) { + this.type = type; + } + + /** + * The policy of the access policy. A policy path or policy string is allowed. + */ + @Updatable + public String getPolicy() { + if (policy != null && policy.contains(".json")) { + try (InputStream input = openInput(policy)) { + policy = PolicyResource.formatPolicy(IoUtils.toUtf8String(input)); + return policy; + } catch (IOException err) { + throw new GyroException(err.getMessage()); + } + } else { + return PolicyResource.formatPolicy(policy); + } + } + + public void setPolicy(String policy) { + this.policy = policy; + } + + /** + * The policy version of the access policy. + */ + @Updatable + public String getPolicyVersion() { + return policyVersion; + } + + public void setPolicyVersion(String policyVersion) { + this.policyVersion = policyVersion; + } + + @Override + public void copyFrom(AccessPolicyDetail model) { + setName(model.name()); + setDescription(model.description()); + setType(model.type()); + setPolicy(model.policy().toString()); + setPolicyVersion(model.policyVersion()); + } + + @Override + public boolean refresh() { + try { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + GetAccessPolicyResponse response = client.getAccessPolicy(r -> r.name(getName()).type(getType()).build()); + copyFrom(response.accessPolicyDetail()); + return true; + } catch (ResourceNotFoundException ex) { + return false; + } + } + + @Override + public void create(GyroUI ui, State state) throws Exception { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + String token = UUID.randomUUID().toString(); + CreateAccessPolicyResponse response = client.createAccessPolicy(r -> r.clientToken(token) + .description(getDescription()) + .name(getName()) + .type(getType()) + .policy(getPolicy()) + ); + + copyFrom(response.accessPolicyDetail()); + } + + @Override + public void update(GyroUI ui, State state, Resource current, Set changedFieldNames) throws Exception { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + String token = UUID.randomUUID().toString(); + client.updateAccessPolicy(r -> r.clientToken(token) + .description(getDescription()) + .name(getName()) + .type(getType()) + .policy(getPolicy()) + .policyVersion(getPolicyVersion()) + ); + } + + @Override + public void delete(GyroUI ui, State state) throws Exception { + try { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + String token = UUID.randomUUID().toString(); + client.deleteAccessPolicy(r -> r.clientToken(token) + .name(getName()) + .type(getType()) + ); + } catch (ResourceNotFoundException ex) { + // ignore + } + } +} diff --git a/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessCollectionFinder.java b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessCollectionFinder.java new file mode 100644 index 000000000..198ac16ba --- /dev/null +++ b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessCollectionFinder.java @@ -0,0 +1,105 @@ +/* + * Copyright 2024, Brightspot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gyro.aws.opensearchserverless; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import gyro.aws.AwsFinder; +import gyro.core.Type; +import software.amazon.awssdk.services.opensearchserverless.OpenSearchServerlessClient; +import software.amazon.awssdk.services.opensearchserverless.model.BatchGetCollectionResponse; +import software.amazon.awssdk.services.opensearchserverless.model.CollectionDetail; +import software.amazon.awssdk.services.opensearchserverless.model.CollectionSummary; +import software.amazon.awssdk.services.opensearchserverless.model.ListCollectionsResponse; +import software.amazon.awssdk.services.opensearchserverless.model.ResourceNotFoundException; +import software.amazon.awssdk.utils.builder.SdkBuilder; + +/** + * Query OpenSearch Serverless collection. + * + * Example + * ------- + * + * .. code-block:: gyro + * + * collection: $(external-query aws::opensearch-serverless-collection { collection-id: ''}) + */ +@Type("opensearch-serverless-collection") +public class OpenSearchServerlessCollectionFinder + extends AwsFinder { + + private String collectionId; + private String name; + + /** + * The ID of the collection. + */ + public String getCollectionId() { + return collectionId; + } + + public void setCollectionId(String collectionId) { + this.collectionId = collectionId; + } + + /** + * The name of the collection. + */ + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + protected List findAllAws(OpenSearchServerlessClient client) { + List collectionDetails = new ArrayList<>(); + ListCollectionsResponse response = client.listCollections(SdkBuilder::build); + List collectionIds = response.collectionSummaries() + .stream() + .map(CollectionSummary::id) + .collect(Collectors.toList()); + + if (!collectionIds.isEmpty()) { + BatchGetCollectionResponse batchGetCollectionResponse = client.batchGetCollection(r -> r.ids(collectionIds)); + collectionDetails = batchGetCollectionResponse.collectionDetails(); + } + + return collectionDetails; + } + + @Override + protected List findAws(OpenSearchServerlessClient client, Map filters) { + List collectionDetails = new ArrayList<>(); + try { + if (filters.containsKey("collection-id")) { + collectionDetails = client.batchGetCollection(r -> r.ids(filters.get("collection-id"))) + .collectionDetails(); + } else if (filters.containsKey("name")) { + collectionDetails = client.batchGetCollection(r -> r.names(filters.get("name"))).collectionDetails(); + } + } catch (ResourceNotFoundException ex) { + // Ignore + } + return collectionDetails; + } +} diff --git a/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessCollectionResource.java b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessCollectionResource.java new file mode 100644 index 000000000..bb6ad08d9 --- /dev/null +++ b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessCollectionResource.java @@ -0,0 +1,299 @@ +/* + * Copyright 2024, Brightspot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gyro.aws.opensearchserverless; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import gyro.aws.AwsResource; +import gyro.aws.Copyable; +import gyro.core.GyroUI; +import gyro.core.TimeoutSettings; +import gyro.core.Type; +import gyro.core.Wait; +import gyro.core.resource.Id; +import gyro.core.resource.Output; +import gyro.core.resource.Resource; +import gyro.core.resource.Updatable; +import gyro.core.scope.State; +import gyro.core.validation.Required; +import gyro.core.validation.ValidStrings; +import software.amazon.awssdk.services.opensearchserverless.OpenSearchServerlessClient; +import software.amazon.awssdk.services.opensearchserverless.model.BatchGetCollectionResponse; +import software.amazon.awssdk.services.opensearchserverless.model.CollectionDetail; +import software.amazon.awssdk.services.opensearchserverless.model.CollectionStatus; +import software.amazon.awssdk.services.opensearchserverless.model.CollectionType; +import software.amazon.awssdk.services.opensearchserverless.model.CreateCollectionResponse; +import software.amazon.awssdk.services.opensearchserverless.model.ResourceNotFoundException; +import software.amazon.awssdk.services.opensearchserverless.model.StandbyReplicas; +import software.amazon.awssdk.services.opensearchserverless.model.Tag; + +/** + * Create an OpenSearch Serverless collection. + * + * Example + * ------- + * + * .. code-block:: gyro + * + * aws::opensearch-serverless-collection collection-example + * name: "collection-example" + * description: "Collection example" + * type: "SEARCH" + * standby-replicas: "ENABLED" + * tags: { + * Name: "collection-example" + * } + * end + */ +@Type("opensearch-serverless-collection") +public class OpenSearchServerlessCollectionResource extends AwsResource implements Copyable { + + private String name; + private String description; + private CollectionType type; + private StandbyReplicas standbyReplicas; + private Map tags; + + //output + private String arn; + private String id; + + /** + * The name of the collection. + */ + @Required + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * The description of the collection. + */ + @Updatable + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + /** + * The type of the collection. + */ + @Required + @ValidStrings({ "SEARCH", "TIMESERIES", "VECTORSEARCH" }) + public CollectionType getType() { + return type; + } + + public void setType(CollectionType type) { + this.type = type; + } + + /** + * Should the collection have standby replicas. + */ + @Required + @ValidStrings({ "ENABLED", "DISABLED" }) + public StandbyReplicas getStandbyReplicas() { + return standbyReplicas; + } + + public void setStandbyReplicas(StandbyReplicas standbyReplicas) { + this.standbyReplicas = standbyReplicas; + } + + /** + * The tags attached to the collection. + */ + @Updatable + public Map getTags() { + if (tags == null) { + tags = new HashMap<>(); + } + + return tags; + } + + public void setTags(Map tags) { + this.tags = tags; + } + + /** + * The ARN of the collection. + */ + @Output + public String getArn() { + return arn; + } + + public void setArn(String arn) { + this.arn = arn; + } + + /** + * The ID of the collection. + */ + @Id + @Output + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public void copyFrom(CollectionDetail model) { + setArn(model.arn()); + setId(model.id()); + setName(model.name()); + setDescription(model.description()); + setType(model.type()); + setStandbyReplicas(model.standbyReplicas()); + + getTags().clear(); + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + client.listTagsForResource(r -> r.resourceArn(model.arn())) + .tags().forEach(t -> getTags().put(t.key(), t.value())); + } + + @Override + public boolean refresh() { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + CollectionDetail collectionDetail = getOpenSearchServerlessCollection(client); + if (collectionDetail == null) { + return false; + } + + copyFrom(collectionDetail); + return true; + } + + @Override + public void create(GyroUI ui, State state) throws Exception { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + String token = UUID.randomUUID().toString(); + CreateCollectionResponse collection = client.createCollection(r -> r.name(getName()) + .clientToken(token) + .description(getDescription()) + .standbyReplicas(getStandbyReplicas()) + .tags(getTags().entrySet().stream() + .map(e -> Tag.builder() + .key(e.getKey()) + .value(e.getValue()) + .build()) + .collect(Collectors.toList())) + .type(getType()) + ); + + setId(collection.createCollectionDetail().id()); + + Wait.atMost(20, TimeUnit.MINUTES) + .checkEvery(4, TimeUnit.MINUTES) + .resourceOverrides(this, TimeoutSettings.Action.CREATE) + .prompt(false) + .until(() -> { + CollectionDetail collectionDetail = getOpenSearchServerlessCollection(client); + return collectionDetail != null && collectionDetail.status().equals(CollectionStatus.ACTIVE); + }); + } + + @Override + public void update(GyroUI ui, State state, Resource current, Set changedFieldNames) throws Exception { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + if (changedFieldNames.contains("description")) { + String token = UUID.randomUUID().toString(); + client.updateCollection(r -> r.id(getId()) + .clientToken(token) + .description(getDescription()) + ); + } + + if (changedFieldNames.contains("tags")) { + OpenSearchServerlessCollectionResource old = (OpenSearchServerlessCollectionResource) current; + + if (!old.getTags().isEmpty()) { + client.untagResource(r -> r.resourceArn(getArn()) + .tagKeys(old.getTags().keySet()) + ); + } + + if (!getTags().isEmpty()) { + client.tagResource(r -> r.resourceArn(getArn()) + .tags(getTags().entrySet().stream() + .map(e -> Tag.builder() + .key(e.getKey()) + .value(e.getValue()) + .build()) + .collect(Collectors.toList())) + ); + } + } + + Wait.atMost(20, TimeUnit.MINUTES) + .checkEvery(4, TimeUnit.MINUTES) + .resourceOverrides(this, TimeoutSettings.Action.UPDATE) + .prompt(false) + .until(() -> { + CollectionDetail collectionDetail = getOpenSearchServerlessCollection(client); + return collectionDetail != null && collectionDetail.status().equals(CollectionStatus.ACTIVE); + }); + } + + @Override + public void delete(GyroUI ui, State state) throws Exception { + try { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + client.deleteCollection(r -> r.id(getId())); + + Wait.atMost(20, TimeUnit.MINUTES) + .checkEvery(4, TimeUnit.MINUTES) + .resourceOverrides(this, TimeoutSettings.Action.DELETE) + .prompt(false) + .until(() -> getOpenSearchServerlessCollection(client) == null); + } catch (ResourceNotFoundException ex) { + // ignore + } + } + + private CollectionDetail getOpenSearchServerlessCollection(OpenSearchServerlessClient client) { + CollectionDetail collectionDetail = null; + try { + BatchGetCollectionResponse response = client.batchGetCollection(r -> r.ids(getId())); + if (!response.collectionDetails().isEmpty()) { + collectionDetail = response.collectionDetails().get(0); + } + } catch (ResourceNotFoundException ex) { + // ignore + } + + return collectionDetail; + } + +} diff --git a/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessIamIdentityCenterConfig.java b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessIamIdentityCenterConfig.java new file mode 100644 index 000000000..09be288a2 --- /dev/null +++ b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessIamIdentityCenterConfig.java @@ -0,0 +1,144 @@ +/* + * Copyright 2024, Brightspot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gyro.aws.opensearchserverless; + +import gyro.aws.Copyable; +import gyro.core.resource.Diffable; +import gyro.core.resource.Output; +import gyro.core.resource.Updatable; +import gyro.core.validation.Required; +import gyro.core.validation.ValidStrings; +import software.amazon.awssdk.services.opensearchserverless.model.CreateIamIdentityCenterConfigOptions; +import software.amazon.awssdk.services.opensearchserverless.model.IamIdentityCenterConfigOptions; +import software.amazon.awssdk.services.opensearchserverless.model.IamIdentityCenterGroupAttribute; +import software.amazon.awssdk.services.opensearchserverless.model.IamIdentityCenterUserAttribute; +import software.amazon.awssdk.services.opensearchserverless.model.UpdateIamIdentityCenterConfigOptions; + +public class OpenSearchServerlessIamIdentityCenterConfig extends Diffable + implements Copyable { + + private IamIdentityCenterUserAttribute userAttribute; + private IamIdentityCenterGroupAttribute groupAttribute; + private String applicationArn; + private String applicationDescription; + private String applicationName; + private String instanceArn; + + /** + * The user attribute to use for the IAM identity center. + */ + @Updatable + @ValidStrings({ "UserId", "Email", "UserName" }) + public IamIdentityCenterUserAttribute getUserAttribute() { + return userAttribute; + } + + public void setUserAttribute(IamIdentityCenterUserAttribute userAttribute) { + this.userAttribute = userAttribute; + } + + /** + * The group attribute to use for the IAM identity center. + */ + @Updatable + @ValidStrings({ "GroupName", "GroupId" }) + public IamIdentityCenterGroupAttribute getGroupAttribute() { + return groupAttribute; + } + + public void setGroupAttribute(IamIdentityCenterGroupAttribute groupAttribute) { + this.groupAttribute = groupAttribute; + } + + /** + * The ARN of the instance. + */ + @Required + public String getInstanceArn() { + return instanceArn; + } + + public void setInstanceArn(String instanceArn) { + this.instanceArn = instanceArn; + } + + /** + * The ARN of the application. + */ + @Output + public String getApplicationArn() { + return applicationArn; + } + + public void setApplicationArn(String applicationArn) { + this.applicationArn = applicationArn; + } + + /** + * The description of the application. + */ + @Output + public String getApplicationDescription() { + return applicationDescription; + } + + public void setApplicationDescription(String applicationDescription) { + this.applicationDescription = applicationDescription; + } + + /** + * The name of the application. + */ + @Output + public String getApplicationName() { + return applicationName; + } + + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } + + @Override + public void copyFrom(IamIdentityCenterConfigOptions model) { + setUserAttribute(model.userAttribute()); + setGroupAttribute(model.groupAttribute()); + setInstanceArn(model.instanceArn()); + setApplicationArn(model.applicationArn()); + setApplicationDescription(model.applicationDescription()); + setApplicationName(model.applicationName()); + } + + @Override + public String primaryKey() { + return ""; + } + + CreateIamIdentityCenterConfigOptions toIamIdentityCenterConfigOptionsCreate() { + return CreateIamIdentityCenterConfigOptions.builder() + .userAttribute(getUserAttribute()) + .groupAttribute(getGroupAttribute()) + .instanceArn(getInstanceArn()) + .build(); + } + + UpdateIamIdentityCenterConfigOptions toIamIdentityCenterConfigOptionsUpdate() { + return UpdateIamIdentityCenterConfigOptions.builder() + .userAttribute(getUserAttribute()) + .groupAttribute(getGroupAttribute()) + .build(); + } +} diff --git a/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessLifecyclePolicyFinder.java b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessLifecyclePolicyFinder.java new file mode 100644 index 000000000..89afbeb67 --- /dev/null +++ b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessLifecyclePolicyFinder.java @@ -0,0 +1,116 @@ +/* + * Copyright 2024, Brightspot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gyro.aws.opensearchserverless; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import gyro.aws.AwsFinder; +import gyro.core.Type; +import software.amazon.awssdk.services.opensearchserverless.OpenSearchServerlessClient; +import software.amazon.awssdk.services.opensearchserverless.model.BatchGetLifecyclePolicyResponse; +import software.amazon.awssdk.services.opensearchserverless.model.LifecyclePolicyDetail; +import software.amazon.awssdk.services.opensearchserverless.model.LifecyclePolicyIdentifier; +import software.amazon.awssdk.services.opensearchserverless.model.LifecyclePolicySummary; +import software.amazon.awssdk.services.opensearchserverless.model.LifecyclePolicyType; +import software.amazon.awssdk.services.opensearchserverless.model.ListLifecyclePoliciesResponse; +import software.amazon.awssdk.services.opensearchserverless.model.ResourceNotFoundException; + +/** + * Query OpenSearch Serverless lifecycle policy. + * + * Example + * ------- + * + * .. code-block:: gyro + * + * lifecycle-policy: $(external-query aws::opensearch-serverless-lifecycle-policy { name: ''}) + */ +@Type("opensearch-serverless-lifecycle-policy") +public class OpenSearchServerlessLifecyclePolicyFinder + extends AwsFinder { + + private String name; + private String type; + + /** + * The name of the lifecycle policy. + */ + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * The type of the lifecycle policy. + */ + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Override + protected List findAllAws(OpenSearchServerlessClient client) { + List lifecyclePolicyDetails = new ArrayList<>(); + for (LifecyclePolicyType type : LifecyclePolicyType.knownValues()) { + ListLifecyclePoliciesResponse response = client.listLifecyclePolicies(r -> r.type(type).build()); + List policyNames = response.lifecyclePolicySummaries() + .stream() + .map(LifecyclePolicySummary::name) + .collect(Collectors.toList()); + + if (!policyNames.isEmpty()) { + List identifiers = policyNames.stream() + .map(policyName -> LifecyclePolicyIdentifier.builder().name(policyName).type(type).build()) + .collect(Collectors.toList()); + BatchGetLifecyclePolicyResponse batchGetLifecyclePolicyResponse = client.batchGetLifecyclePolicy(r -> r.identifiers( + identifiers) + .build()); + lifecyclePolicyDetails.addAll(batchGetLifecyclePolicyResponse.lifecyclePolicyDetails()); + } + } + + return lifecyclePolicyDetails; + } + + @Override + protected List findAws(OpenSearchServerlessClient client, Map filters) { + List lifecyclePolicyDetails = new ArrayList<>(); + + if (filters.containsKey("name") && filters.containsKey("type")) { + try { + lifecyclePolicyDetails = client.batchGetLifecyclePolicy(r -> r.identifiers( + LifecyclePolicyIdentifier.builder().name(filters.get("name")) + .type(LifecyclePolicyType.fromValue(filters.get("type"))) + .build()).build()).lifecyclePolicyDetails(); + } catch (ResourceNotFoundException ex) { + // Ignore + } + } + + return lifecyclePolicyDetails; + } + +} diff --git a/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessLifecyclePolicyResource.java b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessLifecyclePolicyResource.java new file mode 100644 index 000000000..a6da5df56 --- /dev/null +++ b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessLifecyclePolicyResource.java @@ -0,0 +1,210 @@ +/* + * Copyright 2024, Brightspot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gyro.aws.opensearchserverless; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Set; +import java.util.UUID; + +import gyro.aws.AwsResource; +import gyro.aws.Copyable; +import gyro.aws.iam.PolicyResource; +import gyro.core.GyroException; +import gyro.core.GyroUI; +import gyro.core.Type; +import gyro.core.resource.Id; +import gyro.core.resource.Resource; +import gyro.core.resource.Updatable; +import gyro.core.scope.State; +import gyro.core.validation.Required; +import gyro.core.validation.ValidStrings; +import software.amazon.awssdk.services.opensearchserverless.OpenSearchServerlessClient; +import software.amazon.awssdk.services.opensearchserverless.model.BatchGetLifecyclePolicyResponse; +import software.amazon.awssdk.services.opensearchserverless.model.CreateLifecyclePolicyResponse; +import software.amazon.awssdk.services.opensearchserverless.model.LifecyclePolicyDetail; +import software.amazon.awssdk.services.opensearchserverless.model.LifecyclePolicyIdentifier; +import software.amazon.awssdk.services.opensearchserverless.model.LifecyclePolicyType; +import software.amazon.awssdk.services.opensearchserverless.model.ResourceNotFoundException; +import software.amazon.awssdk.utils.IoUtils; + +/** + * Create an OpenSearch Serverless lifecycle policy. + * + * Example + * ------- + * + * .. code-block:: gyro + * + * aws::opensearch-serverless-lifecycle-policy serverless-lifecycle-policy-example + * name: "serverless-lifecycle-policy-example" + * description: "serverless-lifecycle-policy-example-desc" + * type: "retention" + * policy: "retention-policy.json" + * end + */ +@Type("opensearch-serverless-lifecycle-policy") +public class OpenSearchServerlessLifecyclePolicyResource extends AwsResource + implements Copyable { + + private String name; + private String description; + private LifecyclePolicyType type; + private String policy; + private String policyVersion; + + /** + * The name of the lifecycle policy. + */ + @Id + @Required + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * The description of the lifecycle policy. + */ + @Updatable + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + /** + * The type of the lifecycle policy. + */ + @Required + @ValidStrings("retention") + public LifecyclePolicyType getType() { + return type; + } + + public void setType(LifecyclePolicyType type) { + this.type = type; + } + + /** + * The policy of the lifecycle policy. A policy path or policy string is allowed. + */ + @Required + @Updatable + public String getPolicy() { + if (policy != null && policy.contains(".json")) { + try (InputStream input = openInput(policy)) { + policy = PolicyResource.formatPolicy(IoUtils.toUtf8String(input)); + return policy; + } catch (IOException err) { + throw new GyroException(err.getMessage()); + } + } else { + return PolicyResource.formatPolicy(policy); + } + } + + public void setPolicy(String policy) { + this.policy = policy; + } + + /** + * The policy version of the lifecycle policy. + */ + @Updatable + public String getPolicyVersion() { + return policyVersion; + } + + public void setPolicyVersion(String policyVersion) { + this.policyVersion = policyVersion; + } + + @Override + public void copyFrom(LifecyclePolicyDetail model) { + setName(model.name()); + setDescription(model.description()); + setType(model.type()); + setPolicy(model.policy().toString()); + setPolicyVersion(model.policyVersion()); + } + + @Override + public boolean refresh() { + try { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + BatchGetLifecyclePolicyResponse response = client.batchGetLifecyclePolicy(r -> r.identifiers( + LifecyclePolicyIdentifier.builder().name(getName()) + .type(getType()) + .build()).build()); + if (response.hasLifecyclePolicyDetails()) { + copyFrom(response.lifecyclePolicyDetails().get(0)); + return true; + } + } catch (ResourceNotFoundException ex) { + // ignore + } + + return false; + } + + @Override + public void create(GyroUI ui, State state) throws Exception { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + String token = UUID.randomUUID().toString(); + CreateLifecyclePolicyResponse response = client.createLifecyclePolicy(r -> r.clientToken(token) + .description(getDescription()) + .name(getName()) + .policy(getPolicy()) + .type(getType()) + ); + + copyFrom(response.lifecyclePolicyDetail()); + } + + @Override + public void update(GyroUI ui, State state, Resource current, Set changedFieldNames) throws Exception { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + String token = UUID.randomUUID().toString(); + client.updateLifecyclePolicy(r -> r.clientToken(token) + .description(getDescription()) + .type(getType()) + .name(getName()) + .policy(getPolicy()) + .policyVersion(getPolicyVersion()) + ); + } + + @Override + public void delete(GyroUI ui, State state) throws Exception { + try { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + String token = UUID.randomUUID().toString(); + client.deleteLifecyclePolicy(r -> r.clientToken(token) + .name(getName()) + .type(getType()) + ); + } catch (ResourceNotFoundException ex) { + // ignore + } + } +} diff --git a/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSamlConfig.java b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSamlConfig.java new file mode 100644 index 000000000..ae95d3325 --- /dev/null +++ b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSamlConfig.java @@ -0,0 +1,117 @@ +/* + * Copyright 2024, Brightspot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gyro.aws.opensearchserverless; + +import java.io.IOException; +import java.io.InputStream; + +import gyro.aws.Copyable; +import gyro.core.GyroException; +import gyro.core.resource.Diffable; +import gyro.core.resource.Updatable; +import gyro.core.validation.Required; +import software.amazon.awssdk.services.opensearchserverless.model.SamlConfigOptions; +import software.amazon.awssdk.utils.IoUtils; + +public class OpenSearchServerlessSamlConfig extends Diffable implements Copyable { + + private String groupAttribute; + private String metadata; + private Integer sessionTimeout; + private String userAttribute; + + /** + * The group attribute to use for the SAML configuration. + */ + @Required + @Updatable + public String getGroupAttribute() { + return groupAttribute; + } + + public void setGroupAttribute(String groupAttribute) { + this.groupAttribute = groupAttribute; + } + + /** + * The metadata document to use for the SAML configuration. A xml path or xml string is allowed. + */ + @Updatable + public String getMetadata() { + if (metadata != null && metadata.contains(".xml")) { + try (InputStream input = openInput(metadata)) { + metadata = IoUtils.toUtf8String(input); + return metadata; + } catch (IOException err) { + throw new GyroException(err.getMessage()); + } + } else { + return metadata; + } + } + + public void setMetadata(String metadata) { + this.metadata = metadata; + } + + /** + * The session timeout in minutes for the SAML configuration. + */ + @Required + @Updatable + public Integer getSessionTimeout() { + return sessionTimeout; + } + + public void setSessionTimeout(Integer sessionTimeout) { + this.sessionTimeout = sessionTimeout; + } + + /** + * The user attribute to use for the SAML configuration. + */ + @Updatable + public String getUserAttribute() { + return userAttribute; + } + + public void setUserAttribute(String userAttribute) { + this.userAttribute = userAttribute; + } + + @Override + public void copyFrom(SamlConfigOptions model) { + setGroupAttribute(model.groupAttribute()); + setMetadata(model.metadata()); + setSessionTimeout(model.sessionTimeout()); + setUserAttribute(model.userAttribute()); + } + + @Override + public String primaryKey() { + return ""; + } + + SamlConfigOptions toSamlConfigOptions() { + return SamlConfigOptions.builder() + .groupAttribute(getGroupAttribute()) + .metadata(getMetadata()) + .sessionTimeout(getSessionTimeout()) + .userAttribute(getUserAttribute()) + .build(); + } +} diff --git a/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSecurityConfigFinder.java b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSecurityConfigFinder.java new file mode 100644 index 000000000..1f3dbd428 --- /dev/null +++ b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSecurityConfigFinder.java @@ -0,0 +1,94 @@ +/* + * Copyright 2024, Brightspot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gyro.aws.opensearchserverless; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import gyro.aws.AwsFinder; +import gyro.core.Type; +import software.amazon.awssdk.services.opensearchserverless.OpenSearchServerlessClient; +import software.amazon.awssdk.services.opensearchserverless.model.ListSecurityConfigsResponse; +import software.amazon.awssdk.services.opensearchserverless.model.ResourceNotFoundException; +import software.amazon.awssdk.services.opensearchserverless.model.SecurityConfigDetail; +import software.amazon.awssdk.services.opensearchserverless.model.SecurityConfigSummary; +import software.amazon.awssdk.services.opensearchserverless.model.SecurityConfigType; + +/** + * Query OpenSearch Serverless security configuration. + * + * Example + * ------- + * + * .. code-block:: gyro + * + * security-config: $(external-query aws::opensearch-serverless-security-config { security-config-id: ''}) + */ +@Type("opensearch-serverless-security-config") +public class OpenSearchServerlessSecurityConfigFinder + extends AwsFinder { + + private String securityConfigId; + + /** + * The id of the security configuration. + */ + public String getSecurityConfigId() { + return securityConfigId; + } + + public void setSecurityConfigId(String securityConfigId) { + this.securityConfigId = securityConfigId; + } + + @Override + protected List findAllAws(OpenSearchServerlessClient client) { + List securityConfigurations = new ArrayList<>(); + for (SecurityConfigType type : SecurityConfigType.knownValues()) { + ListSecurityConfigsResponse response = client.listSecurityConfigs(r -> r.type(type)); + List securityConfigIds = response.securityConfigSummaries() + .stream() + .map(SecurityConfigSummary::id) + .collect(Collectors.toList()); + + for (String securityConfigId : securityConfigIds) { + securityConfigurations.add(client.getSecurityConfig(r -> r.id(securityConfigId)) + .securityConfigDetail()); + } + } + + return securityConfigurations; + } + + @Override + protected List findAws(OpenSearchServerlessClient client, Map filters) { + List securityConfigurations = new ArrayList<>(); + if (filters.containsKey("security-config-id")) { + try { + securityConfigurations.add(client.getSecurityConfig(r -> r.id(filters.get("security-config-id"))) + .securityConfigDetail()); + } catch (ResourceNotFoundException ex) { + // Ignore + } + } + + return securityConfigurations; + } + +} diff --git a/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSecurityConfigResource.java b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSecurityConfigResource.java new file mode 100644 index 000000000..e1f06e9e5 --- /dev/null +++ b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSecurityConfigResource.java @@ -0,0 +1,282 @@ +/* + * Copyright 2024, Brightspot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gyro.aws.opensearchserverless; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import gyro.aws.AwsResource; +import gyro.aws.Copyable; +import gyro.core.GyroUI; +import gyro.core.Type; +import gyro.core.resource.Id; +import gyro.core.resource.Output; +import gyro.core.resource.Resource; +import gyro.core.resource.Updatable; +import gyro.core.scope.State; +import gyro.core.validation.ConflictsWith; +import gyro.core.validation.DependsOn; +import gyro.core.validation.Required; +import gyro.core.validation.ValidStrings; +import gyro.core.validation.ValidationError; +import software.amazon.awssdk.services.opensearchserverless.OpenSearchServerlessClient; +import software.amazon.awssdk.services.opensearchserverless.model.CreateSecurityConfigRequest; +import software.amazon.awssdk.services.opensearchserverless.model.CreateSecurityConfigResponse; +import software.amazon.awssdk.services.opensearchserverless.model.GetSecurityConfigResponse; +import software.amazon.awssdk.services.opensearchserverless.model.ResourceNotFoundException; +import software.amazon.awssdk.services.opensearchserverless.model.SecurityConfigDetail; +import software.amazon.awssdk.services.opensearchserverless.model.SecurityConfigType; +import software.amazon.awssdk.services.opensearchserverless.model.UpdateSecurityConfigRequest; + +/** + * Creates an OpenSearch Serverless security configuration. + * + * Example + * ------- + * + * .. code-block:: gyro + * + * aws::opensearch-serverless-security-config example-security-config + * name: "example-security-config" + * description: "example-security-config" + * type: "saml" + * saml-config + * group-attribute: "group-attribute" + * metadata: "metadata" + * session-timeout: 60 + * user-attribute: "user-attribute" + * end + * end + */ +@Type("opensearch-serverless-security-config") +public class OpenSearchServerlessSecurityConfigResource extends AwsResource implements Copyable { + + private String name; + private String description; + private SecurityConfigType type; + private String configVersion; + private OpenSearchServerlessSamlConfig samlConfig; + private OpenSearchServerlessIamIdentityCenterConfig iamIdentityCenterConfig; + private String id; + + /** + * The name of the security configuration. + */ + @Required + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * The description of the security configuration. + */ + @Updatable + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + /** + * The type of the security configuration. + */ + @Required + @ValidStrings({ "saml", "iamidentitycenter" }) + public SecurityConfigType getType() { + return type; + } + + public void setType(SecurityConfigType type) { + this.type = type; + } + + /** + * The version of the security configuration. + */ + @Updatable + public String getConfigVersion() { + return configVersion; + } + + public void setConfigVersion(String configVersion) { + this.configVersion = configVersion; + } + + /** + * The SAML configuration for the security configuration. + * + * @subresource gyro.aws.opensearchserverless.OpenSearchServerlessSamlConfig + */ + @Updatable + @DependsOn("type") + @ConflictsWith("iam-identity-center-config") + public OpenSearchServerlessSamlConfig getSamlConfig() { + return samlConfig; + } + + public void setSamlConfig(OpenSearchServerlessSamlConfig samlConfig) { + this.samlConfig = samlConfig; + } + + /** + * The IAM Identity Center configuration for the security configuration. + * + * @subresource gyro.aws.opensearchserverless.OpenSearchServerlessIamIdentityCenterConfig + */ + @Updatable + @DependsOn("type") + @ConflictsWith("saml-config") + public OpenSearchServerlessIamIdentityCenterConfig getIamIdentityCenterConfig() { + return iamIdentityCenterConfig; + } + + public void setIamIdentityCenterConfig(OpenSearchServerlessIamIdentityCenterConfig iamIdentityCenterConfig) { + this.iamIdentityCenterConfig = iamIdentityCenterConfig; + } + + /** + * The ID of the security configuration. + */ + @Id + @Output + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public void copyFrom(SecurityConfigDetail model) { + setDescription(model.description()); + setType(model.type()); + setConfigVersion(model.configVersion()); + setId(model.id()); + setSamlConfig(null); + setIamIdentityCenterConfig(null); + if (model.samlOptions() != null) { + OpenSearchServerlessSamlConfig samlConfig = newSubresource(OpenSearchServerlessSamlConfig.class); + samlConfig.copyFrom(model.samlOptions()); + setSamlConfig(samlConfig); + } + + if (model.iamIdentityCenterOptions() != null) { + OpenSearchServerlessIamIdentityCenterConfig iamIdentityCenterConfig = newSubresource( + OpenSearchServerlessIamIdentityCenterConfig.class); + iamIdentityCenterConfig.copyFrom(model.iamIdentityCenterOptions()); + setIamIdentityCenterConfig(iamIdentityCenterConfig); + } + } + + @Override + public boolean refresh() { + try { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + GetSecurityConfigResponse response = client.getSecurityConfig(r -> r.id(getId())); + copyFrom(response.securityConfigDetail()); + return true; + } catch (ResourceNotFoundException ex) { + // ignore + } + + return false; + } + + @Override + public void create(GyroUI ui, State state) throws Exception { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + String token = UUID.randomUUID().toString(); + CreateSecurityConfigRequest.Builder builder = CreateSecurityConfigRequest.builder() + .clientToken(token) + .description(getDescription()) + .name(getName()) + .type(getType()); + + if (getSamlConfig() != null) { + builder = builder.samlOptions(getSamlConfig().toSamlConfigOptions()); + } + + if (getIamIdentityCenterConfig() != null) { + builder = builder.iamIdentityCenterOptions(getIamIdentityCenterConfig().toIamIdentityCenterConfigOptionsCreate()); + } + + CreateSecurityConfigResponse response = client.createSecurityConfig(builder.build()); + + copyFrom(response.securityConfigDetail()); + } + + @Override + public void update(GyroUI ui, State state, Resource current, Set changedFieldNames) throws Exception { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + String token = UUID.randomUUID().toString(); + UpdateSecurityConfigRequest.Builder builder = UpdateSecurityConfigRequest.builder() + .clientToken(token) + .id(getId()) + .description(getDescription()) + .configVersion(getConfigVersion()); + + if (getSamlConfig() != null) { + builder = builder.samlOptions(getSamlConfig().toSamlConfigOptions()); + } + + if (getIamIdentityCenterConfig() != null) { + builder = builder.iamIdentityCenterOptionsUpdates(getIamIdentityCenterConfig() + .toIamIdentityCenterConfigOptionsUpdate()); + } + + client.updateSecurityConfig(builder.build()); + } + + @Override + public void delete(GyroUI ui, State state) throws Exception { + try { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + String token = UUID.randomUUID().toString(); + client.deleteSecurityConfig(r -> r.id(getId()).clientToken(token)); + } catch (ResourceNotFoundException ex) { + // ignore + } + } + + @Override + public List validate(Set configuredFields) { + List errors = new ArrayList<>(); + if (getType() != null) { + if (getType().equals(SecurityConfigType.SAML) && getSamlConfig() == null) { + errors.add(new ValidationError(this, null, "SAML configuration is required for 'saml' type.")); + } + + if (getType().equals(SecurityConfigType.IAMIDENTITYCENTER) && getIamIdentityCenterConfig() == null) { + errors.add(new ValidationError( + this, + null, + "IAM Identity Center configuration is required for 'iamidentitycenter' type.")); + } + } + + return errors; + } +} diff --git a/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSecurityPolicyFinder.java b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSecurityPolicyFinder.java new file mode 100644 index 000000000..477bf7d70 --- /dev/null +++ b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSecurityPolicyFinder.java @@ -0,0 +1,107 @@ +/* + * Copyright 2024, Brightspot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gyro.aws.opensearchserverless; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import gyro.aws.AwsFinder; +import gyro.core.Type; +import software.amazon.awssdk.services.opensearchserverless.OpenSearchServerlessClient; +import software.amazon.awssdk.services.opensearchserverless.model.SecurityPolicyDetail; +import software.amazon.awssdk.services.opensearchserverless.model.SecurityPolicySummary; +import software.amazon.awssdk.services.opensearchserverless.model.SecurityPolicyType; + +/** + * Query OpenSearch Serverless security policy. + * + * Example + * ------- + * + * .. code-block:: gyro + * + * security-policy: $(external-query aws::opensearch-serverless-security-policy { name: ''}) + */ +@Type("opensearch-serverless-security-policy") +public class OpenSearchServerlessSecurityPolicyFinder + extends AwsFinder { + + private String name; + private String type; + + /** + * The name of the security policy. + */ + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * The type of the security policy. + */ + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Override + protected List findAllAws(OpenSearchServerlessClient client) { + List securityPolicyDetails = new ArrayList<>(); + for (SecurityPolicyType type : SecurityPolicyType.knownValues()) { + List securityPolicies = client.listSecurityPolicies(r -> r.type(type)) + .securityPolicySummaries() + .stream() + .map(SecurityPolicySummary::name) + .collect(Collectors.toList()); + + for (String securityPolicyName : securityPolicies) { + securityPolicyDetails.add(client.getSecurityPolicy(r -> r + .name(securityPolicyName) + .type(type)) + .securityPolicyDetail()); + } + } + + return securityPolicyDetails; + } + + @Override + protected List findAws(OpenSearchServerlessClient client, Map filters) { + List securityPolicyDetails = new ArrayList<>(); + + if (filters.containsKey("name") && filters.containsKey("type")) { + try { + securityPolicyDetails.add(client.getSecurityPolicy(r -> r + .name(filters.get("name")) + .type(SecurityPolicyType.fromValue(filters.get("type")))).securityPolicyDetail()); + } catch (Exception ex) { + // ignore + } + } + + return securityPolicyDetails; + } +} diff --git a/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSecurityPolicyResource.java b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSecurityPolicyResource.java new file mode 100644 index 000000000..1e03416ae --- /dev/null +++ b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessSecurityPolicyResource.java @@ -0,0 +1,202 @@ +/* + * Copyright 2024, Brightspot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gyro.aws.opensearchserverless; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Set; +import java.util.UUID; + +import gyro.aws.AwsResource; +import gyro.aws.Copyable; +import gyro.aws.iam.PolicyResource; +import gyro.core.GyroException; +import gyro.core.GyroUI; +import gyro.core.Type; +import gyro.core.resource.Id; +import gyro.core.resource.Resource; +import gyro.core.resource.Updatable; +import gyro.core.scope.State; +import gyro.core.validation.Required; +import gyro.core.validation.ValidStrings; +import software.amazon.awssdk.services.opensearchserverless.OpenSearchServerlessClient; +import software.amazon.awssdk.services.opensearchserverless.model.CreateSecurityPolicyResponse; +import software.amazon.awssdk.services.opensearchserverless.model.GetSecurityPolicyResponse; +import software.amazon.awssdk.services.opensearchserverless.model.ResourceNotFoundException; +import software.amazon.awssdk.services.opensearchserverless.model.SecurityPolicyDetail; +import software.amazon.awssdk.services.opensearchserverless.model.SecurityPolicyType; +import software.amazon.awssdk.utils.IoUtils; + +/** + * Create an OpenSearch Serverless security policy. + * + * Example + * ------- + * + * .. code-block:: gyro + * + * aws::opensearch-serverless-security-policy security-policy + * name: "example-security-policy" + * description: "example-security-policy-description" + * type: "encryption" + * policy: "encryption-policy.json" + * end + */ +@Type("opensearch-serverless-security-policy") +public class OpenSearchServerlessSecurityPolicyResource extends AwsResource implements Copyable { + + private String name; + private String description; + private SecurityPolicyType type; + private String policy; + private String policyVersion; + + /** + * The name of the security policy. + */ + @Id + @Required + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * The description of the security policy. + */ + @Updatable + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + /** + * The type of the security policy. + */ + @Required + @ValidStrings({ "network", "encryption" }) + public SecurityPolicyType getType() { + return type; + } + + public void setType(SecurityPolicyType type) { + this.type = type; + } + + /** + * The policy of the security policy. A policy path or policy string is allowed. + */ + @Required + @Updatable + public String getPolicy() { + if (policy != null && policy.contains(".json")) { + try (InputStream input = openInput(policy)) { + policy = PolicyResource.formatPolicy(IoUtils.toUtf8String(input)); + return policy; + } catch (IOException err) { + throw new GyroException(err.getMessage()); + } + } else { + return PolicyResource.formatPolicy(policy); + } + } + + public void setPolicy(String policy) { + this.policy = policy; + } + + /** + * The policy version of the security policy. + */ + @Updatable + public String getPolicyVersion() { + return policyVersion; + } + + public void setPolicyVersion(String policyVersion) { + this.policyVersion = policyVersion; + } + + @Override + public void copyFrom(SecurityPolicyDetail model) { + setName(model.name()); + setDescription(model.description()); + setType(model.type()); + setPolicy(model.policy().toString()); + setPolicyVersion(model.policyVersion()); + } + + @Override + public boolean refresh() { + try { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + GetSecurityPolicyResponse response = client.getSecurityPolicy(r -> r.name(getName()) + .type(getType()) + .build()); + copyFrom(response.securityPolicyDetail()); + return true; + } catch (ResourceNotFoundException ex) { + // ignore + } + + return false; + } + + @Override + public void create(GyroUI ui, State state) throws Exception { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + String token = UUID.randomUUID().toString(); + CreateSecurityPolicyResponse response = client.createSecurityPolicy(r -> r.clientToken(token) + .description(getDescription()) + .name(getName()) + .policy(getPolicy()) + .type(getType()) + ); + + copyFrom(response.securityPolicyDetail()); + } + + @Override + public void update(GyroUI ui, State state, Resource current, Set changedFieldNames) throws Exception { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + String token = UUID.randomUUID().toString(); + client.updateSecurityPolicy(r -> r.clientToken(token) + .name(getName()) + .policy(getPolicy()) + .type(getType()) + .description(getDescription()) + .policyVersion(getPolicyVersion()) + ); + } + + @Override + public void delete(GyroUI ui, State state) throws Exception { + try { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + String token = UUID.randomUUID().toString(); + client.deleteSecurityPolicy(r -> r.clientToken(token).name(getName()).type(getType())); + } catch (ResourceNotFoundException ex) { + // ignore + } + } +} diff --git a/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessVpcEndpointFinder.java b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessVpcEndpointFinder.java new file mode 100644 index 000000000..083de751a --- /dev/null +++ b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessVpcEndpointFinder.java @@ -0,0 +1,89 @@ +/* + * Copyright 2024, Brightspot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gyro.aws.opensearchserverless; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import gyro.aws.AwsFinder; +import gyro.core.Type; +import software.amazon.awssdk.services.opensearchserverless.OpenSearchServerlessClient; +import software.amazon.awssdk.services.opensearchserverless.model.ResourceNotFoundException; +import software.amazon.awssdk.services.opensearchserverless.model.VpcEndpointDetail; +import software.amazon.awssdk.services.opensearchserverless.model.VpcEndpointSummary; +import software.amazon.awssdk.utils.builder.SdkBuilder; + +/** + * Query OpenSearch Serverless VPC endpoint. + * + * Example + * ------- + * + * .. code-block:: gyro + * + * vpc-endpoint: $(external-query aws::opensearch-serverless-vpc-endpoint { vpc-endpoint-id: ''}) + */ +@Type("opensearch-serverless-vpc-endpoint") +public class OpenSearchServerlessVpcEndpointFinder + extends AwsFinder { + + private String vpcEndpointId; + + /** + * The ID of the VPC endpoint. + */ + public String getVpcEndpointId() { + return vpcEndpointId; + } + + public void setVpcEndpointId(String vpcEndpointId) { + this.vpcEndpointId = vpcEndpointId; + } + + @Override + protected List findAllAws(OpenSearchServerlessClient client) { + List vpcEndpoints = new ArrayList<>(); + List vpcEndpointIds = client.listVpcEndpoints(SdkBuilder::build).vpcEndpointSummaries().stream() + .map(VpcEndpointSummary::id) + .collect(Collectors.toList()); + + if (!vpcEndpointIds.isEmpty()) { + vpcEndpoints = client.batchGetVpcEndpoint(r -> r.ids(vpcEndpointIds).build()).vpcEndpointDetails(); + } + + return vpcEndpoints; + } + + @Override + protected List findAws(OpenSearchServerlessClient client, Map filters) { + List vpcEndpoints = new ArrayList<>(); + + if (filters.containsKey("vpc-endpoint-id")) { + try { + vpcEndpoints = client.batchGetVpcEndpoint(r -> r.ids(filters.get("vpc-endpoint-id"))) + .vpcEndpointDetails(); + } catch (ResourceNotFoundException ex) { + // Ignore + } + } + + return vpcEndpoints; + } + +} diff --git a/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessVpcEndpointResource.java b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessVpcEndpointResource.java new file mode 100644 index 000000000..8a2b1addd --- /dev/null +++ b/src/main/java/gyro/aws/opensearchserverless/OpenSearchServerlessVpcEndpointResource.java @@ -0,0 +1,294 @@ +/* + * Copyright 2024, Brightspot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gyro.aws.opensearchserverless; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import gyro.aws.AwsResource; +import gyro.aws.Copyable; +import gyro.aws.ec2.SecurityGroupResource; +import gyro.aws.ec2.SubnetResource; +import gyro.aws.ec2.VpcResource; +import gyro.core.GyroUI; +import gyro.core.TimeoutSettings; +import gyro.core.Type; +import gyro.core.Wait; +import gyro.core.resource.Id; +import gyro.core.resource.Output; +import gyro.core.resource.Resource; +import gyro.core.resource.Updatable; +import gyro.core.scope.State; +import gyro.core.validation.Required; +import software.amazon.awssdk.services.opensearchserverless.OpenSearchServerlessClient; +import software.amazon.awssdk.services.opensearchserverless.model.BatchGetVpcEndpointResponse; +import software.amazon.awssdk.services.opensearchserverless.model.CreateVpcEndpointResponse; +import software.amazon.awssdk.services.opensearchserverless.model.ResourceNotFoundException; +import software.amazon.awssdk.services.opensearchserverless.model.UpdateVpcEndpointRequest; +import software.amazon.awssdk.services.opensearchserverless.model.VpcEndpointDetail; +import software.amazon.awssdk.services.opensearchserverless.model.VpcEndpointStatus; + +/** + * Create an OpenSearch Serverless VPC endpoint. + * + * Example + * ------- + * + * .. code-block:: gyro + * + * aws::opensearch-serverless-vpc-endpoint vpc-endpoint + * name: "opensearch-serverless-vpc-endpoint-example" + * vpc: $(aws::vpc vpc-example) + * subnets: [ + * $(aws::subnet subnet-example-1), + * $(aws::subnet subnet-example-2) + * ] + * security-groups: [ + * $(aws::security-group security-group-example-1), + * $(aws::security-group security-group-example-2) + * ] + * end + */ +@Type("opensearch-serverless-vpc-endpoint") +public class OpenSearchServerlessVpcEndpointResource extends AwsResource implements Copyable { + + private String name; + private Set subnets; + private Set securityGroups; + private VpcResource vpc; + private String id; + + /** + * The name of the VPC endpoint. + */ + @Required + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * The subnets in which the VPC endpoint will be created. + */ + @Updatable + public Set getSubnets() { + if (subnets == null) { + subnets = new HashSet<>(); + } + + return subnets; + } + + public void setSubnets(Set subnets) { + this.subnets = subnets; + } + + /** + * The security groups that will be associated with the VPC endpoint. + */ + @Updatable + public Set getSecurityGroups() { + if (securityGroups == null) { + securityGroups = new HashSet<>(); + } + + return securityGroups; + } + + public void setSecurityGroups(Set securityGroups) { + this.securityGroups = securityGroups; + } + + /** + * The VPC in which the VPC endpoint will be created. + */ + @Required + public VpcResource getVpc() { + return vpc; + } + + public void setVpc(VpcResource vpc) { + this.vpc = vpc; + } + + /** + * The ID of the VPC endpoint. + */ + @Id + @Output + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public void copyFrom(VpcEndpointDetail model) { + setName(model.name()); + setId(model.id()); + setSubnets(model.subnetIds().stream().map(s -> findById(SubnetResource.class, s)).collect(Collectors.toSet())); + setSecurityGroups(model.securityGroupIds() + .stream() + .map(s -> findById(SecurityGroupResource.class, s)) + .collect(Collectors.toSet())); + setVpc(findById(VpcResource.class, model.vpcId())); + } + + @Override + public boolean refresh() { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + VpcEndpointDetail endpoint = getVpcEndpoint(client); + if (endpoint != null) { + copyFrom(endpoint); + return true; + } + + return false; + } + + @Override + public void create(GyroUI ui, State state) throws Exception { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + String token = UUID.randomUUID().toString(); + + CreateVpcEndpointResponse response = client.createVpcEndpoint(r -> r.clientToken(token) + .name(getName()) + .subnetIds(getSubnets().stream().map(SubnetResource::getId).collect(Collectors.toList())) + .securityGroupIds(getSecurityGroups().stream() + .map(SecurityGroupResource::getId) + .collect(Collectors.toList())) + .vpcId(getVpc().getId()) + ); + + setId(response.createVpcEndpointDetail().id()); + + Wait.atMost(20, TimeUnit.MINUTES) + .checkEvery(1, TimeUnit.MINUTES) + .resourceOverrides(this, TimeoutSettings.Action.CREATE) + .prompt(false) + .until(() -> { + VpcEndpointDetail endpoint = getVpcEndpoint(client); + return endpoint != null && endpoint.status().equals(VpcEndpointStatus.ACTIVE); + }); + } + + @Override + public void update(GyroUI ui, State state, Resource current, Set changedFieldNames) throws Exception { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + String token = UUID.randomUUID().toString(); + UpdateVpcEndpointRequest.Builder builder = UpdateVpcEndpointRequest.builder() + .clientToken(token) + .id(getId()); + + OpenSearchServerlessVpcEndpointResource old = (OpenSearchServerlessVpcEndpointResource) current; + + if (changedFieldNames.contains("subnets")) { + Optional.of(old.getSubnets().stream() + .map(SubnetResource::getId) + .filter(id -> getSubnets().stream() + .map(SubnetResource::getId) + .noneMatch(id::equals)) + .collect(Collectors.toList())) + .filter(list -> !list.isEmpty()) + .ifPresent(builder::removeSubnetIds); + + Optional.of(getSubnets().stream() + .map(SubnetResource::getId) + .filter(id -> old.getSubnets().stream() + .map(SubnetResource::getId) + .noneMatch(id::equals)) + .collect(Collectors.toList())) + .filter(list -> !list.isEmpty()) + .ifPresent(builder::addSubnetIds); + + } + + if (changedFieldNames.contains("security-groups")) { + Optional.of(old.getSecurityGroups().stream() + .map(SecurityGroupResource::getId) + .filter(id -> getSecurityGroups().stream() + .map(SecurityGroupResource::getId) + .noneMatch(id::equals)) + .collect(Collectors.toList())) + .filter(list -> !list.isEmpty()) + .ifPresent(builder::removeSecurityGroupIds); + + Optional.of(getSecurityGroups().stream() + .map(SecurityGroupResource::getId) + .filter(id -> old.getSecurityGroups().stream() + .map(SecurityGroupResource::getId) + .noneMatch(id::equals)) + .collect(Collectors.toList())) + .filter(list -> !list.isEmpty()) + .ifPresent(builder::addSecurityGroupIds); + } + + client.updateVpcEndpoint(builder.build()); + + Wait.atMost(20, TimeUnit.MINUTES) + .checkEvery(1, TimeUnit.MINUTES) + .resourceOverrides(this, TimeoutSettings.Action.CREATE) + .prompt(false) + .until(() -> { + VpcEndpointDetail endpoint = getVpcEndpoint(client); + return endpoint != null && endpoint.status().equals(VpcEndpointStatus.ACTIVE); + }); + } + + @Override + public void delete(GyroUI ui, State state) throws Exception { + try { + OpenSearchServerlessClient client = createClient(OpenSearchServerlessClient.class); + String token = UUID.randomUUID().toString(); + client.deleteVpcEndpoint(r -> r.clientToken(token) + .id(getId()) + ); + + Wait.atMost(20, TimeUnit.MINUTES) + .checkEvery(1, TimeUnit.MINUTES) + .resourceOverrides(this, TimeoutSettings.Action.UPDATE) + .prompt(false) + .until(() -> getVpcEndpoint(client) == null); + } catch (ResourceNotFoundException ex) { + // ignore + } + } + + private VpcEndpointDetail getVpcEndpoint(OpenSearchServerlessClient client) { + VpcEndpointDetail vpcEndpoint = null; + + try { + BatchGetVpcEndpointResponse response = client.batchGetVpcEndpoint(r -> r.ids(getId())); + if (response.hasVpcEndpointDetails() && !response.vpcEndpointDetails().isEmpty()) { + vpcEndpoint = response.vpcEndpointDetails().get(0); + } + } catch (ResourceNotFoundException ex) { + // Ignore + } + + return vpcEndpoint; + } +} diff --git a/src/main/java/gyro/aws/opensearchserverless/package-info.java b/src/main/java/gyro/aws/opensearchserverless/package-info.java new file mode 100644 index 000000000..e12b23cf5 --- /dev/null +++ b/src/main/java/gyro/aws/opensearchserverless/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2024, Brightspot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@DocGroup("Open Search Serverless") +package gyro.aws.opensearchserverless; + +import gyro.core.resource.DocGroup;