Files
QSfera/Server/services/search/pkg/opensearch/index.go
T
Курнат Андрей 2315f25754 Initial QSfera import
2026-06-07 10:20:04 +03:00

144 lines
3.7 KiB
Go

package opensearch
import (
"bytes"
"context"
"embed"
"errors"
"fmt"
"path"
"reflect"
"github.com/go-jose/go-jose/v3/json"
opensearchgoAPI "github.com/opensearch-project/opensearch-go/v4/opensearchapi"
"github.com/tidwall/gjson"
)
var (
ErrManualActionRequired = errors.New("manual action required")
IndexManagerLatest = IndexIndexManagerResourceV2
IndexIndexManagerResourceV1 IndexManager = "resource_v1.json"
IndexIndexManagerResourceV2 IndexManager = "resource_v2.json"
)
//go:embed internal/indexes/*.json
var indexes embed.FS
type IndexManager string
func (m IndexManager) String() string {
b, err := m.MarshalJSON()
if err != nil {
return ""
}
return string(b)
}
func (m IndexManager) MarshalJSON() ([]byte, error) {
filePath := string(m)
body, err := indexes.ReadFile(path.Join("./internal/indexes", filePath))
switch {
case err != nil:
return nil, fmt.Errorf("failed to read index file %s: %w", filePath, err)
case len(body) <= 0:
return nil, fmt.Errorf("index file %s is empty", filePath)
}
return body, nil
}
func (m IndexManager) Apply(ctx context.Context, name string, client *opensearchgoAPI.Client) error {
localIndexB, err := m.MarshalJSON()
if err != nil {
return fmt.Errorf("failed to marshal index %s: %w", name, err)
}
indicesExistsResp, err := client.Indices.Exists(ctx, opensearchgoAPI.IndicesExistsReq{
Indices: []string{name},
})
switch {
case indicesExistsResp != nil && indicesExistsResp.StatusCode == 404:
break
case err != nil:
return fmt.Errorf("failed to check if index %s exists: %w", name, err)
case indicesExistsResp == nil:
return fmt.Errorf("indicesExistsResp is nil for index %s", name)
}
if indicesExistsResp.StatusCode == 200 {
resp, err := client.Indices.Get(ctx, opensearchgoAPI.IndicesGetReq{
Indices: []string{name},
})
if err != nil {
return fmt.Errorf("failed to get index %s: %w", name, err)
}
remoteIndex, ok := resp.Indices[name]
if !ok {
return fmt.Errorf("index %s not found in response", name)
}
remoteIndexB, err := json.Marshal(remoteIndex)
if err != nil {
return fmt.Errorf("failed to marshal index %s: %w", name, err)
}
localIndexJson := gjson.ParseBytes(localIndexB)
remoteIndexJson := gjson.ParseBytes(remoteIndexB)
compare := func(lvPath, rvPath string) (any, any, bool) {
lv := localIndexJson.Get(lvPath).Raw
rv := remoteIndexJson.Get(rvPath).Raw
var lvv, rvv any
if err := json.Unmarshal([]byte(lv), &lvv); err != nil {
return nil, nil, false
}
if err := json.Unmarshal([]byte(rv), &rvv); err != nil {
return nil, nil, false
}
return lv, rv, reflect.DeepEqual(lvv, rvv)
}
var errs []error
for k := range localIndexJson.Get("settings").Map() {
if lv, rv, ok := compare("settings."+k, "settings.index."+k); !ok {
errs = append(errs, fmt.Errorf("settings.%s local %s, remote %s", k, lv, rv))
}
}
for k := range localIndexJson.Get("mappings.properties").Map() {
if _, _, ok := compare("mappings.properties."+k, "mappings.properties."+k); !ok {
errs = append(errs, fmt.Errorf("mappings.properties.%s", k))
}
}
if errs != nil {
return fmt.Errorf(
"index %s already exists and is different from the requested version, %w: %w",
name,
ErrManualActionRequired,
errors.Join(errs...),
)
}
return nil // Index is already up to date, no action needed
}
createResp, err := client.Indices.Create(ctx, opensearchgoAPI.IndicesCreateReq{
Index: name,
Body: bytes.NewReader(localIndexB),
})
switch {
case err != nil:
return fmt.Errorf("failed to create index %s: %w", name, err)
case !createResp.Acknowledged:
return fmt.Errorf("failed to create index %s: not acknowledged", name)
}
return nil
}