Files
es-demo/client/client.go

136 lines
3.5 KiB
Go

// Package client provides a high-level interface for interacting with AWS OpenSearch Service.
// It handles connection management, authentication, and request signing.
package client
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net/http"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
"github.com/opensearch-project/opensearch-go/v2"
requestsigner "github.com/opensearch-project/opensearch-go/v2/signer/awsv2"
)
var (
// ErrInvalidConfig is returned when the client configuration is invalid.
ErrInvalidConfig = errors.New("invalid configuration")
// ErrRequestFailed is returned when a request to OpenSearch fails.
ErrRequestFailed = errors.New("request failed")
)
const (
// DefaultTimeout is the default timeout for requests.
DefaultTimeout = 30 * time.Second
)
// Config holds the configuration for the OpenSearch client.
type Config struct {
// Endpoint is the OpenSearch cluster endpoint URL.
Endpoint string
// Region is the AWS region where the OpenSearch cluster is located.
Region string
// AccessKey is the AWS access key ID for authentication.
AccessKey string
// SecretKey is the AWS secret access key for authentication.
SecretKey string
// Timeout is the request timeout duration. Defaults to DefaultTimeout if not set.
Timeout time.Duration
}
// Client represents an OpenSearch client that manages connections.
type Client struct {
config *Config
client *opensearch.Client
signer *v4.Signer
}
// NewClient creates a new OpenSearch client with the given configuration.
// It returns an error if the configuration is invalid or if the client cannot be initialized.
func NewClient(cfg *Config) (*Client, error) {
if cfg == nil {
return nil, fmt.Errorf("%w: config cannot be nil", ErrInvalidConfig)
}
if err := validateConfig(cfg); err != nil {
return nil, fmt.Errorf("%w: %v", ErrInvalidConfig, err)
}
// TODO: Use cfg.Timeout for client configuration
// Currently timeout is not directly supported by opensearch-go client config
// Create AWS credentials
awsConfig := aws.Config{
Region: cfg.Region,
Credentials: aws.CredentialsProviderFunc(func(ctx context.Context) (aws.Credentials, error) {
return aws.Credentials{
AccessKeyID: cfg.AccessKey,
SecretAccessKey: cfg.SecretKey,
}, nil
}),
}
// Create AWS v4 signer
signer := v4.NewSigner()
// Create request signer for OpenSearch
awsSigner, err := requestsigner.NewSignerWithService(awsConfig, "es")
if err != nil {
return nil, fmt.Errorf("failed to create request signer: %w", err)
}
// Configure OpenSearch client
osConfig := opensearch.Config{
Addresses: []string{cfg.Endpoint},
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
},
},
Signer: awsSigner,
}
// Create OpenSearch client
osClient, err := opensearch.NewClient(osConfig)
if err != nil {
return nil, fmt.Errorf("failed to create opensearch client: %w", err)
}
return &Client{
config: cfg,
client: osClient,
signer: signer,
}, nil
}
// GetClient returns the underlying OpenSearch client for use by operation packages.
func (c *Client) GetClient() *opensearch.Client {
return c.client
}
// validateConfig validates the client configuration.
func validateConfig(cfg *Config) error {
if cfg.Endpoint == "" {
return errors.New("endpoint is required")
}
if cfg.Region == "" {
return errors.New("region is required")
}
if cfg.AccessKey == "" {
return errors.New("access key is required")
}
if cfg.SecretKey == "" {
return errors.New("secret key is required")
}
return nil
}