diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3e5882907..014c1f3c7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -121,6 +121,15 @@ jobs: KIBANA_SYSTEM_USERNAME: ${{ env.KIBANA_SYSTEM_USERNAME }} KIBANA_SYSTEM_PASSWORD: ${{ env.KIBANA_SYSTEM_PASSWORD }} + - id: get-api-key + name: Get ES API key + run: |- + echo "apikey=$(make create-es-api-key | jq -r .encoded)" >> "$GITHUB_OUTPUT" + env: + ELASTICSEARCH_ENDPOINTS: "http://localhost:9200" + ELASTICSEARCH_USERNAME: "elastic" + ELASTICSEARCH_PASSWORD: ${{ env.ELASTIC_PASSWORD }} + - name: TF acceptance tests timeout-minutes: 10 run: make testacc @@ -131,3 +140,4 @@ jobs: ELASTICSEARCH_USERNAME: "elastic" ELASTICSEARCH_PASSWORD: ${{ env.ELASTIC_PASSWORD }} KIBANA_ENDPOINT: "http://localhost:5601" + KIBANA_API_KEY: ${{ steps.get-api-key.outputs.apikey }} diff --git a/Makefile b/Makefile index 96411111b..d7b159b58 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,7 @@ KIBANA_NAME ?= terraform-elasticstack-kb KIBANA_ENDPOINT ?= http://$(KIBANA_NAME):5601 KIBANA_SYSTEM_USERNAME ?= kibana_system KIBANA_SYSTEM_PASSWORD ?= password +KIBANA_API_KEY_NAME ?= kibana-api-key SOURCE_LOCATION ?= $(shell pwd) @@ -129,6 +130,10 @@ docker-network: ## Create a dedicated network for ES and test runs set-kibana-password: ## Sets the ES KIBANA_SYSTEM_USERNAME's password to KIBANA_SYSTEM_PASSWORD. This expects Elasticsearch to be available at localhost:9200 @ $(call retry, 10, curl -X POST -u $(ELASTICSEARCH_USERNAME):$(ELASTICSEARCH_PASSWORD) -H "Content-Type: application/json" http://localhost:9200/_security/user/$(KIBANA_SYSTEM_USERNAME)/_password -d "{\"password\":\"$(KIBANA_SYSTEM_PASSWORD)\"}" | grep -q "^{}") +.PHONY: create-es-api-key +create-es-api-key: ## Creates and outputs a new API Key. This expects Elasticsearch to be available at localhost:9200 + @ $(call retry, 10, curl -X POST -u $(ELASTICSEARCH_USERNAME):$(ELASTICSEARCH_PASSWORD) -H "Content-Type: application/json" http://localhost:9200/_security/api_key -d "{\"name\":\"$(KIBANA_API_KEY_NAME)\"}") + .PHONY: docker-clean docker-clean: ## Try to remove provisioned nodes and assigned network @ docker rm -f $(ELASTICSEARCH_NAME) $(KIBANA_NAME) || true diff --git a/docs/index.md b/docs/index.md index 66f6cd34c..87c2a1a75 100644 --- a/docs/index.md +++ b/docs/index.md @@ -92,6 +92,7 @@ Kibana resources will re-use any Elasticsearch credentials specified, these may - `KIBANA_USERNAME` - The username to use for Kibana authentication - `KIBANA_PASSWORD` - The password to use for Kibana authentication - `KIBANA_ENDPOINT` - The Kibana host to connect to +- `KIBANA_API_KEY` - An Elasticsearch API key to use instead of `KIBANA_USERNAME` and `KIBANA_PASSWORD` Fleet resources will re-use any Kibana or Elasticsearch credentials specified, these may be overridden with the following variables: - `FLEET_USERNAME` - The username to use for Kibana authentication @@ -179,6 +180,7 @@ Optional: Optional: +- `api_key` (String, Sensitive) API Key to use for authentication to Kibana - `endpoints` (List of String, Sensitive) A comma-separated list of endpoints where the terraform provider will point to, this must include the http(s) schema and port number. - `insecure` (Boolean) Disable TLS certificate validation - `password` (String, Sensitive) Password to use for API authentication to Kibana. diff --git a/docs/resources/kibana_alerting_rule.md b/docs/resources/kibana_alerting_rule.md index 3ac8ef4fe..66dcb1aa6 100644 --- a/docs/resources/kibana_alerting_rule.md +++ b/docs/resources/kibana_alerting_rule.md @@ -40,6 +40,13 @@ resource "elasticstack_kibana_alerting_rule" "example" { } ``` + +**NOTE:** `api_key` authentication is only supported for alerting rule resources from version 8.8.0 of the Elastic stack. Using an `api_key` will result in an error message like: + +``` +Could not create API key - Unsupported scheme "ApiKey" for granting API Key +``` + ## Schema diff --git a/generated/alerting/api/openapi.yaml b/generated/alerting/api/openapi.yaml index 327cf3829..291bcc645 100644 --- a/generated/alerting/api/openapi.yaml +++ b/generated/alerting/api/openapi.yaml @@ -3627,5 +3627,5 @@ components: type: http apiKeyAuth: in: header - name: ApiKey + name: Authorization type: apiKey diff --git a/generated/alerting/api_alerting.go b/generated/alerting/api_alerting.go index d8eeebe64..a36195a81 100644 --- a/generated/alerting/api_alerting.go +++ b/generated/alerting/api_alerting.go @@ -583,7 +583,7 @@ func (a *AlertingApiService) CreateRuleExecute(r ApiCreateRuleRequest) (*RuleRes } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -734,7 +734,7 @@ func (a *AlertingApiService) DeleteRuleExecute(r ApiDeleteRuleRequest) (*http.Re } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -876,7 +876,7 @@ func (a *AlertingApiService) DisableRuleExecute(r ApiDisableRuleRequest) (*http. } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -1018,7 +1018,7 @@ func (a *AlertingApiService) EnableRuleExecute(r ApiEnableRuleRequest) (*http.Re } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -1256,7 +1256,7 @@ func (a *AlertingApiService) FindRulesExecute(r ApiFindRulesRequest) (*FindRules } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -1384,7 +1384,7 @@ func (a *AlertingApiService) GetAlertingHealthExecute(r ApiGetAlertingHealthRequ } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -1516,7 +1516,7 @@ func (a *AlertingApiService) GetRuleExecute(r ApiGetRuleRequest) (*RuleResponseP } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -1655,7 +1655,7 @@ func (a *AlertingApiService) GetRuleTypesExecute(r ApiGetRuleTypesRequest) ([]Ge } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -1813,7 +1813,7 @@ func (a *AlertingApiService) LegacyCreateAlertExecute(r ApiLegacyCreateAlertRequ } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -1956,7 +1956,7 @@ func (a *AlertingApiService) LegacyDisableAlertExecute(r ApiLegacyDisableAlertRe } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -2090,7 +2090,7 @@ func (a *AlertingApiService) LegacyEnableAlertExecute(r ApiLegacyEnableAlertRequ } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -2321,7 +2321,7 @@ func (a *AlertingApiService) LegacyFindAlertsExecute(r ApiLegacyFindAlertsReques } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -2457,7 +2457,7 @@ func (a *AlertingApiService) LegacyGetAlertExecute(r ApiLegacyGetAlertRequest) ( } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -2589,7 +2589,7 @@ func (a *AlertingApiService) LegacyGetAlertTypesExecute(r ApiLegacyGetAlertTypes } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -2721,7 +2721,7 @@ func (a *AlertingApiService) LegacyGetAlertingHealthExecute(r ApiLegacyGetAlerti } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -2868,7 +2868,7 @@ func (a *AlertingApiService) LegacyMuteAlertInstanceExecute(r ApiLegacyMuteAlert } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -3002,7 +3002,7 @@ func (a *AlertingApiService) LegacyMuteAllAlertInstancesExecute(r ApiLegacyMuteA } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -3140,7 +3140,7 @@ func (a *AlertingApiService) LegacyUnmuteAlertInstanceExecute(r ApiLegacyUnmuteA } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -3274,7 +3274,7 @@ func (a *AlertingApiService) LegacyUnmuteAllAlertInstancesExecute(r ApiLegacyUnm } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -3423,7 +3423,7 @@ func (a *AlertingApiService) LegacyUpdateAlertExecute(r ApiLegacyUpdateAlertRequ } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -3566,7 +3566,7 @@ func (a *AlertingApiService) LegaryDeleteAlertExecute(r ApiLegaryDeleteAlertRequ } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -3701,7 +3701,7 @@ func (a *AlertingApiService) MuteAlertExecute(r ApiMuteAlertRequest) (*http.Resp } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -3832,7 +3832,7 @@ func (a *AlertingApiService) MuteAllAlertsExecute(r ApiMuteAllAlertsRequest) (*h } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -3967,7 +3967,7 @@ func (a *AlertingApiService) UnmuteAlertExecute(r ApiUnmuteAlertRequest) (*http. } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -4098,7 +4098,7 @@ func (a *AlertingApiService) UnmuteAllAlertsExecute(r ApiUnmuteAllAlertsRequest) } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -4243,7 +4243,7 @@ func (a *AlertingApiService) UpdateRuleExecute(r ApiUpdateRuleRequest) (*RuleRes } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } diff --git a/generated/alerting/client.go b/generated/alerting/client.go index 416200a38..94ccb6505 100644 --- a/generated/alerting/client.go +++ b/generated/alerting/client.go @@ -414,6 +414,11 @@ func (c *APIClient) prepareRequest( localVarRequest.SetBasicAuth(auth.UserName, auth.Password) } + // APIKey Authentication + if auth, ok := ctx.Value(ContextAPIKeys).(APIKey); ok { + localVarRequest.Header.Add("Authorization", "ApiKey "+auth.Key) + } + } for header, value := range c.cfg.DefaultHeader { diff --git a/generated/slo/api/openapi.yaml b/generated/slo/api/openapi.yaml index c5477b6b0..a3cd384b2 100644 --- a/generated/slo/api/openapi.yaml +++ b/generated/slo/api/openapi.yaml @@ -1550,5 +1550,5 @@ components: type: http apiKeyAuth: in: header - name: ApiKey + name: Authorization type: apiKey diff --git a/generated/slo/api_slo.go b/generated/slo/api_slo.go index fb518a05d..31d8c4ada 100644 --- a/generated/slo/api_slo.go +++ b/generated/slo/api_slo.go @@ -254,7 +254,7 @@ func (a *SloAPIService) CreateSloOpExecute(r ApiCreateSloOpRequest) (*CreateSloR } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -427,7 +427,7 @@ func (a *SloAPIService) DeleteSloOpExecute(r ApiDeleteSloOpRequest) (*http.Respo } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -591,7 +591,7 @@ func (a *SloAPIService) DisableSloOpExecute(r ApiDisableSloOpRequest) (*http.Res } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -755,7 +755,7 @@ func (a *SloAPIService) EnableSloOpExecute(r ApiEnableSloOpRequest) (*http.Respo } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -968,7 +968,7 @@ func (a *SloAPIService) FindSlosOpExecute(r ApiFindSlosOpRequest) (*FindSloRespo } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -1154,7 +1154,7 @@ func (a *SloAPIService) GetSloOpExecute(r ApiGetSloOpRequest) (*SloResponse, *ht } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -1337,7 +1337,7 @@ func (a *SloAPIService) HistoricalSummaryOpExecute(r ApiHistoricalSummaryOpReque } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } @@ -1513,7 +1513,7 @@ func (a *SloAPIService) UpdateSloOpExecute(r ApiUpdateSloOpRequest) (*SloRespons } else { key = apiKey.Key } - localVarHeaderParams["ApiKey"] = key + localVarHeaderParams["Authorization"] = key } } } diff --git a/go.mod b/go.mod index 306567115..3626e221b 100644 --- a/go.mod +++ b/go.mod @@ -73,3 +73,5 @@ require ( google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/disaster37/go-kibana-rest/v8 => ./libs/go-kibana-rest \ No newline at end of file diff --git a/internal/clients/api_client.go b/internal/clients/api_client.go index 8fb82f76d..993ba8333 100644 --- a/internal/clients/api_client.go +++ b/internal/clients/api_client.go @@ -226,17 +226,33 @@ func (a *ApiClient) GetFleetClient() (*fleet.Client, error) { } func (a *ApiClient) SetSloAuthContext(ctx context.Context) context.Context { - return context.WithValue(ctx, slo.ContextBasicAuth, slo.BasicAuth{ - UserName: a.kibanaConfig.Username, - Password: a.kibanaConfig.Password, - }) + if a.kibanaConfig.ApiKey != "" { + return context.WithValue(ctx, slo.ContextAPIKeys, map[string]slo.APIKey{ + "apiKeyAuth": { + Prefix: "ApiKey", + Key: a.kibanaConfig.ApiKey, + }}) + } else { + return context.WithValue(ctx, slo.ContextBasicAuth, slo.BasicAuth{ + UserName: a.kibanaConfig.Username, + Password: a.kibanaConfig.Password, + }) + } } func (a *ApiClient) SetAlertingAuthContext(ctx context.Context) context.Context { - return context.WithValue(ctx, alerting.ContextBasicAuth, alerting.BasicAuth{ - UserName: a.kibanaConfig.Username, - Password: a.kibanaConfig.Password, - }) + if a.kibanaConfig.ApiKey != "" { + return context.WithValue(ctx, alerting.ContextAPIKeys, map[string]alerting.APIKey{ + "apiKeyAuth": { + Prefix: "ApiKey", + Key: a.kibanaConfig.ApiKey, + }}) + } else { + return context.WithValue(ctx, alerting.ContextBasicAuth, alerting.BasicAuth{ + UserName: a.kibanaConfig.Username, + Password: a.kibanaConfig.Password, + }) + } } func (a *ApiClient) ID(ctx context.Context, resourceId string) (*CompositeId, diag.Diagnostics) { @@ -331,6 +347,7 @@ func buildKibanaClient(cfg config.Client) (*kibana.Client, error) { } kib, err := kibana.NewClient(*cfg.Kibana) + if err != nil { return nil, err } @@ -356,9 +373,23 @@ func buildAlertingClient(cfg config.Client) *alerting.APIClient { } func buildConnectorsClient(cfg config.Client) (*connectors.Client, error) { - basicAuthProvider, err := securityprovider.NewSecurityProviderBasicAuth(cfg.Kibana.Username, cfg.Kibana.Password) - if err != nil { - return nil, fmt.Errorf("unable to create basic auth provider: %w", err) + var authInterceptor connectors.ClientOption + if cfg.Kibana.ApiKey != "" { + apiKeyProvider, err := securityprovider.NewSecurityProviderApiKey( + "header", + "Authorization", + "ApiKey "+cfg.Kibana.ApiKey, + ) + if err != nil { + return nil, fmt.Errorf("unable to create api key auth provider: %w", err) + } + authInterceptor = connectors.WithRequestEditorFn(apiKeyProvider.Intercept) + } else { + basicAuthProvider, err := securityprovider.NewSecurityProviderBasicAuth(cfg.Kibana.Username, cfg.Kibana.Password) + if err != nil { + return nil, fmt.Errorf("unable to create basic auth provider: %w", err) + } + authInterceptor = connectors.WithRequestEditorFn(basicAuthProvider.Intercept) } httpClient := &http.Client{} @@ -371,7 +402,7 @@ func buildConnectorsClient(cfg config.Client) (*connectors.Client, error) { return connectors.NewClient( cfg.Kibana.Address, - connectors.WithRequestEditorFn(basicAuthProvider.Intercept), + authInterceptor, connectors.WithHTTPClient(httpClient), ) } diff --git a/internal/clients/config/base.go b/internal/clients/config/base.go index 89e2f3b45..ced9ee3e8 100644 --- a/internal/clients/config/base.go +++ b/internal/clients/config/base.go @@ -72,6 +72,7 @@ func (b baseConfig) toKibanaConfig() kibanaConfig { return kibanaConfig{ Username: b.Username, Password: b.Password, + ApiKey: b.ApiKey, } } diff --git a/internal/clients/config/fleet_test.go b/internal/clients/config/fleet_test.go index decc53ff8..914801714 100644 --- a/internal/clients/config/fleet_test.go +++ b/internal/clients/config/fleet_test.go @@ -177,7 +177,7 @@ func Test_newFleetConfigFromFramework(t *testing.T) { }, }, { - name: "should use the provided config optios", + name: "should use the provided config options", args: func() args { kibanaCfg := kibanaConfig{ Address: "example.com/kibana", diff --git a/internal/clients/config/kibana.go b/internal/clients/config/kibana.go index b0f80c806..526082dad 100644 --- a/internal/clients/config/kibana.go +++ b/internal/clients/config/kibana.go @@ -34,6 +34,10 @@ func newKibanaConfigFromSDK(d *schema.ResourceData, base baseConfig) (kibanaConf config.Password = password.(string) } + if apiKey, ok := kibConfig["api_key"]; ok && apiKey != "" { + config.ApiKey = apiKey.(string) + } + if endpoints, ok := kibConfig["endpoints"]; ok && len(endpoints.([]interface{})) > 0 { // We're curently limited by the API to a single endpoint if endpoint := endpoints.([]interface{})[0]; endpoint != nil { @@ -60,6 +64,9 @@ func newKibanaConfigFromFramework(ctx context.Context, cfg ProviderConfiguration if kibConfig.Password.ValueString() != "" { config.Password = kibConfig.Password.ValueString() } + if kibConfig.ApiKey.ValueString() != "" { + config.ApiKey = kibConfig.ApiKey.ValueString() + } var endpoints []string diags := kibConfig.Endpoints.ElementsAs(ctx, &endpoints, true) if diags.HasError() { @@ -79,6 +86,7 @@ func newKibanaConfigFromFramework(ctx context.Context, cfg ProviderConfiguration func (k kibanaConfig) withEnvironmentOverrides() kibanaConfig { k.Username = withEnvironmentOverride(k.Username, "KIBANA_USERNAME") k.Password = withEnvironmentOverride(k.Password, "KIBANA_PASSWORD") + k.ApiKey = withEnvironmentOverride(k.ApiKey, "KIBANA_API_KEY") k.Address = withEnvironmentOverride(k.Address, "KIBANA_ENDPOINT") if insecure, ok := os.LookupEnv("KIBANA_INSECURE"); ok { @@ -95,6 +103,7 @@ func (k kibanaConfig) toFleetConfig() fleetConfig { URL: k.Address, Username: k.Username, Password: k.Password, + APIKey: k.ApiKey, Insecure: k.DisableVerifySSL, } } diff --git a/internal/clients/config/kibana_test.go b/internal/clients/config/kibana_test.go index a7b8fbbac..5a5b68f99 100644 --- a/internal/clients/config/kibana_test.go +++ b/internal/clients/config/kibana_test.go @@ -42,7 +42,7 @@ func Test_newKibanaConfigFromSDK(t *testing.T) { }, }, { - name: "should use the provided config optios", + name: "should use the provided config options", args: func() args { baseCfg := baseConfig{ Username: "elastic", @@ -113,6 +113,7 @@ func Test_newKibanaConfigFromSDK(t *testing.T) { os.Unsetenv("KIBANA_PASSWORD") os.Unsetenv("KIBANA_ENDPOINT") os.Unsetenv("KIBANA_INSECURE") + os.Unsetenv("KIBANA_API_KEY") args := tt.args() rd := schema.TestResourceDataRaw(t, map[string]*schema.Schema{ @@ -159,7 +160,7 @@ func Test_newKibanaConfigFromFramework(t *testing.T) { }, }, { - name: "should use the provided config optios", + name: "should use the provided config options", args: func() args { baseCfg := baseConfig{ Username: "elastic", @@ -189,6 +190,34 @@ func Test_newKibanaConfigFromFramework(t *testing.T) { } }, }, + { + name: "should use api_key when provided in config options", + args: func() args { + baseCfg := baseConfig{ + ApiKey: "test", + } + + return args{ + baseCfg: baseCfg, + providerConfig: ProviderConfiguration{ + Kibana: []KibanaConnection{ + { + ApiKey: types.StringValue("test"), + Endpoints: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("example.com/kibana"), + }), + Insecure: types.BoolValue(true), + }, + }, + }, + expectedConfig: kibanaConfig{ + Address: "example.com/kibana", + ApiKey: "test", + DisableVerifySSL: true, + }, + } + }, + }, { name: "should prefer environment variables", args: func() args { @@ -232,6 +261,7 @@ func Test_newKibanaConfigFromFramework(t *testing.T) { t.Run(tt.name, func(t *testing.T) { os.Unsetenv("KIBANA_USERNAME") os.Unsetenv("KIBANA_PASSWORD") + os.Unsetenv("KIBANA_API_KEY") os.Unsetenv("KIBANA_ENDPOINT") os.Unsetenv("KIBANA_INSECURE") diff --git a/internal/clients/config/provider.go b/internal/clients/config/provider.go index 7da987528..b089b8493 100644 --- a/internal/clients/config/provider.go +++ b/internal/clients/config/provider.go @@ -25,6 +25,7 @@ type ElasticsearchConnection struct { type KibanaConnection struct { Username types.String `tfsdk:"username"` Password types.String `tfsdk:"password"` + ApiKey types.String `tfsdk:"api_key"` Endpoints types.List `tfsdk:"endpoints"` Insecure types.Bool `tfsdk:"insecure"` } diff --git a/internal/kibana/alerting_test.go b/internal/kibana/alerting_test.go index e225ab39c..65f4016af 100644 --- a/internal/kibana/alerting_test.go +++ b/internal/kibana/alerting_test.go @@ -18,6 +18,8 @@ import ( func TestAccResourceAlertingRule(t *testing.T) { minSupportedVersion := version.Must(version.NewSemver("7.14.0")) + t.Setenv("KIBANA_API_KEY", "") + ruleName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum) resource.Test(t, resource.TestCase{ diff --git a/internal/schema/connection.go b/internal/schema/connection.go index c77e1a2c8..5810aac46 100644 --- a/internal/schema/connection.go +++ b/internal/schema/connection.go @@ -119,6 +119,14 @@ func GetKbFWConnectionBlock() fwschema.Block { MarkdownDescription: "Kibana connection configuration block.", NestedObject: fwschema.NestedBlockObject{ Attributes: map[string]fwschema.Attribute{ + "api_key": fwschema.StringAttribute{ + MarkdownDescription: "API Key to use for authentication to Kibana", + Optional: true, + Sensitive: true, + Validators: []validator.String{ + stringvalidator.ConflictsWith(usernamePath, passwordPath), + }, + }, "username": fwschema.StringAttribute{ MarkdownDescription: "Username to use for API authentication to Kibana.", Optional: true, @@ -314,6 +322,7 @@ func GetEsConnectionSchema(keyName string, isProviderConfiguration bool) *schema } func GetKibanaConnectionSchema() *schema.Schema { + withEnvDefault := func(key string, dv interface{}) schema.SchemaDefaultFunc { return nil } return &schema.Schema{ Description: "Kibana connection configuration block.", Type: schema.TypeList, @@ -321,6 +330,14 @@ func GetKibanaConnectionSchema() *schema.Schema { Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "api_key": { + Description: "API Key to use for authentication to Kibana", + Type: schema.TypeString, + Optional: true, + Sensitive: true, + DefaultFunc: withEnvDefault("KIBANA_API_KEY", nil), + ConflictsWith: []string{"kibana.0.password", "kibana.0.username"}, + }, "username": { Description: "Username to use for API authentication to Kibana.", Type: schema.TypeString, diff --git a/libs/go-kibana-rest/.github/workflows/workflow.yml b/libs/go-kibana-rest/.github/workflows/workflow.yml new file mode 100644 index 000000000..cc918ffdb --- /dev/null +++ b/libs/go-kibana-rest/.github/workflows/workflow.yml @@ -0,0 +1,51 @@ +name: "build" + +on: + push: + branches: + - main + - 7.x + - 8.x + tags: + - "*" + pull_request: + branches: + - main + - 7.x + - 8.x + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: "1.19" + - name: Setup Elasticsearch / Kibana + run: | + set -e + docker-compose up elasticsearch & + echo "Waiting for Elasticsearch availability" + until curl -s http://localhost:9200 | grep -q 'missing authentication credentials'; do sleep 30; done; + echo "Setting kibana_system password" + until curl -s -X POST -u elastic:changeme -H "Content-Type: application/json" http://localhost:9200/_security/user/kibana_system/_password -d "{\"password\":\"changeme\"}" | grep -q "^{}"; do sleep 10; done + curl -XPOST -u elastic:changeme http://localhost:9200/_license/start_trial?acknowledge=true + docker-compose up kibana & + until $(curl --output /dev/null --silent --head --fail -u elastic:changeme http://localhost:5601); do sleep 5; done + sleep 10 + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: latest + args: --timeout 600s + - name: Run build + run: go build + - name: Run test + run: make test + - uses: codecov/codecov-action@v2 + with: + files: coverage.out + flags: unittests + fail_ci_if_error: true \ No newline at end of file diff --git a/libs/go-kibana-rest/.gitignore b/libs/go-kibana-rest/.gitignore new file mode 100644 index 000000000..f1c181ec9 --- /dev/null +++ b/libs/go-kibana-rest/.gitignore @@ -0,0 +1,12 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out diff --git a/libs/go-kibana-rest/.theia/launch.json b/libs/go-kibana-rest/.theia/launch.json new file mode 100644 index 000000000..ee6285c12 --- /dev/null +++ b/libs/go-kibana-rest/.theia/launch.json @@ -0,0 +1,12 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug current file", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${fileDirname}" + } +] +} diff --git a/libs/go-kibana-rest/LICENSE b/libs/go-kibana-rest/LICENSE new file mode 100644 index 000000000..d56aa8ec4 --- /dev/null +++ b/libs/go-kibana-rest/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 disaster37 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/libs/go-kibana-rest/Makefile b/libs/go-kibana-rest/Makefile new file mode 100644 index 000000000..0091d2468 --- /dev/null +++ b/libs/go-kibana-rest/Makefile @@ -0,0 +1,18 @@ +TEST?=./... +PKG_NAME=kbapi +KIBANA_URL ?= http://127.0.0.1:5601 +KIBANA_USERNAME ?= elastic +KIBANA_PASSWORD ?= changeme + +all: help + + +test: fmt + KIBANA_URL=${KIBANA_URL} KIBANA_USERNAME=${KIBANA_USERNAME} KIBANA_PASSWORD=${KIBANA_PASSWORD} go test $(TEST) -v -count 1 -parallel 1 -race -coverprofile=coverage.out -covermode=atomic $(TESTARGS) -timeout 120m + +fmt: + @echo "==> Fixing source code with gofmt..." + gofmt -s -w ./ + + + diff --git a/libs/go-kibana-rest/README.md b/libs/go-kibana-rest/README.md new file mode 100644 index 000000000..c0c276bef --- /dev/null +++ b/libs/go-kibana-rest/README.md @@ -0,0 +1,337 @@ +# go-kibana-rest + +[](https://circleci.com/gh/disaster37/go-kibana-rest) +[](https://goreportcard.com/report/github.com/disaster37/go-kibana-rest) +[](http://godoc.org/github.com/disaster37/go-kibana-rest) +[](https://codecov.io/gh/disaster37/go-kibana-rest) + +Go framework to handle kibana API + +## Compatibility + +It work with Kibana 7.x. and 8.x + +## Installation + +In your go.mod, put: +```go +require github.com/disaster37/go-kibana-rest/v8 +``` + +## Usage + +### Init the client + +```go +cfg := kibana.Config{ + Address: "http://127.0.0.1:5601", + Username: "elastic", + Password: "changeme", + DisableVerifySSL: true, +} + +client, err := kibana.NewClient(cfg) + +if err != nil { + log.Fatalf("Error creating the client: %s", err) +} + +status, err := client.API.KibanaStatus.Get() +if err != nil { + log.Fatalf("Error getting response: %s", err) +} +log.Println(status) +``` + +### Handle shorten URL + +```go +// Shorten long URL +shortenURL := &kbapi.ShortenURL{ + URL: "/app/kibana#/dashboard?_g=()&_a=(description:'',filters:!(),fullScreenMode:!f,options:(hidePanelTitles:!f,useMargins:!t),panels:!((embeddableConfig:(),gridData:(h:15,i:'1',w:24,x:0,y:0),id:'8f4d0c00-4c86-11e8-b3d7-01146121b73d',panelIndex:'1',type:visualization,version:'7.0.0-alpha1')),query:(language:lucene,query:''),timeRestore:!f,title:'New%20Dashboard',viewMode:edit)", +} +shortenURLResponse, err := client.API.KibanaShortenURL.Create(shortenURL) +if err != nil { + log.Fatalf("Error creating shorten URL: %s", err) +} +log.Println(fmt.Sprintf("http://localhost:5601/goto/%s", shortenURLResponse.ID)) +``` + +### Handle logstash Pipeline +```go +// Create or update Logstash pipeline +logstashPipeline := &kbapi.LogstashPipeline{ + ID: "sample", + Description: "Sample logstash pipeline", + Pipeline: "input { stdin {} } output { stdout {} }", + Settings: map[string]interface{}{ + "queue.type": "persisted", + }, +} +logstashPipeline, err = client.API.KibanaLogstashPipeline.CreateOrUpdate(logstashPipeline) +if err != nil { + log.Fatalf("Error creating logstash pipeline: %s", err) +} +log.Println(logstashPipeline) + +// Get the logstash pipeline +logstashPipeline, err = client.API.KibanaLogstashPipeline.Get("sample") +if err != nil { + log.Fatalf("Error getting logstash pipeline: %s", err) +} +log.Println(logstashPipeline) + +// Get all logstash pipeline +logstashPipelines, err := client.API.KibanaLogstashPipeline.List() +if err != nil { + log.Fatalf("Error getting all logstash pipeline: %s", err) +} +log.Println(logstashPipelines) + +// Delete logstash pipeline +err = client.API.KibanaLogstashPipeline.Delete("sample") +if err != nil { + log.Fatalf("Error deleting logstash pipeline: %s", err) +} +log.Println("Logstash pipeline 'sample' successfully deleted") +``` + +### Handle user space + +```go +// Create user space +space := &kbapi.KibanaSpace{ + ID: "test", + Name: "test", + Description: "My test", +} +space, err = client.API.KibanaSpaces.Create(space) +if err != nil { + log.Fatalf("Error creating user space: %s", err) +} +log.Println(space) + +// Update user space +space.Name = "new name" +space, err = client.API.KibanaSpaces.Update(space) +if err != nil { + log.Fatalf("Error updating user space: %s", err) +} +log.Println(space) + +// Get the user space +space, err = client.API.KibanaSpaces.Get("test") +if err != nil { + log.Fatalf("Error getting user space: %s", err) +} +log.Println(space) + +// Get all user space +spaces, err := client.API.KibanaSpaces.List() +if err != nil { + log.Fatalf("Error getting all user spaces: %s", err) +} +log.Println(spaces) + +// Copy config object from default space to test space +parameter := &kbapi.KibanaSpaceCopySavedObjectParameter{ + Spaces: []string{"test"}, + IncludeReferences: true, + Overwrite: true, + Objects: []kbapi.KibanaSpaceObjectParameter{ + { + Type: "config", + ID: "7.4.2", + }, + }, +} +err = client.API.KibanaSpaces.CopySavedObjects(parameter, "") +if err != nil { + log.Fatalf("Error copying object from another user space: %s", err) +} +log.Println("Copying config object from 'default' to 'test' user space successfully") + + +// Delete user space +err = client.API.KibanaSpaces.Delete("test") +if err != nil { + log.Fatalf("Error deleteing user space: %s", err) +} +log.Println("User space 'test' successfully deleted") +``` + +### Handle dashboard + +```go +// Import dashboard from file in default user space +b, err := ioutil.ReadFile("../fixtures/kibana-dashboard.json") +if err != nil { + log.Fatalf("Error reading file: %s", err) +} +data := make(map[string]interface{}) +err = json.Unmarshal(b, &data) +err = client.API.KibanaDashboard.Import(data, nil, true, "default") +if err != nil { + log.Fatalf("Error importing dashboard: %s", err) +} +log.Println("Importing dashboard successfully") + +// Export dashboard from default user space +data, err = client.API.KibanaDashboard.Export([]string{"edf84fe0-e1a0-11e7-b6d5-4dc382ef7f5b"}, "default") +if err != nil { + log.Fatalf("Error exporting dashboard: %s", err) +} +log.Println("Exporting dashboard successfully: %s", data) +``` + +### Handle role management + +```go +// Create or update role +role := &kbapi.KibanaRole{ + Name: "test", + Elasticsearch: &kbapi.KibanaRoleElasticsearch{ + Indices: []kbapi.KibanaRoleElasticsearchIndice{ + { + Names: []string{ + "*", + }, + Privileges: []string{ + "read", + }, + }, + }, + }, + Kibana: []kbapi.KibanaRoleKibana{ + { + Base: []string{ + "read", + }, + }, + }, +} +role, err = client.API.KibanaRoleManagement.CreateOrUpdate(role) +if err != nil { + log.Fatalf("Error creating role: %s", role) +} +log.Println(role) + +// Get the role +role, err = client.API.KibanaRoleManagement.Get("test") +if err != nil { + log.Fatalf("Error reading role: %s", role) +} +log.Println(role) + +// List all roles +roles, err := client.API.KibanaRoleManagement.List() +if err != nil { + log.Fatalf("Error reading all roles: %s", err) +} +log.Println(roles) + +// Delete role +err = client.API.KibanaRoleManagement.Delete("test") +if err != nil { + log.Fatalf("Error deleting role: %s", err) +} +log.Println("Role successfully deleted") +``` + +### Handle save object + +```go +// Create new index pattern in default user space +dataJSON := `{"attributes": {"title": "test-pattern-*"}}` +data = make(map[string]interface{}) +err = json.Unmarshal([]byte(dataJSON), &data) +if err != nil { + log.Fatalf("Error converting json to struct: %s", err) +} +resp, err := client.API.KibanaSavedObject.Create(data, "index-pattern", "test", true, "default") +if err != nil { + log.Fatalf("Error creating object: %s", err) +} +log.Println(resp) + +// Get index pattern save object from default user space +resp, err = client.API.KibanaSavedObject.Get("index-pattern", "test", "default") +if err != nil { + log.Fatalf("Error getting index pattern save object: %s", err) +} +log.Println(resp) + +// Search index pattern from default user space +parameters := &kbapi.OptionalFindParameters{ + Search: "test", + SearchFields: []string{"id"}, + Fields: []string{"id"}, +} +resp, err = client.API.KibanaSavedObject.Find("index-pattern", "default", parameters) +if err != nil { + log.Fatalf("Error searching index pattern: %s", err) +} +log.Println(resp) + +// Update index pattern in default user space +dataJSON = `{"attributes": {"title": "test-pattern2-*"}}` +err = json.Unmarshal([]byte(dataJSON), &data) +if err != nil { + log.Fatalf("Error converting json to struct") +} +resp, err = client.API.KibanaSavedObject.Update(data, "index-pattern", "test", "default") +if err != nil { + log.Fatalf("Error updating index pattern: %s", err) +} + +// Export index pattern from default user space +request := []map[string]string{ + { + "type": "index-pattern", + "id": "test", + }, +} +resp, err = client.API.KibanaSavedObject.Export(nil, request, true, "default") +if err != nil { + log.Fatalf("Error exporting index pattern: %s", err) +} +log.Println(resp) + +// import index pattern in default user space +b, err = json.Marshal(resp) +if err != nil { + log.Fatalf("Error converting struct to json") +} +resp2, err := client.API.KibanaSavedObject.Import(b, true, "default") +if err != nil { + log.Fatalf("Error importing index pattern: %s", err) +} +log.Println(resp2) + +// Delete index pattern in default user space +err = client.API.KibanaSavedObject.Delete("index-pattern", "test", "default") +if err != nil { + log.Fatalf("Error deleting index pattern: %s", err) +} +log.Println("Index pattern successfully deleted") +``` + +### Handle status + +```go +status, err := client.API.KibanaStatus.Get() +if err != nil { + log.Fatalf("Error getting status: %s", err) +} +log.Println(status) +``` + +## Contribute + +First, if you use kibana module that required license like Logstash Pipeline, you need to have valid license or start trial license. + +Start trial license: +```bash +curl -XPOST -u elastic:changeme "http://localhost:9200/_license/start_trial?acknowledge=true&pretty" + +``` \ No newline at end of file diff --git a/libs/go-kibana-rest/_examples/sample.go b/libs/go-kibana-rest/_examples/sample.go new file mode 100644 index 000000000..053a8513a --- /dev/null +++ b/libs/go-kibana-rest/_examples/sample.go @@ -0,0 +1,283 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + + "github.com/disaster37/go-kibana-rest/v8" + "github.com/disaster37/go-kibana-rest/v8/kbapi" +) + +func main() { + + cfg := kibana.Config{ + Address: "http://127.0.0.1:5601", + Username: "elastic", + Password: "changeme", + DisableVerifySSL: true, + } + + client, err := kibana.NewClient(cfg) + + if err != nil { + log.Fatalf("Error creating the client: %s", err) + } + + status, err := client.API.KibanaStatus.Get() + if err != nil { + log.Fatalf("Error getting response: %s", err) + } + log.Println(status) + + // Shorten long URL + shortenURL := &kbapi.ShortenURL{ + URL: "/app/kibana#/dashboard?_g=()&_a=(description:'',filters:!(),fullScreenMode:!f,options:(hidePanelTitles:!f,useMargins:!t),panels:!((embeddableConfig:(),gridData:(h:15,i:'1',w:24,x:0,y:0),id:'8f4d0c00-4c86-11e8-b3d7-01146121b73d',panelIndex:'1',type:visualization,version:'7.0.0-alpha1')),query:(language:lucene,query:''),timeRestore:!f,title:'New%20Dashboard',viewMode:edit)", + } + shortenURLResponse, err := client.API.KibanaShortenURL.Create(shortenURL) + if err != nil { + log.Fatalf("Error creating shorten URL: %s", err) + } + log.Println(fmt.Sprintf("http://localhost:5601/goto/%s", shortenURLResponse.ID)) + + // Create or update Logstash pipeline + logstashPipeline := &kbapi.LogstashPipeline{ + ID: "sample", + Description: "Sample logstash pipeline", + Pipeline: "input { stdin {} } output { stdout {} }", + Settings: map[string]interface{}{ + "queue.type": "persisted", + }, + } + logstashPipeline, err = client.API.KibanaLogstashPipeline.CreateOrUpdate(logstashPipeline) + if err != nil { + log.Fatalf("Error creating logstash pipeline: %s", err) + } + log.Println(logstashPipeline) + + // Get the logstash pipeline + logstashPipeline, err = client.API.KibanaLogstashPipeline.Get("sample") + if err != nil { + log.Fatalf("Error getting logstash pipeline: %s", err) + } + log.Println(logstashPipeline) + + // Get all logstash pipeline + logstashPipelines, err := client.API.KibanaLogstashPipeline.List() + if err != nil { + log.Fatalf("Error getting all logstash pipeline: %s", err) + } + log.Println(logstashPipelines) + + // Delete logstash pipeline + err = client.API.KibanaLogstashPipeline.Delete("sample") + if err != nil { + log.Fatalf("Error deleting logstash pipeline: %s", err) + } + log.Println("Logstash pipeline 'sample' successfully deleted") + + // Create user space + space := &kbapi.KibanaSpace{ + ID: "test", + Name: "test", + Description: "My test", + } + space, err = client.API.KibanaSpaces.Create(space) + if err != nil { + log.Fatalf("Error creating user space: %s", err) + } + log.Println(space) + + // Update user space + space.Name = "new name" + space, err = client.API.KibanaSpaces.Update(space) + if err != nil { + log.Fatalf("Error updating user space: %s", err) + } + log.Println(space) + + // Get the user space + space, err = client.API.KibanaSpaces.Get("test") + if err != nil { + log.Fatalf("Error getting user space: %s", err) + } + log.Println(space) + + // Get all user space + spaces, err := client.API.KibanaSpaces.List() + if err != nil { + log.Fatalf("Error getting all user spaces: %s", err) + } + log.Println(spaces) + + // Copy config object from default space to test space + parameter := &kbapi.KibanaSpaceCopySavedObjectParameter{ + Spaces: []string{"test"}, + IncludeReferences: true, + Overwrite: true, + Objects: []kbapi.KibanaSpaceObjectParameter{ + { + Type: "config", + ID: "7.4.2", + }, + }, + } + err = client.API.KibanaSpaces.CopySavedObjects(parameter, "") + if err != nil { + log.Fatalf("Error copying object from another user space: %s", err) + } + log.Println("Copying config object from 'default' to 'test' user space successfully") + + // Delete user space + err = client.API.KibanaSpaces.Delete("test") + if err != nil { + log.Fatalf("Error deleteing user space: %s", err) + } + log.Println("User space 'test' successfully deleted") + + // Import dashboard from file in default user space + b, err := ioutil.ReadFile("../fixtures/kibana-dashboard.json") + if err != nil { + log.Fatalf("Error reading file: %s", err) + } + data := make(map[string]interface{}) + err = json.Unmarshal(b, &data) + err = client.API.KibanaDashboard.Import(data, nil, true, "default") + if err != nil { + log.Fatalf("Error importing dashboard: %s", err) + } + log.Println("Importing dashboard successfully") + + // Export dashboard from default user space + data, err = client.API.KibanaDashboard.Export([]string{"edf84fe0-e1a0-11e7-b6d5-4dc382ef7f5b"}, "default") + if err != nil { + log.Fatalf("Error exporting dashboard: %s", err) + } + log.Println(data) + + // Create or update role + role := &kbapi.KibanaRole{ + Name: "test", + Elasticsearch: &kbapi.KibanaRoleElasticsearch{ + Indices: []kbapi.KibanaRoleElasticsearchIndice{ + { + Names: []string{ + "*", + }, + Privileges: []string{ + "read", + }, + }, + }, + }, + Kibana: []kbapi.KibanaRoleKibana{ + { + Base: []string{ + "read", + }, + }, + }, + } + role, err = client.API.KibanaRoleManagement.CreateOrUpdate(role) + if err != nil { + log.Fatalf("Error creating role: %s", role) + } + log.Println(role) + + // Get the role + role, err = client.API.KibanaRoleManagement.Get("test") + if err != nil { + log.Fatalf("Error reading role: %s", role) + } + log.Println(role) + + // List all roles + roles, err := client.API.KibanaRoleManagement.List() + if err != nil { + log.Fatalf("Error reading all roles: %s", err) + } + log.Println(roles) + + // Delete role + err = client.API.KibanaRoleManagement.Delete("test") + if err != nil { + log.Fatalf("Error deleting role: %s", err) + } + log.Println("Role successfully deleted") + + // Create new index pattern in default user space + dataJSON := `{"attributes": {"title": "test-pattern-*"}}` + data = make(map[string]interface{}) + err = json.Unmarshal([]byte(dataJSON), &data) + if err != nil { + log.Fatalf("Error converting json to struct: %s", err) + } + resp, err := client.API.KibanaSavedObject.Create(data, "index-pattern", "test", true, "default") + if err != nil { + log.Fatalf("Error creating object: %s", err) + } + log.Println(resp) + + // Get index pattern save object from default user space + resp, err = client.API.KibanaSavedObject.Get("index-pattern", "test", "default") + if err != nil { + log.Fatalf("Error getting index pattern save object: %s", err) + } + log.Println(resp) + + // Search index pattern from default user space + parameters := &kbapi.OptionalFindParameters{ + Search: "test", + SearchFields: []string{"id"}, + Fields: []string{"id"}, + } + resp, err = client.API.KibanaSavedObject.Find("index-pattern", "default", parameters) + if err != nil { + log.Fatalf("Error searching index pattern: %s", err) + } + log.Println(resp) + + // Update index pattern in default user space + dataJSON = `{"attributes": {"title": "test-pattern2-*"}}` + err = json.Unmarshal([]byte(dataJSON), &data) + if err != nil { + log.Fatalf("Error converting json to struct") + } + resp, err = client.API.KibanaSavedObject.Update(data, "index-pattern", "test", "default") + if err != nil { + log.Fatalf("Error updating index pattern: %s", err) + } + + // Export index pattern from default user space + request := []map[string]string{ + { + "type": "index-pattern", + "id": "test", + }, + } + response, error := client.API.KibanaSavedObject.Export(nil, request, true, "default") + if error != nil { + log.Fatalf("Error exporting index pattern: %s", error) + } + log.Println(response) + + // import index pattern in default user space + b, err = json.Marshal(response) + if err != nil { + log.Fatalf("Error converting struct to json") + } + resp2, err := client.API.KibanaSavedObject.Import(b, true, "default") + if err != nil { + log.Fatalf("Error importing index pattern: %s", err) + } + log.Println(resp2) + + // Delete index pattern in default user space + err = client.API.KibanaSavedObject.Delete("index-pattern", "test", "default") + if err != nil { + log.Fatalf("Error deleting index pattern: %s", err) + } + log.Println("Index pattern successfully deleted") + +} diff --git a/libs/go-kibana-rest/coverage.txt b/libs/go-kibana-rest/coverage.txt new file mode 100644 index 000000000..5b61039f0 --- /dev/null +++ b/libs/go-kibana-rest/coverage.txt @@ -0,0 +1,354 @@ +mode: atomic +github.com/disaster37/go-kibana-rest/v7/kibana.go:26.42,28.2 1 1 +github.com/disaster37/go-kibana-rest/v7/kibana.go:31.45,32.23 1 2 +github.com/disaster37/go-kibana-rest/v7/kibana.go:36.2,42.31 2 2 +github.com/disaster37/go-kibana-rest/v7/kibana.go:46.2,51.34 2 2 +github.com/disaster37/go-kibana-rest/v7/kibana.go:55.2,55.20 1 2 +github.com/disaster37/go-kibana-rest/v7/kibana.go:32.23,34.3 1 1 +github.com/disaster37/go-kibana-rest/v7/kibana.go:42.31,44.3 1 0 +github.com/disaster37/go-kibana-rest/v7/kibana.go:51.34,53.3 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api._.go:72.32,114.2 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:67.38,70.2 2 4 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:73.78,74.48 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:74.48,76.17 1 3 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:79.3,83.17 4 3 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:86.3,87.31 2 3 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:93.3,95.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:98.3,100.25 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:76.17,78.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:83.17,85.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:87.31,88.32 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:91.4,91.61 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:88.32,90.5 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:95.17,97.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:106.80,107.37 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:107.37,110.17 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:113.3,114.31 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:117.3,119.17 3 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:122.3,124.26 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:110.17,112.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:114.31,116.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:119.17,121.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:130.100,131.59 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:131.59,133.24 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:136.3,143.17 7 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:146.3,147.17 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:150.3,151.31 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:156.3,157.17 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:161.3,163.25 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:133.24,135.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:143.17,145.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:147.17,149.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:151.31,153.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:157.17,159.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:169.84,170.33 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:170.33,172.17 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:175.3,179.17 4 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:182.3,183.31 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:187.3,187.13 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:172.17,174.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:179.17,181.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_role_management.go:183.31,185.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_status.go:21.62,22.38 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_status.go:22.38,24.17 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_status.go:27.3,28.31 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_status.go:34.3,36.17 3 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_status.go:39.3,41.27 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_status.go:24.17,26.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_status.go:28.31,29.32 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_status.go:32.4,32.61 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_status.go:29.32,31.5 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_status.go:36.17,38.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:22.74,23.83 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:23.83,25.23 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:28.3,32.52 4 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:38.3,42.17 4 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:45.3,46.31 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:53.3,55.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:58.3,60.19 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:25.23,27.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:32.52,34.4 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:34.9,36.4 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:42.17,44.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:46.31,47.32 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:50.4,50.61 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:47.32,49.5 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:55.17,57.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:66.74,67.107 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:67.107,69.18 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:72.3,78.52 6 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:84.3,87.31 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:90.3,91.17 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:94.3,95.17 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:98.3,99.31 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:102.3,104.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:107.3,111.13 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:69.18,71.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:78.52,80.4 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:80.9,82.4 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:87.31,89.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:91.17,93.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:95.17,97.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:99.31,101.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_dashboard.go:104.17,106.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:45.44,48.2 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:51.82,52.52 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:52.52,54.15 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:57.3,61.17 4 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:64.3,65.31 2 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:71.3,73.17 3 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:76.3,78.31 2 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:54.15,56.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:61.17,63.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:65.31,66.32 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:69.4,69.61 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:66.32,68.5 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:73.17,75.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:83.84,84.43 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:84.43,88.17 3 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:91.3,92.31 2 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:95.3,97.17 3 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:100.3,102.46 2 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:88.17,90.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:92.31,94.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:97.17,99.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:108.104,109.77 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:109.77,111.30 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:115.3,118.17 3 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:122.3,124.17 3 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:128.3,129.31 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:134.3,135.17 2 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:138.3,138.30 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:142.3,144.31 2 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:111.30,113.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:118.17,120.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:124.17,126.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:129.31,131.4 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:135.17,137.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:138.30,140.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:149.88,150.31 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:150.31,152.15 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:155.3,159.17 4 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:162.3,163.31 2 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:167.3,167.13 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:152.15,154.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:159.17,161.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_logstash_pipeline.go:163.31,165.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:52.50,55.2 2 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:58.72,59.96 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:59.96,61.23 1 4 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:64.3,64.15 1 4 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:67.3,72.52 5 4 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:77.3,80.17 3 4 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:83.3,84.31 2 4 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:91.3,93.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:96.3,98.19 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:61.23,63.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:64.15,66.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:72.52,74.4 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:74.9,76.4 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:80.17,82.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:84.31,85.32 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:88.4,88.61 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:85.32,87.5 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:93.17,95.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:104.74,105.129 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:105.129,107.23 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:110.3,116.32 4 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:151.3,152.52 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:157.3,160.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:163.3,164.31 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:171.3,173.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:176.3,178.19 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:107.23,109.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:116.32,125.46 9 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:128.4,128.36 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:131.4,131.39 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:134.4,134.54 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:137.4,137.46 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:140.4,140.40 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:143.4,143.42 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:146.4,146.45 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:125.46,127.5 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:128.36,130.5 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:131.39,133.5 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:134.54,136.5 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:137.46,139.5 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:140.40,142.5 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:143.42,145.5 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:146.45,148.5 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:152.52,154.4 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:154.9,156.4 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:160.17,162.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:164.31,165.32 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:168.4,168.61 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:165.32,167.5 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:173.17,175.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:184.78,185.141 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:185.141,187.18 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:190.3,190.23 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:193.3,200.52 7 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:205.3,208.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:211.3,212.17 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:215.3,216.31 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:219.3,221.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:224.3,226.27 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:187.18,189.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:190.23,192.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:200.52,202.4 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:202.9,204.4 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:208.17,210.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:212.17,214.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:216.31,218.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:221.17,223.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:231.78,232.125 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:232.125,234.18 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:237.3,237.23 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:240.3,240.15 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:243.3,249.52 6 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:254.3,257.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:260.3,261.17 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:264.3,265.31 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:268.3,270.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:273.3,275.27 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:234.18,236.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:237.23,239.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:240.15,242.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:249.52,251.4 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:251.9,253.4 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:257.17,259.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:261.17,263.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:265.31,267.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:270.17,272.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:280.78,281.70 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:281.70,283.23 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:286.3,286.15 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:289.3,294.52 5 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:299.3,302.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:305.3,306.31 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:309.3,311.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:314.3,316.13 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:283.23,285.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:286.15,288.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:294.52,296.4 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:296.9,298.4 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:302.17,304.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:306.31,308.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:311.17,313.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:321.78,322.121 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:322.121,331.27 6 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:334.3,334.23 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:337.3,341.52 4 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:346.3,349.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:352.3,353.17 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:356.3,357.31 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:361.3,364.19 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:331.27,333.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:334.23,336.4 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:341.52,343.4 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:343.9,345.4 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:349.17,351.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:353.17,355.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:357.31,359.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:370.78,371.95 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:371.95,373.21 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:377.3,382.52 5 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:387.3,393.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:396.3,397.31 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:400.3,402.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:405.3,407.27 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:373.21,375.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:382.52,384.4 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:384.9,386.4 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:393.17,395.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:397.31,399.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_save_object.go:402.17,404.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_shorten_url.go:28.38,31.2 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_shorten_url.go:34.46,37.2 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_shorten_url.go:40.76,41.67 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_shorten_url.go:41.67,43.24 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_shorten_url.go:46.3,49.17 3 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_shorten_url.go:53.3,56.17 3 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_shorten_url.go:60.3,61.31 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_shorten_url.go:65.3,67.17 3 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_shorten_url.go:70.3,72.33 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_shorten_url.go:43.24,45.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_shorten_url.go:49.17,51.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_shorten_url.go:56.17,58.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_shorten_url.go:61.31,63.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_shorten_url.go:67.17,69.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:63.39,66.2 2 7 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:69.60,70.47 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:70.47,72.15 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:75.3,79.17 4 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:82.3,83.31 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:90.3,92.17 3 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:95.3,97.26 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:72.15,74.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:79.17,81.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:83.31,84.32 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:87.4,87.61 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:84.32,86.5 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:92.17,94.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:103.62,104.38 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:104.38,108.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:111.3,112.31 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:115.3,117.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:120.3,122.27 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:108.17,110.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:112.31,114.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:117.17,119.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:128.66,129.62 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:129.62,131.25 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:134.3,137.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:140.3,142.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:146.3,147.31 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:150.3,152.17 3 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:155.3,157.26 2 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:131.25,133.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:137.17,139.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:142.17,144.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:147.31,149.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:152.17,154.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:163.86,164.88 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:164.88,166.23 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:169.3,173.52 4 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:178.3,179.17 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:182.3,183.17 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:187.3,188.31 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:191.3,193.17 3 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:196.3,199.34 3 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:204.3,204.22 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:208.3,208.13 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:166.23,168.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:173.52,175.4 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:175.9,177.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:179.17,181.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:183.17,185.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:188.31,190.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:193.17,195.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:199.34,200.66 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:200.66,202.5 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:204.22,206.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:214.66,215.31 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:215.31,217.15 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:221.3,225.17 4 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:228.3,229.31 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:235.3,235.13 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:217.15,219.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:225.17,227.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:229.31,233.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:241.66,242.62 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:242.62,244.25 1 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:247.3,250.17 3 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:253.3,255.17 3 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:259.3,260.31 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:263.3,265.17 3 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:268.3,270.26 2 1 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:244.25,246.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:250.17,252.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:255.17,257.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:260.31,262.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/api.kibana_spaces.go:265.17,267.4 1 0 +github.com/disaster37/go-kibana-rest/v7/kbapi/error.go:14.34,16.2 1 2 +github.com/disaster37/go-kibana-rest/v7/kbapi/error.go:19.76,24.2 1 2 diff --git a/libs/go-kibana-rest/doc.go b/libs/go-kibana-rest/doc.go new file mode 100644 index 000000000..dd57b9f87 --- /dev/null +++ b/libs/go-kibana-rest/doc.go @@ -0,0 +1,4 @@ +/* +Package kibana provides a Go client for Kibana. +*/ +package kibana diff --git a/libs/go-kibana-rest/docker-compose.yml b/libs/go-kibana-rest/docker-compose.yml new file mode 100644 index 000000000..31aaa2449 --- /dev/null +++ b/libs/go-kibana-rest/docker-compose.yml @@ -0,0 +1,21 @@ +version: '2.3' +services: + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:8.5.0 + environment: + cluster.name: test + discovery.type: single-node + ELASTIC_PASSWORD: changeme + xpack.security.enabled: "true" + ports: + - "9200:9200/tcp" + kibana: + image: docker.elastic.co/kibana/kibana:8.5.0 + environment: + ELASTICSEARCH_HOSTS: http://es:9200 + ELASTICSEARCH_USERNAME: kibana_system + ELASTICSEARCH_PASSWORD: changeme + links: + - elasticsearch:es + ports: + - "5601:5601/tcp" \ No newline at end of file diff --git a/libs/go-kibana-rest/fixtures/kibana-dashboard.json b/libs/go-kibana-rest/fixtures/kibana-dashboard.json new file mode 100644 index 000000000..0749a5253 --- /dev/null +++ b/libs/go-kibana-rest/fixtures/kibana-dashboard.json @@ -0,0 +1,371 @@ +{ + "version": "7.2.0", + "objects": [ + { + "id": "edf84fe0-e1a0-11e7-b6d5-4dc382ef7f5b", + "type": "dashboard", + "updated_at": "2019-07-08T15:35:44.395Z", + "version": "WzIxLDFd", + "attributes": { + "title": "[Logs] Web Traffic", + "hits": 0, + "description": "Analyze mock web traffic log data for Elastic's website", + "panelsJSON": "[{\"embeddableConfig\":{\"vis\":{\"colors\":{\"Avg. Bytes\":\"#6ED0E0\",\"Unique Visitors\":\"#0A437C\"},\"legendOpen\":false}},\"gridData\":{\"x\":27,\"y\":11,\"w\":21,\"h\":13,\"i\":\"2\"},\"panelIndex\":\"2\",\"version\":\"7.0.0-alpha1\",\"panelRefName\":\"panel_0\"},{\"gridData\":{\"x\":0,\"y\":49,\"w\":24,\"h\":18,\"i\":\"4\"},\"panelIndex\":\"4\",\"version\":\"7.0.0-alpha1\",\"panelRefName\":\"panel_1\"},{\"embeddableConfig\":{\"vis\":{\"defaultColors\":{\"0 - 22\":\"rgb(247,251,255)\",\"22 - 44\":\"rgb(208,225,242)\",\"44 - 66\":\"rgb(148,196,223)\",\"66 - 88\":\"rgb(74,152,201)\",\"88 - 110\":\"rgb(23,100,171)\"},\"legendOpen\":false}},\"gridData\":{\"x\":0,\"y\":36,\"w\":24,\"h\":13,\"i\":\"7\"},\"panelIndex\":\"7\",\"version\":\"6.3.0\",\"panelRefName\":\"panel_2\"},{\"embeddableConfig\":{\"mapCenter\":[36.8092847020594,-96.94335937500001],\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"gridData\":{\"x\":27,\"y\":24,\"w\":21,\"h\":12,\"i\":\"9\"},\"panelIndex\":\"9\",\"version\":\"6.3.0\",\"panelRefName\":\"panel_3\"},{\"embeddableConfig\":{\"vis\":{\"colors\":{\"0 - 500\":\"#BF1B00\",\"1000 - 1500\":\"#7EB26D\",\"500 - 1000\":\"#F2C96D\"},\"defaultColors\":{\"0 - 500\":\"rgb(165,0,38)\",\"1000 - 1500\":\"rgb(0,104,55)\",\"500 - 1000\":\"rgb(255,255,190)\"},\"legendOpen\":false}},\"gridData\":{\"x\":10,\"y\":0,\"w\":9,\"h\":11,\"i\":\"11\"},\"panelIndex\":\"11\",\"title\":\"\",\"version\":\"6.3.0\",\"panelRefName\":\"panel_4\"},{\"gridData\":{\"x\":0,\"y\":24,\"w\":27,\"h\":12,\"i\":\"13\"},\"panelIndex\":\"13\",\"version\":\"6.3.0\",\"panelRefName\":\"panel_5\"},{\"gridData\":{\"x\":24,\"y\":36,\"w\":24,\"h\":31,\"i\":\"14\"},\"panelIndex\":\"14\",\"version\":\"6.3.0\",\"panelRefName\":\"panel_6\"},{\"gridData\":{\"x\":0,\"y\":11,\"w\":27,\"h\":13,\"i\":\"15\"},\"panelIndex\":\"15\",\"version\":\"6.3.0\",\"panelRefName\":\"panel_7\"},{\"gridData\":{\"x\":19,\"y\":0,\"w\":15,\"h\":11,\"i\":\"16\"},\"panelIndex\":\"16\",\"title\":\"\",\"version\":\"6.3.0\",\"panelRefName\":\"panel_8\"},{\"embeddableConfig\":{\"vis\":{\"legendOpen\":false}},\"gridData\":{\"x\":34,\"y\":0,\"w\":14,\"h\":11,\"i\":\"17\"},\"panelIndex\":\"17\",\"version\":\"6.3.0\",\"panelRefName\":\"panel_9\"},{\"embeddableConfig\":{},\"gridData\":{\"x\":0,\"y\":0,\"w\":10,\"h\":11,\"i\":\"18\"},\"panelIndex\":\"18\",\"title\":\"\",\"version\":\"7.0.0-alpha1\",\"panelRefName\":\"panel_10\"}]", + "optionsJSON": "{\"hidePanelTitles\":false,\"useMargins\":true}", + "version": 1, + "timeRestore": true, + "timeTo": "now", + "timeFrom": "now-7d", + "refreshInterval": { + "pause": false, + "value": 900000 + }, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[],\"highlightAll\":true,\"version\":true}" + } + }, + "references": [ + { + "name": "panel_0", + "type": "visualization", + "id": "e1d0f010-9ee7-11e7-8711-e7a007dcef99" + }, + { + "name": "panel_1", + "type": "visualization", + "id": "06cf9c40-9ee8-11e7-8711-e7a007dcef99" + }, + { + "name": "panel_2", + "type": "visualization", + "id": "935afa20-e0cd-11e7-9d07-1398ccfcefa3" + }, + { + "name": "panel_3", + "type": "visualization", + "id": "4eb6e500-e1c7-11e7-b6d5-4dc382ef7f5b" + }, + { + "name": "panel_4", + "type": "visualization", + "id": "69a34b00-9ee8-11e7-8711-e7a007dcef99" + }, + { + "name": "panel_5", + "type": "visualization", + "id": "42b997f0-0c26-11e8-b0ec-3bb475f6b6ff" + }, + { + "name": "panel_6", + "type": "visualization", + "id": "7cbd2350-2223-11e8-b802-5bcf64c2cfb4" + }, + { + "name": "panel_7", + "type": "visualization", + "id": "314c6f60-2224-11e8-b802-5bcf64c2cfb4" + }, + { + "name": "panel_8", + "type": "visualization", + "id": "24a3e970-4257-11e8-b3aa-73fdaf54bfc9" + }, + { + "name": "panel_9", + "type": "visualization", + "id": "14e2e710-4258-11e8-b3aa-73fdaf54bfc9" + }, + { + "name": "panel_10", + "type": "visualization", + "id": "47f2c680-a6e3-11e8-94b4-c30c0228351b" + } + ], + "migrationVersion": { + "dashboard": "7.0.0" + } + }, + { + "id": "e1d0f010-9ee7-11e7-8711-e7a007dcef99", + "type": "visualization", + "updated_at": "2019-07-08T15:35:44.395Z", + "version": "WzksMV0=", + "attributes": { + "title": "[Logs] Unique Visitors vs. Average Bytes", + "visState": "{\"title\":\"[Logs] Unique Visitors vs. Average Bytes\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Avg. Bytes\"}},{\"id\":\"ValueAxis-2\",\"name\":\"RightAxis-1\",\"type\":\"value\",\"position\":\"right\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Unique Visitors\"}}],\"seriesParams\":[{\"show\":\"true\",\"type\":\"histogram\",\"mode\":\"stacked\",\"data\":{\"label\":\"Avg. Bytes\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"},{\"show\":true,\"mode\":\"stacked\",\"type\":\"line\",\"drawLinesBetweenPoints\":false,\"showCircles\":true,\"interpolate\":\"linear\",\"data\":{\"id\":\"2\",\"label\":\"Unique Visitors\"},\"valueAxis\":\"ValueAxis-2\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false,\"radiusRatio\":17},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"avg\",\"schema\":\"metric\",\"params\":{\"field\":\"bytes\",\"customLabel\":\"Avg. Bytes\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"cardinality\",\"schema\":\"metric\",\"params\":{\"field\":\"clientip\",\"customLabel\":\"Unique Visitors\"}},{\"id\":\"3\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"timestamp\",\"interval\":\"auto\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"4\",\"enabled\":true,\"type\":\"count\",\"schema\":\"radius\",\"params\":{}}]}", + "uiStateJSON": "{\"vis\":{\"colors\":{\"Avg. Bytes\":\"#70DBED\",\"Unique Visitors\":\"#0A437C\"}}}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + } + }, + "references": [ + { + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern", + "id": "90943e30-9a47-11e8-b64d-95841ca0b247" + } + ], + "migrationVersion": { + "visualization": "7.2.0" + } + }, + { + "id": "06cf9c40-9ee8-11e7-8711-e7a007dcef99", + "type": "visualization", + "updated_at": "2019-07-08T15:35:44.395Z", + "version": "WzEwLDFd", + "attributes": { + "title": "[Logs] Unique Visitors by Country", + "visState": "{\"title\":\"[Logs] Unique Visitors by Country\",\"type\":\"region_map\",\"params\":{\"legendPosition\":\"bottomright\",\"addTooltip\":true,\"colorSchema\":\"Reds\",\"selectedLayer\":{\"attribution\":\"
Made with NaturalEarth | Elastic Maps Service
\",\"name\":\"World Countries\",\"weight\":1,\"format\":{\"type\":\"geojson\"},\"url\":\"https://vector.maps.elastic.co/blob/5659313586569216?elastic_tile_service_tos=agree&my_app_version=6.2.3&license=77ab0ecf-a521-499d-bd52-fbd740bb81d0\",\"fields\":[{\"name\":\"iso2\",\"description\":\"Two letter abbreviation\"},{\"name\":\"name\",\"description\":\"Country name\"},{\"name\":\"iso3\",\"description\":\"Three letter abbreviation\"}],\"created_at\":\"2017-04-26T17:12:15.978370\",\"tags\":[],\"id\":5659313586569216,\"layerId\":\"elastic_maps_service.World Countries\"},\"selectedJoinField\":{\"name\":\"iso2\",\"description\":\"Two letter abbreviation\"},\"isDisplayWarning\":false,\"wms\":{\"enabled\":false,\"options\":{\"format\":\"image/png\",\"transparent\":true},\"baseLayersAreLoaded\":{},\"tmsLayers\":[{\"id\":\"road_map\",\"url\":\"https://tiles.maps.elastic.co/v2/default/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana&my_app_version=6.2.3&license=77ab0ecf-a521-499d-bd52-fbd740bb81d0\",\"minZoom\":0,\"maxZoom\":18,\"attribution\":\"© OpenStreetMap contributors | Elastic Maps Service
\",\"subdomains\":[]}],\"selectedTmsLayer\":{\"id\":\"road_map\",\"url\":\"https://tiles.maps.elastic.co/v2/default/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana&my_app_version=6.2.3&license=77ab0ecf-a521-499d-bd52-fbd740bb81d0\",\"minZoom\":0,\"maxZoom\":18,\"attribution\":\"© OpenStreetMap contributors | Elastic Maps Service
\",\"subdomains\":[]}},\"mapZoom\":2,\"mapCenter\":[0,0],\"outlineWeight\":1,\"showAllShapes\":true,\"emsHotLink\":null},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"cardinality\",\"schema\":\"metric\",\"params\":{\"field\":\"clientip\",\"customLabel\":\"Unique Visitors\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"geo.src\",\"size\":50,\"order\":\"desc\",\"orderBy\":\"1\",\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"}}]}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + } + }, + "references": [ + { + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern", + "id": "90943e30-9a47-11e8-b64d-95841ca0b247" + } + ], + "migrationVersion": { + "visualization": "7.2.0" + } + }, + { + "id": "935afa20-e0cd-11e7-9d07-1398ccfcefa3", + "type": "visualization", + "updated_at": "2019-07-08T15:35:44.395Z", + "version": "WzExLDFd", + "attributes": { + "title": "[Logs] Heatmap", + "visState": "{\"title\":\"[Logs] Heatmap\",\"type\":\"heatmap\",\"params\":{\"type\":\"heatmap\",\"addTooltip\":true,\"addLegend\":true,\"enableHover\":true,\"legendPosition\":\"right\",\"times\":[],\"colorsNumber\":10,\"colorSchema\":\"Reds\",\"setColorRange\":false,\"colorsRange\":[],\"invertColors\":false,\"percentageMode\":false,\"valueAxes\":[{\"show\":false,\"id\":\"ValueAxis-1\",\"type\":\"value\",\"scale\":{\"type\":\"linear\",\"defaultYExtents\":false},\"labels\":{\"show\":false,\"rotate\":0,\"color\":\"#555\",\"overwriteColor\":false}}]},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"cardinality\",\"schema\":\"metric\",\"params\":{\"field\":\"clientip\"}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"geo.src\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"Country Source\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"hour_of_day\",\"size\":25,\"order\":\"asc\",\"orderBy\":\"_key\",\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"Hour of Day\"}}]}", + "uiStateJSON": "{\"vis\":{\"defaultColors\":{\"0 - 4\":\"rgb(255,245,240)\",\"4 - 8\":\"rgb(254,228,216)\",\"8 - 12\":\"rgb(253,202,181)\",\"12 - 16\":\"rgb(252,171,142)\",\"16 - 20\":\"rgb(252,138,106)\",\"20 - 24\":\"rgb(251,106,74)\",\"24 - 28\":\"rgb(241,68,50)\",\"28 - 32\":\"rgb(217,38,35)\",\"32 - 36\":\"rgb(188,20,26)\",\"36 - 40\":\"rgb(152,12,19)\"}}}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + } + }, + "references": [ + { + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern", + "id": "90943e30-9a47-11e8-b64d-95841ca0b247" + } + ], + "migrationVersion": { + "visualization": "7.2.0" + } + }, + { + "id": "4eb6e500-e1c7-11e7-b6d5-4dc382ef7f5b", + "type": "visualization", + "updated_at": "2019-07-08T15:35:44.395Z", + "version": "WzEyLDFd", + "attributes": { + "title": "[Logs] Host, Visits and Bytes Table", + "visState": "{\"title\":\"[Logs] Host, Visits and Bytes Table\",\"type\":\"metrics\",\"params\":{\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"table\",\"series\":[{\"id\":\"bd09d600-e5b1-11e7-bfc2-a1f7e71965a1\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"metrics\":[{\"id\":\"bd09d601-e5b1-11e7-bfc2-a1f7e71965a1\",\"type\":\"sum\",\"field\":\"bytes\"},{\"sigma\":\"\",\"id\":\"c9514c90-e5b1-11e7-bfc2-a1f7e71965a1\",\"type\":\"sum_bucket\",\"field\":\"bd09d601-e5b1-11e7-bfc2-a1f7e71965a1\"}],\"seperate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"bytes\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\",\"color_rules\":[{\"id\":\"c0c668d0-e5b1-11e7-bfc2-a1f7e71965a1\"}],\"label\":\"Bytes (Total)\"},{\"id\":\"b7672c30-a6df-11e8-8b18-1da1dfc50975\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"metrics\":[{\"id\":\"b7672c31-a6df-11e8-8b18-1da1dfc50975\",\"type\":\"sum\",\"field\":\"bytes\"}],\"seperate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"bytes\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\",\"color_rules\":[{\"id\":\"c0c668d0-e5b1-11e7-bfc2-a1f7e71965a1\"}],\"label\":\"Bytes (Last Hour)\"},{\"id\":\"f2c20700-a6df-11e8-8b18-1da1dfc50975\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"metrics\":[{\"id\":\"f2c20701-a6df-11e8-8b18-1da1dfc50975\",\"type\":\"cardinality\",\"field\":\"ip\"},{\"sigma\":\"\",\"id\":\"f46333e0-a6df-11e8-8b18-1da1dfc50975\",\"type\":\"sum_bucket\",\"field\":\"f2c20701-a6df-11e8-8b18-1da1dfc50975\"}],\"seperate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\",\"label\":\"Unique Visits (Total)\",\"color_rules\":[{\"value\":1000,\"id\":\"2e963080-a6e0-11e8-8b18-1da1dfc50975\",\"text\":\"rgba(211,49,21,1)\",\"operator\":\"lt\"},{\"value\":1000,\"id\":\"3d4fb880-a6e0-11e8-8b18-1da1dfc50975\",\"text\":\"rgba(252,196,0,1)\",\"operator\":\"gte\"},{\"value\":1500,\"id\":\"435f8a20-a6e0-11e8-8b18-1da1dfc50975\",\"text\":\"rgba(104,188,0,1)\",\"operator\":\"gte\"}],\"offset_time\":\"\",\"value_template\":\"\",\"trend_arrows\":1},{\"id\":\"46fd7fc0-e5b1-11e7-bfc2-a1f7e71965a1\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"metrics\":[{\"id\":\"46fd7fc1-e5b1-11e7-bfc2-a1f7e71965a1\",\"type\":\"cardinality\",\"field\":\"ip\"}],\"seperate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\",\"label\":\"Unique Visits (Last Hour)\",\"color_rules\":[{\"value\":10,\"id\":\"4e90aeb0-a6e0-11e8-8b18-1da1dfc50975\",\"text\":\"rgba(211,49,21,1)\",\"operator\":\"lt\"},{\"value\":10,\"id\":\"6d59b1c0-a6e0-11e8-8b18-1da1dfc50975\",\"text\":\"rgba(252,196,0,1)\",\"operator\":\"gte\"},{\"value\":25,\"id\":\"77578670-a6e0-11e8-8b18-1da1dfc50975\",\"text\":\"rgba(104,188,0,1)\",\"operator\":\"gte\"}],\"offset_time\":\"\",\"value_template\":\"\",\"trend_arrows\":1}],\"time_field\":\"timestamp\",\"index_pattern\":\"kibana_sample_data_logs\",\"interval\":\"1h\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"show_legend\":1,\"show_grid\":1,\"bar_color_rules\":[{\"id\":\"e9b4e490-e1c6-11e7-b4f6-0f68c45f7387\"}],\"pivot_id\":\"extension.keyword\",\"pivot_label\":\"Type\",\"drilldown_url\":\"\",\"axis_scale\":\"normal\"},\"aggs\":[]}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}" + } + }, + "references": [], + "migrationVersion": { + "visualization": "7.2.0" + } + }, + { + "id": "69a34b00-9ee8-11e7-8711-e7a007dcef99", + "type": "visualization", + "updated_at": "2019-07-08T15:35:44.395Z", + "version": "WzEzLDFd", + "attributes": { + "title": "[Logs] Goals", + "visState": "{\"title\":\"[Logs] Goals\",\"type\":\"gauge\",\"params\":{\"type\":\"gauge\",\"addTooltip\":true,\"addLegend\":false,\"gauge\":{\"verticalSplit\":false,\"extendRange\":true,\"percentageMode\":false,\"gaugeType\":\"Arc\",\"gaugeStyle\":\"Full\",\"backStyle\":\"Full\",\"orientation\":\"vertical\",\"colorSchema\":\"Green to Red\",\"gaugeColorMode\":\"Labels\",\"colorsRange\":[{\"from\":0,\"to\":500},{\"from\":500,\"to\":1000},{\"from\":1000,\"to\":1500}],\"invertColors\":true,\"labels\":{\"show\":false,\"color\":\"black\"},\"scale\":{\"show\":true,\"labels\":false,\"color\":\"#333\"},\"type\":\"meter\",\"style\":{\"bgWidth\":0.9,\"width\":0.9,\"mask\":false,\"bgMask\":false,\"maskBars\":50,\"bgFill\":\"#eee\",\"bgColor\":false,\"subText\":\"visitors\",\"fontSize\":60,\"labelColor\":true}},\"isDisplayWarning\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"cardinality\",\"schema\":\"metric\",\"params\":{\"field\":\"clientip\",\"customLabel\":\"Unique Visitors\"}}]}", + "uiStateJSON": "{\"vis\":{\"defaultColors\":{\"0 - 500\":\"rgb(165,0,38)\",\"500 - 1000\":\"rgb(255,255,190)\",\"1000 - 1500\":\"rgb(0,104,55)\"},\"colors\":{\"75 - 100\":\"#629E51\",\"50 - 75\":\"#EAB839\",\"0 - 50\":\"#E24D42\",\"0 - 100\":\"#E24D42\",\"200 - 300\":\"#7EB26D\",\"500 - 1000\":\"#E5AC0E\",\"0 - 500\":\"#E24D42\",\"1000 - 1500\":\"#7EB26D\"},\"legendOpen\":true}}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + } + }, + "references": [ + { + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern", + "id": "90943e30-9a47-11e8-b64d-95841ca0b247" + } + ], + "migrationVersion": { + "visualization": "7.2.0" + } + }, + { + "id": "42b997f0-0c26-11e8-b0ec-3bb475f6b6ff", + "type": "visualization", + "updated_at": "2019-07-08T15:35:44.395Z", + "version": "WzE0LDFd", + "attributes": { + "title": "[Logs] File Type Scatter Plot", + "visState": "{\"title\":\"[Logs] File Type Scatter Plot\",\"type\":\"vega\",\"params\":{\"spec\":\"{\\n $schema: \\\"https://vega.github.io/schema/vega-lite/v2.json\\\"\\n // Use points for drawing to actually create a scatterplot\\n mark: point\\n // Specify where to load data from\\n data: {\\n // By using an object to the url parameter we will\\n // construct an Elasticsearch query\\n url: {\\n // Context == true means filters of the dashboard will be taken into account\\n %context%: true\\n // Specify on which field the time picker should operate\\n %timefield%: timestamp\\n // Specify the index pattern to load data from\\n index: kibana_sample_data_logs\\n // This body will be send to Elasticsearch's _search endpoint\\n // You can use everything the ES Query DSL supports here\\n body: {\\n // Set the size to load 10000 documents\\n size: 10000,\\n // Just ask for the fields we actually need for visualization\\n _source: [\\\"timestamp\\\", \\\"bytes\\\", \\\"extension\\\"]\\n }\\n }\\n // Tell Vega, that the array of data will be inside hits.hits of the response\\n // since the result returned from Elasticsearch fill have a format like:\\n // {\\n // hits: {\\n // total: 42000,\\n // max_score: 2,\\n // hits: [\\n // < our individual documents >\\n // ]\\n // }\\n // }\\n format: { property: \\\"hits.hits\\\" }\\n }\\n // You can do transformation and calculation of the data before drawing it\\n transform: [\\n // Since timestamp is a string value, we need to convert it to a unix timestamp\\n // so that Vega can work on it properly.\\n {\\n // Convert _source.timestamp field to a date\\n calculate: \\\"toDate(datum._source['timestamp'])\\\"\\n // Store the result in a field named \\\"time\\\" in the object\\n as: \\\"time\\\"\\n }\\n ]\\n // Specify what data will be drawn on which axis\\n encoding: {\\n x: {\\n // Draw the time field on the x-axis in temporal mode (i.e. as a time axis)\\n field: time\\n type: temporal\\n // Hide the axis label for the x-axis\\n axis: { title: false }\\n }\\n y: {\\n // Draw the bytes of each document on the y-axis\\n field: _source.bytes\\n // Mark the y-axis as quantitative\\n type: quantitative\\n // Specify the label for this axis\\n axis: { title: \\\"Transferred bytes\\\" }\\n }\\n color: {\\n // Make the color of each point depend on the _source.extension field\\n field: _source.extension\\n // Treat different values as completely unrelated values to each other.\\n // You could switch this to quantitative if you have a numeric field and\\n // want to create a color scale from one color to another depending on that\\n // field's value.\\n type: nominal\\n // Rename the legend title so it won't just state: \\\"_source.extension\\\"\\n legend: { title: 'File type' }\\n }\\n shape: {\\n // Also make the shape of each point dependent on the extension.\\n field: _source.extension\\n type: nominal\\n }\\n }\\n}\"},\"aggs\":[]}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}" + } + }, + "references": [], + "migrationVersion": { + "visualization": "7.2.0" + } + }, + { + "id": "7cbd2350-2223-11e8-b802-5bcf64c2cfb4", + "type": "visualization", + "updated_at": "2019-07-08T15:35:44.395Z", + "version": "WzE1LDFd", + "attributes": { + "title": "[Logs] Source and Destination Sankey Chart", + "visState": "{\"title\":\"[Logs] Source and Destination Sankey Chart\",\"type\":\"vega\",\"params\":{\"spec\":\"{ \\n $schema: https://vega.github.io/schema/vega/v3.0.json\\n data: [\\n\\t{\\n \\t// query ES based on the currently selected time range and filter string\\n \\tname: rawData\\n \\turl: {\\n \\t%context%: true\\n \\t%timefield%: timestamp\\n \\tindex: kibana_sample_data_logs\\n \\tbody: {\\n \\tsize: 0\\n \\taggs: {\\n \\ttable: {\\n \\tcomposite: {\\n \\tsize: 10000\\n \\tsources: [\\n \\t{\\n \\tstk1: {\\n \\tterms: {field: \\\"geo.src\\\"}\\n \\t}\\n \\t}\\n \\t{\\n \\tstk2: {\\n \\tterms: {field: \\\"geo.dest\\\"}\\n \\t}\\n \\t}\\n \\t]\\n \\t}\\n \\t}\\n \\t}\\n \\t}\\n \\t}\\n \\t// From the result, take just the data we are interested in\\n \\tformat: {property: \\\"aggregations.table.buckets\\\"}\\n \\t// Convert key.stk1 -> stk1 for simpler access below\\n \\ttransform: [\\n \\t{type: \\\"formula\\\", expr: \\\"datum.key.stk1\\\", as: \\\"stk1\\\"}\\n \\t{type: \\\"formula\\\", expr: \\\"datum.key.stk2\\\", as: \\\"stk2\\\"}\\n \\t{type: \\\"formula\\\", expr: \\\"datum.doc_count\\\", as: \\\"size\\\"}\\n \\t]\\n\\t}\\n\\t{\\n \\tname: nodes\\n \\tsource: rawData\\n \\ttransform: [\\n \\t// when a country is selected, filter out unrelated data\\n \\t{\\n \\ttype: filter\\n \\texpr: !groupSelector || groupSelector.stk1 == datum.stk1 || groupSelector.stk2 == datum.stk2\\n \\t}\\n \\t// Set new key for later lookups - identifies each node\\n \\t{type: \\\"formula\\\", expr: \\\"datum.stk1+datum.stk2\\\", as: \\\"key\\\"}\\n \\t// instead of each table row, create two new rows,\\n \\t// one for the source (stack=stk1) and one for destination node (stack=stk2).\\n \\t// The country code stored in stk1 and stk2 fields is placed into grpId field.\\n \\t{\\n \\ttype: fold\\n \\tfields: [\\\"stk1\\\", \\\"stk2\\\"]\\n \\tas: [\\\"stack\\\", \\\"grpId\\\"]\\n \\t}\\n \\t// Create a sortkey, different for stk1 and stk2 stacks.\\n \\t{\\n \\ttype: formula\\n \\texpr: datum.stack == 'stk1' ? datum.stk1+datum.stk2 : datum.stk2+datum.stk1\\n \\tas: sortField\\n \\t}\\n \\t// Calculate y0 and y1 positions for stacking nodes one on top of the other,\\n \\t// independently for each stack, and ensuring they are in the proper order,\\n \\t// alphabetical from the top (reversed on the y axis)\\n \\t{\\n \\ttype: stack\\n \\tgroupby: [\\\"stack\\\"]\\n \\tsort: {field: \\\"sortField\\\", order: \\\"descending\\\"}\\n \\tfield: size\\n \\t}\\n \\t// calculate vertical center point for each node, used to draw edges\\n \\t{type: \\\"formula\\\", expr: \\\"(datum.y0+datum.y1)/2\\\", as: \\\"yc\\\"}\\n \\t]\\n\\t}\\n\\t{\\n \\tname: groups\\n \\tsource: nodes\\n \\ttransform: [\\n \\t// combine all nodes into country groups, summing up the doc counts\\n \\t{\\n \\ttype: aggregate\\n \\tgroupby: [\\\"stack\\\", \\\"grpId\\\"]\\n \\tfields: [\\\"size\\\"]\\n \\tops: [\\\"sum\\\"]\\n \\tas: [\\\"total\\\"]\\n \\t}\\n \\t// re-calculate the stacking y0,y1 values\\n \\t{\\n \\ttype: stack\\n \\tgroupby: [\\\"stack\\\"]\\n \\tsort: {field: \\\"grpId\\\", order: \\\"descending\\\"}\\n \\tfield: total\\n \\t}\\n \\t// project y0 and y1 values to screen coordinates\\n \\t// doing it once here instead of doing it several times in marks\\n \\t{type: \\\"formula\\\", expr: \\\"scale('y', datum.y0)\\\", as: \\\"scaledY0\\\"}\\n \\t{type: \\\"formula\\\", expr: \\\"scale('y', datum.y1)\\\", as: \\\"scaledY1\\\"}\\n \\t// boolean flag if the label should be on the right of the stack\\n \\t{type: \\\"formula\\\", expr: \\\"datum.stack == 'stk1'\\\", as: \\\"rightLabel\\\"}\\n \\t// Calculate traffic percentage for this country using \\\"y\\\" scale\\n \\t// domain upper bound, which represents the total traffic\\n \\t{\\n \\ttype: formula\\n \\texpr: datum.total/domain('y')[1]\\n \\tas: percentage\\n \\t}\\n \\t]\\n\\t}\\n\\t{\\n \\t// This is a temp lookup table with all the 'stk2' stack nodes\\n \\tname: destinationNodes\\n \\tsource: nodes\\n \\ttransform: [\\n \\t{type: \\\"filter\\\", expr: \\\"datum.stack == 'stk2'\\\"}\\n \\t]\\n\\t}\\n\\t{\\n \\tname: edges\\n \\tsource: nodes\\n \\ttransform: [\\n \\t// we only want nodes from the left stack\\n \\t{type: \\\"filter\\\", expr: \\\"datum.stack == 'stk1'\\\"}\\n \\t// find corresponding node from the right stack, keep it as \\\"target\\\"\\n \\t{\\n \\ttype: lookup\\n \\tfrom: destinationNodes\\n \\tkey: key\\n \\tfields: [\\\"key\\\"]\\n \\tas: [\\\"target\\\"]\\n \\t}\\n \\t// calculate SVG link path between stk1 and stk2 stacks for the node pair\\n \\t{\\n \\ttype: linkpath\\n \\torient: horizontal\\n \\tshape: diagonal\\n \\tsourceY: {expr: \\\"scale('y', datum.yc)\\\"}\\n \\tsourceX: {expr: \\\"scale('x', 'stk1') + bandwidth('x')\\\"}\\n \\ttargetY: {expr: \\\"scale('y', datum.target.yc)\\\"}\\n \\ttargetX: {expr: \\\"scale('x', 'stk2')\\\"}\\n \\t}\\n \\t// A little trick to calculate the thickness of the line.\\n \\t// The value needs to be the same as the hight of the node, but scaling\\n \\t// size to screen's height gives inversed value because screen's Y\\n \\t// coordinate goes from the top to the bottom, whereas the graph's Y=0\\n \\t// is at the bottom. So subtracting scaled doc count from screen height\\n \\t// (which is the \\\"lower\\\" bound of the \\\"y\\\" scale) gives us the right value\\n \\t{\\n \\ttype: formula\\n \\texpr: range('y')[0]-scale('y', datum.size)\\n \\tas: strokeWidth\\n \\t}\\n \\t// Tooltip needs individual link's percentage of all traffic\\n \\t{\\n \\ttype: formula\\n \\texpr: datum.size/domain('y')[1]\\n \\tas: percentage\\n \\t}\\n \\t]\\n\\t}\\n ]\\n scales: [\\n\\t{\\n \\t// calculates horizontal stack positioning\\n \\tname: x\\n \\ttype: band\\n \\trange: width\\n \\tdomain: [\\\"stk1\\\", \\\"stk2\\\"]\\n \\tpaddingOuter: 0.05\\n \\tpaddingInner: 0.95\\n\\t}\\n\\t{\\n \\t// this scale goes up as high as the highest y1 value of all nodes\\n \\tname: y\\n \\ttype: linear\\n \\trange: height\\n \\tdomain: {data: \\\"nodes\\\", field: \\\"y1\\\"}\\n\\t}\\n\\t{\\n \\t// use rawData to ensure the colors stay the same when clicking.\\n \\tname: color\\n \\ttype: ordinal\\n \\trange: category\\n \\tdomain: {data: \\\"rawData\\\", field: \\\"stk1\\\"}\\n\\t}\\n\\t{\\n \\t// this scale is used to map internal ids (stk1, stk2) to stack names\\n \\tname: stackNames\\n \\ttype: ordinal\\n \\trange: [\\\"Source\\\", \\\"Destination\\\"]\\n \\tdomain: [\\\"stk1\\\", \\\"stk2\\\"]\\n\\t}\\n ]\\n axes: [\\n\\t{\\n \\t// x axis should use custom label formatting to print proper stack names\\n \\torient: bottom\\n \\tscale: x\\n \\tencode: {\\n \\tlabels: {\\n \\tupdate: {\\n \\ttext: {scale: \\\"stackNames\\\", field: \\\"value\\\"}\\n \\t}\\n \\t}\\n \\t}\\n\\t}\\n\\t{orient: \\\"left\\\", scale: \\\"y\\\"}\\n ]\\n marks: [\\n\\t{\\n \\t// draw the connecting line between stacks\\n \\ttype: path\\n \\tname: edgeMark\\n \\tfrom: {data: \\\"edges\\\"}\\n \\t// this prevents some autosizing issues with large strokeWidth for paths\\n \\tclip: true\\n \\tencode: {\\n \\tupdate: {\\n \\t// By default use color of the left node, except when showing traffic\\n \\t// from just one country, in which case use destination color.\\n \\tstroke: [\\n \\t{\\n \\ttest: groupSelector && groupSelector.stack=='stk1'\\n \\tscale: color\\n \\tfield: stk2\\n \\t}\\n \\t{scale: \\\"color\\\", field: \\\"stk1\\\"}\\n \\t]\\n \\tstrokeWidth: {field: \\\"strokeWidth\\\"}\\n \\tpath: {field: \\\"path\\\"}\\n \\t// when showing all traffic, and hovering over a country,\\n \\t// highlight the traffic from that country.\\n \\tstrokeOpacity: {\\n \\tsignal: !groupSelector && (groupHover.stk1 == datum.stk1 || groupHover.stk2 == datum.stk2) ? 0.9 : 0.3\\n \\t}\\n \\t// Ensure that the hover-selected edges show on top\\n \\tzindex: {\\n \\tsignal: !groupSelector && (groupHover.stk1 == datum.stk1 || groupHover.stk2 == datum.stk2) ? 1 : 0\\n \\t}\\n \\t// format tooltip string\\n \\ttooltip: {\\n \\tsignal: datum.stk1 + ' → ' + datum.stk2 + '\\t' + format(datum.size, ',.0f') + ' (' + format(datum.percentage, '.1%') + ')'\\n \\t}\\n \\t}\\n \\t// Simple mouseover highlighting of a single line\\n \\thover: {\\n \\tstrokeOpacity: {value: 1}\\n \\t}\\n \\t}\\n\\t}\\n\\t{\\n \\t// draw stack groups (countries)\\n \\ttype: rect\\n \\tname: groupMark\\n \\tfrom: {data: \\\"groups\\\"}\\n \\tencode: {\\n \\tenter: {\\n \\tfill: {scale: \\\"color\\\", field: \\\"grpId\\\"}\\n \\twidth: {scale: \\\"x\\\", band: 1}\\n \\t}\\n \\tupdate: {\\n \\tx: {scale: \\\"x\\\", field: \\\"stack\\\"}\\n \\ty: {field: \\\"scaledY0\\\"}\\n \\ty2: {field: \\\"scaledY1\\\"}\\n \\tfillOpacity: {value: 0.6}\\n \\ttooltip: {\\n \\tsignal: datum.grpId + ' ' + format(datum.total, ',.0f') + ' (' + format(datum.percentage, '.1%') + ')'\\n \\t}\\n \\t}\\n \\thover: {\\n \\tfillOpacity: {value: 1}\\n \\t}\\n \\t}\\n\\t}\\n\\t{\\n \\t// draw country code labels on the inner side of the stack\\n \\ttype: text\\n \\tfrom: {data: \\\"groups\\\"}\\n \\t// don't process events for the labels - otherwise line mouseover is unclean\\n \\tinteractive: false\\n \\tencode: {\\n \\tupdate: {\\n \\t// depending on which stack it is, position x with some padding\\n \\tx: {\\n \\tsignal: scale('x', datum.stack) + (datum.rightLabel ? bandwidth('x') + 8 : -8)\\n \\t}\\n \\t// middle of the group\\n \\tyc: {signal: \\\"(datum.scaledY0 + datum.scaledY1)/2\\\"}\\n \\talign: {signal: \\\"datum.rightLabel ? 'left' : 'right'\\\"}\\n \\tbaseline: {value: \\\"middle\\\"}\\n \\tfontWeight: {value: \\\"bold\\\"}\\n \\t// only show text label if the group's height is large enough\\n \\ttext: {signal: \\\"abs(datum.scaledY0-datum.scaledY1) > 13 ? datum.grpId : ''\\\"}\\n \\t}\\n \\t}\\n\\t}\\n\\t{\\n \\t// Create a \\\"show all\\\" button. Shown only when a country is selected.\\n \\ttype: group\\n \\tdata: [\\n \\t// We need to make the button show only when groupSelector signal is true.\\n \\t// Each mark is drawn as many times as there are elements in the backing data.\\n \\t// Which means that if values list is empty, it will not be drawn.\\n \\t// Here I create a data source with one empty object, and filter that list\\n \\t// based on the signal value. This can only be done in a group.\\n \\t{\\n \\tname: dataForShowAll\\n \\tvalues: [{}]\\n \\ttransform: [{type: \\\"filter\\\", expr: \\\"groupSelector\\\"}]\\n \\t}\\n \\t]\\n \\t// Set button size and positioning\\n \\tencode: {\\n \\tenter: {\\n \\txc: {signal: \\\"width/2\\\"}\\n \\ty: {value: 30}\\n \\twidth: {value: 80}\\n \\theight: {value: 30}\\n \\t}\\n \\t}\\n \\tmarks: [\\n \\t{\\n \\t// This group is shown as a button with rounded corners.\\n \\ttype: group\\n \\t// mark name allows signal capturing\\n \\tname: groupReset\\n \\t// Only shows button if dataForShowAll has values.\\n \\tfrom: {data: \\\"dataForShowAll\\\"}\\n \\tencode: {\\n \\tenter: {\\n \\tcornerRadius: {value: 6}\\n \\tfill: {value: \\\"#F5F7FA\\\"}\\n \\tstroke: {value: \\\"#c1c1c1\\\"}\\n \\tstrokeWidth: {value: 2}\\n \\t// use parent group's size\\n \\theight: {\\n \\tfield: {group: \\\"height\\\"}\\n \\t}\\n \\twidth: {\\n \\tfield: {group: \\\"width\\\"}\\n \\t}\\n \\t}\\n \\tupdate: {\\n \\t// groups are transparent by default\\n \\topacity: {value: 1}\\n \\t}\\n \\thover: {\\n \\topacity: {value: 0.7}\\n \\t}\\n \\t}\\n \\tmarks: [\\n \\t{\\n \\ttype: text\\n \\t// if true, it will prevent clicking on the button when over text.\\n \\tinteractive: false\\n \\tencode: {\\n \\tenter: {\\n \\t// center text in the paren group\\n \\txc: {\\n \\tfield: {group: \\\"width\\\"}\\n \\tmult: 0.5\\n \\t}\\n \\tyc: {\\n \\tfield: {group: \\\"height\\\"}\\n \\tmult: 0.5\\n \\toffset: 2\\n \\t}\\n \\talign: {value: \\\"center\\\"}\\n \\tbaseline: {value: \\\"middle\\\"}\\n \\tfontWeight: {value: \\\"bold\\\"}\\n \\ttext: {value: \\\"Show All\\\"}\\n \\t}\\n \\t}\\n \\t}\\n \\t]\\n \\t}\\n \\t]\\n\\t}\\n ]\\n signals: [\\n\\t{\\n \\t// used to highlight traffic to/from the same country\\n \\tname: groupHover\\n \\tvalue: {}\\n \\ton: [\\n \\t{\\n \\tevents: @groupMark:mouseover\\n \\tupdate: \\\"{stk1:datum.stack=='stk1' && datum.grpId, stk2:datum.stack=='stk2' && datum.grpId}\\\"\\n \\t}\\n \\t{events: \\\"mouseout\\\", update: \\\"{}\\\"}\\n \\t]\\n\\t}\\n\\t// used to filter only the data related to the selected country\\n\\t{\\n \\tname: groupSelector\\n \\tvalue: false\\n \\ton: [\\n \\t{\\n \\t// Clicking groupMark sets this signal to the filter values\\n \\tevents: @groupMark:click!\\n \\tupdate: \\\"{stack:datum.stack, stk1:datum.stack=='stk1' && datum.grpId, stk2:datum.stack=='stk2' && datum.grpId}\\\"\\n \\t}\\n \\t{\\n \\t// Clicking \\\"show all\\\" button, or double-clicking anywhere resets it\\n \\tevents: [\\n \\t{type: \\\"click\\\", markname: \\\"groupReset\\\"}\\n \\t{type: \\\"dblclick\\\"}\\n \\t]\\n \\tupdate: \\\"false\\\"\\n \\t}\\n \\t]\\n\\t}\\n ]\\n}\\n\"},\"aggs\":[]}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}" + } + }, + "references": [], + "migrationVersion": { + "visualization": "7.2.0" + } + }, + { + "id": "314c6f60-2224-11e8-b802-5bcf64c2cfb4", + "type": "visualization", + "updated_at": "2019-07-08T15:35:44.395Z", + "version": "WzE2LDFd", + "attributes": { + "title": "[Logs] Response Codes Over Time + Annotations", + "visState": "{\"title\":\"[Logs] Response Codes Over Time + Annotations\",\"type\":\"metrics\",\"params\":{\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"timeseries\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"rgba(115,216,255,1)\",\"split_mode\":\"terms\",\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"cardinality\",\"field\":\"ip\"}],\"seperate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"percent\",\"chart_type\":\"line\",\"line_width\":\"2\",\"point_size\":\"0\",\"fill\":\"0.5\",\"stacked\":\"percent\",\"terms_field\":\"response.keyword\",\"terms_order_by\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"label\":\"Response Code Count\",\"split_color_mode\":\"gradient\"}],\"time_field\":\"timestamp\",\"index_pattern\":\"kibana_sample_data_logs\",\"interval\":\">=4h\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"show_legend\":1,\"show_grid\":1,\"annotations\":[{\"fields\":\"geo.src, host\",\"template\":\"Security Error from {{geo.src}} on {{host}}\",\"index_pattern\":\"kibana_sample_data_logs\",\"query_string\":\"tags:error AND tags:security\",\"id\":\"bd7548a0-2223-11e8-832f-d5027f3c8a47\",\"color\":\"rgba(211,49,21,1)\",\"time_field\":\"timestamp\",\"icon\":\"fa-asterisk\",\"ignore_global_filters\":1,\"ignore_panel_filters\":1}],\"legend_position\":\"bottom\",\"axis_scale\":\"normal\",\"drop_last_bucket\":0},\"aggs\":[]}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}" + } + }, + "references": [], + "migrationVersion": { + "visualization": "7.2.0" + } + }, + { + "id": "24a3e970-4257-11e8-b3aa-73fdaf54bfc9", + "type": "visualization", + "updated_at": "2019-07-08T15:35:44.395Z", + "version": "WzE3LDFd", + "attributes": { + "title": "[Logs] Input Controls", + "visState": "{\"title\":\"[Logs] Input Controls\",\"type\":\"input_control_vis\",\"params\":{\"controls\":[{\"id\":\"1523980210832\",\"fieldName\":\"geo.src\",\"label\":\"Source Country\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"size\":100,\"order\":\"desc\"},\"parent\":\"\",\"indexPatternRefName\":\"control_0_index_pattern\"},{\"id\":\"1523980191978\",\"fieldName\":\"machine.os.keyword\",\"label\":\"OS\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"size\":100,\"order\":\"desc\"},\"parent\":\"1523980210832\",\"indexPatternRefName\":\"control_1_index_pattern\"},{\"id\":\"1523980232790\",\"fieldName\":\"bytes\",\"label\":\"Bytes\",\"type\":\"range\",\"options\":{\"decimalPlaces\":0,\"step\":1024},\"indexPatternRefName\":\"control_2_index_pattern\"}],\"updateFiltersOnChange\":true,\"useTimeFilter\":true,\"pinFilters\":false},\"aggs\":[]}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}" + } + }, + "references": [ + { + "name": "control_0_index_pattern", + "type": "index-pattern", + "id": "90943e30-9a47-11e8-b64d-95841ca0b247" + }, + { + "name": "control_1_index_pattern", + "type": "index-pattern", + "id": "90943e30-9a47-11e8-b64d-95841ca0b247" + }, + { + "name": "control_2_index_pattern", + "type": "index-pattern", + "id": "90943e30-9a47-11e8-b64d-95841ca0b247" + } + ], + "migrationVersion": { + "visualization": "7.2.0" + } + }, + { + "id": "14e2e710-4258-11e8-b3aa-73fdaf54bfc9", + "type": "visualization", + "updated_at": "2019-07-08T15:35:44.395Z", + "version": "WzE4LDFd", + "attributes": { + "title": "[Logs] Visitors by OS", + "visState": "{\"title\":\"[Logs] Visitors by OS\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":true,\"values\":true,\"last_level\":true,\"truncate\":100}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"machine.os.keyword\",\"otherBucket\":true,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"size\":10,\"order\":\"desc\",\"orderBy\":\"1\"}}]}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + } + }, + "references": [ + { + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern", + "id": "90943e30-9a47-11e8-b64d-95841ca0b247" + } + ], + "migrationVersion": { + "visualization": "7.2.0" + } + }, + { + "id": "47f2c680-a6e3-11e8-94b4-c30c0228351b", + "type": "visualization", + "updated_at": "2019-07-08T15:35:44.395Z", + "version": "WzE5LDFd", + "attributes": { + "title": "[Logs] Markdown Instructions", + "visState": "{\"title\":\"[Logs] Markdown Instructions\",\"type\":\"markdown\",\"params\":{\"fontSize\":12,\"openLinksInNewTab\":true,\"markdown\":\"### Sample Logs Data\\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html).\"},\"aggs\":[]}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}" + } + }, + "references": [], + "migrationVersion": { + "visualization": "7.2.0" + } + }, + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern", + "updated_at": "2019-07-08T15:35:44.395Z", + "version": "WzIwLDFd", + "attributes": { + "title": "kibana_sample_data_logs", + "timeFieldName": "timestamp", + "fields": "[{\"name\":\"@timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"agent\",\"subType\":\"multi\"},{\"name\":\"bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"extension\",\"subType\":\"multi\"},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"host\",\"subType\":\"multi\"},{\"name\":\"index\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"index\",\"subType\":\"multi\"},{\"name\":\"ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"machine.os\",\"subType\":\"multi\"},{\"name\":\"machine.ram\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"message.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"message\",\"subType\":\"multi\"},{\"name\":\"phpmemory\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"request\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"request\",\"subType\":\"multi\"},{\"name\":\"response\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"response\",\"subType\":\"multi\"},{\"name\":\"tags\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"tags.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"tags\",\"subType\":\"multi\"},{\"name\":\"timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"url\",\"subType\":\"multi\"},{\"name\":\"utc_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hour_of_day\",\"type\":\"number\",\"count\":0,\"scripted\":true,\"script\":\"doc['timestamp'].value.getHour()\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]", + "fieldFormatMap": "{\"hour_of_day\":{}}" + }, + "references": [], + "migrationVersion": { + "index-pattern": "6.5.0" + } + } + ] +} \ No newline at end of file diff --git a/libs/go-kibana-rest/go.mod b/libs/go-kibana-rest/go.mod new file mode 100644 index 000000000..e2d53ae90 --- /dev/null +++ b/libs/go-kibana-rest/go.mod @@ -0,0 +1,25 @@ +module github.com/disaster37/go-kibana-rest/v8 + +go 1.19 + +require ( + github.com/go-resty/resty/v2 v2.7.0 + github.com/sirupsen/logrus v1.9.0 + github.com/stretchr/testify v1.8.1 + github.com/x-cray/logrus-prefixed-formatter v0.5.2 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/mattn/go-colorable v0.1.8 // indirect + github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect + github.com/onsi/ginkgo v1.16.5 // indirect + github.com/onsi/gomega v1.24.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/crypto v0.1.0 // indirect + golang.org/x/net v0.1.0 // indirect + golang.org/x/sys v0.1.0 // indirect + golang.org/x/term v0.1.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/libs/go-kibana-rest/go.sum b/libs/go-kibana-rest/go.sum new file mode 100644 index 000000000..4a88f4a05 --- /dev/null +++ b/libs/go-kibana-rest/go.sum @@ -0,0 +1,118 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= +github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= +github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= +github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/libs/go-kibana-rest/kbapi/api._.go b/libs/go-kibana-rest/kbapi/api._.go new file mode 100644 index 000000000..d4d622d64 --- /dev/null +++ b/libs/go-kibana-rest/kbapi/api._.go @@ -0,0 +1,114 @@ +package kbapi + +import ( + "github.com/go-resty/resty/v2" +) + +// API handle the API specification +type API struct { + KibanaSpaces *KibanaSpacesAPI + KibanaRoleManagement *KibanaRoleManagementAPI + KibanaDashboard *KibanaDashboardAPI + KibanaSavedObject *KibanaSavedObjectAPI + KibanaStatus *KibanaStatusAPI + KibanaLogstashPipeline *KibanaLogstashPipelineAPI + KibanaShortenURL *KibanaShortenURLAPI +} + +// KibanaSpacesAPI handle the spaces API +type KibanaSpacesAPI struct { + Get KibanaSpaceGet + List KibanaSpaceList + Create KibanaSpaceCreate + Delete KibanaSpaceDelete + Update KibanaSpaceUpdate + CopySavedObjects KibanaSpaceCopySavedObjects +} + +// KibanaRoleManagementAPI handle the role management API +type KibanaRoleManagementAPI struct { + Get KibanaRoleManagementGet + List KibanaRoleManagementList + CreateOrUpdate KibanaRoleManagementCreateOrUpdate + Delete KibanaRoleManagementDelete +} + +// KibanaDashboardAPI handle the dashboard API +type KibanaDashboardAPI struct { + Export KibanaDashboardExport + Import KibanaDashboardImport +} + +// KibanaSavedObjectAPI handle the saved object API +type KibanaSavedObjectAPI struct { + Get KibanaSavedObjectGet + Find KibanaSavedObjectFind + Create KibanaSavedObjectCreate + Update KibanaSavedObjectUpdate + Delete KibanaSavedObjectDelete + Import KibanaSavedObjectImport + Export KibanaSavedObjectExport +} + +// KibanaStatusAPI handle the status API +type KibanaStatusAPI struct { + Get KibanaStatusGet +} + +// KibanaLogstashPipelineAPI handle the logstash configuration management API +type KibanaLogstashPipelineAPI struct { + Get KibanaLogstashPipelineGet + List KibanaLogstashPipelineList + CreateOrUpdate KibanaLogstashPipelineCreateOrUpdate + Delete KibanaLogstashPipelineDelete +} + +// KibanaShortenURLAPI handle the shorten URL API +type KibanaShortenURLAPI struct { + Create KibanaShortenURLCreate +} + +// New initialise the API implementation +func New(c *resty.Client) *API { + return &API{ + KibanaSpaces: &KibanaSpacesAPI{ + Get: newKibanaSpaceGetFunc(c), + List: newKibanaSpaceListFunc(c), + Create: newKibanaSpaceCreateFunc(c), + Update: newKibanaSpaceUpdateFunc(c), + Delete: newKibanaSpaceDeleteFunc(c), + CopySavedObjects: newKibanaSpaceCopySavedObjectsFunc(c), + }, + KibanaRoleManagement: &KibanaRoleManagementAPI{ + Get: newKibanaRoleManagementGetFunc(c), + List: newKibanaRoleManagementListFunc(c), + CreateOrUpdate: newKibanaRoleManagementCreateOrUpdateFunc(c), + Delete: newKibanaRoleManagementDeleteFunc(c), + }, + KibanaDashboard: &KibanaDashboardAPI{ + Export: newKibanaDashboardExportFunc(c), + Import: newKibanaDashboardImportFunc(c), + }, + KibanaSavedObject: &KibanaSavedObjectAPI{ + Get: newKibanaSavedObjectGetFunc(c), + Find: newKibanaSavedObjectFindFunc(c), + Create: newKibanaSavedObjectCreateFunc(c), + Update: newKibanaSavedObjectUpdateFunc(c), + Delete: newKibanaSavedObjectDeleteFunc(c), + Import: newKibanaSavedObjectImportFunc(c), + Export: newKibanaSavedObjectExportFunc(c), + }, + KibanaStatus: &KibanaStatusAPI{ + Get: newKibanaStatusGetFunc(c), + }, + KibanaLogstashPipeline: &KibanaLogstashPipelineAPI{ + Get: newKibanaLogstashPipelineGetFunc(c), + List: newKibanaLogstashPipelineListFunc(c), + CreateOrUpdate: newKibanaLogstashPipelineCreateOrUpdateFunc(c), + Delete: newKibanaLogstashPipelineDeleteFunc(c), + }, + KibanaShortenURL: &KibanaShortenURLAPI{ + Create: newKibanaShortenURLCreateFunc(c), + }, + } +} diff --git a/libs/go-kibana-rest/kbapi/api.kibana_dashboard.go b/libs/go-kibana-rest/kbapi/api.kibana_dashboard.go new file mode 100644 index 000000000..69be75594 --- /dev/null +++ b/libs/go-kibana-rest/kbapi/api.kibana_dashboard.go @@ -0,0 +1,114 @@ +package kbapi + +import ( + "encoding/json" + "fmt" + "github.com/go-resty/resty/v2" + log "github.com/sirupsen/logrus" + "strings" +) + +const ( + basePathKibanaDashboard = "/api/kibana/dashboards" // Base URL to access on Kibana dashboard +) + +// KibanaDashboardExport permit to export dashboard +type KibanaDashboardExport func(listID []string, kibanaSpace string) (map[string]interface{}, error) + +// KibanaDashboardImport permit to import dashboard +type KibanaDashboardImport func(data map[string]interface{}, listExcludeType []string, force bool, kibanaSpace string) error + +// newKibanaDashboardExportFunc permit to export Kibana dashboard by its names +func newKibanaDashboardExportFunc(c *resty.Client) KibanaDashboardExport { + return func(listID []string, kibanaSpace string) (map[string]interface{}, error) { + + if len(listID) == 0 { + return nil, NewAPIError(600, "You must provide on or more dashboard ID") + } + log.Debug("listID: ", listID) + log.Debug("kibanaSpace: ", kibanaSpace) + + var path string + if kibanaSpace == "" || kibanaSpace == "default" { + path = fmt.Sprintf("%s/export", basePathKibanaDashboard) + } else { + path = fmt.Sprintf("/s/%s%s/export", kibanaSpace, basePathKibanaDashboard) + } + + log.Debugf("Url to export: %s", path) + + query := fmt.Sprintf("dashboard=%s", strings.Join(listID, ",")) + resp, err := c.R().SetQueryString(query).Get(path) + if err != nil { + return nil, err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + if resp.StatusCode() == 404 { + return nil, nil + } + return nil, NewAPIError(resp.StatusCode(), resp.Status()) + + } + var data map[string]interface{} + err = json.Unmarshal(resp.Body(), &data) + if err != nil { + return nil, err + } + log.Debug("Data: ", data) + + return data, nil + } + +} + +// newKibanaDashboardImportFunc permit to import kibana dashboard +func newKibanaDashboardImportFunc(c *resty.Client) KibanaDashboardImport { + return func(data map[string]interface{}, listExcludeType []string, force bool, kibanaSpace string) error { + + if data == nil { + return NewAPIError(600, "You must provide one or more dashboard to import") + } + log.Debug("data: ", data) + log.Debug("List type to exclude: ", listExcludeType) + log.Debug("Force import: ", force) + log.Debug("KibanaSpace: ", kibanaSpace) + + var path string + if kibanaSpace == "" || kibanaSpace == "default" { + path = fmt.Sprintf("%s/import", basePathKibanaDashboard) + } else { + path = fmt.Sprintf("/s/%s%s/import", kibanaSpace, basePathKibanaDashboard) + } + + log.Debugf("URL to import %s", path) + + request := c.R().SetQueryString(fmt.Sprintf("force=%t", force)) + if len(listExcludeType) > 0 { + request = request.SetQueryString(fmt.Sprintf("exclude=%s", strings.Join(listExcludeType, ","))) + } + jsonData, err := json.Marshal(data) + if err != nil { + return err + } + resp, err := request.SetBody(jsonData).Post(path) + if err != nil { + return err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + return NewAPIError(resp.StatusCode(), resp.Status()) + } + var dataResponse map[string]interface{} + err = json.Unmarshal(resp.Body(), &dataResponse) + if err != nil { + return err + } + log.Debug("Data response: ", dataResponse) + + // Need to manage error returned in response + + return nil + } + +} diff --git a/libs/go-kibana-rest/kbapi/api.kibana_dashboard_test.go b/libs/go-kibana-rest/kbapi/api.kibana_dashboard_test.go new file mode 100644 index 000000000..93150c668 --- /dev/null +++ b/libs/go-kibana-rest/kbapi/api.kibana_dashboard_test.go @@ -0,0 +1,46 @@ +package kbapi + +import ( + "encoding/json" + "os" + + "github.com/stretchr/testify/assert" +) + +func (s *KBAPITestSuite) TestKibanaDashboard() { + + // Import dashboard from fixtures + b, err := os.ReadFile("../fixtures/kibana-dashboard.json") + if err != nil { + panic(err) + } + data := make(map[string]interface{}) + if err = json.Unmarshal(b, &data); err != nil { + panic(err) + } + err = s.API.KibanaDashboard.Import(data, nil, true, "default") + assert.NoError(s.T(), err) + + // Export dashboard + data, err = s.API.KibanaDashboard.Export([]string{"edf84fe0-e1a0-11e7-b6d5-4dc382ef7f5b"}, "default") + assert.NoError(s.T(), err) + assert.NotNil(s.T(), data) + + // Import dashboard from fixtures in specific space + b, err = os.ReadFile("../fixtures/kibana-dashboard.json") + if err != nil { + panic(err) + } + data = make(map[string]interface{}) + if err = json.Unmarshal(b, &data); err != nil { + panic(err) + } + err = s.API.KibanaDashboard.Import(data, nil, true, "testacc") + assert.NoError(s.T(), err) + + // Export dashboard from specific space + data, err = s.API.KibanaDashboard.Export([]string{"edf84fe0-e1a0-11e7-b6d5-4dc382ef7f5b"}, "testacc") + assert.NoError(s.T(), err) + assert.NotNil(s.T(), data) + +} diff --git a/libs/go-kibana-rest/kbapi/api.kibana_logstash_pipeline.go b/libs/go-kibana-rest/kbapi/api.kibana_logstash_pipeline.go new file mode 100644 index 000000000..698e399b0 --- /dev/null +++ b/libs/go-kibana-rest/kbapi/api.kibana_logstash_pipeline.go @@ -0,0 +1,182 @@ +package kbapi + +import ( + "encoding/json" + "fmt" + + "github.com/go-resty/resty/v2" + log "github.com/sirupsen/logrus" +) + +const ( + basePathKibanaLogstashPipeline = "/api/logstash/pipeline" // Base URL to access on Kibana Logstash pipeline +) + +// LogstashPipeline is the Logstash pipeline object +type LogstashPipeline struct { + ID string `json:"id"` + Description string `json:"description,omitempty"` + Pipeline string `json:"pipeline,omitempty"` + Settings map[string]interface{} `json:"settings,omitempty"` + Username string `json:"username,omitempty"` +} + +type LogstashPipelineRequest struct { + Description string `json:"description,omitempty"` + Pipeline string `json:"pipeline,omitempty"` + Settings map[string]interface{} `json:"settings,omitempty"` + Username string `json:"username,omitempty"` +} + +// LogstashPipelinesList is the logstash pipeline list result when get the list +type LogstashPipelinesList struct { + Pipelines LogstashPipelines `json:"pipelines"` +} + +// LogstashPipelines is list of Logstash pipeline object +type LogstashPipelines []LogstashPipeline + +// KibanaLogstashPipelineCreateOrUpdate permit to create or update logstash pipeline +type KibanaLogstashPipelineCreateOrUpdate func(logstashPipeline *LogstashPipeline) (*LogstashPipeline, error) + +// KibanaLogstashPipelineGet permit to get the logstash pipeline +type KibanaLogstashPipelineGet func(id string) (*LogstashPipeline, error) + +// KibanaLogstashPipelineList permit to get all the logstash pipeline +type KibanaLogstashPipelineList func() (LogstashPipelines, error) + +// KibanaLogstashPipelineDelete permit to delete the logstash pipeline +type KibanaLogstashPipelineDelete func(id string) error + +// String permit to return LogstashPipeline object as JSON string +func (o *LogstashPipeline) String() string { + json, _ := json.Marshal(o) + return string(json) +} + +// newKibanaLogstashPipelineGetFunc permit to get the kibana role with it name +func newKibanaLogstashPipelineGetFunc(c *resty.Client) KibanaLogstashPipelineGet { + return func(id string) (*LogstashPipeline, error) { + + if id == "" { + return nil, NewAPIError(600, "You must provide logstash pipline ID") + } + log.Debug("ID: ", id) + + path := fmt.Sprintf("%s/%s", basePathKibanaLogstashPipeline, id) + resp, err := c.R().Get(path) + if err != nil { + return nil, err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + if resp.StatusCode() == 404 { + return nil, nil + } + return nil, NewAPIError(resp.StatusCode(), resp.Status()) + } + logstashPipeline := &LogstashPipeline{} + err = json.Unmarshal(resp.Body(), logstashPipeline) + if err != nil { + return nil, err + } + log.Debug("LogstashPipeline: ", logstashPipeline) + + return logstashPipeline, nil + } +} + +// newKibanaLogstashPipelineListFunc permit to get all kibana role +func newKibanaLogstashPipelineListFunc(c *resty.Client) KibanaLogstashPipelineList { + return func() (LogstashPipelines, error) { + + path := fmt.Sprintf("%ss", basePathKibanaLogstashPipeline) + resp, err := c.R().Get(path) + if err != nil { + return nil, err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + return nil, NewAPIError(resp.StatusCode(), resp.Status()) + } + logstashPipelinesList := &LogstashPipelinesList{} + err = json.Unmarshal(resp.Body(), logstashPipelinesList) + if err != nil { + return nil, err + } + log.Debug("LogstashPipelines: ", logstashPipelinesList) + + return logstashPipelinesList.Pipelines, nil + } + +} + +// newKibanaPipelineCreateOrUpdateFunc permit to create or update logstash pipeline +func newKibanaLogstashPipelineCreateOrUpdateFunc(c *resty.Client) KibanaLogstashPipelineCreateOrUpdate { + return func(logstashPipeline *LogstashPipeline) (*LogstashPipeline, error) { + + if logstashPipeline == nil { + return nil, NewAPIError(600, "You must provide the logstash pipeline object") + } + + log.Debug("LogstashPipeline: ", logstashPipeline) + + logstashPipelineRequest := &LogstashPipelineRequest{ + Description: logstashPipeline.Description, + Pipeline: logstashPipeline.Pipeline, + Settings: logstashPipeline.Settings, + } + + jsonData, err := json.Marshal(logstashPipelineRequest) + if err != nil { + return nil, err + } + + path := fmt.Sprintf("%s/%s", basePathKibanaLogstashPipeline, logstashPipeline.ID) + resp, err := c.R().SetBody(jsonData).Put(path) + if err != nil { + return nil, err + } + + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + return nil, NewAPIError(resp.StatusCode(), resp.Status()) + } + + // Retrive the object to return it + logstashPipeline, err = newKibanaLogstashPipelineGetFunc(c)(logstashPipeline.ID) + if err != nil { + return nil, err + } + if logstashPipeline == nil { + return nil, NewAPIError(404, "Logstash pipeline %s not found", logstashPipeline.ID) + } + + log.Debug("logstashPipeline: ", logstashPipeline) + + return logstashPipeline, nil + } +} + +// newKibanaLogstashPipelineDeleteFunc permit to delete logstash pipeline with it ID +func newKibanaLogstashPipelineDeleteFunc(c *resty.Client) KibanaLogstashPipelineDelete { + return func(id string) error { + + if id == "" { + return NewAPIError(600, "You must provide logstash pipeline ID") + } + log.Debug("ID: ", id) + + path := fmt.Sprintf("%s/%s", basePathKibanaLogstashPipeline, id) + resp, err := c.R().Delete(path) + if err != nil { + return err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + return NewAPIError(resp.StatusCode(), resp.Status()) + } + + return nil + } +} diff --git a/libs/go-kibana-rest/kbapi/api.kibana_logstash_pipeline_test.go b/libs/go-kibana-rest/kbapi/api.kibana_logstash_pipeline_test.go new file mode 100644 index 000000000..de636de26 --- /dev/null +++ b/libs/go-kibana-rest/kbapi/api.kibana_logstash_pipeline_test.go @@ -0,0 +1,41 @@ +package kbapi + +import ( + "github.com/stretchr/testify/assert" +) + +func (s *KBAPITestSuite) TestKibanaLogstashPipeline() { + + // Create new logstash pipeline + logstashPipeline := &LogstashPipeline{ + ID: "test", + Description: "Acceptance test", + Pipeline: "input { stdin {} } output { stdout {} }", + Settings: map[string]interface{}{ + "queue.type": "persisted", + }, + } + logstashPipeline, err := s.API.KibanaLogstashPipeline.CreateOrUpdate(logstashPipeline) + assert.NoError(s.T(), err) + assert.NotNil(s.T(), logstashPipeline) + assert.Equal(s.T(), "test", logstashPipeline.ID) + + // Get logstash pipeline + logstashPipeline, err = s.API.KibanaLogstashPipeline.Get("test") + assert.NoError(s.T(), err) + assert.NotNil(s.T(), logstashPipeline) + assert.Equal(s.T(), "test", logstashPipeline.ID) + + // List logstash pipeline + logstashPipelines, err := s.API.KibanaLogstashPipeline.List() + assert.NoError(s.T(), err) + assert.NotEmpty(s.T(), logstashPipelines) + + // Delete logstash pipeline + err = s.API.KibanaLogstashPipeline.Delete("test") + assert.NoError(s.T(), err) + logstashPipeline, err = s.API.KibanaLogstashPipeline.Get("test") + assert.NoError(s.T(), err) + assert.Nil(s.T(), logstashPipeline) + +} diff --git a/libs/go-kibana-rest/kbapi/api.kibana_role_management.go b/libs/go-kibana-rest/kbapi/api.kibana_role_management.go new file mode 100644 index 000000000..897466ac7 --- /dev/null +++ b/libs/go-kibana-rest/kbapi/api.kibana_role_management.go @@ -0,0 +1,190 @@ +package kbapi + +import ( + "encoding/json" + "fmt" + + "github.com/go-resty/resty/v2" + log "github.com/sirupsen/logrus" +) + +const ( + basePathKibanaRoleManagement = "/api/security/role" // Base URL to access on Kibana role management +) + +// KibanaRole is the API role object +type KibanaRole struct { + Name string `json:"name,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty"` + TransientMedata *KibanaRoleTransientMetadata `json:"transient_metadata,omitempty"` + Elasticsearch *KibanaRoleElasticsearch `json:"elasticsearch,omitempty"` + Kibana []KibanaRoleKibana `json:"kibana,omitempty"` +} + +// KibanaRoleTransientMetadata is the API TransientMedata object +type KibanaRoleTransientMetadata struct { + Enabled bool `json:"enabled,omitempty"` +} + +// KibanaRoleElasticsearch is the API Elasticsearch object +type KibanaRoleElasticsearch struct { + Indices []KibanaRoleElasticsearchIndice `json:"indices,omitempty"` + Cluster []string `json:"cluster,omitempty"` + RunAs []string `json:"run_as,omitempty"` +} + +// KibanaRoleKibana is the API Kibana object +type KibanaRoleKibana struct { + Base []string `json:"base,omitempty"` + Feature map[string][]string `json:"feature,omitempty"` + Spaces []string `json:"spaces,omitempty"` +} + +// KibanaRoleElasticsearchIndice is the API indice object +type KibanaRoleElasticsearchIndice struct { + Names []string `json:"names,omitempty"` + Privileges []string `json:"privileges,omitempty"` + FieldSecurity map[string]interface{} `json:"field_security,omitempty"` + Query interface{} `json:"query,omitempty"` +} + +// KibanaRoles is a list of role object +type KibanaRoles []KibanaRole + +// KibanaRoleManagementGet permit to get role from Kibana +type KibanaRoleManagementGet func(name string) (*KibanaRole, error) + +// KibanaRoleManagementList permit to get all roles from Kibana +type KibanaRoleManagementList func() (KibanaRoles, error) + +// KibanaRoleManagementCreateOrUpdate permit to create or update role in Kibana +type KibanaRoleManagementCreateOrUpdate func(kibanaRole *KibanaRole) (*KibanaRole, error) + +// KibanaRoleManagementDelete permit to delete role in Kibana +type KibanaRoleManagementDelete func(name string) error + +// String permit to return KibanaRole object as JSON string +func (k *KibanaRole) String() string { + json, _ := json.Marshal(k) + return string(json) +} + +// newKibanaRoleManagementGetFunc permit to get the kibana role with it name +func newKibanaRoleManagementGetFunc(c *resty.Client) KibanaRoleManagementGet { + return func(name string) (*KibanaRole, error) { + + if name == "" { + return nil, NewAPIError(600, "You must provide kibana role name") + } + log.Debug("Name: ", name) + + path := fmt.Sprintf("%s/%s", basePathKibanaRoleManagement, name) + resp, err := c.R().Get(path) + if err != nil { + return nil, err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + if resp.StatusCode() == 404 { + return nil, nil + } + return nil, NewAPIError(resp.StatusCode(), resp.Status()) + } + kibanaRole := &KibanaRole{} + err = json.Unmarshal(resp.Body(), kibanaRole) + if err != nil { + return nil, err + } + log.Debug("KibanaRole: ", kibanaRole) + + return kibanaRole, nil + } + +} + +// newKibanaRoleManagementListFunc permit to get all kibana role +func newKibanaRoleManagementListFunc(c *resty.Client) KibanaRoleManagementList { + return func() (KibanaRoles, error) { + + resp, err := c.R().Get(basePathKibanaRoleManagement) + if err != nil { + return nil, err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + return nil, NewAPIError(resp.StatusCode(), resp.Status()) + } + kibanaRoles := make(KibanaRoles, 0, 1) + err = json.Unmarshal(resp.Body(), &kibanaRoles) + if err != nil { + return nil, err + } + log.Debug("KibanaRoles: ", kibanaRoles) + + return kibanaRoles, nil + } + +} + +// newKibanaRoleManagementGetFunc permit to create or update the kibana role +func newKibanaRoleManagementCreateOrUpdateFunc(c *resty.Client) KibanaRoleManagementCreateOrUpdate { + return func(kibanaRole *KibanaRole) (*KibanaRole, error) { + + if kibanaRole == nil { + return nil, NewAPIError(600, "You must provide kibana role object") + } + log.Debug("Kibana role: ", kibanaRole) + roleName := kibanaRole.Name + + path := fmt.Sprintf("%s/%s", basePathKibanaRoleManagement, roleName) + kibanaRole.Name = "" + jsonData, err := json.Marshal(kibanaRole) + log.Debugf("Payload: %s", jsonData) + if err != nil { + return nil, err + } + resp, err := c.R().SetBody(jsonData).Put(path) + if err != nil { + return nil, err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + return nil, NewAPIError(resp.StatusCode(), resp.Status()) + } + + // Retrive the object to return it + kibanaRole, err = newKibanaRoleManagementGetFunc(c)(roleName) + if err != nil { + return nil, err + } + + log.Debug("KibanaRole: ", kibanaRole) + + return kibanaRole, nil + } + +} + +// newKibanaRoleManagementDeleteFunc permit to delete kibana role with it name +func newKibanaRoleManagementDeleteFunc(c *resty.Client) KibanaRoleManagementDelete { + return func(name string) error { + + if name == "" { + return NewAPIError(600, "You must provide kibana role name") + } + log.Debug("Name: ", name) + + path := fmt.Sprintf("%s/%s", basePathKibanaRoleManagement, name) + resp, err := c.R().Delete(path) + if err != nil { + return err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + return NewAPIError(resp.StatusCode(), resp.Status()) + } + + return nil + } + +} diff --git a/libs/go-kibana-rest/kbapi/api.kibana_role_management_test.go b/libs/go-kibana-rest/kbapi/api.kibana_role_management_test.go new file mode 100644 index 000000000..81c90b582 --- /dev/null +++ b/libs/go-kibana-rest/kbapi/api.kibana_role_management_test.go @@ -0,0 +1,55 @@ +package kbapi + +import ( + "github.com/stretchr/testify/assert" +) + +func (s *KBAPITestSuite) TestKibanaRoleManagement() { + + // Create new role + kibanaRole := &KibanaRole{ + Name: "test", + Elasticsearch: &KibanaRoleElasticsearch{ + Indices: []KibanaRoleElasticsearchIndice{ + { + Names: []string{ + "*", + }, + Privileges: []string{ + "read", + }, + }, + }, + }, + Kibana: []KibanaRoleKibana{ + { + Base: []string{ + "read", + }, + }, + }, + } + kibanaRole, err := s.API.KibanaRoleManagement.CreateOrUpdate(kibanaRole) + assert.NoError(s.T(), err) + assert.NotNil(s.T(), kibanaRole) + assert.Equal(s.T(), "test", kibanaRole.Name) + + // Get role + kibanaRole, err = s.API.KibanaRoleManagement.Get("test") + assert.NoError(s.T(), err) + assert.NotNil(s.T(), kibanaRole) + assert.Equal(s.T(), "test", kibanaRole.Name) + + // List role + kibanaRoles, err := s.API.KibanaRoleManagement.List() + assert.NoError(s.T(), err) + assert.NotEmpty(s.T(), kibanaRoles) + + // Delete role + err = s.API.KibanaRoleManagement.Delete("test") + assert.NoError(s.T(), err) + kibanaRole, err = s.API.KibanaRoleManagement.Get("test") + assert.NoError(s.T(), err) + assert.Nil(s.T(), kibanaRole) + +} diff --git a/libs/go-kibana-rest/kbapi/api.kibana_save_object.go b/libs/go-kibana-rest/kbapi/api.kibana_save_object.go new file mode 100644 index 000000000..a04f18145 --- /dev/null +++ b/libs/go-kibana-rest/kbapi/api.kibana_save_object.go @@ -0,0 +1,411 @@ +package kbapi + +import ( + "bytes" + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/go-resty/resty/v2" + log "github.com/sirupsen/logrus" +) + +const ( + basePathKibanaSavedObject = "/api/saved_objects" // Base URL to access on Kibana save objects +) + +// OptionalFindParameters contain optional parameters to find objects +type OptionalFindParameters struct { + ObjectsPerPage int + Page int + Search string + DefaultSearchOperator string + SearchFields []string + Fields []string + SortField string + HasReference string +} + +// KibanaSavedObjectGet permit to get saved object from Kibana +type KibanaSavedObjectGet func(objectType string, id string, kibanaSpace string) (map[string]interface{}, error) + +// KibanaSavedObjectFind permit to find saved objects from Kibana +type KibanaSavedObjectFind func(objectType string, kibanaSpace string, optionalParameters *OptionalFindParameters) (map[string]interface{}, error) + +// KibanaSavedObjectCreate permit to create saved object in Kibana +type KibanaSavedObjectCreate func(data map[string]interface{}, objectType string, id string, overwrite bool, kibanaSpace string) (map[string]interface{}, error) + +// KibanaSavedObjectUpdate permit to update saved object in Kibana +type KibanaSavedObjectUpdate func(data map[string]interface{}, objectType string, id string, kibanaSpace string) (map[string]interface{}, error) + +// KibanaSavedObjectDelete permit to delete saved object in Kibana +type KibanaSavedObjectDelete func(objectType string, id string, kibanaSpace string) error + +// KibanaSavedObjectExport permit to export saved objects from Kibana +type KibanaSavedObjectExport func(objectTypes []string, objects []map[string]string, deepReference bool, kibanaSpace string) ([]byte, error) + +// KibanaSavedObjectImport permit to import saved objects in Kibana +type KibanaSavedObjectImport func(data []byte, overwrite bool, kibanaSpace string) (map[string]interface{}, error) + +// String permit to return OptionalFindParameters object as JSON string +func (o *OptionalFindParameters) String() string { + json, _ := json.Marshal(o) + return string(json) +} + +// newKibanaSavedObjectGetFunc permit to get saved obejct by it id and type +func newKibanaSavedObjectGetFunc(c *resty.Client) KibanaSavedObjectGet { + return func(objectType string, id string, kibanaSpace string) (map[string]interface{}, error) { + + if objectType == "" { + return nil, NewAPIError(600, "You must provide the object type") + } + if id == "" { + return nil, NewAPIError(600, "You must provide the object ID") + } + log.Debug("ObjectType: ", objectType) + log.Debug("ID: ", id) + log.Debug("KibanaSpace: ", kibanaSpace) + + var path string + if kibanaSpace == "" || kibanaSpace == "default" { + path = fmt.Sprintf("%s/%s/%s", basePathKibanaSavedObject, objectType, id) + } else { + path = fmt.Sprintf("/s/%s%s/%s/%s", kibanaSpace, basePathKibanaSavedObject, objectType, id) + } + log.Debugf("URL to get object: %s", path) + + resp, err := c.R().Get(path) + if err != nil { + return nil, err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + if resp.StatusCode() == 404 { + return nil, nil + } + return nil, NewAPIError(resp.StatusCode(), resp.Status()) + + } + var data map[string]interface{} + err = json.Unmarshal(resp.Body(), &data) + if err != nil { + return nil, err + } + log.Debug("Data: ", data) + + return data, nil + } + +} + +// newKibanaSavedObjectFindFunc permit to search objects +func newKibanaSavedObjectFindFunc(c *resty.Client) KibanaSavedObjectFind { + return func(objectType string, kibanaSpace string, optionalParameters *OptionalFindParameters) (map[string]interface{}, error) { + + if objectType == "" { + return nil, NewAPIError(600, "You must provide the object type") + } + log.Debug("ObjectType: ", objectType) + log.Debug("KibanaSpace : ", kibanaSpace) + + queryParams := map[string]string{ + "type": objectType, + } + if optionalParameters != nil { + log.Debug("Objects Per Page: ", optionalParameters.ObjectsPerPage) + log.Debug("Page: ", optionalParameters.Page) + log.Debug("Search: ", optionalParameters.Search) + log.Debug("DefaultSearchOperator: ", optionalParameters.DefaultSearchOperator) + log.Debug("SearchFields: ", optionalParameters.SearchFields) + log.Debug("Fields: ", optionalParameters.Fields) + log.Debug("SortField: ", optionalParameters.SortField) + log.Debug("HasReference: ", optionalParameters.HasReference) + if optionalParameters.ObjectsPerPage != 0 { + queryParams["per_page"] = strconv.Itoa(optionalParameters.ObjectsPerPage) + } + if optionalParameters.Page != 0 { + queryParams["page"] = strconv.Itoa(optionalParameters.Page) + } + if optionalParameters.Search != "" { + queryParams["search"] = optionalParameters.Search + } + if optionalParameters.DefaultSearchOperator != "" { + queryParams["default_search_operator"] = optionalParameters.DefaultSearchOperator + } + if optionalParameters.SearchFields != nil { + queryParams["search_fields"] = strings.Join(optionalParameters.SearchFields, ",") + } + if optionalParameters.Fields != nil { + queryParams["fields"] = strings.Join(optionalParameters.Fields, ",") + } + if optionalParameters.SortField != "" { + queryParams["sort_field"] = optionalParameters.SortField + } + if optionalParameters.HasReference != "" { + queryParams["has_reference"] = optionalParameters.HasReference + } + } + + var path string + if kibanaSpace == "" || kibanaSpace == "default" { + path = fmt.Sprintf("%s/_find", basePathKibanaSavedObject) + } else { + path = fmt.Sprintf("/s/%s%s/_find", kibanaSpace, basePathKibanaSavedObject) + } + log.Debugf("URL to find object: %s", path) + + resp, err := c.R().SetQueryParams(queryParams).Get(path) + if err != nil { + return nil, err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + if resp.StatusCode() == 404 { + return nil, nil + } + return nil, NewAPIError(resp.StatusCode(), resp.Status()) + + } + var data map[string]interface{} + err = json.Unmarshal(resp.Body(), &data) + if err != nil { + return nil, err + } + log.Debug("Data: ", data) + + return data, nil + } + +} + +// newKibanaSavedObjectCreateFunc permit to create new object on Kibana +func newKibanaSavedObjectCreateFunc(c *resty.Client) KibanaSavedObjectCreate { + return func(data map[string]interface{}, objectType string, id string, overwrite bool, kibanaSpace string) (map[string]interface{}, error) { + + if data == nil { + return nil, NewAPIError(600, "You must provide one or more dashboard to import") + } + if objectType == "" { + return nil, NewAPIError(600, "You must provide the object type") + } + log.Debug("data: ", data) + log.Debug("ObjectType: ", objectType) + log.Debug("ID: ", id) + log.Debug("Overwrite: ", overwrite) + log.Debug("KibanaSpace: ", kibanaSpace) + + var path string + if kibanaSpace == "" || kibanaSpace == "default" { + path = fmt.Sprintf("%s/%s/%s", basePathKibanaSavedObject, objectType, id) + } else { + path = fmt.Sprintf("/s/%s%s/%s/%s", kibanaSpace, basePathKibanaSavedObject, objectType, id) + } + log.Debugf("URL to create object: %s", path) + + jsonData, err := json.Marshal(data) + if err != nil { + return nil, err + } + resp, err := c.R().SetQueryString(fmt.Sprintf("overwrite=%t", overwrite)).SetBody(jsonData).Post(path) + if err != nil { + return nil, err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + return nil, NewAPIError(resp.StatusCode(), resp.Status()) + } + var dataResponse map[string]interface{} + err = json.Unmarshal(resp.Body(), &dataResponse) + if err != nil { + return nil, err + } + log.Debug("Data response: ", dataResponse) + + return dataResponse, nil + } +} + +// newKibanaSavedObjectUpdateFunc permit to update object on Kibana +func newKibanaSavedObjectUpdateFunc(c *resty.Client) KibanaSavedObjectUpdate { + return func(data map[string]interface{}, objectType string, id string, kibanaSpace string) (map[string]interface{}, error) { + + if data == nil { + return nil, NewAPIError(600, "You must provide one or more dashboard to import") + } + if objectType == "" { + return nil, NewAPIError(600, "You must provide the object type") + } + if id == "" { + return nil, NewAPIError(600, "You must provide the ID") + } + log.Debug("data: ", data) + log.Debug("ObjectType: ", objectType) + log.Debug("ID: ", id) + log.Debug("kibanaSpace: ", kibanaSpace) + + var path string + if kibanaSpace == "" || kibanaSpace == "default" { + path = fmt.Sprintf("%s/%s/%s", basePathKibanaSavedObject, objectType, id) + } else { + path = fmt.Sprintf("/s/%s%s/%s/%s", kibanaSpace, basePathKibanaSavedObject, objectType, id) + } + log.Debugf("URL to update object: %s", path) + + jsonData, err := json.Marshal(data) + if err != nil { + return nil, err + } + resp, err := c.R().SetBody(jsonData).Put(path) + if err != nil { + return nil, err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + return nil, NewAPIError(resp.StatusCode(), resp.Status()) + } + var dataResponse map[string]interface{} + err = json.Unmarshal(resp.Body(), &dataResponse) + if err != nil { + return nil, err + } + log.Debug("Data response: ", dataResponse) + + return dataResponse, nil + } +} + +// newKibanaSavedObjectDeleteFunc permit to delete object on Kibana +func newKibanaSavedObjectDeleteFunc(c *resty.Client) KibanaSavedObjectDelete { + return func(objectType string, id string, kibanaSpace string) error { + + if objectType == "" { + return NewAPIError(600, "You must provide the object type") + } + if id == "" { + return NewAPIError(600, "You must provide the id") + } + log.Debug("objectType: ", objectType) + log.Debug("ID: ", id) + log.Debug("KibanaSpace: ", kibanaSpace) + + var path string + if kibanaSpace == "" || kibanaSpace == "default" { + path = fmt.Sprintf("%s/%s/%s", basePathKibanaSavedObject, objectType, id) + } else { + path = fmt.Sprintf("/s/%s%s/%s/%s", kibanaSpace, basePathKibanaSavedObject, objectType, id) + } + log.Debugf("URL to delete object: %s", path) + + resp, err := c.R().Delete(path) + if err != nil { + return err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + return NewAPIError(resp.StatusCode(), resp.Status()) + } + var dataResponse map[string]interface{} + err = json.Unmarshal(resp.Body(), &dataResponse) + if err != nil { + return err + } + log.Debug("Data response: ", dataResponse) + + return nil + } +} + +// newKibanaSavedObjectExportFunc permit to export Kibana object +func newKibanaSavedObjectExportFunc(c *resty.Client) KibanaSavedObjectExport { + return func(objectTypes []string, objects []map[string]string, deepReference bool, kibanaSpace string) ([]byte, error) { + + log.Debug("ObjectTypes: ", objectTypes) + log.Debug("Objects: ", objects) + log.Debug("DeepReference: ", deepReference) + log.Debug("KibanaSpace: ", kibanaSpace) + + payload := make(map[string]interface{}) + payload["excludeExportDetails"] = true + + if len(objectTypes) > 0 { + payload["type"] = objectTypes + } + if len(objects) > 0 { + payload["objects"] = objects + } + payload["includeReferencesDeep"] = deepReference + log.Debug("Payload: ", payload) + + var path string + if kibanaSpace == "" || kibanaSpace == "default" { + path = fmt.Sprintf("%s/_export", basePathKibanaSavedObject) + } else { + path = fmt.Sprintf("/s/%s%s/_export", kibanaSpace, basePathKibanaSavedObject) + } + log.Debugf("URL to export object: %s", path) + + jsonData, err := json.Marshal(payload) + if err != nil { + return nil, err + } + resp, err := c.R().SetBody(jsonData).Post(path) + if err != nil { + return nil, err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + return nil, NewAPIError(resp.StatusCode(), resp.Status()) + } + + data := resp.Body() + log.Debug("Data response: ", data) + + return data, nil + + } +} + +// newKibanaSavedObjectImportFunc permit to import Kibana object +func newKibanaSavedObjectImportFunc(c *resty.Client) KibanaSavedObjectImport { + return func(data []byte, overwrite bool, kibanaSpace string) (map[string]interface{}, error) { + + if len(data) == 0 { + return nil, NewAPIError(600, "You must provide data parameters") + } + + log.Debug("Data: ", data) + log.Debug("Overwrite: ", overwrite) + log.Debug("kibanaSpace: ", kibanaSpace) + + var path string + if kibanaSpace == "" || kibanaSpace == "default" { + path = fmt.Sprintf("%s/_import", basePathKibanaSavedObject) + } else { + path = fmt.Sprintf("/s/%s%s/_import", kibanaSpace, basePathKibanaSavedObject) + } + log.Debugf("URL to export object: %s", path) + + resp, err := c.R(). + SetQueryString(fmt.Sprintf("overwrite=%t", overwrite)). + SetFileReader("file", "file.ndjson", bytes.NewReader(data)). + Post(path) + if err != nil { + return nil, err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + return nil, NewAPIError(resp.StatusCode(), resp.Status()) + } + var dataResponse map[string]interface{} + err = json.Unmarshal(resp.Body(), &dataResponse) + if err != nil { + return nil, err + } + log.Debug("Data response: ", dataResponse) + + return dataResponse, nil + + } +} diff --git a/libs/go-kibana-rest/kbapi/api.kibana_save_object_test.go b/libs/go-kibana-rest/kbapi/api.kibana_save_object_test.go new file mode 100644 index 000000000..988d91ba7 --- /dev/null +++ b/libs/go-kibana-rest/kbapi/api.kibana_save_object_test.go @@ -0,0 +1,152 @@ +package kbapi + +import ( + "encoding/json" + + "github.com/stretchr/testify/assert" +) + +func (s *KBAPITestSuite) TestKibanaSaveObject() { + + // Create new index pattern + dataJSON := `{"attributes": {"title": "test-pattern-*"}}` + data := make(map[string]interface{}) + err := json.Unmarshal([]byte(dataJSON), &data) + if err != nil { + panic(err) + } + resp, err := s.API.KibanaSavedObject.Create(data, "index-pattern", "test", true, "default") + assert.NoError(s.T(), err) + assert.NotNil(s.T(), resp) + assert.Equal(s.T(), "test", resp["id"]) + assert.Equal(s.T(), "test-pattern-*", resp["attributes"].(map[string]interface{})["title"]) + + // Create new index pattern in space + dataJSON = `{"attributes": {"title": "test-pattern-*"}}` + data = make(map[string]interface{}) + err = json.Unmarshal([]byte(dataJSON), &data) + if err != nil { + panic(err) + } + resp, err = s.API.KibanaSavedObject.Create(data, "index-pattern", "test2", true, "testacc") + assert.NoError(s.T(), err) + assert.NotNil(s.T(), resp) + assert.Equal(s.T(), "test2", resp["id"]) + assert.Equal(s.T(), "test-pattern-*", resp["attributes"].(map[string]interface{})["title"]) + + // Get index pattern + resp, err = s.API.KibanaSavedObject.Get("index-pattern", "test", "default") + assert.NoError(s.T(), err) + assert.NotNil(s.T(), resp) + assert.Equal(s.T(), "test", resp["id"]) + + // Get index pattern from space + resp, err = s.API.KibanaSavedObject.Get("index-pattern", "test2", "testacc") + assert.NoError(s.T(), err) + assert.NotNil(s.T(), resp) + assert.Equal(s.T(), "test2", resp["id"]) + + // Search index pattern + parameters := &OptionalFindParameters{ + Search: "test*", + SearchFields: []string{"title"}, + Fields: []string{"id"}, + } + resp, err = s.API.KibanaSavedObject.Find("index-pattern", "default", parameters) + assert.NoError(s.T(), err) + assert.NotNil(s.T(), resp) + dataRes := resp["saved_objects"].([]interface{})[0].(map[string]interface{}) + assert.NotEmpty(s.T(), dataRes["id"].(string)) + + // Search index pattern from space + parameters = &OptionalFindParameters{ + Search: "test*", + SearchFields: []string{"title"}, + Fields: []string{"id"}, + } + resp, err = s.API.KibanaSavedObject.Find("index-pattern", "testacc", parameters) + assert.NoError(s.T(), err) + assert.NotNil(s.T(), resp) + dataRes = resp["saved_objects"].([]interface{})[0].(map[string]interface{}) + assert.NotEmpty(s.T(), dataRes["id"].(string)) + + // Update index pattern + dataJSON = `{"attributes": {"title": "test-pattern2-*"}}` + err = json.Unmarshal([]byte(dataJSON), &data) + if err != nil { + panic(err) + } + resp, err = s.API.KibanaSavedObject.Update(data, "index-pattern", "test", "default") + assert.NoError(s.T(), err) + assert.NotNil(s.T(), resp) + assert.Equal(s.T(), "test", resp["id"]) + assert.Equal(s.T(), "test-pattern2-*", resp["attributes"].(map[string]interface{})["title"]) + + // Update index pattern in space + dataJSON = `{"attributes": {"title": "test-pattern2-*"}}` + err = json.Unmarshal([]byte(dataJSON), &data) + if err != nil { + panic(err) + } + resp, err = s.API.KibanaSavedObject.Update(data, "index-pattern", "test2", "testacc") + assert.NoError(s.T(), err) + assert.NotNil(s.T(), resp) + assert.Equal(s.T(), "test2", resp["id"]) + assert.Equal(s.T(), "test-pattern2-*", resp["attributes"].(map[string]interface{})["title"]) + + // Export index pattern + request := []map[string]string{ + { + "type": "index-pattern", + "id": "test", + }, + } + dataExported, err := s.API.KibanaSavedObject.Export(nil, request, true, "default") + assert.NoError(s.T(), err) + assert.NotEmpty(s.T(), dataExported) + + // Export index pattern from space + request = []map[string]string{ + { + "type": "index-pattern", + "id": "test2", + }, + } + dataExported, err = s.API.KibanaSavedObject.Export(nil, request, true, "testacc") + assert.NoError(s.T(), err) + assert.NotEmpty(s.T(), dataExported) + + // import index pattern + b, err := json.Marshal(resp) + if err != nil { + panic(err) + } + resp2, err := s.API.KibanaSavedObject.Import(b, true, "default") + assert.NoError(s.T(), err) + assert.NotNil(s.T(), resp2) + assert.Equal(s.T(), true, resp2["success"]) + + // import index pattern in space + b, err = json.Marshal(resp) + if err != nil { + panic(err) + } + resp, err = s.API.KibanaSavedObject.Import(b, true, "testacc") + assert.NoError(s.T(), err) + assert.NotNil(s.T(), resp) + assert.Equal(s.T(), true, resp["success"]) + + // Delete index pattern + err = s.API.KibanaSavedObject.Delete("index-pattern", "test", "default") + assert.NoError(s.T(), err) + resp, err = s.API.KibanaSavedObject.Get("index-pattern", "test", "default") + assert.NoError(s.T(), err) + assert.Nil(s.T(), resp) + + // Delete index pattern in space + err = s.API.KibanaSavedObject.Delete("index-pattern", "test2", "testacc") + assert.NoError(s.T(), err) + resp, err = s.API.KibanaSavedObject.Get("index-pattern", "test2", "testacc") + assert.NoError(s.T(), err) + assert.Nil(s.T(), resp) +} diff --git a/libs/go-kibana-rest/kbapi/api.kibana_shorten_url.go b/libs/go-kibana-rest/kbapi/api.kibana_shorten_url.go new file mode 100644 index 000000000..257d9bb44 --- /dev/null +++ b/libs/go-kibana-rest/kbapi/api.kibana_shorten_url.go @@ -0,0 +1,78 @@ +package kbapi + +import ( + "encoding/json" + + "github.com/go-resty/resty/v2" + log "github.com/sirupsen/logrus" +) + +const ( + basePathKibanaShortenURL = "/api/short_url" // Base URL to access on Kibana shorten URL +) + +// ShortenURL is the shorten URL object +type ShortenURL struct { + LocatorId string `json:"locatorId"` + Params map[string]any `json:"params"` + Slug string `json:"slug,omitempty"` + HumanReadableSlug bool `json:"humanReadableSlug,omitempty"` +} + +// ShortenURLResponse is the shorten URL object response +type ShortenURLResponse struct { + ID string `json:"id"` + Locator *ShortenURL `json:"locator"` +} + +// KibanaShortenURLCreate permit to create new shorten URL +type KibanaShortenURLCreate func(shortenURL *ShortenURL) (*ShortenURLResponse, error) + +// String permit to return ShortenURL object as JSON string +func (o *ShortenURL) String() string { + json, _ := json.Marshal(o) + return string(json) +} + +// String permit to return ShortenURLResponse object as JSON string +func (o *ShortenURLResponse) String() string { + json, _ := json.Marshal(o) + return string(json) +} + +// newKibanaShortenURLCreateFunc permit to create new shorten URL +func newKibanaShortenURLCreateFunc(c *resty.Client) KibanaShortenURLCreate { + return func(shortenURL *ShortenURL) (*ShortenURLResponse, error) { + + if shortenURL == nil { + return nil, NewAPIError(600, "You must provide shorten URL object") + } + log.Debug("Shorten URL: ", shortenURL) + + jsonData, err := json.Marshal(shortenURL) + if err != nil { + return nil, err + } + + log.Debugf("Shorten URL payload: %s", jsonData) + + resp, err := c.R().SetBody(jsonData).Post(basePathKibanaShortenURL) + if err != nil { + return nil, err + } + + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + return nil, NewAPIError(resp.StatusCode(), resp.Status()) + } + + shortenURLResponse := &ShortenURLResponse{} + err = json.Unmarshal(resp.Body(), shortenURLResponse) + if err != nil { + return nil, err + } + log.Debug("ShortenURLResponse: ", shortenURLResponse) + + return shortenURLResponse, nil + } +} diff --git a/libs/go-kibana-rest/kbapi/api.kibana_shorten_url_test.go b/libs/go-kibana-rest/kbapi/api.kibana_shorten_url_test.go new file mode 100644 index 000000000..ca9e21fc9 --- /dev/null +++ b/libs/go-kibana-rest/kbapi/api.kibana_shorten_url_test.go @@ -0,0 +1,20 @@ +package kbapi + +import ( + "github.com/stretchr/testify/assert" +) + +func (s *KBAPITestSuite) TestKibanaShortenURL() { + + // Create new shorten URL + shortenURL := &ShortenURL{ + LocatorId: "LEGACY_SHORT_URL_LOCATOR", + Params: map[string]any{ + "url": "/app/kibana#/dashboard?_g=()&_a=(description:'',filters:!(),fullScreenMode:!f,options:(hidePanelTitles:!f,useMargins:!t),panels:!((embeddableConfig:(),gridData:(h:15,i:'1',w:24,x:0,y:0),id:'8f4d0c00-4c86-11e8-b3d7-01146121b73d',panelIndex:'1',type:visualization,version:'7.0.0-alpha1')),query:(language:lucene,query:''),timeRestore:!f,title:'New%20Dashboard',viewMode:edit)", + }, + } + shortenURLResponse, err := s.API.KibanaShortenURL.Create(shortenURL) + assert.NoError(s.T(), err) + assert.NotNil(s.T(), shortenURLResponse) + assert.NotEmpty(s.T(), shortenURLResponse.ID) +} diff --git a/libs/go-kibana-rest/kbapi/api.kibana_spaces.go b/libs/go-kibana-rest/kbapi/api.kibana_spaces.go new file mode 100644 index 000000000..12e581b65 --- /dev/null +++ b/libs/go-kibana-rest/kbapi/api.kibana_spaces.go @@ -0,0 +1,274 @@ +package kbapi + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/go-resty/resty/v2" + log "github.com/sirupsen/logrus" +) + +const ( + basePathKibanaSpace = "/api/spaces" // Base URL to access on Kibana space API +) + +// KibanaSpace is the Space API object +type KibanaSpace struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + DisabledFeatures []string `json:"disabledFeatures,omitempty"` + Reserved bool `json:"_reserved,omitempty"` + Initials string `json:"initials,omitempty"` + Color string `json:"color,omitempty"` +} + +// KibanaSpaces is the list of KibanaSpace object +type KibanaSpaces []KibanaSpace + +// KibanaSpaceCopySavedObjectParameter is parameters to copy dashboard between spaces +type KibanaSpaceCopySavedObjectParameter struct { + Spaces []string `json:"spaces"` + IncludeReferences bool `json:"includeReferences"` + Overwrite bool `json:"overwrite"` + CreateNewCopies bool `json:"createNewCopies"` + Objects []KibanaSpaceObjectParameter `json:"objects"` +} + +// KibanaSpaceObjectParameter is Object object +type KibanaSpaceObjectParameter struct { + Type string `json:"type"` + ID string `json:"id"` +} + +// KibanaSpaceGet permit to get space +type KibanaSpaceGet func(id string) (*KibanaSpace, error) + +// KibanaSpaceList permit to get all spaces +type KibanaSpaceList func() (KibanaSpaces, error) + +// KibanaSpaceCreate permit to create space +type KibanaSpaceCreate func(kibanaSpace *KibanaSpace) (*KibanaSpace, error) + +// KibanaSpaceDelete permit to delete space +type KibanaSpaceDelete func(id string) error + +// KibanaSpaceUpdate permit to update space +type KibanaSpaceUpdate func(kibanaSpace *KibanaSpace) (*KibanaSpace, error) + +// KibanaSpaceCopySavedObjects permit to copy dashboad between space +type KibanaSpaceCopySavedObjects func(parameter *KibanaSpaceCopySavedObjectParameter, spaceOrigin string) error + +// String permit to return KibanaSpace object as JSON string +func (k *KibanaSpace) String() string { + json, _ := json.Marshal(k) + return string(json) +} + +// newKibanaSpaceGetFunc permit to get the kibana space with it id +func newKibanaSpaceGetFunc(c *resty.Client) KibanaSpaceGet { + return func(id string) (*KibanaSpace, error) { + + if id == "" { + return nil, NewAPIError(600, "You must provide kibana space ID") + } + log.Debug("ID: ", id) + + path := fmt.Sprintf("%s/space/%s", basePathKibanaSpace, id) + resp, err := c.R().Get(path) + if err != nil { + return nil, err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + if resp.StatusCode() == 404 { + return nil, nil + } + return nil, NewAPIError(resp.StatusCode(), resp.Status()) + + } + kibanaSpace := &KibanaSpace{} + err = json.Unmarshal(resp.Body(), kibanaSpace) + if err != nil { + return nil, err + } + log.Debug("KibanaSpace: ", kibanaSpace) + + return kibanaSpace, nil + } + +} + +// newKibanaSpaceListFunc permit to get all Kibana space +func newKibanaSpaceListFunc(c *resty.Client) KibanaSpaceList { + return func() (KibanaSpaces, error) { + + path := fmt.Sprintf("%s/space", basePathKibanaSpace) + resp, err := c.R().Get(path) + if err != nil { + return nil, err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + return nil, NewAPIError(resp.StatusCode(), resp.Status()) + } + kibanaSpaces := make(KibanaSpaces, 0, 1) + err = json.Unmarshal(resp.Body(), &kibanaSpaces) + if err != nil { + return nil, err + } + log.Debug("KibanaSpaces: ", kibanaSpaces) + + return kibanaSpaces, nil + } + +} + +// newKibanaSpaceCreateFunc permit to create new Kibana space +func newKibanaSpaceCreateFunc(c *resty.Client) KibanaSpaceCreate { + return func(kibanaSpace *KibanaSpace) (*KibanaSpace, error) { + + if kibanaSpace == nil { + return nil, NewAPIError(600, "You must provide kibana space object") + } + log.Debug("KibanaSpace: ", kibanaSpace) + + jsonData, err := json.Marshal(kibanaSpace) + if err != nil { + return nil, err + } + path := fmt.Sprintf("%s/space", basePathKibanaSpace) + resp, err := c.R().SetBody(jsonData).Post(path) + if err != nil { + return nil, err + } + + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + return nil, NewAPIError(resp.StatusCode(), resp.Status()) + } + kibanaSpace = &KibanaSpace{} + err = json.Unmarshal(resp.Body(), kibanaSpace) + if err != nil { + return nil, err + } + log.Debug("KibanaSpace: ", kibanaSpace) + + return kibanaSpace, nil + } + +} + +// newKibanaSpaceCopySavedObjectsFunc permit to copy extings objects from user space to another userSpace +func newKibanaSpaceCopySavedObjectsFunc(c *resty.Client) KibanaSpaceCopySavedObjects { + return func(parameter *KibanaSpaceCopySavedObjectParameter, spaceOrigin string) error { + + if parameter == nil { + return NewAPIError(600, "You must provide parameter to copy existing objects on other user spaces") + } + log.Debug("Parameter: ", parameter) + log.Debug("SpaceOrigin: ", spaceOrigin) + + var path string + if spaceOrigin == "" || spaceOrigin == "default" { + path = fmt.Sprintf("%s/_copy_saved_objects", basePathKibanaSpace) + } else { + path = fmt.Sprintf("/s/%s%s/_copy_saved_objects", spaceOrigin, basePathKibanaSpace) + } + jsonData, err := json.Marshal(parameter) + if err != nil { + return err + } + resp, err := c.R().SetBody(jsonData).Post(path) + if err != nil { + return err + } + + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + return NewAPIError(resp.StatusCode(), resp.Status()) + } + data := make(map[string]interface{}) + err = json.Unmarshal(resp.Body(), &data) + if err != nil { + return err + } + log.Debug("Response: ", data) + + var errors []string + for name, object := range data { + if !object.(map[string]interface{})["success"].(bool) { + errors = append(errors, fmt.Sprintf("Error to process user space %s", name)) + } + } + if len(errors) > 0 { + return NewAPIError(500, strings.Join(errors, "\n")) + } + + return nil + } + +} + +// newKibanaSpaceDeleteFunc permit to delete the kubana space wiht it id +func newKibanaSpaceDeleteFunc(c *resty.Client) KibanaSpaceDelete { + return func(id string) error { + + if id == "" { + return NewAPIError(600, "You must provide kibana space ID") + } + + log.Debug("ID: ", id) + + path := fmt.Sprintf("%s/space/%s", basePathKibanaSpace, id) + resp, err := c.R().Delete(path) + if err != nil { + return err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + + return NewAPIError(resp.StatusCode(), resp.Status()) + + } + + return nil + } + +} + +// newKibanaSpaceUpdateFunc permit to update the Kibana space +func newKibanaSpaceUpdateFunc(c *resty.Client) KibanaSpaceUpdate { + return func(kibanaSpace *KibanaSpace) (*KibanaSpace, error) { + + if kibanaSpace == nil { + return nil, NewAPIError(600, "You must provide kibana space object") + } + log.Debug("KibanaSpace: ", kibanaSpace) + + jsonData, err := json.Marshal(kibanaSpace) + if err != nil { + return nil, err + } + path := fmt.Sprintf("%s/space/%s", basePathKibanaSpace, kibanaSpace.ID) + resp, err := c.R().SetBody(jsonData).Put(path) + if err != nil { + return nil, err + } + + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + return nil, NewAPIError(resp.StatusCode(), resp.Status()) + } + kibanaSpace = &KibanaSpace{} + err = json.Unmarshal(resp.Body(), kibanaSpace) + if err != nil { + return nil, err + } + log.Debug("KibanaSpace: ", kibanaSpace) + + return kibanaSpace, nil + } + +} diff --git a/libs/go-kibana-rest/kbapi/api.kibana_spaces_test.go b/libs/go-kibana-rest/kbapi/api.kibana_spaces_test.go new file mode 100644 index 000000000..09cd72d9d --- /dev/null +++ b/libs/go-kibana-rest/kbapi/api.kibana_spaces_test.go @@ -0,0 +1,59 @@ +package kbapi + +import ( + "github.com/stretchr/testify/assert" +) + +func (s *KBAPITestSuite) TestKibanaSpaces() { + + // List kibana space + kibanaSpaces, err := s.API.KibanaSpaces.List() + assert.NoError(s.T(), err) + assert.NotEmpty(s.T(), kibanaSpaces) + + // Get the default Space + kibanaSpace, err := s.API.KibanaSpaces.Get(kibanaSpaces[0].ID) + assert.NoError(s.T(), err) + assert.Equal(s.T(), kibanaSpaces[0].ID, kibanaSpace.ID) + assert.Equal(s.T(), "Default", kibanaSpace.Name) + + // Create new space + kibanaSpace = &KibanaSpace{ + ID: "test", + Name: "test", + Description: "My test", + } + kibanaSpace, err = s.KibanaSpaces.Create(kibanaSpace) + assert.NoError(s.T(), err) + assert.NotEmpty(s.T(), kibanaSpace.ID) + + // Update space + kibanaSpace.Name = "test2" + kibanaSpace, err = s.KibanaSpaces.Update(kibanaSpace) + assert.NoError(s.T(), err) + assert.Equal(s.T(), "test2", kibanaSpace.Name) + + // Copy object on space + parameter := &KibanaSpaceCopySavedObjectParameter{ + Spaces: []string{"test"}, + IncludeReferences: true, + Overwrite: false, + CreateNewCopies: true, + Objects: []KibanaSpaceObjectParameter{ + { + Type: "config", + ID: "8.5.0", + }, + }, + } + err = s.KibanaSpaces.CopySavedObjects(parameter, "") + assert.NoError(s.T(), err) + + // Delete space + err = s.KibanaSpaces.Delete(kibanaSpace.ID) + assert.NoError(s.T(), err) + kibanaSpace, err = s.KibanaSpaces.Get(kibanaSpace.ID) + assert.NoError(s.T(), err) + assert.Nil(s.T(), kibanaSpace) + +} diff --git a/libs/go-kibana-rest/kbapi/api.kibana_status.go b/libs/go-kibana-rest/kbapi/api.kibana_status.go new file mode 100644 index 000000000..c6ad95539 --- /dev/null +++ b/libs/go-kibana-rest/kbapi/api.kibana_status.go @@ -0,0 +1,43 @@ +package kbapi + +import ( + "encoding/json" + + "github.com/go-resty/resty/v2" + log "github.com/sirupsen/logrus" +) + +const ( + basePathKibanaStatus = "/api/status" // Base URL to access on Kibana status +) + +// KibanaStatus is the map of string that contain the API status +type KibanaStatus map[string]interface{} + +// KibanaStatusGet permit to get the current status of Kibana +type KibanaStatusGet func() (KibanaStatus, error) + +// newKibanaStatusGetFunc permit to get the kibana status and some usefull information +func newKibanaStatusGetFunc(c *resty.Client) KibanaStatusGet { + return func() (KibanaStatus, error) { + resp, err := c.R().Get(basePathKibanaStatus) + if err != nil { + return nil, err + } + log.Debug("Response: ", resp) + if resp.StatusCode() >= 300 { + if resp.StatusCode() == 404 { + return nil, nil + } + return nil, NewAPIError(resp.StatusCode(), resp.Status()) + } + kibanaStatus := make(KibanaStatus) + err = json.Unmarshal(resp.Body(), &kibanaStatus) + if err != nil { + return nil, err + } + log.Debug("KibanaStatus: ", kibanaStatus) + + return kibanaStatus, nil + } +} diff --git a/libs/go-kibana-rest/kbapi/api.kibana_status_test.go b/libs/go-kibana-rest/kbapi/api.kibana_status_test.go new file mode 100644 index 000000000..d67bb7cc9 --- /dev/null +++ b/libs/go-kibana-rest/kbapi/api.kibana_status_test.go @@ -0,0 +1,13 @@ +package kbapi + +import ( + "github.com/stretchr/testify/assert" +) + +func (s *KBAPITestSuite) TestKibanaStatus() { + + // List kibana space + kibanaStatus, err := s.API.KibanaStatus.Get() + assert.NoError(s.T(), err) + assert.NotEmpty(s.T(), kibanaStatus) +} diff --git a/libs/go-kibana-rest/kbapi/api_test.go b/libs/go-kibana-rest/kbapi/api_test.go new file mode 100644 index 000000000..6dbe3eba5 --- /dev/null +++ b/libs/go-kibana-rest/kbapi/api_test.go @@ -0,0 +1,82 @@ +package kbapi + +import ( + "fmt" + "os" + "testing" + "time" + + "github.com/go-resty/resty/v2" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/suite" + prefixed "github.com/x-cray/logrus-prefixed-formatter" +) + +type KBAPITestSuite struct { + suite.Suite + client *resty.Client + *API +} + +func (s *KBAPITestSuite) SetupSuite() { + + // Init logger + logrus.SetFormatter(new(prefixed.TextFormatter)) + logrus.SetLevel(logrus.DebugLevel) + + address := os.Getenv("KIBANA_URL") + username := os.Getenv("KIBANA_USERNAME") + password := os.Getenv("KIBANA_PASSWORD") + + if address == "" { + panic("You need to put kibana url on environment variable KIBANA_URL. If you need auth, you can use KIBANA_USERNAME and KIBANA_PASSWORD") + } + + restyClient := resty.New(). + SetBaseURL(address). + SetBasicAuth(username, password). + SetHeader("kbn-xsrf", "true"). + SetHeader("Content-Type", "application/json") + + s.client = restyClient + s.API = New(restyClient) + + // Wait kb is online + isOnline := false + nbTry := 0 + for isOnline == false { + _, err := s.API.KibanaSpaces.List() + if err == nil { + isOnline = true + } else { + time.Sleep(5 * time.Second) + if nbTry == 10 { + panic(fmt.Sprintf("We wait 50s that Kibana start: %s", err)) + } + nbTry++ + } + } + + // Create kibana space + space := &KibanaSpace{ + ID: "testacc", + Name: "testacc", + } + _, err := s.API.KibanaSpaces.Create(space) + if err != nil { + if err.(APIError).Code != 409 { + panic(err) + } + } + +} + +func (s *KBAPITestSuite) SetupTest() { + + // Do somethink before each test + +} + +func TestKBAPITestSuite(t *testing.T) { + suite.Run(t, new(KBAPITestSuite)) +} diff --git a/libs/go-kibana-rest/kbapi/doc.go b/libs/go-kibana-rest/kbapi/doc.go new file mode 100644 index 000000000..40e821754 --- /dev/null +++ b/libs/go-kibana-rest/kbapi/doc.go @@ -0,0 +1,4 @@ +/* +Package kbapi provides the GO API for Kibana +*/ +package kbapi diff --git a/libs/go-kibana-rest/kbapi/error.go b/libs/go-kibana-rest/kbapi/error.go new file mode 100644 index 000000000..1f918bbcd --- /dev/null +++ b/libs/go-kibana-rest/kbapi/error.go @@ -0,0 +1,24 @@ +package kbapi + +import ( + "fmt" +) + +// APIError is the error object +type APIError struct { + Code int + Message string +} + +// Error return error message +func (e APIError) Error() string { + return e.Message +} + +// NewAPIError create new API error with code and message +func NewAPIError(code int, message string, params ...interface{}) APIError { + return APIError{ + Code: code, + Message: fmt.Sprintf(message, params...), + } +} diff --git a/libs/go-kibana-rest/kbapi/error_test.go b/libs/go-kibana-rest/kbapi/error_test.go new file mode 100644 index 000000000..49852a8d4 --- /dev/null +++ b/libs/go-kibana-rest/kbapi/error_test.go @@ -0,0 +1,10 @@ +package kbapi + +import "github.com/stretchr/testify/assert" + +func (s *KBAPITestSuite) TestError() { + + err := NewAPIError(404, "test %s error", "plop") + assert.Equal(s.T(), 404, err.Code) + assert.Equal(s.T(), "test plop error", err.Error()) +} diff --git a/libs/go-kibana-rest/kibana.go b/libs/go-kibana-rest/kibana.go new file mode 100644 index 000000000..560820a4b --- /dev/null +++ b/libs/go-kibana-rest/kibana.go @@ -0,0 +1,63 @@ +package kibana + +import ( + "crypto/tls" + + "github.com/disaster37/go-kibana-rest/v8/kbapi" + "github.com/go-resty/resty/v2" +) + +// Config contain the value to access on Kibana API +type Config struct { + Address string + Username string + Password string + ApiKey string + DisableVerifySSL bool + CAs []string +} + +// Client contain the REST client and the API specification +type Client struct { + *kbapi.API + Client *resty.Client +} + +// NewDefaultClient init client with empty config +func NewDefaultClient() (*Client, error) { + return NewClient(Config{}) +} + +// NewClient init client with custom config +func NewClient(cfg Config) (*Client, error) { + if cfg.Address == "" { + cfg.Address = "http://localhost:5601" + } + + restyClient := resty.New(). + SetBaseURL(cfg.Address). + SetHeader("kbn-xsrf", "true"). + SetHeader("Content-Type", "application/json") + + if cfg.ApiKey != "" { + restyClient.SetAuthScheme("ApiKey").SetAuthToken(cfg.ApiKey) + } else { + restyClient.SetBasicAuth(cfg.Username, cfg.Password) + } + + for _, path := range cfg.CAs { + restyClient.SetRootCertificate(path) + } + + client := &Client{ + Client: restyClient, + API: kbapi.New(restyClient), + } + + if cfg.DisableVerifySSL { + client.Client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) + } + + return client, nil + +} diff --git a/libs/go-kibana-rest/kibana_test.go b/libs/go-kibana-rest/kibana_test.go new file mode 100644 index 000000000..5eed29a0c --- /dev/null +++ b/libs/go-kibana-rest/kibana_test.go @@ -0,0 +1,57 @@ +package kibana + +import ( + "testing" + + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + prefixed "github.com/x-cray/logrus-prefixed-formatter" +) + +type KBTestSuite struct { + suite.Suite +} + +func (s *KBTestSuite) SetupSuite() { + + // Init logger + logrus.SetFormatter(new(prefixed.TextFormatter)) + logrus.SetLevel(logrus.DebugLevel) + +} + +func (s *KBTestSuite) SetupTest() { + + // Do somethink before each test + +} + +func TestKBTestSuite(t *testing.T) { + suite.Run(t, new(KBTestSuite)) +} + +func (s *KBTestSuite) TestNewClient() { + + cfg := Config{ + Address: "http://127.0.0.1:5601", + Username: "elastic", + Password: "changeme", + DisableVerifySSL: true, + } + + client, err := NewClient(cfg) + + assert.NoError(s.T(), err) + assert.NotNil(s.T(), client) + +} + +func (s *KBTestSuite) TestNewDefaultClient() { + + client, err := NewDefaultClient() + + assert.NoError(s.T(), err) + assert.NotNil(s.T(), client) + +} diff --git a/provider/provider_test.go b/provider/provider_test.go index e778a88f4..fdb7a9313 100644 --- a/provider/provider_test.go +++ b/provider/provider_test.go @@ -59,20 +59,69 @@ func TestFleetConfiguration(t *testing.T) { } func TestKibanaConfiguration(t *testing.T) { - envConfig := config.NewFromEnv("acceptance-testing") - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ProtoV6ProviderFactories: acctest.Providers, - Steps: []resource.TestStep{ - { - Config: testKibanaConfiguration(envConfig), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrSet("elasticstack_kibana_space.acc_test", "name"), - ), + var envConfig config.Client + + testCases := []struct { + name string + tc func() resource.TestCase + pre func(t *testing.T) + post func(t *testing.T) + }{ + { + name: "with username and password", + pre: func(t *testing.T) { + envConfig = config.NewFromEnv("acceptance-testing") + }, + post: func(t *testing.T) {}, + tc: func() resource.TestCase { + return resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testKibanaConfiguration(envConfig), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("elasticstack_kibana_space.acc_test", "name"), + ), + }, + }, + } }, }, - }) + { + name: "with api key", + pre: func(t *testing.T) { + apiKey := os.Getenv("KIBANA_API_KEY") + t.Setenv("KIBANA_USERNAME", "") + t.Setenv("KIBANA_PASSWORD", "") + t.Setenv("KIBANA_API_KEY", apiKey) + envConfig = config.NewFromEnv("acceptance-testing") + }, + post: func(t *testing.T) {}, + tc: func() resource.TestCase { + return resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testKibanaApiKeyConfiguration(envConfig), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("elasticstack_kibana_space.acc_test", "name"), + ), + }, + }, + } + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tc.pre(t) + resource.Test(t, tc.tc()) + tc.post(t) + }) + + } } func testKibanaConfiguration(cfg config.Client) string { @@ -92,6 +141,22 @@ resource "elasticstack_kibana_space" "acc_test" { }`, cfg.Kibana.Address, cfg.Kibana.Username, cfg.Kibana.Password) } +func testKibanaApiKeyConfiguration(cfg config.Client) string { + return fmt.Sprintf(` +provider "elasticstack" { + elasticsearch {} + kibana { + endpoints = ["%s"] + api_key = "%s" + } +} + +resource "elasticstack_kibana_space" "acc_test" { + space_id = "acc_test_space" + name = "Acceptance Test Space" +}`, cfg.Kibana.Address, cfg.Kibana.ApiKey) +} + func testFleetConfiguration(cfg config.Client) string { return fmt.Sprintf(` provider "elasticstack" { diff --git a/templates/index.md.tmpl b/templates/index.md.tmpl index 6eb749862..54a5ada79 100644 --- a/templates/index.md.tmpl +++ b/templates/index.md.tmpl @@ -59,6 +59,7 @@ Kibana resources will re-use any Elasticsearch credentials specified, these may - `KIBANA_USERNAME` - The username to use for Kibana authentication - `KIBANA_PASSWORD` - The password to use for Kibana authentication - `KIBANA_ENDPOINT` - The Kibana host to connect to +- `KIBANA_API_KEY` - An Elasticsearch API key to use instead of `KIBANA_USERNAME` and `KIBANA_PASSWORD` Fleet resources will re-use any Kibana or Elasticsearch credentials specified, these may be overridden with the following variables: - `FLEET_USERNAME` - The username to use for Kibana authentication diff --git a/templates/resources/kibana_alerting_rule.md.tmpl b/templates/resources/kibana_alerting_rule.md.tmpl index 03d53510c..b259e8130 100644 --- a/templates/resources/kibana_alerting_rule.md.tmpl +++ b/templates/resources/kibana_alerting_rule.md.tmpl @@ -14,6 +14,13 @@ Creates or updates a Kibana alerting rule. See https://www.elastic.co/guide/en/k {{ tffile "examples/resources/elasticstack_kibana_alerting_rule/resource.tf" }} + +**NOTE:** `api_key` authentication is only supported for alerting rule resources from version 8.8.0 of the Elastic stack. Using an `api_key` will result in an error message like: + +``` +Could not create API key - Unsupported scheme "ApiKey" for granting API Key +``` + {{ .SchemaMarkdown | trimspace }} ## Import