Repository: opensearch-project/opensearch-go (go)

Issue:
[BUG] User guide example doesn't create index with given mapping
**What is the bug?**
Example at UserGuide (https://github.com/opensearch-project/opensearch-go/blob/main/USER_GUIDE.md) is incomplete. It does not create an index with specified mapping.

**How can one reproduce the bug?**
Execute example at https://github.com/opensearch-project/opensearch-go/blob/main/USER_GUIDE.md 

**What is the expected behavior?**
Use [this](https://github.com/opensearch-project/opensearch-go/blob/main/opensearchapi/api.indices.create.go) api to create mapping before inserting documents.

**What is your host/environment?**
N/A

**Do you have any screenshots?**
N/A

**Do you have any additional context?**
N/A


Pull request:
Update to do request of create index in USER_GUIDE
### Description

In the original example, no indexing was done and the variable is only used in fmt.Println(). This could be misleading to the user, so we have corrected it so that indexing is performed.

### Issues Resolved

Closes #98

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).


Patch:
diff --git a/USER_GUIDE.md b/USER_GUIDE.md
index b6ee25a..afd7788 100644
--- a/USER_GUIDE.md
+++ b/USER_GUIDE.md
@@ -51,12 +51,17 @@ func main() {
           }
      }`)
     
-    // Create an index with non-default settings.
-    res := opensearchapi.CreateRequest{
-        Index: IndexName,
+    // Create an index with non-default settings. 
+    createIndex := opensearchapi.IndicesCreateRequest{
+        Index: IndexName, 
         Body:  mapping,
     }
-    fmt.Println("creating index", res)
+    createIndexResponse, err := createIndex.Do(context.Background(), client)
+    if err != nil {
+        fmt.Println("failed to create index ", err)
+        os.Exit(1)
+    }
+    fmt.Println(createIndexResponse)
     
     // Add a document to the index.
     document := strings.NewReader(`{
diff --git a/go.mod b/go.mod
index df2ad6f..0b81c7b 100644
--- a/go.mod
+++ b/go.mod
@@ -1,9 +1,10 @@
 module github.com/opensearch-project/opensearch-go/v2
 
-go 1.11
+go 1.15
 
 require (
-	github.com/aws/aws-sdk-go v1.42.27
-	github.com/stretchr/testify v1.7.0
-	golang.org/x/text v0.3.7 // indirect
+	github.com/aws/aws-sdk-go v1.44.36
+	github.com/aws/aws-sdk-go-v2 v1.16.5
+	github.com/aws/aws-sdk-go-v2/config v1.15.11
+	github.com/stretchr/testify v1.7.2
 )
diff --git a/go.sum b/go.sum
index 186d995..1579987 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,31 @@
-github.com/aws/aws-sdk-go v1.42.27 h1:kxsBXQg3ee6LLbqjp5/oUeDgG7TENFrWYDmEVnd7spU=
-github.com/aws/aws-sdk-go v1.42.27/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc=
+github.com/aws/aws-sdk-go v1.44.36 h1:zeHQau9fGEIE0CsBnQFCqqYWc98m2bk7BQKtkdEqzF4=
+github.com/aws/aws-sdk-go v1.44.36/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
+github.com/aws/aws-sdk-go-v2 v1.16.5 h1:Ah9h1TZD9E2S1LzHpViBO3Jz9FPL5+rmflmb8hXirtI=
+github.com/aws/aws-sdk-go-v2 v1.16.5/go.mod h1:Wh7MEsmEApyL5hrWzpDkba4gwAPc5/piwLVLFnCxp48=
+github.com/aws/aws-sdk-go-v2/config v1.15.11 h1:qfec8AtiCqVbwMcx51G1yO2PYVfWfhp2lWkDH65V9HA=
+github.com/aws/aws-sdk-go-v2/config v1.15.11/go.mod h1:mD5tNFciV7YHNjPpFYqJ6KGpoSfY107oZULvTHIxtbI=
+github.com/aws/aws-sdk-go-v2/credentials v1.12.6 h1:No1wZFW4bcM/uF6Tzzj6IbaeQJM+xxqXOYmoObm33ws=
+github.com/aws/aws-sdk-go-v2/credentials v1.12.6/go.mod h1:mQgnRmBPF2S/M01W4T4Obp3ZaZB6o1s/R8cOUda9vtI=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.6 h1:+NZzDh/RpcQTpo9xMFUgkseIam6PC+YJbdhbQp1NOXI=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.6/go.mod h1:ClLMcuQA/wcHPmOIfNzNI4Y1Q0oDbmEkbYhMFOzHDh8=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.12 h1:Zt7DDk5V7SyQULUUwIKzsROtVzp/kVvcz15uQx/Tkow=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.12/go.mod h1:Afj/U8svX6sJ77Q+FPWMzabJ9QjbwP32YlopgKALUpg=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.6 h1:eeXdGVtXEe+2Jc49+/vAzna3FAQnUD4AagAw8tzbmfc=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.6/go.mod h1:FwpAKI+FBPIELJIdmQzlLtRe8LQSOreMcM2wBsPMvvc=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.13 h1:L/l0WbIpIadRO7i44jZh1/XeXpNDX0sokFppb4ZnXUI=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.13/go.mod h1:hiM/y1XPp3DoEPhoVEYc/CZcS58dP6RKJRDFp99wdX0=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.6 h1:0ZxYAZ1cn7Swi/US55VKciCE6RhRHIwCKIWaMLdT6pg=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.6/go.mod h1:DxAPjquoEHf3rUHh1b9+47RAaXB8/7cB6jkzCt/GOEI=
+github.com/aws/aws-sdk-go-v2/service/sso v1.11.9 h1:Gju1UO3E8ceuoYc/AHcdXLuTZ0WGE1PT2BYDwcYhJg8=
+github.com/aws/aws-sdk-go-v2/service/sso v1.11.9/go.mod h1:UqRD9bBt15P0ofRyDZX6CfsIqPpzeHOhZKWzgSuAzpo=
+github.com/aws/aws-sdk-go-v2/service/sts v1.16.7 h1:HLzjwQM9975FQWSF3uENDGHT1gFQm/q3QXu2BYIcI08=
+github.com/aws/aws-sdk-go-v2/service/sts v1.16.7/go.mod h1:lVxTdiiSHY3jb1aeg+BBFtDzZGSUCv6qaNOyEGCJ1AY=
+github.com/aws/smithy-go v1.11.3 h1:DQixirEFM9IaKxX1olZ3ke3nvxRS2xMDteKIDWxozW8=
+github.com/aws/smithy-go v1.11.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
 github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
@@ -10,14 +34,13 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM=
-golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
+github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -25,5 +48,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/signer/awsv2/sdkv2signer.go b/signer/awsv2/sdkv2signer.go
new file mode 100644
index 0000000..3d44a17
--- /dev/null
+++ b/signer/awsv2/sdkv2signer.go
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: Apache-2.0
+//
+// The OpenSearch Contributors require contributions made to
+// this file be licensed under the Apache-2.0 license or a
+// compatible open source license.
+//
+// Modifications Copyright OpenSearch Contributors. See
+// GitHub history for details.
+
+package awsv2
+
+import (
+	"bytes"
+	"context"
+	"crypto/sha256"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"github.com/aws/aws-sdk-go-v2/aws"
+	awsSignerV4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
+	"github.com/opensearch-project/opensearch-go/v2/signer"
+	"io/ioutil"
+	"net/http"
+	"strings"
+	"time"
+)
+
+const (
+	openSearchService = "es"
+	emptyStringSHA256 = `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`
+)
+
+type awsSdkV2Signer struct {
+	service string
+	signer  *awsSignerV4.Signer
+	awsCfg  aws.Config
+}
+
+// NewSigner returns an instance of Signer for AWS OpenSearchService
+func NewSigner(cfg aws.Config) (signer.Signer, error) {
+	return NewSignerWithService(cfg, openSearchService)
+}
+
+// NewSignerWithService returns an instance of Signer for given service
+func NewSignerWithService(cfg aws.Config, service string) (signer.Signer, error) {
+	if len(strings.TrimSpace(service)) < 1 {
+		return nil, errors.New("service cannot be empty")
+	}
+
+	return &awsSdkV2Signer{
+		service: service,
+		signer:  awsSignerV4.NewSigner(),
+		awsCfg:  cfg,
+	}, nil
+}
+
+func (s *awsSdkV2Signer) SignRequest(r *http.Request) error {
+	ctx := context.Background()
+	t := time.Now()
+
+	creds, err := s.awsCfg.Credentials.Retrieve(ctx)
+	if err != nil {
+		return err
+	}
+
+	if len(s.awsCfg.Region) == 0 {
+		return fmt.Errorf("aws region cannot be empty")
+	}
+
+	hash, err := hexEncodedSha256OfRequest(r)
+	if err != nil {
+		return err
+	}
+
+	return s.signer.SignHTTP(ctx, creds, r, hash, s.service, s.awsCfg.Region, t)
+}
+
+func hexEncodedSha256OfRequest(r *http.Request) (string, error) {
+	if r.Body == nil {
+		return emptyStringSHA256, nil
+	}
+
+	hasher := sha256.New()
+	reqBodyBytes, err := ioutil.ReadAll(r.Body)
+	if err != nil {
+		return "", err
+	}
+
+	if err := r.Body.Close(); err != nil {
+		return "", err
+	}
+	r.Body = ioutil.NopCloser(bytes.NewBuffer(reqBodyBytes))
+
+	hasher.Write(reqBodyBytes)
+	digest := hasher.Sum(nil)
+
+	return hex.EncodeToString(digest), nil
+}


Test patch:
diff --git a/signer/awsv2/sdkv2signer_test.go b/signer/awsv2/sdkv2signer_test.go
new file mode 100644
index 0000000..95d1f24
--- /dev/null
+++ b/signer/awsv2/sdkv2signer_test.go
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: Apache-2.0
+//
+// The OpenSearch Contributors require contributions made to
+// this file be licensed under the Apache-2.0 license or a
+// compatible open source license.
+//
+// Modifications Copyright OpenSearch Contributors. See
+// GitHub history for details.
+
+package awsv2
+
+import (
+	"bytes"
+	"context"
+	"github.com/aws/aws-sdk-go-v2/aws"
+	"github.com/aws/aws-sdk-go-v2/config"
+	"github.com/stretchr/testify/assert"
+	"net/http"
+	"os"
+	"testing"
+)
+
+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
+	}
+}
+
+func TestV4SignerAwsSdkV2(t *testing.T) {
+	t.Run("sign request failed due to no region found", func(t *testing.T) {
+		req, err := http.NewRequest(http.MethodGet, "https://localhost:9200", nil)
+		assert.NoError(t, err)
+		region := os.Getenv("AWS_REGION")
+		os.Setenv("AWS_REGION", "")
+		defer func() {
+			os.Setenv("AWS_REGION", region)
+		}()
+		awsCfg, err := config.LoadDefaultConfig(context.TODO(),
+			config.WithCredentialsProvider(
+				getCredentialProvider("AKID", "SECRET_KEY", "TOKEN"),
+			),
+			config.WithRegion(""),
+		)
+		assert.NoError(t, err)
+
+		signer, err := NewSigner(awsCfg)
+		assert.NoError(t, err)
+		err = signer.SignRequest(req)
+
+		assert.EqualErrorf(
+			t, err, "aws region cannot be empty", "unexpected error")
+	})
+	t.Run("sign request success", func(t *testing.T) {
+		req, err := http.NewRequest(http.MethodGet, "https://localhost:9200", nil)
+		assert.NoError(t, err)
+		region := os.Getenv("AWS_REGION")
+		os.Setenv("AWS_REGION", "us-west-2")
+		defer func() {
+			os.Setenv("AWS_REGION", region)
+		}()
+
+		awsCfg, err := config.LoadDefaultConfig(context.TODO(),
+			config.WithRegion("us-west-2"),
+			config.WithCredentialsProvider(
+				getCredentialProvider("AKID", "SECRET_KEY", "TOKEN"),
+			),
+		)
+		assert.NoError(t, err)
+
+		signer, err := NewSigner(awsCfg)
+		assert.NoError(t, err)
+
+		err = signer.SignRequest(req)
+		assert.NoError(t, err)
+
+		q := req.Header
+		assert.NotEmpty(t, q.Get("Authorization"))
+		assert.NotEmpty(t, q.Get("X-Amz-Date"))
+	})
+	t.Run("sign request success with body", func(t *testing.T) {
+		req, err := http.NewRequest(
+			http.MethodPost, "https://localhost:9200",
+			bytes.NewBuffer([]byte(`some data`)))
+		assert.NoError(t, err)
+		region := os.Getenv("AWS_REGION")
+		os.Setenv("AWS_REGION", "us-west-2")
+		defer func() {
+			os.Setenv("AWS_REGION", region)
+		}()
+
+		awsCfg, err := config.LoadDefaultConfig(context.TODO(),
+			config.WithRegion("us-west-2"),
+			config.WithCredentialsProvider(
+				getCredentialProvider("AKID", "SECRET_KEY", "TOKEN"),
+			),
+		)
+		assert.NoError(t, err)
+
+		signer, err := NewSigner(awsCfg)
+		assert.NoError(t, err)
+
+		err = signer.SignRequest(req)
+		assert.NoError(t, err)
+		q := req.Header
+		assert.NotEmpty(t, q.Get("Authorization"))
+		assert.NotEmpty(t, q.Get("X-Amz-Date"))
+	})
+
+	t.Run("sign request success with body for other AWS Services", func(t *testing.T) {
+		req, err := http.NewRequest(
+			http.MethodPost, "https://localhost:9200",
+			bytes.NewBuffer([]byte(`some data`)))
+		assert.NoError(t, err)
+		region := os.Getenv("AWS_REGION")
+		os.Setenv("AWS_REGION", "us-west-2")
+		defer func() {
+			os.Setenv("AWS_REGION", region)
+		}()
+
+		awsCfg, err := config.LoadDefaultConfig(context.TODO(),
+			config.WithRegion("us-west-2"),
+			config.WithCredentialsProvider(
+				getCredentialProvider("AKID", "SECRET_KEY", "TOKEN"),
+			),
+		)
+		assert.NoError(t, err)
+
+		signer, err := NewSignerWithService(awsCfg, "ec")
+		assert.NoError(t, err)
+
+		assert.NoError(t, err)
+		err = signer.SignRequest(req)
+		assert.NoError(t, err)
+		q := req.Header
+		assert.NotEmpty(t, q.Get("Authorization"))
+		assert.NotEmpty(t, q.Get("X-Amz-Date"))
+	})
+
+	t.Run("sign request failed due to invalid service", func(t *testing.T) {
+		awsCfg, err := config.LoadDefaultConfig(context.TODO(),
+			config.WithRegion("us-west-2"),
+			config.WithCredentialsProvider(
+				getCredentialProvider("AKID", "SECRET_KEY", "TOKEN"),
+			),
+		)
+		assert.NoError(t, err)
+
+		_, err = NewSignerWithService(awsCfg, "")
+		assert.EqualError(t, err, "service cannot be empty")
+	})
+}