package search import ( "context" "crypto/tls" "encoding/json" "net/http" "strings" opensearch "github.com/opensearch-project/opensearch-go" opensearchapi "github.com/opensearch-project/opensearch-go/opensearchapi" "gitlab.com/uafrica/go-utils/errors" "gitlab.com/uafrica/go-utils/logger" ) type Writer interface { TimeSeries(name string, tmpl interface{}) (TimeSeries, error) //tmpl must embed TimeSeriesHeader as first unanymous field DelOldTimeSeries(name string, olderThanDays int) ([]string, error) } func New(config Config) (Writer, error) { if err := config.Validate(); err != nil { return nil, errors.Wrapf(err, "invalid config") } w := &writer{ config: config, timeSeriesByName: map[string]TimeSeries{}, } searchConfig := opensearch.Config{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, Addresses: config.Addresses, Username: config.Username, Password: config.Password, } // Initialize the client with SSL/TLS enabled. var err error w.client, err = opensearch.NewClient(searchConfig) if err != nil { return nil, errors.Wrapf(err, "cannot initialize opensearch connection") } // Print OpenSearch version information on console. logger.Debugf("Search client created with config: %+v", searchConfig) w.api = opensearchapi.New(w.client) return w, nil } //implements audit.Auditor type writer struct { config Config client *opensearch.Client api *opensearchapi.API timeSeriesByName map[string]TimeSeries } func (writer writer) Write(indexName string, doc interface{}) error { if writer.client == nil { return errors.Errorf("writer closed") } jsonDocStr, ok := doc.(string) if !ok { jsonDoc, err := json.Marshal(doc) if err != nil { return errors.Wrapf(err, "failed to JSON encode document") } jsonDocStr = string(jsonDoc) } indexResponse, err := writer.api.Index( indexName, strings.NewReader(jsonDocStr), ) if err != nil { return errors.Wrapf(err, "failed to index document") } var res IndexResponse if err := json.NewDecoder(indexResponse.Body).Decode(&res); err != nil { return errors.Wrapf(err, "failed to decode JSON response") } //success example: //res = map[ // _id:oZJZxXwBPnbPDFcjNpuO // _index:go-utils-audit-test // _primary_term:1 // _seq_no:5 // _shards:map[failed:0 successful:2 total:2] // _type:_doc // _version:1 // result:created // ] //error example: //res = map[ // error:map[ // reason:object mapping for [response.Body] tried to parse field [Body] as object, but found a concrete value // root_cause:[ // map[reason:object mapping for [response.Body] tried to parse field [Body] as object, but found a concrete value type:mapper_parsing_exception] // ] // type:mapper_parsing_exception] // status:400 //] if res.Error != nil { return errors.Errorf("failed to insert: %v", res.Error.Reason) } return nil } func (writer writer) Search() ([]interface{}, error) { if writer.client == nil { return nil, errors.Errorf("writer closed") } // Search for the document. content := strings.NewReader(`{ "size": 5, "query": { "multi_match": { "query": "miller", "fields": ["title^2", "director"] } } }`) search := opensearchapi.SearchRequest{ Body: content, } searchResponse, err := search.Do(context.Background(), writer.client) if err != nil { return nil, errors.Wrapf(err, "failed to search document") } var res interface{} if err := json.NewDecoder(searchResponse.Body).Decode(&res); err != nil { return nil, errors.Wrapf(err, "failed to decode JSON body") } return nil, errors.Errorf("NYI search result processing: %v", res) } type CreateResponse struct { Error *Error `json:"error,omitempty"` } type IndexResponse struct { Error *Error `json:"error,omitempty"` Result string `json:"result,omitempty"` //e.g. "created" Index string `json:"_index,omitempty"` ID string `json:"_id,omitempty"` Version int `json:"_version,omitempty"` SeqNo int `json:"_seq_no,omitempty"` Shards *Shards `json:"_shards,omitempty"` Type string `json:"_type,omitempty"` PrimaryTerm int `json:"_primary_term,omitempty"` } type Shards struct { Total int `json:"total"` Successful int `json:"successful"` Failed int `json:"failed"` } type Error struct { Reason string `json:"reason,omitempty"` RootCause []Cause `json:"root_cause,omitempty"` } type Cause struct { Reason string `json:"reason,omitempty"` Type string `json:"type,omitempty"` }