// 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 }