Files
es-demo/operations/index/template.go
mouseleee fc14798af5 feat: 增加ism管理接口
test: 索引模板和ism的单元测试和集成测试
2025-11-16 23:00:31 +08:00

248 lines
6.7 KiB
Go

// Package index provides index-level operations for OpenSearch.
package index
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"es-demo/client"
"github.com/opensearch-project/opensearch-go/v2/opensearchapi"
)
var (
// ErrInvalidTemplate is returned when the template configuration is invalid.
ErrInvalidTemplate = errors.New("invalid template configuration")
// ErrTemplateNotFound is returned when the specified template does not exist.
ErrTemplateNotFound = errors.New("template not found")
)
// Template represents an OpenSearch index template.
type Template struct {
// IndexPatterns defines the index patterns this template applies to.
IndexPatterns []string `json:"index_patterns"`
// Settings contains index settings like number of shards and replicas.
Settings map[string]any `json:"settings,omitempty"`
// Mappings defines the field mappings for the index.
Mappings map[string]any `json:"mappings,omitempty"`
// Aliases defines index aliases.
Aliases map[string]any `json:"aliases,omitempty"`
// Priority determines template precedence when multiple templates match.
Priority int `json:"priority,omitempty"`
// Version is used for external version management.
Version int `json:"version,omitempty"`
}
// TemplateResponse represents the response when getting a template.
type TemplateResponse struct {
IndexTemplates []struct {
Name string `json:"name"`
IndexTemplate Template `json:"index_template"`
} `json:"index_templates"`
}
// PutTemplate creates or updates an index template.
func PutTemplate(ctx context.Context, c *client.Client, name string, template *Template) error {
if err := validateTemplate(template); err != nil {
return err
}
if name == "" {
return fmt.Errorf("%w: template name is required", ErrInvalidTemplate)
}
// Build template object with only non-empty fields
templateObj := make(map[string]any)
if len(template.Settings) > 0 {
templateObj["settings"] = template.Settings
}
if len(template.Mappings) > 0 {
templateObj["mappings"] = template.Mappings
}
if len(template.Aliases) > 0 {
templateObj["aliases"] = template.Aliases
}
// Wrap template in template field for the API
requestBody := map[string]any{
"index_patterns": template.IndexPatterns,
}
// Only add template field if it has content
if len(templateObj) > 0 {
requestBody["template"] = templateObj
}
// Add optional fields
if template.Priority > 0 {
requestBody["priority"] = template.Priority
}
if template.Version > 0 {
requestBody["version"] = template.Version
}
body, err := json.Marshal(requestBody)
if err != nil {
return fmt.Errorf("failed to marshal template: %w", err)
}
req := opensearchapi.IndicesPutIndexTemplateRequest{
Name: name,
Body: bytes.NewReader(body),
}
res, err := req.Do(ctx, c.GetClient())
if err != nil {
return fmt.Errorf("failed to execute put template request: %w", err)
}
defer func() {
if closeErr := res.Body.Close(); closeErr != nil {
fmt.Fprintf(os.Stderr, "warning: failed to close response body: %v\n", closeErr)
}
}()
if res.IsError() {
bodyBytes, _ := io.ReadAll(res.Body)
return fmt.Errorf("put template failed with status %s: %s", res.Status(), string(bodyBytes))
}
return nil
}
// GetTemplate retrieves an index template by name.
func GetTemplate(ctx context.Context, c *client.Client, name string) (*Template, error) {
if name == "" {
return nil, fmt.Errorf("%w: template name is required", ErrInvalidTemplate)
}
req := opensearchapi.IndicesGetIndexTemplateRequest{
Name: []string{name},
}
res, err := req.Do(ctx, c.GetClient())
if err != nil {
return nil, fmt.Errorf("failed to execute get template request: %w", err)
}
defer func() {
if closeErr := res.Body.Close(); closeErr != nil {
fmt.Fprintf(os.Stderr, "warning: failed to close response body: %v\n", closeErr)
}
}()
if res.IsError() {
if res.StatusCode == 404 {
return nil, ErrTemplateNotFound
}
bodyBytes, _ := io.ReadAll(res.Body)
return nil, fmt.Errorf("get template failed with status %s: %s", res.Status(), string(bodyBytes))
}
var response TemplateResponse
if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
return nil, fmt.Errorf("failed to decode template response: %w", err)
}
if len(response.IndexTemplates) == 0 {
return nil, ErrTemplateNotFound
}
return &response.IndexTemplates[0].IndexTemplate, nil
}
// DeleteTemplate deletes an index template.
func DeleteTemplate(ctx context.Context, c *client.Client, name string) error {
if name == "" {
return fmt.Errorf("%w: template name is required", ErrInvalidTemplate)
}
req := opensearchapi.IndicesDeleteIndexTemplateRequest{
Name: name,
}
res, err := req.Do(ctx, c.GetClient())
if err != nil {
return fmt.Errorf("failed to execute delete template request: %w", err)
}
defer func() {
if closeErr := res.Body.Close(); closeErr != nil {
fmt.Fprintf(os.Stderr, "warning: failed to close response body: %v\n", closeErr)
}
}()
if res.IsError() {
if res.StatusCode == 404 {
return ErrTemplateNotFound
}
bodyBytes, _ := io.ReadAll(res.Body)
return fmt.Errorf("delete template failed with status %s: %s", res.Status(), string(bodyBytes))
}
return nil
}
// ListTemplates retrieves all index templates or templates matching a pattern.
func ListTemplates(ctx context.Context, c *client.Client, names ...string) (map[string]*Template, error) {
var nameList []string
if len(names) > 0 {
nameList = names
}
req := opensearchapi.IndicesGetIndexTemplateRequest{
Name: nameList,
}
res, err := req.Do(ctx, c.GetClient())
if err != nil {
return nil, fmt.Errorf("failed to execute list templates request: %w", err)
}
defer func() {
if closeErr := res.Body.Close(); closeErr != nil {
fmt.Fprintf(os.Stderr, "warning: failed to close response body: %v\n", closeErr)
}
}()
if res.IsError() {
if res.StatusCode == 404 {
return make(map[string]*Template), nil
}
bodyBytes, _ := io.ReadAll(res.Body)
return nil, fmt.Errorf("list templates failed with status %s: %s", res.Status(), string(bodyBytes))
}
var response TemplateResponse
if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
return nil, fmt.Errorf("failed to decode templates response: %w", err)
}
templates := make(map[string]*Template)
for _, item := range response.IndexTemplates {
templates[item.Name] = &item.IndexTemplate
}
return templates, nil
}
// validateTemplate validates the template configuration.
func validateTemplate(template *Template) error {
if template == nil {
return fmt.Errorf("%w: template cannot be nil", ErrInvalidTemplate)
}
if len(template.IndexPatterns) == 0 {
return fmt.Errorf("%w: index patterns are required", ErrInvalidTemplate)
}
return nil
}