-
Notifications
You must be signed in to change notification settings - Fork 119
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
6 changed files
with
267 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
// or more contributor license agreements. Licensed under the Elastic License; | ||
// you may not use this file except in compliance with the Elastic License. | ||
|
||
package cmd | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"net/url" | ||
"strings" | ||
|
||
"github.com/spf13/cobra" | ||
|
||
"github.com/elastic/elastic-package/internal/cobraext" | ||
"github.com/elastic/elastic-package/internal/common" | ||
"github.com/elastic/elastic-package/internal/install" | ||
"github.com/elastic/elastic-package/internal/kibana" | ||
"github.com/elastic/elastic-package/internal/stack" | ||
) | ||
|
||
const editLongDescription = `Use this command to edit assets relevant for the package, e.g. Kibana dashboards.` | ||
|
||
const editDashboardsLongDescription = `Use this command to make dashboards editable. | ||
Pass a comma-separated list of dashboard ids with -d or use the interactive prompt to make managed dashboards editable in Kibana.` | ||
|
||
func setupEditCommand() *cobraext.Command { | ||
editDashboardsCmd := &cobra.Command{ | ||
Use: "dashboards", | ||
Short: "Make dashboards editable in Kibana", | ||
Long: editDashboardsLongDescription, | ||
Args: cobra.NoArgs, | ||
RunE: editDashboardsCmd, | ||
} | ||
editDashboardsCmd.Flags().StringSliceP(cobraext.DashboardIDsFlagName, "d", nil, cobraext.DashboardIDsFlagDescription) | ||
editDashboardsCmd.Flags().Bool(cobraext.TLSSkipVerifyFlagName, false, cobraext.TLSSkipVerifyFlagDescription) | ||
editDashboardsCmd.Flags().Bool(cobraext.AllowSnapshotFlagName, false, cobraext.AllowSnapshotDescription) | ||
|
||
cmd := &cobra.Command{ | ||
Use: "edit", | ||
Short: "Edit package assets", | ||
Long: editLongDescription, | ||
} | ||
cmd.AddCommand(editDashboardsCmd) | ||
cmd.PersistentFlags().StringP(cobraext.ProfileFlagName, "p", "", fmt.Sprintf(cobraext.ProfileFlagDescription, install.ProfileNameEnvVar)) | ||
|
||
return cobraext.NewCommand(cmd, cobraext.ContextPackage) | ||
} | ||
|
||
func editDashboardsCmd(cmd *cobra.Command, args []string) error { | ||
cmd.Println("Make Kibana dashboards editable") | ||
|
||
dashboardIDs, err := cmd.Flags().GetStringSlice(cobraext.DashboardIDsFlagName) | ||
if err != nil { | ||
return cobraext.FlagParsingError(err, cobraext.DashboardIDsFlagName) | ||
} | ||
|
||
common.TrimStringSlice(dashboardIDs) | ||
|
||
var opts []kibana.ClientOption | ||
tlsSkipVerify, _ := cmd.Flags().GetBool(cobraext.TLSSkipVerifyFlagName) | ||
if tlsSkipVerify { | ||
opts = append(opts, kibana.TLSSkipVerify()) | ||
} | ||
|
||
allowSnapshot, _ := cmd.Flags().GetBool(cobraext.AllowSnapshotFlagName) | ||
if err != nil { | ||
return cobraext.FlagParsingError(err, cobraext.AllowSnapshotFlagName) | ||
} | ||
|
||
profile, err := cobraext.GetProfileFlag(cmd) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
kibanaClient, err := stack.NewKibanaClientFromProfile(profile, opts...) | ||
if err != nil { | ||
return fmt.Errorf("can't create Kibana client: %w", err) | ||
} | ||
|
||
kibanaVersion, err := kibanaClient.Version() | ||
if err != nil { | ||
return fmt.Errorf("can't get Kibana status information: %w", err) | ||
} | ||
|
||
if kibanaVersion.IsSnapshot() { | ||
message := fmt.Sprintf("editing dashboards from a SNAPSHOT version of Kibana (%s) is discouraged. It could lead to invalid dashboards (for example if they use features that are reverted or modified before the final release)", kibanaVersion.Version()) | ||
if !allowSnapshot { | ||
return fmt.Errorf("%s. --%s flag can be used to ignore this error", message, cobraext.AllowSnapshotFlagName) | ||
} | ||
fmt.Printf("Warning: %s\n", message) | ||
} | ||
|
||
if len(dashboardIDs) == 0 { | ||
dashboardIDs, err = promptDashboardIDs(kibanaClient) | ||
if err != nil { | ||
return fmt.Errorf("prompt for dashboard selection failed: %w", err) | ||
} | ||
|
||
if len(dashboardIDs) == 0 { | ||
fmt.Println("No dashboards were found in Kibana.") | ||
return nil | ||
} | ||
} | ||
|
||
updatedDashboardIDs := make([]string, 0, len(dashboardIDs)) | ||
failedDashboardUpdates := make(map[string]error, len(dashboardIDs)) | ||
for _, dashboardID := range dashboardIDs { | ||
err = kibanaClient.SetManagedSavedObject("dashboard", dashboardID, false) | ||
if err != nil { | ||
failedDashboardUpdates[dashboardID] = err | ||
} else { | ||
updatedDashboardIDs = append(updatedDashboardIDs, dashboardID) | ||
} | ||
} | ||
|
||
if len(updatedDashboardIDs) > 0 { | ||
urls, err := dashboardURLs(*kibanaClient, updatedDashboardIDs) | ||
if err != nil { | ||
cmd.Println(fmt.Sprintf("\nFailed to retrieve dashboard URLS: %s", err.Error())) | ||
cmd.Println(fmt.Sprintf("The following dashboards are now editable in Kibana:\n%s", strings.Join(updatedDashboardIDs, "\n"))) | ||
} else { | ||
cmd.Println(fmt.Sprintf("\nThe following dashboards are now editable in Kibana:%s\n\nRemember to export modified dashboards with elastic-package export dashboards", urls)) | ||
} | ||
} | ||
|
||
if len(failedDashboardUpdates) > 0 { | ||
var combinedErr error | ||
for _, err := range failedDashboardUpdates { | ||
combinedErr = errors.Join(combinedErr, err) | ||
} | ||
fmt.Println("") | ||
return fmt.Errorf("failed to make one or more dashboards editable: %s", combinedErr.Error()) | ||
} | ||
|
||
fmt.Println("\nDone") | ||
return nil | ||
} | ||
|
||
func dashboardURLs(kibanaClient kibana.Client, dashboardIDs []string) (string, error) { | ||
kibanaHost := kibanaClient.Address() | ||
kibanaURL, err := url.Parse(kibanaHost) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to retrieve Kibana URL: %w", err) | ||
} | ||
var urls strings.Builder | ||
for _, dashboardID := range dashboardIDs { | ||
dashboardURL := *kibanaURL | ||
dashboardURL.Path = "app/dashboards" | ||
dashboardURL.Fragment = "/view/" + dashboardID | ||
fmt.Fprintf(&urls, "\n%s", dashboardURL.String()) | ||
} | ||
return urls.String(), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# HOWTO: Make dashboards editable in Kibana | ||
|
||
## Introduction | ||
|
||
As of 8.11, managed assets, including dashboards, are read-only in Kibana. This change was introduced to prevent users from losing changes on package upgrades. Integrations authors, however, need the ability to edit assets in order to adopt new features. | ||
|
||
## Making a dashboard editable | ||
|
||
Dashboards can be made editable in Kibana by using the `elastic-package edit dashboards` command. This command can either be run interactively, allowing manual selection of dashboards, or be passed a comma-separated list of dashboard ids. | ||
|
||
NB: after modifying dashboards, these need to be exported using `elastic-package export dashboards`. | ||
|
||
### Using the interactive dashboard selection prompt | ||
|
||
Run the following command: | ||
``` | ||
elastic-package edit dashboards | ||
``` | ||
|
||
Use the interactive dashboard selection prompt to select the dashboard(s) that should be made editable. | ||
|
||
### Using a comma-separated list of dashboard ids | ||
|
||
Pass the list with the `-d` flag: | ||
``` | ||
elastic-package edit dashboards -d 123,456,789 | ||
``` | ||
|
||
Each dashboard id will be processed and the outcome of the updates will be listed in the command's final output. | ||
|
||
### Command output | ||
|
||
The final output will provide the outcome (success or failure) of the update for each dashboard. | ||
|
||
For example, assuming the following command: | ||
``` | ||
elastic-package edit dashboards -d 123,456,789 | ||
``` | ||
|
||
#### Success | ||
|
||
Assuming '123', '456' and '789' are valid dashboard ids and all three updates succeed, the output will be successful and report the URLs of the updated dashboards: | ||
``` | ||
Make Kibana dashboards editable | ||
The following dashboards are now editable in Kibana: | ||
https://<kibanaURL>/app/dashboards#/view/123 | ||
https://<kibanaURL>/app/dashboards#/view/456 | ||
https://<kibanaURL>/app/dashboards#/view/789 | ||
Done | ||
``` | ||
|
||
#### Partial failure | ||
|
||
Assuming that `456` is an invalid dashboard id and that the update is successful for ids `123` and `789`, the output will report the URLs of the updated dashboards as well as an error listing the failures: | ||
``` | ||
Make Kibana dashboards editable | ||
The following dashboards are now editable in Kibana: | ||
https://<kibanaURL>/app/dashboards#/view/123 | ||
https://<kibanaURL>/app/dashboards#/view/789 | ||
Error: failed to make one or more dashboards editable: failed to export dashboard 456: could not export saved objects; API status code = 400; response body = {"statusCode":400,"error":"Bad Request","message":"Error fetching objects to export","attributes":{"objects":[{"id":"456","type":"dashboard","error":{"statusCode":404,"error":"Not Found","message":"Saved object [dashboard/456] not found"}}]}} | ||
``` | ||
|
||
### Optional flags | ||
|
||
* `allow-snapshot`: to allow exporting dashboards from a Elastic stack SNAPSHOT version | ||
* `tls-skip-verify`: to skip TLS verify |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters