github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/pkg/opensearch_dashboards.go (about) 1 // Copyright (c) 2022, 2023, 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 pkg 5 6 import ( 7 "bytes" 8 "context" 9 "encoding/json" 10 "fmt" 11 "github.com/hashicorp/go-retryablehttp" 12 "github.com/verrazzano/verrazzano/pkg/k8sutil" 13 "net/http" 14 "reflect" 15 "strings" 16 "text/template" 17 18 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 19 ) 20 21 type IndexPattern struct { 22 Name string 23 } 24 25 // ListIndexPatterns gets the configured index patterns in OpenSearch Dashboards 26 func ListIndexPatterns(kubeconfigPath string) []string { 27 list := []string{} 28 url := fmt.Sprintf("%s/api/saved_objects/_find?type=index-pattern&fields=title", getOpenSearchDashboardsURL(kubeconfigPath)) 29 username, password, err := getOpenSearchDashboardsUsernamePassword(kubeconfigPath) 30 if err != nil { 31 return list 32 } 33 resp, err := getOpenSearchDashboardsWithBasicAuth(url, "", username, password, kubeconfigPath) 34 if err != nil { 35 Log(Error, fmt.Sprintf("Error getting Opensearch indices: url=%s, error=%v", url, err)) 36 return list 37 } 38 if resp.StatusCode != http.StatusOK { 39 Log(Error, fmt.Sprintf("Error retrieving Opensearch indices: url=%s, status=%d", url, resp.StatusCode)) 40 return list 41 } 42 Log(Debug, fmt.Sprintf("indices: %s", resp.Body)) 43 var responseMap map[string]interface{} 44 if err := json.Unmarshal(resp.Body, &responseMap); err != nil { 45 Log(Error, fmt.Sprintf("OpenSearch Dashboards: Error unmarshalling index patterns response body: %v", err)) 46 } 47 if responseMap["saved_objects"] != nil { 48 savedObjects := reflect.ValueOf(responseMap["saved_objects"]) 49 for i := 0; i < savedObjects.Len(); i++ { 50 Log(Debug, fmt.Sprintf("OpenSearch Dashboards: Index pattern details: %v", savedObjects.Index(i))) 51 savedObject := savedObjects.Index(i).Interface().(map[string]interface{}) 52 attributes := savedObject["attributes"].(map[string]interface{}) 53 if attributes["title"].(string) != "" { 54 list = append(list, attributes["title"].(string)) 55 } 56 } 57 } 58 return list 59 } 60 61 // LogIndexPatternFound confirms a named index pattern can be found in OpenSearch Dashboards in the cluster specified in the environment 62 func LogIndexPatternFound(indexName string) bool { 63 kubeconfigPath, err := k8sutil.GetKubeConfigLocation() 64 if err != nil { 65 Log(Error, fmt.Sprintf("Error getting kubeconfig, error: %v", err)) 66 return false 67 } 68 69 return LogIndexFoundInCluster(indexName, kubeconfigPath) 70 } 71 72 // LogIndexFoundInCluster confirms a named index pattern can be found in OpenSearch Dashboards on the given cluster 73 func LogIndexPatternFoundInCluster(indexName, kubeconfigPath string) bool { 74 Log(Info, fmt.Sprintf("Looking for log index %s in cluster with kubeconfig %s", indexName, kubeconfigPath)) 75 for _, name := range ListIndexPatterns(kubeconfigPath) { 76 if name == indexName { 77 return true 78 } 79 } 80 Log(Error, fmt.Sprintf("Expected to find log index %s", indexName)) 81 return false 82 } 83 84 // CreateIndexPattern creates the specified index pattern in OpenSearch Dashboards 85 func CreateIndexPattern(pattern string) map[string]interface{} { 86 template, err := template.New("indexPatternTemplate").Parse(indexPatternTemplate) 87 if err != nil { 88 Log(Error, fmt.Sprintf("Error: %v", err)) 89 } 90 var buffer bytes.Buffer 91 err = template.Execute(&buffer, IndexPattern{Name: pattern}) 92 if err != nil { 93 Log(Error, fmt.Sprintf("Error: %v", err)) 94 } 95 var result map[string]interface{} 96 resp, err := PostOpensearchDashboards("api/saved_objects/index-pattern", buffer.String(), "osd-xsrf:true", "kbn-xsrf: true") 97 if err != nil { 98 Log(Error, fmt.Sprintf("Error creating index patterns in OpenSearchDashboards: error=%s", err)) 99 return result 100 } 101 if resp.StatusCode != http.StatusOK { 102 Log(Error, fmt.Sprintf("Error creating index patterns in OpenSearchDashboards: status=%d", resp.StatusCode)) 103 return result 104 } 105 json.Unmarshal(resp.Body, &result) 106 return result 107 } 108 109 // PostOpensearchDashboards POST the request entity body to Opensearch API path 110 // The provided path is appended to the OpenSearchDashboards base URL 111 func PostOpensearchDashboards(path string, body string, additionalHeaders ...string) (*HTTPResponse, error) { 112 kubeconfigPath, err := k8sutil.GetKubeConfigLocation() 113 if err != nil { 114 Log(Error, fmt.Sprintf("Error getting kubeconfig: %v", err)) 115 return nil, err 116 } 117 url := fmt.Sprintf("%s/%s", getOpenSearchDashboardsURL(kubeconfigPath), path) 118 configPath, err := k8sutil.GetKubeConfigLocation() 119 if err != nil { 120 Log(Error, fmt.Sprintf("Error retrieving kubeconfig, error=%v", err)) 121 return nil, err 122 } 123 username, password, err := getOpenSearchDashboardsUsernamePassword(configPath) 124 if err != nil { 125 return nil, err 126 } 127 Log(Debug, fmt.Sprintf("REST API path: %v \nQuery: \n%v", url, body)) 128 resp, err := postOpenSearchDashboardsWithBasicAuth(url, body, username, password, configPath, additionalHeaders...) 129 return resp, err 130 } 131 132 // getOpenSearchDashboardsURL gets the OpenSearch Dashboards Ingress host in the given cluster 133 func getOpenSearchDashboardsURL(kubeconfigPath string) string { 134 clientset, err := GetKubernetesClientsetForCluster(kubeconfigPath) 135 isMinversion150, _ := IsVerrazzanoMinVersion("1.5.0", kubeconfigPath) 136 isMinversion170, _ := IsVerrazzanoMinVersion("1.7.0", kubeconfigPath) 137 138 if err != nil { 139 Log(Error, fmt.Sprintf("Failed to get clientset for cluster %v", err)) 140 return "" 141 } 142 ingressList, _ := clientset.NetworkingV1().Ingresses("verrazzano-system").List(context.TODO(), metav1.ListOptions{}) 143 for _, ingress := range ingressList.Items { 144 if (isMinversion170 && ingress.Name == "opensearch-dashboards") || (isMinversion150 && ingress.Name == "vmi-system-osd") || (!isMinversion150 && ingress.Name == "vmi-system-kibana") { 145 Log(Info, fmt.Sprintf("Found Kibana/OpenSearch Dashboards Ingress %v, host %s", ingress.Name, ingress.Spec.Rules[0].Host)) 146 return fmt.Sprintf("https://%s", ingress.Spec.Rules[0].Host) 147 } 148 } 149 return "" 150 } 151 152 // getOpenSearchDashboardsUsernamePassword gets the Verrazzano user name and password 153 func getOpenSearchDashboardsUsernamePassword(kubeconfigPath string) (username, password string, err error) { 154 password, err = GetVerrazzanoPasswordInCluster(kubeconfigPath) 155 if err != nil { 156 return "", "", err 157 } 158 return "verrazzano", password, err 159 } 160 161 // getOpenSearchDashboardsWithBasicAuth access OpenSearch Dashboards with GET using basic auth, using a given kubeconfig 162 func getOpenSearchDashboardsWithBasicAuth(url string, hostHeader string, username string, password string, kubeconfigPath string) (*HTTPResponse, error) { 163 retryableClient, err := getOpenSearchDashboardsClient(kubeconfigPath) 164 if err != nil { 165 return nil, err 166 } 167 return doReq(url, "GET", "", hostHeader, username, password, nil, retryableClient) 168 } 169 170 // postOpenSearchDashboardsWithBasicAuth retries POST to OpenSearch Dashboards using basic auth 171 func postOpenSearchDashboardsWithBasicAuth(url, body, username, password, kubeconfigPath string, additionalHeaders ...string) (*HTTPResponse, error) { 172 retryableClient, err := getOpenSearchDashboardsClient(kubeconfigPath) 173 if err != nil { 174 return nil, err 175 } 176 return doReq(url, "POST", "application/json", "", username, password, strings.NewReader(body), retryableClient, additionalHeaders...) 177 } 178 179 func getOpenSearchDashboardsClient(kubeconfigPath string) (*retryablehttp.Client, error) { 180 var retryableClient *retryablehttp.Client 181 var err error 182 retryableClient, err = GetVerrazzanoHTTPClient(kubeconfigPath) 183 if err != nil { 184 return nil, err 185 } 186 return retryableClient, nil 187 } 188 189 const indexPatternTemplate = `{ 190 "attributes": { 191 "title": "{{.Name}}" 192 } 193 } 194 `