feat: opensearch客户端初始化 feat: 索引模板接口 ai: 开发准则 chore: TDD流水线脚本
This commit is contained in:
135
client/client.go
Normal file
135
client/client.go
Normal file
@@ -0,0 +1,135 @@
|
||||
// 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
|
||||
}
|
||||
113
client/client_test.go
Normal file
113
client/client_test.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config *Config
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "valid config",
|
||||
config: &Config{
|
||||
Endpoint: "https://example.com",
|
||||
Region: "us-east-1",
|
||||
AccessKey: "test-key",
|
||||
SecretKey: "test-secret",
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "nil config",
|
||||
config: nil,
|
||||
wantErr: ErrInvalidConfig,
|
||||
},
|
||||
{
|
||||
name: "missing endpoint",
|
||||
config: &Config{
|
||||
Region: "us-east-1",
|
||||
AccessKey: "test-key",
|
||||
SecretKey: "test-secret",
|
||||
},
|
||||
wantErr: ErrInvalidConfig,
|
||||
},
|
||||
{
|
||||
name: "missing region",
|
||||
config: &Config{
|
||||
Endpoint: "https://example.com",
|
||||
AccessKey: "test-key",
|
||||
SecretKey: "test-secret",
|
||||
},
|
||||
wantErr: ErrInvalidConfig,
|
||||
},
|
||||
{
|
||||
name: "missing access key",
|
||||
config: &Config{
|
||||
Endpoint: "https://example.com",
|
||||
Region: "us-east-1",
|
||||
SecretKey: "test-secret",
|
||||
},
|
||||
wantErr: ErrInvalidConfig,
|
||||
},
|
||||
{
|
||||
name: "missing secret key",
|
||||
config: &Config{
|
||||
Endpoint: "https://example.com",
|
||||
Region: "us-east-1",
|
||||
AccessKey: "test-key",
|
||||
},
|
||||
wantErr: ErrInvalidConfig,
|
||||
},
|
||||
{
|
||||
name: "with custom timeout",
|
||||
config: &Config{
|
||||
Endpoint: "https://example.com",
|
||||
Region: "us-east-1",
|
||||
AccessKey: "test-key",
|
||||
SecretKey: "test-secret",
|
||||
Timeout: 60 * time.Second,
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := NewClient(tt.config)
|
||||
if !errors.Is(err, tt.wantErr) {
|
||||
t.Errorf("NewClient() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if tt.wantErr == nil && got == nil {
|
||||
t.Error("NewClient() returned nil client with no error")
|
||||
}
|
||||
if tt.wantErr != nil && got != nil {
|
||||
t.Error("NewClient() returned client with error")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_GetClient(t *testing.T) {
|
||||
cfg := &Config{
|
||||
Endpoint: "https://example.com",
|
||||
Region: "us-east-1",
|
||||
AccessKey: "test-key",
|
||||
SecretKey: "test-secret",
|
||||
}
|
||||
|
||||
client, err := NewClient(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create client: %v", err)
|
||||
}
|
||||
|
||||
osClient := client.GetClient()
|
||||
if osClient == nil {
|
||||
t.Error("GetClient() returned nil")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user