Repository: opensearch-project/opensearch-go
Pull request #216 (2023-01-17 19:36:38)

Adding support and examples for AOSS
Signed-off-by: Vacha Shah <vachshah@amazon.com>

### Description
Adding support and examples for AOSS.

### Issues Resolved
#204 

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check [here](https://github.com/opensearch-project/OpenSearch/blob/main/CONTRIBUTING.md#developer-certificate-of-origin).


dblock: I fully expect this not to be needed soon for AOSS, this is work in progress server-side.
dblock: The root API will be (re)added to OpenSearch Serverless.

I propose we remove the product check altogether, similar to https://github.com/opensearch-project/opensearch-ruby/issues/130. We try to do a version check to see if the client is compatible with the server. I believe this was inherited from Elastic that was trying to maintain some sense of version compatibility without semver, but since we follow semver it's easy to know now whether your client will work against a server or not. My concern is that a version request every single time a new instance of a client is created is not insignificant overhead (in a web app example this could end up making a root request every time a web app serves a client search request!), and is an unexpected side effect when users try to use the client.
VachaShah: > I fully expect this not to be needed soon for AOSS, this is work in progress server-side.

Sounds good! I removed the content-length change from this PR.
VachaShah: > The root API will be (re)added to OpenSearch Serverless.
> 
> I propose we remove the product check altogether, similar to [opensearch-project/opensearch-ruby#130](https://github.com/opensearch-project/opensearch-ruby/issues/130). We try to do a version check to see if the client is compatible with the server. I believe this was inherited from Elastic that was trying to maintain some sense of version compatibility without semver, but since we follow semver it's easy to know now whether your client will work against a server or not. My concern is that a version request every single time a new instance of a client is created is not insignificant overhead (in a web app example this could end up making a root request every time a web app serves a client search request!), and is an unexpected side effect when users try to use the client.

Sure! I removed the product info check altogether.
VachaShah: @dblock This is ready for review.
VachaShah: > I think we should make a separate PR with an entry that removes the version check, so we can make it very visible. Should we major increment version, too?
> 
> For docs I'd say managed and serverless work the same and the only thing you want to change is the service signing name. Let's say that in the user guide?

Created a separate PR #219 for removing the version check and incrementing the version. 
Updated the user guide.
VachaShah: @dblock I addressed the comments for documentation, it needs your approval once more.
VachaShah: @VijayanB @dblock Review again please?

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6aabd2c..163f453 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
 ### Added
 - Github workflow for changelog verification ([#172](https://github.com/opensearch-project/opensearch-go/pull/172))
 - Add Go Documentation link for the client ([#182](https://github.com/opensearch-project/opensearch-go/pull/182))
+- Support for Amazon OpenSearch Serverless ([#216](https://github.com/opensearch-project/opensearch-go/pull/216))
 
 ### Dependencies
 - Bumps `github.com/stretchr/testify` from 1.8.0 to 1.8.1
diff --git a/USER_GUIDE.md b/USER_GUIDE.md
index 727e0eb..7cf07b4 100644
--- a/USER_GUIDE.md
+++ b/USER_GUIDE.md
@@ -1,6 +1,8 @@
 - [User Guide](#user-guide)
-    - [Example](#example)
-    - [How to use IAMs as authentication method](#how-to-use-iams-as-authentication-method)
+	- [Example](#example)
+	- [Amazon OpenSearch Service](#amazon-opensearch-service)
+			- [AWS SDK V1](#aws-sdk-v1)
+			- [AWS SDK V2](#aws-sdk-v2)
 
 # User Guide
 
@@ -136,7 +138,7 @@ func main() {
 
 ```
 
-## How to use IAMs as authentication method
+## Amazon OpenSearch Service
 
 Before starting, we strongly recommend reading the full AWS documentation regarding using IAM credentials to sign
 requests to OpenSearch APIs.
@@ -221,32 +223,39 @@ func main() {
 
 #### AWS SDK V2
 
+Use the AWS SDK v2 for Go to authenticate with Amazon OpenSearch service.
+
 ```go
 package main
 
 import (
 	"context"
-	"io"
 	"log"
 
+	"github.com/aws/aws-sdk-go-v2/aws"
 	"github.com/aws/aws-sdk-go-v2/config"
-	"github.com/opensearch-project/opensearch-go/v2/opensearchapi"
+	opensearch "github.com/opensearch-project/opensearch-go/v2"
+	opensearchapi "github.com/opensearch-project/opensearch-go/v2/opensearchapi"
 	requestsigner "github.com/opensearch-project/opensearch-go/v2/signer/awsv2"
 )
 
-const endpoint = "" // e.g. https://opensearch-domain.region.com
+const endpoint = "" // e.g. https://opensearch-domain.region.com or Amazon OpenSearch Serverless endpoint
 
 func main() {
 	ctx := context.Background()
 
-	cfg, err := config.LoadDefaultConfig(ctx)
+	awsCfg, err := config.LoadDefaultConfig(ctx,
+		config.WithRegion("<AWS_REGION>"),
+		config.WithCredentialsProvider(
+			getCredentialProvider("<AWS_ACCESS_KEY>", "<AWS_SECRET_ACCESS_KEY>", "<AWS_SESSION_TOKEN>"),
+		),
+	)
 	if err != nil {
 		log.Fatal(err) // Do not log.fatal in a production ready app.
 	}
 
 	// Create an AWS request Signer and load AWS configuration using default config folder or env vars.
-	// See https://docs.aws.amazon.com/opensearch-service/latest/developerguide/request-signing.html#request-signing-go
-	signer, err := requestsigner.NewSigner(cfg)
+	signer, err := requestsigner.NewSignerWithService(awsCfg, "es") // "aoss" for Amazon OpenSearch Serverless
 	if err != nil {
 		log.Fatal(err) // Do not log.fatal in a production ready app.
 	}
@@ -260,26 +269,52 @@ func main() {
 		log.Fatal("client creation err", err)
 	}
 
-	ping := opensearchapi.PingRequest{}
+	indexName = "go-test-index"
 
-	resp, err := ping.Do(ctx, client)
+	// Define index mapping.
+	mapping := strings.NewReader(`{
+	 "settings": {
+	   "index": {
+	        "number_of_shards": 4
+	        }
+	      }
+	 }`)
+    
+	// Create an index with non-default settings.
+	createIndex := opensearchapi.IndicesCreateRequest{
+		Index: indexName,
+		Body:  mapping,
+	}
+	createIndexResponse, err := createIndex.Do(context.Background(), client)
 	if err != nil {
-		log.Fatal(err)
+		log.Println("Error ", err.Error())
+		log.Println("failed to create index ", err)
+		log.Fatal("create response body read err", err)
 	}
-	defer resp.Body.Close()
+	log.Println(createIndexResponse)
 
-	if resp.IsError() {
-		log.Println("ping response status ", resp.Status())
-
-		respBody, err := io.ReadAll(resp.Body)
-		if err != nil {
-			log.Fatal("response body read err", err)
-		}
+	// Delete previously created index.
+	deleteIndex := opensearchapi.IndicesDeleteRequest{
+		Index: []string{indexName},
+	}
 
-		log.Fatal("ping resp body", respBody)
+	deleteIndexResponse, err := deleteIndex.Do(context.Background(), client)
+	if err != nil {
+		log.Println("failed to delete index ", err)
+		log.Fatal("delete index response body read err", err)
 	}
+	log.Println("deleting index", deleteIndexResponse)
+}
 
-	log.Println("PING OK")
+func getCredentialProvider(accessKey, secretAccessKey, token string) aws.CredentialsProviderFunc {
+	return func(ctx context.Context) (aws.Credentials, error) {
+		c := &aws.Credentials{
+			AccessKeyID:     accessKey,
+			SecretAccessKey: secretAccessKey,
+			SessionToken:    token,
+		}
+		return *c, nil
+	}
 }
 
 ```
diff --git a/signer/awsv2/sdkv2signer.go b/signer/awsv2/sdkv2signer.go
index 3d44a17..1312fae 100644
--- a/signer/awsv2/sdkv2signer.go
+++ b/signer/awsv2/sdkv2signer.go
@@ -68,6 +68,7 @@ func (s *awsSdkV2Signer) SignRequest(r *http.Request) error {
 	}
 
 	hash, err := hexEncodedSha256OfRequest(r)
+	r.Header.Set("X-Amz-Content-Sha256", hash)
 	if err != nil {
 		return err
 	}


Test patch:
diff --git a/signer/awsv2/sdkv2signer_test.go b/signer/awsv2/sdkv2signer_test.go
index 95d1f24..fcc0ce3 100644
--- a/signer/awsv2/sdkv2signer_test.go
+++ b/signer/awsv2/sdkv2signer_test.go
@@ -81,6 +81,7 @@ func TestV4SignerAwsSdkV2(t *testing.T) {
 		q := req.Header
 		assert.NotEmpty(t, q.Get("Authorization"))
 		assert.NotEmpty(t, q.Get("X-Amz-Date"))
+		assert.NotEmpty(t, q.Get("X-Amz-Content-Sha256"))
 	})
 	t.Run("sign request success with body", func(t *testing.T) {
 		req, err := http.NewRequest(
@@ -109,6 +110,7 @@ func TestV4SignerAwsSdkV2(t *testing.T) {
 		q := req.Header
 		assert.NotEmpty(t, q.Get("Authorization"))
 		assert.NotEmpty(t, q.Get("X-Amz-Date"))
+		assert.NotEmpty(t, q.Get("X-Amz-Content-Sha256"))
 	})
 
 	t.Run("sign request success with body for other AWS Services", func(t *testing.T) {
@@ -139,6 +141,7 @@ func TestV4SignerAwsSdkV2(t *testing.T) {
 		q := req.Header
 		assert.NotEmpty(t, q.Get("Authorization"))
 		assert.NotEmpty(t, q.Get("X-Amz-Date"))
+		assert.NotEmpty(t, q.Get("X-Amz-Content-Sha256"))
 	})
 
 	t.Run("sign request failed due to invalid service", func(t *testing.T) {