From 22edae9f6b92fb543fee0264d082afad6c7399e6 Mon Sep 17 00:00:00 2001 From: Raihan Khan Date: Fri, 26 Jan 2024 16:36:27 +0600 Subject: [PATCH] Add support for Opensearch Dashboard client (#82) * Add support for Opensearch Dashboard client Signed-off-by: raihankhan Co-authored-by: Md. Ishtiaq Islam --- elasticsearchdashboard/client.go | 4 +- elasticsearchdashboard/ed_client_v7.go | 2 +- elasticsearchdashboard/ed_client_v8.go | 2 +- .../kubedb-client-builder.go | 19 ++- elasticsearchdashboard/os_client.go | 146 ++++++++++++++++++ 5 files changed, 167 insertions(+), 6 deletions(-) create mode 100644 elasticsearchdashboard/os_client.go diff --git a/elasticsearchdashboard/client.go b/elasticsearchdashboard/client.go index b604c196d..ffea20b9c 100644 --- a/elasticsearchdashboard/client.go +++ b/elasticsearchdashboard/client.go @@ -30,9 +30,11 @@ import ( ) const ( - SavedObjectsReqBody = `{"type": ["dashboard", "config", "index-pattern", "url", "query", "tag", "canvas-element", "canvas-workpad", "action", "alert", "visualization", + SavedObjectsReqBodyES = `{"type": ["dashboard", "config", "index-pattern", "url", "query", "tag", "canvas-element", "canvas-workpad", "action", "alert", "visualization", "graph-workspace", "map", "lens", "cases", "search", "osquery-saved-query", "osquery-pack", "uptime-dynamic-settings", "infrastructure-ui-source", "metrics-explorer-view", "inventory-view", "apm-indices"]}` + SavedObjectsReqBodyOS = `{"type": ["config", "url", "index-pattern", "query", "dashboard", "visualization", "visualization-visbuilder", "augment-vis", "map", +"observability-panel", "observability-visualization", "search"]}` SavedObjectsExportURL = "/api/saved_objects/_export" SavedObjectsImportURL = "/api/saved_objects/_import" ) diff --git a/elasticsearchdashboard/ed_client_v7.go b/elasticsearchdashboard/ed_client_v7.go index 2029b64e5..91dda15e9 100644 --- a/elasticsearchdashboard/ed_client_v7.go +++ b/elasticsearchdashboard/ed_client_v7.go @@ -114,7 +114,7 @@ func (h *EDClientV7) ExportSavedObjects() (*Response, error) { "Content-Type": "application/json", "kbn-xsrf": "true", }). - SetBody([]byte(SavedObjectsReqBody)) + SetBody([]byte(SavedObjectsReqBodyES)) res, err := req.Post(SavedObjectsExportURL) if err != nil { klog.Error(err, "Failed to send http request") diff --git a/elasticsearchdashboard/ed_client_v8.go b/elasticsearchdashboard/ed_client_v8.go index b4df628d7..f2a47fdd8 100644 --- a/elasticsearchdashboard/ed_client_v8.go +++ b/elasticsearchdashboard/ed_client_v8.go @@ -112,7 +112,7 @@ func (h *EDClientV8) ExportSavedObjects() (*Response, error) { "Content-Type": "application/json", "kbn-xsrf": "true", }). - SetBody([]byte(SavedObjectsReqBody)) + SetBody([]byte(SavedObjectsReqBodyES)) res, err := req.Post(SavedObjectsExportURL) if err != nil { klog.Error(err, "Failed to send http request") diff --git a/elasticsearchdashboard/kubedb-client-builder.go b/elasticsearchdashboard/kubedb-client-builder.go index 631f0322d..6c4daca7b 100644 --- a/elasticsearchdashboard/kubedb-client-builder.go +++ b/elasticsearchdashboard/kubedb-client-builder.go @@ -170,9 +170,8 @@ func (o *KubeDBClientBuilder) GetElasticsearchDashboardClient() (*Client, error) } switch { - // for Elasticsearch 7.x.x and OpenSearch 1.x.x - case (config.dbVersionInfo.AuthPlugin == catalog.ElasticsearchAuthPluginXpack && version.Major() <= 7) || - (config.dbVersionInfo.AuthPlugin == catalog.ElasticsearchAuthPluginOpenSearch && (version.Major() == 1 || version.Major() == 2)): + // for Elasticsearch 7.x.x + case config.dbVersionInfo.AuthPlugin == catalog.ElasticsearchAuthPluginXpack && version.Major() == 7: newClient := resty.New() newClient.SetTransport(config.transport).SetScheme(config.connectionScheme).SetBaseURL(config.host) newClient.SetHeader("Accept", "application/json") @@ -199,6 +198,20 @@ func (o *KubeDBClientBuilder) GetElasticsearchDashboardClient() (*Client, error) Config: &config, }, }, nil + + case config.dbVersionInfo.AuthPlugin == catalog.ElasticsearchAuthPluginOpenSearch: + newClient := resty.New() + newClient.SetTransport(config.transport).SetScheme(config.connectionScheme).SetBaseURL(config.host) + newClient.SetHeader("Accept", "application/json") + newClient.SetBasicAuth(config.username, config.password) + newClient.SetTimeout(time.Second * 30) + + return &Client{ + &OSClient{ + Client: newClient, + Config: &config, + }, + }, nil } return nil, fmt.Errorf("unknown version: %s", config.dbVersionInfo.Name) diff --git a/elasticsearchdashboard/os_client.go b/elasticsearchdashboard/os_client.go new file mode 100644 index 000000000..abb78d3d9 --- /dev/null +++ b/elasticsearchdashboard/os_client.go @@ -0,0 +1,146 @@ +/* +Copyright AppsCode Inc. and Contributors + +Licensed under the AppsCode Free Trial License 1.0.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://github.com/appscode/licenses/raw/1.0.0/AppsCode-Free-Trial-1.0.0.md + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package elasticsearchdashboard + +import ( + "encoding/json" + "io" + "strings" + + esapi "kubedb.dev/apimachinery/apis/elasticsearch/v1alpha1" + + "github.com/go-resty/resty/v2" + "github.com/pkg/errors" + "k8s.io/klog/v2" +) + +type OSClient struct { + Client *resty.Client + Config *Config +} + +func (h *OSClient) GetHealthStatus() (*Health, error) { + req := h.Client.R().SetDoNotParseResponse(true) + res, err := req.Get(h.Config.api) + if err != nil { + klog.Error(err, "Failed to send http request") + return nil, err + } + + statesList := make(map[string]string) + + healthStatus := &Health{ + ConnectionResponse: Response{ + Code: res.StatusCode(), + header: res.Header(), + Body: res.RawBody(), + }, + StateFailedReason: statesList, + } + + return healthStatus, nil +} + +// GetStateFromHealthResponse parse health response in json from server and +// return overall status of the server +func (h *OSClient) GetStateFromHealthResponse(health *Health) (esapi.DashboardServerState, error) { + resStatus := health.ConnectionResponse + + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + err1 := errors.Wrap(err, "failed to parse response body") + if err1 != nil { + return + } + return + } + }(resStatus.Body) + + var responseBody ResponseBody + body, _ := io.ReadAll(resStatus.Body) + err := json.Unmarshal(body, &responseBody) + if err != nil { + return "", errors.Wrap(err, "Failed to parse response body") + } + + if overallStatus, ok := responseBody.Status["overall"].(map[string]interface{}); ok { + if overallState, ok := overallStatus["state"].(string); ok { + health.OverallState = overallState + } else { + return "", errors.New("Failed to parse overallState") + } + } else { + return "", errors.New("Failed to parse overallStatus") + } + + // get the statuses for plugins stored, + // so that the plugins which are not available or ready can be shown from condition message + if statuses, ok := responseBody.Status["statuses"].([]interface{}); ok { + for _, sts := range statuses { + if curr, ok := sts.(map[string]interface{}); ok { + if curr["state"].(string) != string(esapi.StateGreen) { + health.StateFailedReason[curr["id"].(string)] = strings.Join([]string{curr["state"].(string), curr["message"].(string)}, ",") + } + } else { + return "", errors.New("Failed to convert statuses to map[string]interface{}") + } + } + } else { + return "", errors.New("Failed to convert statuses to []interface{}") + } + + return esapi.DashboardServerState(health.OverallState), nil +} + +func (h *OSClient) ExportSavedObjects() (*Response, error) { + req := h.Client.R(). + SetDoNotParseResponse(true). + SetHeaders(map[string]string{ + "Content-Type": "application/json", + "osd-xsrf": "true", + }). + SetBody([]byte(SavedObjectsReqBodyOS)) + res, err := req.Post(SavedObjectsExportURL) + if err != nil { + klog.Error(err, "Failed to send http request") + return nil, err + } + + return &Response{ + Code: res.StatusCode(), + Body: res.RawBody(), + }, nil +} + +func (h *OSClient) ImportSavedObjects(filepath string) (*Response, error) { + req := h.Client.R(). + SetDoNotParseResponse(true). + SetHeader("osd-xsrf", "true"). + SetFile("file", filepath). + SetQueryParam("overwrite", "true") + res, err := req.Post(SavedObjectsImportURL) + if err != nil { + klog.Error(err, "Failed to send http request") + return nil, err + } + + return &Response{ + Code: res.StatusCode(), + Body: res.RawBody(), + }, nil +}