refactor(search): make the breaking-schema error developer-facing

This commit is contained in:
Dominik Schmidt
2026-07-29 19:55:52 +02:00
parent 2bac094fe5
commit 6450af2e6d
3 changed files with 12 additions and 16 deletions

View File

@@ -62,7 +62,7 @@ func NewIndex(root string) (bleve.Index, searchmapping.Classification, error) {
switch classification.Verdict {
case searchmapping.VerdictBreaking:
_ = index.Close()
return nil, classification, searchmapping.ManualActionRequiredError(destination, "delete the index directory "+destination, classification.Reasons)
return nil, classification, searchmapping.ManualActionRequiredError(destination, classification.Reasons)
case searchmapping.VerdictAdditive:
// Safe: everything else is identical and the new fields hold no data.
// Reopen so the live mapping picks the change up; otherwise the fields

View File

@@ -13,20 +13,16 @@ import (
// ErrManualActionRequired marks schema changes that cannot be applied in place.
var ErrManualActionRequired = errors.New("manual action required")
// ManualActionRequiredError builds the operator-facing error for a breaking
// schema change. index names the index, deleteStep is the engine-specific
// instruction to remove it.
func ManualActionRequiredError(index, deleteStep string, reasons []string) error {
// ManualActionRequiredError reports a breaking schema change. Because the index
// is versioned by search.SchemaVersion, a released instance never hits this: a
// version bump builds a fresh index. It fires in development when the mapping is
// changed in a breaking way without bumping search.SchemaVersion, so the fix is
// to bump it (or revert the change).
func ManualActionRequiredError(index string, reasons []string) error {
return fmt.Errorf(
"%w: search index %s was built with a different schema:\n - %s\n"+
"There is no in-place migration: with the OpenCloud search service stopped, %s, "+
"then start it again (an empty index with the new schema is created), "+
"then rebuild the content by running: opencloud search index --all-spaces. "+
"To bring the instance up without search until a maintenance window, "+
"set OC_EXCLUDE_RUN_SERVICES=search; until the service is back, search "+
"and features built on it (e.g. the search bar and the tag list) are "+
"unavailable",
ErrManualActionRequired, index, strings.Join(reasons, "\n - "), deleteStep,
"%w: the search mapping in code differs from index %s in a breaking way:\n - %s\n"+
"bump search.SchemaVersion to build a fresh index, or revert the mapping change",
ErrManualActionRequired, index, strings.Join(reasons, "\n - "),
)
}

View File

@@ -179,7 +179,7 @@ func (m IndexManager) Apply(ctx context.Context, name string, client *opensearch
)
reasons = append(reasons, classification.Reasons...)
if len(reasons) > 0 {
return searchmapping.ManualActionRequiredError(name, fmt.Sprintf("delete the index (DELETE /%s)", name), reasons)
return searchmapping.ManualActionRequiredError(name, reasons)
}
if len(classification.NewFields) == 0 {
return nil // schema is up to date
@@ -196,7 +196,7 @@ func (m IndexManager) Apply(ctx context.Context, name string, client *opensearch
case err != nil && errors.As(err, &putErr) && putErr.Err.Type == "illegal_argument_exception" &&
(strings.Contains(putErr.Err.Reason, "cannot be changed") || strings.Contains(putErr.Err.Reason, "Cannot update parameter")):
// backstop, should be unreachable after the classification above
return searchmapping.ManualActionRequiredError(name, fmt.Sprintf("delete the index (DELETE /%s)", name), []string{putErr.Err.Reason})
return searchmapping.ManualActionRequiredError(name, []string{putErr.Err.Reason})
case err != nil:
return fmt.Errorf("failed to update mapping of index %s: %w", name, err)
case !putResp.Acknowledged: