Files
Chetan GowdaandGitHub ec4be66ab6 [temporal-elasticsearch-tool] fix index creation (#8455)
## What changed?
`SetupTask.setupIndex()` fails if the index already exists. Added a
check so that it skips index creation if it already exists.

## Why?
This allows re-runs of `make temporal-elasticsearch-tool` without
errors.

## How did you test it?
- [x] built
- [x] run locally and tested manually
- [ ] covered by existing tests
- [ ] added new unit test(s)
- [ ] added new functional test(s)

## Potential risks
N/A

---

> [!NOTE]
> Make Elasticsearch visibility index creation idempotent and plumb
context through index-creation code.
> 
> - **Elasticsearch tool**:
> - **Index creation**: Make `setupIndex(ctx)` idempotent by detecting
`resource_already_exists_exception` and skipping creation; improved
logging.
> - **API changes**: Update `RunIndexCreation(ctx)` to take
`context.Context` and pass it through; `handler.createIndex` now calls
with a background context.
>   - **Cleanup**: Remove monolithic `Run()` method.
> - **Deps**: Add `github.com/olivere/elastic/v7` for error inspection.
> 
> <sup>Written by [Cursor
Bugbot]() for commit
9e570b45c48785dc864f75da00510bd2145c7a3e. This will update automatically
on new commits. Configure
[here]().</sup>
2025-10-20 11:11:28 -07:00

211 lines
6.7 KiB
Go

package elasticsearch
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/olivere/elastic/v7"
"go.temporal.io/server/common/log"
"go.temporal.io/server/common/log/tag"
"go.temporal.io/server/common/persistence/visibility/store/elasticsearch/client"
)
const templateName = "temporal_visibility_v1_template"
type SetupConfig struct {
TemplateContent string
SettingsContent string
VisibilityIndex string
FailSilently bool
}
type SetupTask struct {
esClient client.CLIClient
config *SetupConfig
logger log.Logger
}
// setupClusterSettings handles cluster settings configuration
func (task *SetupTask) setupClusterSettings() error {
config := task.config
if len(config.SettingsContent) == 0 {
task.logger.Info("Skipping cluster settings update, no embedded settings content")
return nil
}
success, err := task.esClient.ClusterPutSettings(context.TODO(), config.SettingsContent)
if err != nil {
return task.handleOperationFailure("cluster settings update failed", err)
} else if !success {
return task.handleOperationFailure("cluster settings update failed without error", errors.New("acknowledged=false"))
}
task.logger.Info("Cluster settings updated successfully")
return nil
}
// setupTemplate handles template configuration
func (task *SetupTask) setupTemplate() error {
config := task.config
if len(config.TemplateContent) == 0 {
task.logger.Info("Skipping template creation, no embedded template content")
return nil
}
success, err := task.esClient.IndexPutTemplate(context.TODO(), templateName, config.TemplateContent)
if err != nil {
return task.handleOperationFailure("template creation failed", err)
} else if !success {
return task.handleOperationFailure("template creation failed without error", errors.New("acknowledged=false"))
}
task.logger.Info("Template created successfully", tag.NewStringTag("templateName", templateName))
return nil
}
// setupIndex handles index creation. It checks if the index exists and skips creation if it does.
func (task *SetupTask) setupIndex(ctx context.Context) error {
config := task.config
if len(config.VisibilityIndex) == 0 {
task.logger.Info("Skipping index creation, missing index name")
return nil
}
success, err := task.esClient.CreateIndex(ctx, config.VisibilityIndex, nil)
if err != nil {
// Check if the error is an Elasticsearch error and if so check if the index already exists.
var esErr *elastic.Error
if errors.As(err, &esErr) {
if esErr.Status == 400 && esErr.Details != nil && esErr.Details.Type == "resource_already_exists_exception" {
task.logger.Info("Index already exists, skipping creation", tag.NewStringTag("indexName", config.VisibilityIndex))
return nil
}
}
return task.handleOperationFailure("index creation failed", err)
} else if !success {
return task.handleOperationFailure("index creation failed without error", errors.New("acknowledged=false"))
}
task.logger.Info("Index created successfully", tag.NewStringTag("indexName", config.VisibilityIndex))
return nil
}
// RunSchemaSetup runs only cluster settings and template setup (no index creation)
func (task *SetupTask) RunSchemaSetup() error {
task.logger.Info("Starting schema setup (cluster settings and template)", tag.NewAnyTag("config", task.config))
if err := task.setupClusterSettings(); err != nil {
task.logger.Error("Failed to setup cluster settings.", tag.Error(err))
return err
}
if err := task.setupTemplate(); err != nil {
task.logger.Error("Failed to setup template.", tag.Error(err))
return err
}
task.logger.Info("Schema setup complete (cluster settings and template)")
return nil
}
// RunTemplateUpgrade runs only template upgrade
func (task *SetupTask) RunTemplateUpgrade() error {
task.logger.Info("Starting template upgrade", tag.NewAnyTag("config", task.config))
if err := task.setupTemplate(); err != nil {
task.logger.Error("Failed to upgrade template.", tag.Error(err))
return err
}
task.logger.Info("Template upgrade complete")
return nil
}
// RunIndexCreation runs only index creation
func (task *SetupTask) RunIndexCreation(ctx context.Context) error {
task.logger.Info("Starting index creation", tag.NewAnyTag("config", task.config))
if err := task.setupIndex(ctx); err != nil {
task.logger.Error("Failed to create index.", tag.Error(err))
return err
}
task.logger.Info("Index creation complete")
return nil
}
// RunIndexUpdate updates the mappings of an existing index
func (task *SetupTask) RunIndexUpdate() error {
task.logger.Info("Starting index mapping update", tag.NewAnyTag("config", task.config))
if err := task.updateIndexMappings(); err != nil {
task.logger.Error("Failed to update index mappings.", tag.Error(err))
return err
}
task.logger.Info("Index mapping update complete")
return nil
}
// updateIndexMappings updates the mappings of an existing index using raw HTTP request
func (task *SetupTask) updateIndexMappings() error {
config := task.config
if len(config.VisibilityIndex) == 0 {
task.logger.Info("Skipping index mapping update, missing index name")
return nil
}
if len(config.TemplateContent) == 0 {
task.logger.Info("Skipping index mapping update, no embedded template content")
return nil
}
// Parse the template to extract mappings
var template map[string]interface{}
if err := json.Unmarshal([]byte(config.TemplateContent), &template); err != nil {
return fmt.Errorf("failed to parse template content: %w", err)
}
mappings, ok := template["mappings"]
if !ok {
return errors.New("no mappings found in template")
}
mappingsBytes, err := json.Marshal(mappings)
if err != nil {
return fmt.Errorf("failed to marshal mappings: %w", err)
}
// Check if the index exists first
indexName := config.VisibilityIndex
exists, err := task.esClient.IndexExists(context.TODO(), indexName)
if err != nil {
return task.handleOperationFailure("failed to check if index exists", err)
}
if !exists {
return task.handleOperationFailure("index does not exist", fmt.Errorf("index %s does not exist", indexName))
}
success, err := task.esClient.IndexPutMapping(context.TODO(), indexName, string(mappingsBytes))
if err != nil {
return task.handleOperationFailure("index mapping update failed", err)
} else if !success {
return task.handleOperationFailure("index mapping update failed without error", errors.New("acknowledged=false"))
}
task.logger.Info("Index mappings updated successfully", tag.NewStringTag("indexName", indexName))
return nil
}
// handleOperationFailure handles operation failures, optionally failing silently
func (task *SetupTask) handleOperationFailure(msg string, err error) error {
if !task.config.FailSilently {
task.logger.Error(msg, tag.Error(err))
return err
}
task.logger.Warn(msg, tag.Error(err))
return nil
}