Tuesday, February 7, 2023

Export and Import Kibana Dashboards using Go




In this post we will review the steps to export and import kibana dashboards using Go code.

To handle these operation we will use a kibana config struct:


const configFolder = "config"

type KibanaConfig struct {
kibanaUrl string
elasticPassword string
}

func ProduceKibanaConfig(
kibanaUrl string,
elasticPassword string,
) *KibanaConfig {
return &KibanaConfig{
kibanaUrl: kibanaUrl,
elasticPassword: elasticPassword,
}
}


To export the dashboards, we send a request to list all dashboards, and then export them one by one.


func (k *KibanaConfig) ExportConfig() {
data := k.sendKibanaApiGet("/api/saved_objects/_find?type=dashboard")
var objects savedObjects
err := json.Unmarshal([]byte(data), &objects)
if err != nil {
panic(err)
}

for _, object := range objects.SavedObjects {
k.exportDashboard(object)
}
}


We keep methods to send GET and POST requests to kibana. The actual implementation is using Go net/http package, and is out of scope for this post.


func (k *KibanaConfig) sendKibanaApiGet(urlSuffix string) string {
webClient := web.CreateClient(0)

fullUrl := k.kibanaUrl + urlSuffix

headers := k.getHeaders()

var result string
webClient.Get(fullUrl, headers, &result)
return result
}

func (k *KibanaConfig) sendKibanaApiPost(
urlSuffix string,
headers map[string]string,
body interface{},
) string {
webClient := web.CreateClient(0)

fullUrl := k.kibanaUrl + urlSuffix

var result string
webClient.PostWithHeaders(fullUrl, body, headers, &result)
return result
}


We use the following structure to communicate with kibana:


type savedObjectAttributes struct {
Title string
}

type savedObject struct {
Id string
Attributes *savedObjectAttributes
}

type savedObjects struct {
SavedObjects []*savedObject `json:"saved_objects"`
}


For each of the dashboards, we get its details, and save it to a file.


func (k *KibanaConfig) exportDashboard(object *savedObject) {
title := object.Attributes.Title

body := exportBody{
Objects: []*exportObject{
{
Id: object.Id,
Type: "dashboard",
},
},
IncludeReferencesDeep: true,
}

headers := k.getHeaders()
data := k.sendKibanaApiPost("/api/saved_objects/_export", headers, &body)

outputPath := fmt.Sprintf("%v/%v.ndjson",
configFolder,
title,
)

err := os.WriteFile(outputPath, []byte(data), 0644)
if err != nil {
panic(err)
}
}


To import the dashboards to kibana we use the following method:


func listFolder(
folderPath string,
returnOnlyNames bool,
) []string {
files, err := os.ReadDir(folderPath)
if err != nil {
panic(err)
}

result := make([]string, 0)
for _, file := range files {
if returnOnlyNames {
result = append(result, file.Name())
} else {
result = append(result, folderPath+"/"+file.Name())
}
}
return result
}

func (k *KibanaConfig) ImportConfig() {
for _, filePath := range listFolder(configFolder, false) {
k.importDashboard(filePath)
}
}


The actual import uses form data to send the file.


func (k *KibanaConfig) importDashboard(filePath string) {
file, _ := os.Open(filePath)
defer func() {
err := file.Close()
if err != nil {
panic(err)
}
}()

body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, _ := writer.CreateFormFile("file", filepath.Base(file.Name()))
_, err := io.Copy(part, file)
if err != nil {
panic(err)
}
err = writer.Close()
if err != nil {
panic(err)
}

headers := k.getHeaders()
headers["Content-Type"] = writer.FormDataContentType()
k.sendKibanaApiPost("/api/saved_objects/_import?overwrite=true", headers, body)
}



Final Note


Once all is implemented, the usage is simple. To export the configuration, we use:

config := kibanaconfig.ProduceKibanaConfig(request.KibanaUrl, request.ElasticPassword)
config.ExportConfig()

And to import the configuration we use the following:

config := kibanaconfig.ProduceKibanaConfig(request.KibanaUrl, request.ElasticPassword)
config.ImportConfig()


No comments:

Post a Comment