github.com/verrazzano/verrazzano-monitoring-operator@v0.0.30/pkg/opensearch_dashboards/opensearch_dashboards_upgrade.go (about)

     1  // Copyright (C) 2022, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package dashboards
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/config"
    10  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/resources"
    11  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/util/logs/vzlog"
    12  	"io/ioutil"
    13  	"net/http"
    14  	"regexp"
    15  	"strings"
    16  )
    17  
    18  const updatePatternPayload = `{"attributes":{"title":"%s"}}`
    19  
    20  type (
    21  	IndexPatterns struct {
    22  		Total        int           `json:"total"`
    23  		Page         int           `json:"page"`
    24  		SavedObjects []SavedObject `json:"saved_objects,omitempty"`
    25  	}
    26  
    27  	SavedObject struct {
    28  		ID         string `json:"id"`
    29  		Attributes `json:"attributes"`
    30  	}
    31  
    32  	Attributes struct {
    33  		Title string `json:"title"`
    34  	}
    35  )
    36  
    37  func (od *OSDashboardsClient) updatePatternsInternal(log vzlog.VerrazzanoLogger, dashboardsEndPoint string) error {
    38  	// Get index patterns configured in OpenSearch Dashboards
    39  	savedObjects, err := od.getPatterns(dashboardsEndPoint, 100)
    40  	if err != nil {
    41  		return err
    42  	}
    43  	for _, savedObject := range savedObjects {
    44  		updatedPattern := constructUpdatedPattern(savedObject.Title)
    45  		if updatedPattern == "" || (savedObject.Title == updatedPattern) {
    46  			continue
    47  		}
    48  		// Invoke update index pattern API
    49  		err = od.executeUpdate(log, dashboardsEndPoint, savedObject.ID, savedObject.Title, updatedPattern)
    50  		if err != nil {
    51  			return fmt.Errorf("failed to updated index pattern %s: %v", savedObject.Title, err)
    52  		}
    53  	}
    54  	return nil
    55  }
    56  
    57  func (od *OSDashboardsClient) getPatterns(dashboardsEndPoint string, perPage int) ([]SavedObject, error) {
    58  	var savedObjects []SavedObject
    59  	currentPage := 1
    60  
    61  	// Index Pattern is a paginated response type, so we need to page out all data
    62  	for {
    63  		url := fmt.Sprintf("%s/api/saved_objects/_find?type=index-pattern&fields=title&per_page=%d&page=%d", dashboardsEndPoint, perPage, currentPage)
    64  		req, err := http.NewRequest("GET", url, nil)
    65  		if err != nil {
    66  			return nil, err
    67  		}
    68  		resp, err := od.DoHTTP(req)
    69  		if err != nil {
    70  			return nil, err
    71  		}
    72  		defer resp.Body.Close()
    73  		if resp.StatusCode != http.StatusOK {
    74  			return nil, fmt.Errorf("got code %d when querying index patterns", resp.StatusCode)
    75  		}
    76  		indexPatterns := &IndexPatterns{}
    77  		if err := json.NewDecoder(resp.Body).Decode(indexPatterns); err != nil {
    78  			return nil, fmt.Errorf("failed to decode index pattern response body: %v", err)
    79  		}
    80  		currentPage++
    81  		savedObjects = append(savedObjects, indexPatterns.SavedObjects...)
    82  		// paginate responses until we have all the index patterns
    83  		if len(savedObjects) >= indexPatterns.Total {
    84  			break
    85  		}
    86  	}
    87  
    88  	return savedObjects, nil
    89  }
    90  
    91  func (od *OSDashboardsClient) executeUpdate(log vzlog.VerrazzanoLogger, dashboardsEndPoint string,
    92  	id string, originalPattern string, updatedPattern string) error {
    93  	payload := createIndexPatternPayload(updatedPattern)
    94  	log.Infof("Replacing index pattern %s with %s in OpenSearch Dashboards", originalPattern, updatedPattern)
    95  	updatedPatternURL := fmt.Sprintf("%s/api/saved_objects/index-pattern/%s", dashboardsEndPoint, id)
    96  	log.Debugf("Executing update saved object API %s", updatedPatternURL)
    97  	req, err := http.NewRequest("PUT", updatedPatternURL, strings.NewReader(payload))
    98  	if err != nil {
    99  		return err
   100  	}
   101  	req.Header.Add("Content-Type", "application/json")
   102  	req.Header.Add("osd-xsrf", "true")
   103  	resp, err := od.DoHTTP(req)
   104  	if err != nil {
   105  		return fmt.Errorf("failed to get index patterns from OpenSearch dashboards: %v", err)
   106  	}
   107  	if resp.StatusCode != http.StatusOK {
   108  		return fmt.Errorf("got status code %d when getting index patterns", resp.StatusCode)
   109  	}
   110  
   111  	responseBody, _ := ioutil.ReadAll(resp.Body)
   112  	log.Debugf("Response from OpenSearch Dashboards update index API: %s", responseBody)
   113  	return nil
   114  }
   115  
   116  func createIndexPatternPayload(indexPattern string) string {
   117  	return fmt.Sprintf(updatePatternPayload, indexPattern)
   118  }
   119  
   120  /*
   121   * constructUpdatedPattern constructs the updated pattern as follows:
   122   * - Update index patterns matching old system indices to match data stream verrazzano-system
   123   * - Update index patterns matching old application indices verrazzano-namespace-<application namespace>
   124   *   to match data stream verrazzano-application-<application namespace>
   125   */
   126  func constructUpdatedPattern(originalPattern string) string {
   127  	var updatedPattern []string
   128  	patternList := strings.Split(originalPattern, ",")
   129  	for _, eachPattern := range patternList {
   130  		if strings.HasPrefix(eachPattern, "verrazzano-") && eachPattern != "verrazzano-*" {
   131  			// To match the exact pattern, add ^ in the beginning and $ in the end
   132  			regexpString := resources.ConvertToRegexp(eachPattern)
   133  			systemIndexMatch := isSystemIndexMatch(regexpString)
   134  			if systemIndexMatch {
   135  				updatedPattern = append(updatedPattern, config.DataStreamName())
   136  			}
   137  			isNamespaceIndexMatch, _ := regexp.MatchString(regexpString, "verrazzano-namespace-")
   138  			if isNamespaceIndexMatch {
   139  				updatedPattern = append(updatedPattern, "verrazzano-application-*")
   140  			} else if strings.HasPrefix(eachPattern, "verrazzano-namespace-") {
   141  				// If the pattern matches system index and no * present in the pattern, then it is considered as only
   142  				// system index
   143  				if systemIndexMatch && !strings.Contains(eachPattern, "*") {
   144  					continue
   145  				}
   146  				updatedPattern = append(updatedPattern, strings.Replace(eachPattern, "verrazzano-namespace-", "verrazzano-application-", 1))
   147  			}
   148  		} else {
   149  			updatedPattern = append(updatedPattern, eachPattern)
   150  		}
   151  	}
   152  	return strings.Join(updatedPattern, ",")
   153  }
   154  
   155  func isSystemIndexMatch(pattern string) bool {
   156  	logStashIndex, _ := regexp.MatchString(pattern, "verrazzano-logstash-")
   157  	systemJournalIndex, _ := regexp.MatchString(pattern, "verrazzano-systemd-journal")
   158  	if logStashIndex || systemJournalIndex {
   159  		return true
   160  	}
   161  	for _, namespace := range config.SystemNamespaces() {
   162  		systemIndex, _ := regexp.MatchString(pattern, "verrazzano-namespace-"+namespace)
   163  		if systemIndex {
   164  			return true
   165  		}
   166  	}
   167  	return false
   168  }