github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/pkg/elasticsearch.go (about) 1 // Copyright (c) 2021, 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 "html/template" 12 "net/http" 13 url2 "net/url" 14 "os" 15 "regexp" 16 "strconv" 17 "strings" 18 "time" 19 20 "github.com/onsi/gomega" 21 vmov1 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/apis/vmcontroller/v1" 22 vzapi "github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1beta1" 23 24 "github.com/hashicorp/go-retryablehttp" 25 "github.com/onsi/ginkgo/v2" 26 "github.com/verrazzano/verrazzano/pkg/k8sutil" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 ) 29 30 const ( 31 // ISO8601Layout defines the timestamp format 32 ISO8601Layout = "2006-01-02T15:04:05.999999999-07:00" 33 opensearchIndexManagement = "opensearch-index-management" 34 opensearchJobScheduler = "opensearch-job-scheduler" 35 opensearchPrometheusExporter = "prometheus-exporter" 36 opensearchAlerting = "opensearch-alerting" 37 ) 38 39 // GetOpenSearchSystemIndex in Verrazzano 1.3.0, indices in the verrazzano-system namespace have been migrated 40 // to the verrazzano-system data stream 41 func GetOpenSearchSystemIndex(name string) (string, error) { 42 return GetOpenSearchSystemIndexWithKC(name, "") 43 } 44 45 // GetOpenSearchSystemIndexWithKC is the same as GetOpenSearchSystemIndex but the kubeconfig may be specified for MC tests 46 func GetOpenSearchSystemIndexWithKC(name, kubeconfigPath string) (string, error) { 47 usingDataStreams, err := isUsingDataStreams(kubeconfigPath) 48 if err != nil { 49 return "", err 50 } 51 if usingDataStreams { 52 return "verrazzano-system", nil 53 } 54 if name == "systemd-journal" { 55 return "verrazzano-systemd-journal", nil 56 } 57 return "verrazzano-namespace-" + name, nil 58 } 59 60 // Retention/Rollover policy names in ISM plugin 61 const ( 62 SystemLogIsmPolicyName = "verrazzano-system" 63 ApplicationLogIsmPolicyName = "verrazzano-application" 64 ) 65 66 // Error logging formats 67 const ( 68 queryErrorFormat = "Error retrieving Opensearch query results: url=%s, error=%s" 69 queryStatusFormat = "Error retrieving Opensearch query results: url=%s, status=%d" 70 kubeconfigErrorFormat = "Error getting kubeconfig: %v" 71 ) 72 73 // URL formats 74 const ( 75 getDataStreamURLFormat = "%s/_data_stream/" 76 listDataStreamURLFormat = "%s/_data_stream" 77 deleteDataStreamURLFormat = "%s/_data_stream/%s" 78 ) 79 80 // Default values for Retention period and Rollover period 81 var ( 82 DefaultRetentionPeriod = "7d" 83 DefaultRolloverPeriod = "1d" 84 ) 85 86 type PolicyList struct { 87 Policies []InlinePolicy `json:"policies"` 88 TotalPolicies int `json:"total_policies"` 89 } 90 91 type InlinePolicy struct { 92 ID *string `json:"_id,omitempty"` 93 PrimaryTerm *int `json:"_primary_term,omitempty"` 94 SequenceNumber *int `json:"_seq_no,omitempty"` 95 Status *int `json:"status,omitempty"` 96 Policy ISMPolicy `json:"policy"` 97 } 98 99 // ISMPolicy definition 100 type ISMPolicy struct { 101 PolicyID string `json:"policy_id"` 102 Description string `json:"description"` 103 LastUpdatedTime int64 `json:"last_updated_time"` 104 SchemaVersion int `json:"schema_version"` 105 DefaultState string `json:"default_state"` 106 States []State `json:"states"` 107 IsmTemplate IsmTemplate `json:"ism_template"` 108 } 109 110 // State defined in ISM policy 111 type State struct { 112 Name string `json:"name"` 113 Actions []Action `json:"actions"` 114 Transitions []Transition `json:"transitions"` 115 } 116 117 // Rollover or Delete action defined in ISM policy 118 type Action struct { 119 Rollover struct { 120 MinIndexAge string `json:"min_index_age"` 121 } `json:"rollover,omitempty"` 122 Delete struct { 123 MinIndexAge string `json:"min_index_age"` 124 } `json:"delete,omitempty"` 125 } 126 127 // Transition defined in ISM policy 128 type Transition struct { 129 StateName string `json:"state_name"` 130 Conditions map[string]string `json:"conditions"` 131 } 132 133 // IsmTemplate defined in ISM policy 134 type IsmTemplate []struct { 135 IndexPatterns []string `json:"index_patterns"` 136 Priority int `json:"priority"` 137 LastUpdatedTime int64 `json:"last_updated_time"` 138 } 139 140 // IndexMetadata contains information about a particular 141 type IndexMetadata struct { 142 Mapping struct { 143 TotalFields struct { 144 Limit string `json:"limit"` 145 } `json:"total_fields"` 146 } `json:"mapping"` 147 RefreshInterval string `json:"refresh_interval"` 148 Hidden string `json:"hidden"` 149 NumberOfShards string `json:"number_of_shards"` 150 AutoExpandReplicas string `json:"auto_expand_replicas"` 151 ProvidedName string `json:"provided_name"` 152 CreationDate string `json:"creation_date"` 153 NumberOfReplicas string `json:"number_of_replicas"` 154 UUID string `json:"uuid"` 155 Version struct { 156 Created string `json:"created"` 157 } `json:"version"` 158 } 159 160 // IndexSettings parent object containing the index metadata 161 type IndexSettings struct { 162 Settings struct { 163 Index IndexMetadata `json:"index"` 164 } `json:"settings"` 165 } 166 167 // DataStream details 168 type DataStream struct { 169 Name string `json:"name"` 170 TimestampField struct { 171 Name string `json:"name"` 172 } `json:"timestamp_field"` 173 Indices []struct { 174 IndexName string `json:"index_name"` 175 IndexUUID string `json:"index_uuid"` 176 } `json:"indices"` 177 Generation int `json:"generation"` 178 Status string `json:"status"` 179 Template string `json:"template"` 180 } 181 182 // SearchResult represents the result of an Opensearch search query 183 type SearchResult struct { 184 Took int `json:"took"` 185 TimedOut bool `json:"timed_out"` 186 Shards struct { 187 Total int `json:"total"` 188 Successful int `json:"successful"` 189 Skipped int `json:"skipped"` 190 Failed int `json:"failed"` 191 } `json:"_shards"` 192 Hits struct { 193 Total struct { 194 Value int `json:"value"` 195 Relation string `json:"relation"` 196 } `json:"total"` 197 MaxScore interface{} `json:"max_score"` 198 Hits []interface{} `json:"hits"` 199 } `json:"hits"` 200 } 201 202 // IndexListData represents the row of /_cat/indices?format=json output 203 type IndexListData struct { 204 Health string `json:"health"` 205 Status string `json:"status"` 206 Index string `json:"index"` 207 UUID string `json:"uuid"` 208 Pri string `json:"pri"` 209 Rep string `json:"rep"` 210 DocsCount string `json:"docsCount"` 211 DocsDeleted string `json:"docsDeleted"` 212 StoreSize string `json:"storeSize"` 213 PriStoreSize string `json:"priStoreSize"` 214 } 215 216 type OpenSearchISMPolicyAddModifier struct{} 217 218 type OpenSearchISMPolicyRemoveModifier struct{} 219 220 var expectedSystemISMPolicies = []string{"vz-application", "vz-custom"} 221 222 func (u OpenSearchISMPolicyAddModifier) ModifyCR(cr *vzapi.Verrazzano) { 223 cr.Spec.Components.OpenSearch = &vzapi.OpenSearchComponent{} 224 if cr.Spec.Components.OpenSearch.Policies == nil { 225 cr.Spec.Components.OpenSearch.Policies = []vmov1.IndexManagementPolicy{ 226 { 227 PolicyName: "verrazzano-system", 228 IndexPattern: "verrazzano-system*", 229 MinIndexAge: &DefaultRetentionPeriod, 230 Rollover: vmov1.RolloverPolicy{ 231 MinIndexAge: &DefaultRolloverPeriod, 232 }, 233 }, 234 { 235 PolicyName: "verrazzano-application", 236 IndexPattern: "verrazzano-application*", 237 MinIndexAge: &DefaultRetentionPeriod, 238 Rollover: vmov1.RolloverPolicy{ 239 MinIndexAge: &DefaultRolloverPeriod, 240 }, 241 }, 242 } 243 } 244 } 245 246 func (u OpenSearchISMPolicyRemoveModifier) ModifyCR(cr *vzapi.Verrazzano) { 247 cr.Spec.Components.OpenSearch = &vzapi.OpenSearchComponent{} 248 } 249 250 func TestOpenSearchPlugins(pollingInterval time.Duration, waitTimeout time.Duration) { 251 if UseExternalOpensearch() { 252 ginkgo.Skip("Skip External OpenSearch") 253 } 254 gomega.Eventually(func() error { 255 return VerifyOpenSearchPlugins() 256 }).WithPolling(pollingInterval).WithTimeout(waitTimeout).Should(gomega.BeNil()) 257 } 258 259 // VerifyOpenSearchPlugins checks that the OpenSearch plugins are installed 260 func VerifyOpenSearchPlugins() error { 261 resp, err := doGetOpenSearchURL("%s/_cat/plugins?format=json") 262 if err != nil { 263 return err 264 } 265 if resp.StatusCode == http.StatusOK { 266 out := string(resp.Body) 267 missingPluginsStr := "" 268 269 missingPlugins := checkMissingPlugin(out, opensearchJobScheduler, &missingPluginsStr) 270 missingPlugins = missingPlugins || checkMissingPlugin(out, opensearchIndexManagement, &missingPluginsStr) 271 missingPlugins = missingPlugins || checkMissingPlugin(out, opensearchPrometheusExporter, &missingPluginsStr) 272 missingPlugins = missingPlugins || checkMissingPlugin(out, opensearchAlerting, &missingPluginsStr) 273 274 if missingPlugins { 275 return fmt.Errorf("missing OpenSearch plugins that were not installed: %s", missingPluginsStr) 276 } 277 } 278 return nil 279 } 280 281 func checkMissingPlugin(response string, plugin string, missingPluginsStr *string) bool { 282 if !strings.Contains(response, plugin) { 283 *missingPluginsStr = *missingPluginsStr + plugin + " " 284 return true 285 } 286 return false 287 } 288 289 // GetOpenSearchAppIndex in Verrazzano 1.3.0, application indices have been migrated to data streams 290 // following the pattern 'verrazzano-application-<application name>' 291 func GetOpenSearchAppIndex(namespace string) (string, error) { 292 return GetOpenSearchAppIndexWithKC(namespace, "") 293 } 294 295 // GetOpenSearchAppIndexWithKC is the same as GetOpenSearchAppIndex but kubeconfig may be specified for MC tests 296 func GetOpenSearchAppIndexWithKC(namespace, kubeconfigPath string) (string, error) { 297 usingDataStreams, err := isUsingDataStreams(kubeconfigPath) 298 if err != nil { 299 return "", err 300 } 301 if usingDataStreams { 302 return "verrazzano-application-" + namespace, nil 303 } 304 return "verrazzano-namespace-" + namespace, nil 305 } 306 307 func isUsingDataStreams(kubeconfigPath string) (bool, error) { 308 kubeConfig, err := getKubeConfigPath(kubeconfigPath) 309 if err != nil { 310 return false, err 311 } 312 return IsVerrazzanoMinVersion("1.3.0", kubeConfig) 313 } 314 315 func UseExternalOpensearch() bool { 316 return os.Getenv("EXTERNAL_ELASTICSEARCH") == "true" 317 } 318 319 // GetExternalOpenSearchURL gets the external Opensearch URL 320 func GetExternalOpenSearchURL(kubeconfigPath string) string { 321 opensearchSvc := "opensearch-cluster-master" 322 // the equivalent of kubectl get svc opensearchSvc -o=jsonpath='{.status.loadBalancer.ingress[0].ip}' 323 clientset, err := GetKubernetesClientsetForCluster(kubeconfigPath) 324 if err != nil { 325 Log(Error, fmt.Sprintf("Failed to get clientset for cluster %v", err)) 326 return "" 327 } 328 svc, err := clientset.CoreV1().Services("default").Get(context.TODO(), opensearchSvc, metav1.GetOptions{}) 329 if err != nil { 330 Log(Info, fmt.Sprintf("Could not get services quickstart-es-http in sockshop: %v\n", err.Error())) 331 return "" 332 } 333 if svc.Status.LoadBalancer.Ingress != nil && len(svc.Status.LoadBalancer.Ingress) > 0 { 334 return fmt.Sprintf("https://%s:9200", svc.Status.LoadBalancer.Ingress[0].IP) 335 } 336 return "" 337 } 338 339 // GetSystemOpenSearchIngressURL gets the system Opensearch Ingress host in the given cluster 340 func GetSystemOpenSearchIngressURL(kubeconfigPath string) string { 341 clientset, err := GetKubernetesClientsetForCluster(kubeconfigPath) 342 if err != nil { 343 Log(Error, fmt.Sprintf("Failed to get clientset for cluster %v", err)) 344 return "" 345 } 346 // Return the newer OS ingress if >= 1.7.0 347 // Else return the vmi ingress as usual 348 useNewOSIngress, _ := IsVerrazzanoMinVersion("1.7.0", kubeconfigPath) 349 ingressList, _ := clientset.NetworkingV1().Ingresses(VerrazzanoNamespace).List(context.TODO(), metav1.ListOptions{}) 350 for _, ingress := range ingressList.Items { 351 if !useNewOSIngress && (ingress.Name == "vmi-system-os-ingest" || ingress.Name == "vmi-system-es-ingest") { 352 Log(Info, fmt.Sprintf("Found Opensearch Ingress %v, host %s", ingress.Name, ingress.Spec.Rules[0].Host)) 353 return fmt.Sprintf("https://%s", ingress.Spec.Rules[0].Host) 354 } else if useNewOSIngress && ingress.Name == "opensearch" { 355 Log(Info, fmt.Sprintf("Found Opensearch Ingress %v, host %s", ingress.Name, ingress.Spec.Rules[0].Host)) 356 return fmt.Sprintf("https://%s", ingress.Spec.Rules[0].Host) 357 } 358 } 359 return "" 360 } 361 362 // getOpenSearchURL returns VMI or external ES URL depending on env var EXTERNAL_ELASTICSEARCH 363 func getOpenSearchURL(kubeconfigPath string) string { 364 if UseExternalOpensearch() { 365 return GetExternalOpenSearchURL(kubeconfigPath) 366 } 367 return GetSystemOpenSearchIngressURL(kubeconfigPath) 368 } 369 370 // getOpenSearchUsernamePassword returns the username/password for connecting to opensearch 371 func getOpenSearchUsernamePassword(kubeconfigPath string) (username, password string, err error) { 372 if UseExternalOpensearch() { 373 esSecret, err := GetSecretInCluster(VerrazzanoNamespace, "external-es-secret", kubeconfigPath) 374 if err != nil { 375 Log(Error, fmt.Sprintf("Failed to get external-es-secret secret: %v", err)) 376 return "", "", err 377 } 378 return string(esSecret.Data["username"]), string(esSecret.Data["password"]), err 379 } 380 password, err = GetVerrazzanoPasswordInCluster(kubeconfigPath) 381 if err != nil { 382 return "", "", err 383 } 384 return "verrazzano", password, err 385 } 386 387 // getOpenSearchWithBasicAuth access ES with GET using basic auth, using a given kubeconfig 388 func getOpenSearchWithBasicAuth(url string, hostHeader string, username string, password string, kubeconfigPath string) (*HTTPResponse, error) { 389 retryableClient, err := getOpenSearchClient(kubeconfigPath) 390 if err != nil { 391 return nil, err 392 } 393 return doReq(url, "GET", "", hostHeader, username, password, nil, retryableClient) 394 } 395 396 // postOpenSearchWithBasicAuth retries POST using basic auth 397 func postOpenSearchWithBasicAuth(url, body, username, password, kubeconfigPath string) (*HTTPResponse, error) { 398 retryableClient, err := getOpenSearchClient(kubeconfigPath) 399 if err != nil { 400 return nil, err 401 } 402 return doReq(url, "POST", "application/json", "", username, password, strings.NewReader(body), retryableClient) 403 } 404 405 // putOpenSearchWithBasicAuth retries PUT using basic auth 406 func putOpenSearchWithBasicAuth(url, body, username, password, kubeconfigPath string) (*HTTPResponse, error) { 407 retryableClient, err := getOpenSearchClient(kubeconfigPath) 408 if err != nil { 409 return nil, err 410 } 411 return doReq(url, "PUT", "application/json", "", username, password, strings.NewReader(body), retryableClient) 412 } 413 414 // deleteOpenSearchWithBasicAuth retries DELETE using basic auth 415 func deleteOpenSearchWithBasicAuth(url, body, username, password, kubeconfigPath string) (*HTTPResponse, error) { 416 retryableClient, err := getOpenSearchClient(kubeconfigPath) 417 if err != nil { 418 return nil, err 419 } 420 return doReq(url, "DELETE", "application/json", "", username, password, strings.NewReader(body), retryableClient) 421 } 422 423 // getOpenSearchClient returns ES client to perform http operations 424 func getOpenSearchClient(kubeconfigPath string) (*retryablehttp.Client, error) { 425 var retryableClient *retryablehttp.Client 426 var err error 427 if UseExternalOpensearch() { 428 caCert, err := getExternalESCACert(kubeconfigPath) 429 if err != nil { 430 return nil, err 431 } 432 client, err := getHTTPClientWithCABundle(caCert, kubeconfigPath) 433 if err != nil { 434 return nil, err 435 } 436 retryableClient = newRetryableHTTPClient(client) 437 if err != nil { 438 return nil, err 439 } 440 } else { 441 retryableClient, err = GetVerrazzanoHTTPClient(kubeconfigPath) 442 if err != nil { 443 return nil, err 444 } 445 } 446 return retryableClient, nil 447 } 448 449 // getExternalESCACert returns the CA cert from external-es-secret in the specified cluster 450 func getExternalESCACert(kubeconfigPath string) ([]byte, error) { 451 clientset, err := GetKubernetesClientsetForCluster(kubeconfigPath) 452 if err != nil { 453 return nil, err 454 } 455 certSecret, err := clientset.CoreV1().Secrets(VerrazzanoNamespace).Get(context.TODO(), "external-es-secret", metav1.GetOptions{}) 456 if err != nil { 457 return nil, err 458 } 459 return certSecret.Data["ca-bundle"], nil 460 } 461 462 // listSystemOpenSearchIndices lists the system Opensearch indices in the given cluster 463 func listSystemOpenSearchIndices(kubeconfigPath string) []string { 464 list := []string{} 465 url := fmt.Sprintf("%s/_all", getOpenSearchURL(kubeconfigPath)) 466 username, password, err := getOpenSearchUsernamePassword(kubeconfigPath) 467 if err != nil { 468 return list 469 } 470 resp, err := getOpenSearchWithBasicAuth(url, "", username, password, kubeconfigPath) 471 if err != nil { 472 Log(Error, fmt.Sprintf("Error getting Opensearch indices: url=%s, error=%v", url, err)) 473 return list 474 } 475 if resp.StatusCode != http.StatusOK { 476 Log(Error, fmt.Sprintf("Error retrieving Opensearch indices: url=%s, status=%d", url, resp.StatusCode)) 477 return list 478 } 479 Log(Debug, fmt.Sprintf("indices: %s", resp.Body)) 480 var indexMap map[string]interface{} 481 json.Unmarshal(resp.Body, &indexMap) 482 for name := range indexMap { 483 list = append(list, name) 484 } 485 return list 486 } 487 488 // querySystemOpenSearch searches the Opensearch index with the fields in the given cluster 489 func querySystemOpenSearch(index string, fields map[string]string, kubeconfigPath string, sortQuery bool) map[string]interface{} { 490 query := "" 491 for name, value := range fields { 492 fieldQuery := fmt.Sprintf("%s:%s", name, value) 493 if query == "" { 494 query = fieldQuery 495 } else { 496 query = fmt.Sprintf("%s+AND+%s", query, fieldQuery) 497 } 498 } 499 if sortQuery { 500 query = query + "&sort=@timestamp:desc&size=10" 501 } 502 503 var result map[string]interface{} 504 url := fmt.Sprintf("%s/%s/_search?q=%s", getOpenSearchURL(kubeconfigPath), index, query) 505 username, password, err := getOpenSearchUsernamePassword(kubeconfigPath) 506 if err != nil { 507 return result 508 } 509 resp, err := getOpenSearchWithBasicAuth(url, "", username, password, kubeconfigPath) 510 if err != nil { 511 Log(Error, fmt.Sprintf(queryErrorFormat, url, err)) 512 return result 513 } 514 if resp.StatusCode != http.StatusOK { 515 Log(Error, fmt.Sprintf(queryStatusFormat, url, resp.StatusCode)) 516 return result 517 } 518 Log(Debug, fmt.Sprintf("records: %s", resp.Body)) 519 json.Unmarshal(resp.Body, &result) 520 return result 521 } 522 523 // queryDocumentsOlderThan searches the Opensearch index with the fields in the given cluster 524 func queryDocumentsOlderThan(index string, retentionPeriod string, kubeconfigPath string) (SearchResult, error) { 525 var result SearchResult 526 527 // validate Retention period 528 _, err := CalculateSeconds(retentionPeriod) 529 if err != nil { 530 return result, err 531 } 532 533 query := "@timestamp:<now-" + retentionPeriod 534 url := fmt.Sprintf("%s/%s/_search?q=%s", getOpenSearchURL(kubeconfigPath), index, url2.QueryEscape(query)) 535 username, password, err := getOpenSearchUsernamePassword(kubeconfigPath) 536 if err != nil { 537 return result, nil 538 } 539 resp, err := getOpenSearchWithBasicAuth(url, "", username, password, kubeconfigPath) 540 if err != nil { 541 Log(Error, fmt.Sprintf(queryErrorFormat, url, err)) 542 return result, nil 543 } 544 if resp.StatusCode != http.StatusOK { 545 Log(Error, fmt.Sprintf(queryStatusFormat, url, resp.StatusCode)) 546 return result, nil 547 } 548 Log(Debug, fmt.Sprintf("records: %s", resp.Body)) 549 json.Unmarshal(resp.Body, &result) 550 return result, nil 551 } 552 553 // LogIndexFound confirms a named index can be found in Opensearch in the cluster specified in the environment 554 func LogIndexFound(indexName string) bool { 555 kubeconfigPath, err := k8sutil.GetKubeConfigLocation() 556 if err != nil { 557 Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err)) 558 return false 559 } 560 561 return LogIndexFoundInCluster(indexName, kubeconfigPath) 562 } 563 564 // LogIndexFoundInCluster confirms a named index can be found in Opensearch on the given cluster 565 func LogIndexFoundInCluster(indexName, kubeconfigPath string) bool { 566 Log(Info, fmt.Sprintf("Looking for log index %s in cluster with kubeconfig %s", indexName, kubeconfigPath)) 567 for _, name := range listSystemOpenSearchIndices(kubeconfigPath) { 568 // If old index or data stream backend index, return true 569 backendIndexRe := regexp.MustCompile(`^\.ds-` + indexName + `-\d+$`) 570 if name == indexName || backendIndexRe.MatchString(name) { 571 return true 572 } 573 } 574 Log(Error, fmt.Sprintf("Expected to find log index %s", indexName)) 575 return false 576 } 577 578 // GetSystemIndices returns metadata of indices of all system indices 579 func GetSystemIndices() ([]IndexMetadata, error) { 580 systemIndices, err := GetIndexMetadataList(ListSystemIndices()) 581 if err != nil { 582 return []IndexMetadata{}, err 583 } 584 return systemIndices, nil 585 } 586 587 // GetApplicationIndices returns the metadata of indices used by application indices 588 func GetApplicationIndices() ([]IndexMetadata, error) { 589 applicationIndices, err := GetIndexMetadataList(ListApplicationIndices()) 590 if err != nil { 591 return []IndexMetadata{}, err 592 } 593 return applicationIndices, nil 594 } 595 596 // GetBackingIndicesForDataStream returns metadata of all backing indices for a given data stream 597 func GetBackingIndicesForDataStream(dataStreamName string) ([]IndexMetadata, error) { 598 dataStream, err := GetDataStream(dataStreamName) 599 if err != nil { 600 return []IndexMetadata{}, err 601 } 602 var indexMetadataList []IndexMetadata 603 for _, index := range dataStream.Indices { 604 indexMetadata, err := GetIndexMetadata(index.IndexName) 605 if err != nil { 606 return indexMetadataList, err 607 } 608 indexMetadataList = append(indexMetadataList, indexMetadata) 609 } 610 return indexMetadataList, nil 611 } 612 613 // ContainsIndicesOlderThanRetentionPeriod returns true if there are any old (backing) indices present for 614 // the given data stream that is older than the retention period. Returns false otherwise. 615 func ContainsIndicesOlderThanRetentionPeriod(indexMetadataList []IndexMetadata, oldestTimestamp int64) (bool, error) { 616 for _, indexMetadata := range indexMetadataList { 617 Log(Info, fmt.Sprintf("Checking if creation time of index %s is older than %d", indexMetadata.ProvidedName, oldestTimestamp)) 618 indexCreationTime, _ := strconv.ParseInt(indexMetadata.CreationDate, 10, 64) 619 Log(Info, fmt.Sprintf("Creation time of index '%s' is '%d'", indexMetadata.ProvidedName, indexCreationTime)) 620 if indexCreationTime < oldestTimestamp { 621 return true, nil 622 } 623 } 624 return false, nil 625 } 626 627 // GetDataStream return the data stream object with the given 628 func GetDataStream(dataStreamName string) (DataStream, error) { 629 var dataStream DataStream 630 resp, err := doGetOpenSearchURL(getDataStreamURLFormat + dataStreamName) 631 if err != nil { 632 return dataStream, err 633 } 634 if resp.StatusCode == http.StatusOK { 635 var dataStreamMap map[string][]DataStream 636 json.Unmarshal(resp.Body, &dataStreamMap) 637 dataStreams := dataStreamMap["data_streams"] 638 if len(dataStreams) > 0 { 639 // since the data stream object is queried using its name which is unique, 640 // atmost one element would be present in this splice 641 dataStream = dataStreams[0] 642 } 643 } 644 return dataStream, nil 645 } 646 647 // IsDataStreamSupported returns true if data stream is supported false otherwise 648 func IsDataStreamSupported() bool { 649 resp, err := doGetOpenSearchURL(listDataStreamURLFormat) 650 if err != nil { 651 Log(Error, err.Error()) 652 return false 653 } 654 if resp.StatusCode == http.StatusOK { 655 var dataStreamMap map[string][]DataStream 656 json.Unmarshal(resp.Body, &dataStreamMap) 657 dataStreams := dataStreamMap["data_streams"] 658 if len(dataStreams) > 0 { 659 return true 660 } 661 } 662 Log(Error, "No data streams created") 663 return false 664 } 665 666 // WaitForISMPolicyUpdate waits for the VMO reconcile to complete and the ISM policies are created 667 func WaitForISMPolicyUpdate(pollingInterval time.Duration, timeout time.Duration) { 668 gomega.Eventually(func() bool { 669 ismPolicyExists, err := ISMPolicyExists(ApplicationLogIsmPolicyName) 670 if err != nil { 671 Log(Error, err.Error()) 672 return false 673 } 674 return ismPolicyExists 675 }).WithPolling(pollingInterval).WithTimeout(timeout).Should(gomega.BeTrue()) 676 } 677 678 func ListSystemIndices() []string { 679 return []string{ 680 "verrazzano-namespace-cert-manager", 681 "verrazzano-namespace-verrazzano-system", 682 "verrazzano-namespace-local-path-storage", 683 "verrazzano-namespace-kube-system", 684 "verrazzano-namespace-cattle-fleet-local-system", 685 "verrazzano-namespace-ingress-nginx", 686 "verrazzano-systemd-journal", 687 "verrazzano-namespace-cattle-fleet-system", 688 "verrazzano-namespace-istio-system", 689 "verrazzano-namespace-monitoring", 690 "verrazzano-namespace-cattle-system", 691 "verrazzano-namespace-verrazzano-install", 692 } 693 } 694 695 func ListApplicationIndices() []string { 696 var indexList []string 697 resp, err := doGetOpenSearchURL("%s/_cat/indices?format=json") 698 if err != nil { 699 return indexList 700 } 701 if resp.StatusCode == http.StatusOK { 702 var indexListData []IndexListData 703 json.Unmarshal(resp.Body, &indexListData) 704 for _, indexData := range indexListData { 705 if !isSystemIndex(indexData.Index) { 706 indexList = append(indexList, indexData.Index) 707 } 708 } 709 } 710 return indexList 711 } 712 713 func GetIndexMetadataList(indexNames []string) ([]IndexMetadata, error) { 714 var indexMetadataList []IndexMetadata 715 for _, systemIndex := range indexNames { 716 systemIndexMetadata, err := GetIndexMetadata(systemIndex) 717 if err != nil { 718 return indexMetadataList, err 719 } 720 indexMetadataList = append(indexMetadataList, systemIndexMetadata) 721 } 722 return indexMetadataList, nil 723 } 724 725 // isSystemIndex returns true if the given index is a verrazzano system index false otherwise 726 func isSystemIndex(indexName string) bool { 727 if strings.HasPrefix(indexName, ".") { 728 return true 729 } 730 for _, systemIndex := range ListSystemIndices() { 731 if systemIndex == indexName { 732 return true 733 } 734 } 735 return false 736 } 737 738 // doGetOpenSearchURL helper method to execute a GET request to open search url and return the response 739 func doGetOpenSearchURL(urlFormat string) (*HTTPResponse, error) { 740 kubeconfigPath, err := k8sutil.GetKubeConfigLocation() 741 if err != nil { 742 return nil, err 743 } 744 url := fmt.Sprintf(urlFormat, getOpenSearchURL(kubeconfigPath)) 745 username, password, err := getOpenSearchUsernamePassword(kubeconfigPath) 746 if err != nil { 747 return nil, err 748 } 749 return getOpenSearchWithBasicAuth(url, "", username, password, kubeconfigPath) 750 } 751 752 // GetApplicationDataStreamNames returns the data stream names of all application logs having 753 // prefix 'verrazzano-application-' 754 func GetApplicationDataStreamNames() ([]string, error) { 755 result := []string{} 756 resp, err := doGetOpenSearchURL("%s/_data_stream") 757 if err != nil { 758 return result, err 759 } 760 if resp.StatusCode == http.StatusOK { 761 var dataStreams map[string][]DataStream 762 json.Unmarshal(resp.Body, &dataStreams) 763 for _, dataStream := range dataStreams["data_streams"] { 764 if strings.HasPrefix(dataStream.Name, "verrazzano-application-") { 765 result = append(result, dataStream.Name) 766 } 767 } 768 } 769 return result, nil 770 } 771 772 // DeleteApplicationDataStream deletes the given applicatoin data stream 773 func DeleteApplicationDataStream(datastreamName string) error { 774 kubeconfigPath, err := k8sutil.GetKubeConfigLocation() 775 if err != nil { 776 return err 777 } 778 username, password, err := getOpenSearchUsernamePassword(kubeconfigPath) 779 if err != nil { 780 return err 781 } 782 url := fmt.Sprintf(deleteDataStreamURLFormat, getOpenSearchURL(kubeconfigPath), datastreamName) 783 resp, err := deleteOpenSearchWithBasicAuth(url, "", username, password, kubeconfigPath) 784 if err != nil { 785 return err 786 } 787 if resp.StatusCode == http.StatusOK { 788 return nil 789 } 790 return nil 791 } 792 793 // GetIndexMetadata returns the metadata of the index 794 func GetIndexMetadata(indexName string) (IndexMetadata, error) { 795 result := IndexMetadata{} 796 resp, err := doGetOpenSearchURL("%s/" + indexName + "/_settings") 797 if err != nil { 798 return result, err 799 } 800 if resp.StatusCode == http.StatusOK { 801 var settings map[string]IndexSettings 802 json.Unmarshal(resp.Body, &settings) 803 return settings[indexName].Settings.Index, nil 804 } 805 return result, nil 806 } 807 808 // GetIndexMetadataForDataStream returns the metadata of all backing indices of a given 809 // datastream 810 func GetIndexMetadataForDataStream(dataStreamName string) ([]IndexMetadata, error) { 811 result := []IndexMetadata{} 812 resp, err := doGetOpenSearchURL("%s/" + dataStreamName + "/_settings") 813 if err != nil { 814 return result, err 815 } 816 if resp.StatusCode == http.StatusOK { 817 var settings map[string]IndexSettings 818 json.Unmarshal(resp.Body, &settings) 819 for _, indexSettings := range settings { 820 result = append(result, indexSettings.Settings.Index) 821 } 822 } 823 return result, nil 824 } 825 826 // LogRecordFound confirms a recent log record for the index with matching fields can be found 827 // in the cluster specified in the environment 828 func LogRecordFound(indexName string, after time.Time, fields map[string]string) bool { 829 kubeconfigPath, err := k8sutil.GetKubeConfigLocation() 830 if err != nil { 831 Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err)) 832 return false 833 } 834 835 return LogRecordFoundInCluster(indexName, after, fields, kubeconfigPath) 836 } 837 838 // LogRecordFoundInCluster confirms a recent log record for the index with matching fields can be found 839 // in the given cluster 840 func LogRecordFoundInCluster(indexName string, after time.Time, fields map[string]string, kubeconfigPath string) bool { 841 searchResult := querySystemOpenSearch(indexName, fields, kubeconfigPath, true) 842 if len(searchResult) == 0 { 843 Log(Info, fmt.Sprintf("Expected to find log record matching fields %v", fields)) 844 return false 845 } 846 found := findHits(searchResult, &after) 847 if !found { 848 Log(Error, fmt.Sprintf("Failed to find recent log record for index %s", indexName)) 849 } 850 return found 851 } 852 853 // ContainsDocsOlderThanRetentionPeriod returns true if the given index contains any doc that 854 // is older than the retention period, returns false otherwise. 855 func ContainsDocsOlderThanRetentionPeriod(indexName string, retentionPeriod string) (bool, error) { 856 kubeconfigPath, err := k8sutil.GetKubeConfigLocation() 857 if err != nil { 858 Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err)) 859 return false, err 860 } 861 oldRecordsSearchResult, err := queryDocumentsOlderThan(indexName, retentionPeriod, kubeconfigPath) 862 if err != nil { 863 return false, err 864 } 865 return oldRecordsSearchResult.Hits.Total.Value > 0, nil 866 } 867 868 // findHits returns the number of hits that match a given search query 869 func findHits(searchResult map[string]interface{}, after *time.Time) bool { 870 hits := Jq(searchResult, "hits", "hits") 871 if hits == nil { 872 Log(Info, "Expected to find hits in log record query results") 873 return false 874 } 875 Log(Info, fmt.Sprintf("Found %d records", len(hits.([]interface{})))) 876 if len(hits.([]interface{})) == 0 { 877 Log(Info, "Expected log record query results to contain at least one hit") 878 return false 879 } 880 if after == nil { 881 return true 882 } 883 for _, hit := range hits.([]interface{}) { 884 timestamp := Jq(hit, "_source", "@timestamp") 885 t, err := time.Parse(ISO8601Layout, timestamp.(string)) 886 if err != nil { 887 t, err = time.Parse(time.RFC3339Nano, timestamp.(string)) 888 if err != nil { 889 Log(Error, fmt.Sprintf("Failed to parse timestamp: %s", timestamp)) 890 return false 891 } 892 } 893 if t.After(*after) { 894 Log(Info, fmt.Sprintf("Found recent record: %s", timestamp)) 895 return true 896 } 897 Log(Info, fmt.Sprintf("Found old record: %s", timestamp)) 898 } 899 return false 900 } 901 902 // OpensearchHit is the type used for a Opensearch hit returned in a search query result 903 type OpensearchHit map[string]interface{} 904 905 // OpensearchHitValidator is a function that validates a hit returned in a search query result 906 type OpensearchHitValidator func(hit OpensearchHit) bool 907 908 // ValidateOpensearchHits invokes the HitValidator on every hit in the searchResults. 909 // The first invalid hit found will return in false being returned. 910 // Otherwise true will be returned. 911 func ValidateOpensearchHits(searchResults map[string]interface{}, hitValidator OpensearchHitValidator, exceptions []*regexp.Regexp) bool { 912 hits := Jq(searchResults, "hits", "hits") 913 if hits == nil { 914 Log(Info, "Expected to find hits in log record query results") 915 return false 916 } 917 Log(Info, fmt.Sprintf("Found %d records", len(hits.([]interface{})))) 918 if len(hits.([]interface{})) == 0 { 919 Log(Info, "Expected log record query results to contain at least one hit") 920 return false 921 } 922 valid := true 923 for _, h := range hits.([]interface{}) { 924 hit := h.(map[string]interface{}) 925 src := hit["_source"].(map[string]interface{}) 926 log := src["log"].(string) 927 if isException(log, exceptions) { 928 Log(Debug, fmt.Sprintf("Exception: %s", log)) 929 } else { 930 if !hitValidator(src) { 931 valid = false 932 } 933 } 934 } 935 return valid 936 } 937 938 func isException(log string, exceptions []*regexp.Regexp) bool { 939 for _, re := range exceptions { 940 if re.MatchString(log) { 941 return true 942 } 943 } 944 return false 945 } 946 947 // FindLog returns true if a recent log record can be found in the index with matching filters. 948 func FindLog(index string, match []Match, mustNot []Match) bool { 949 after := time.Now().Add(-24 * time.Hour) 950 query := OpensearchQuery{ 951 Filters: match, 952 MustNot: mustNot, 953 } 954 result := SearchLog(index, query) 955 found := findHits(result, &after) 956 if !found { 957 Log(Error, fmt.Sprintf("Failed to find recent log record for index %s", index)) 958 } 959 return found 960 } 961 962 // FindAnyLog returns true if a log record of any time can be found in the index with matching filters. 963 func FindAnyLog(index string, match []Match, mustNot []Match) bool { 964 query := OpensearchQuery{ 965 Filters: match, 966 MustNot: mustNot, 967 } 968 result := SearchLog(index, query) 969 found := findHits(result, nil) 970 if !found { 971 Log(Error, fmt.Sprintf("Failed to find recent log record for index %s", index)) 972 } 973 return found 974 } 975 976 const numberOfErrorsToLog = 5 977 978 // NoLog returns true if no matched log record can be found in the index. 979 func NoLog(index string, match []Match, mustNot []Match) bool { 980 query := OpensearchQuery{ 981 Filters: match, 982 MustNot: mustNot, 983 } 984 result := SearchLog(index, query) 985 if len(result) == 0 { 986 return true 987 } 988 hits := Jq(result, "hits", "hits") 989 if hits == nil || len(hits.([]interface{})) == 0 { 990 return true 991 } 992 Log(Error, fmt.Sprintf("Found unexpected %d records", len(hits.([]interface{})))) 993 for i, hit := range hits.([]interface{}) { 994 if i < numberOfErrorsToLog { 995 Log(Error, fmt.Sprintf("Found unexpected log record: %v", hit)) 996 } else { 997 break 998 } 999 } 1000 return false 1001 } 1002 1003 var opensearchQueryTemplate *template.Template 1004 1005 // SearchLog search recent log records for the index with matching filters. 1006 func SearchLog(index string, query OpensearchQuery) map[string]interface{} { 1007 kubeconfigPath, err := k8sutil.GetKubeConfigLocation() 1008 if err != nil { 1009 Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err)) 1010 return nil 1011 } 1012 if opensearchQueryTemplate == nil { 1013 temp, err := template.New("esQueryTemplate").Parse(queryTemplate) 1014 if err != nil { 1015 Log(Error, fmt.Sprintf("Error: %v", err)) 1016 } 1017 opensearchQueryTemplate = temp 1018 } 1019 var buffer bytes.Buffer 1020 err = opensearchQueryTemplate.Execute(&buffer, query) 1021 if err != nil { 1022 Log(Error, fmt.Sprintf("Error: %v", err)) 1023 } 1024 configPath, err := k8sutil.GetKubeConfigLocation() 1025 if err != nil { 1026 Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err)) 1027 return nil 1028 } 1029 var result map[string]interface{} 1030 url := fmt.Sprintf("%s/%s/_search", getOpenSearchURL(kubeconfigPath), index) 1031 username, password, err := getOpenSearchUsernamePassword(configPath) 1032 if err != nil { 1033 return result 1034 } 1035 Log(Debug, fmt.Sprintf("Search: %v \nQuery: \n%v", url, buffer.String())) 1036 resp, err := postOpenSearchWithBasicAuth(url, buffer.String(), username, password, configPath) 1037 if err != nil { 1038 Log(Error, fmt.Sprintf(queryErrorFormat, url, err)) 1039 return result 1040 } 1041 if resp.StatusCode != http.StatusOK { 1042 Log(Error, fmt.Sprintf(queryStatusFormat, url, resp.StatusCode)) 1043 return result 1044 } 1045 json.Unmarshal(resp.Body, &result) 1046 return result 1047 } 1048 1049 // PostOpensearch POST the request entity body to Opensearch API path 1050 // The provided path is appended to the Opensearch base URL 1051 func PostOpensearch(path string, body string) (*HTTPResponse, error) { 1052 kubeconfigPath, err := k8sutil.GetKubeConfigLocation() 1053 if err != nil { 1054 Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err)) 1055 return nil, err 1056 } 1057 url := fmt.Sprintf("%s/%s", getOpenSearchURL(kubeconfigPath), path) 1058 configPath, err := k8sutil.GetKubeConfigLocation() 1059 if err != nil { 1060 Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err)) 1061 return nil, err 1062 } 1063 username, password, err := getOpenSearchUsernamePassword(configPath) 1064 if err != nil { 1065 return nil, err 1066 } 1067 Log(Debug, fmt.Sprintf("REST API path: %v \nQuery: \n%v", url, body)) 1068 resp, err := postOpenSearchWithBasicAuth(url, body, username, password, configPath) 1069 return resp, err 1070 } 1071 1072 func IndicesNotExists(patterns []string) bool { 1073 kubeconfigPath, err := k8sutil.GetKubeConfigLocation() 1074 if err != nil { 1075 Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err)) 1076 return false 1077 } 1078 Log(Debug, fmt.Sprintf("Looking for indices in cluster using kubeconfig %s", kubeconfigPath)) 1079 for _, name := range listSystemOpenSearchIndices(kubeconfigPath) { 1080 for _, pattern := range patterns { 1081 matched, _ := regexp.MatchString(pattern, name) 1082 if matched { 1083 Log(Error, fmt.Sprintf("Index %s matching the pattern %s still exists", name, pattern)) 1084 return false 1085 } 1086 } 1087 } 1088 return true 1089 } 1090 1091 func ISMPolicyExists(policyName string) (bool, error) { 1092 kubeconfigPath, err := k8sutil.GetKubeConfigLocation() 1093 if err != nil { 1094 return false, err 1095 } 1096 username, password, err := getOpenSearchUsernamePassword(kubeconfigPath) 1097 if err != nil { 1098 return false, err 1099 } 1100 url := fmt.Sprintf("%s/_plugins/_ism/policies/%s", getOpenSearchURL(kubeconfigPath), policyName) 1101 resp, err := getOpenSearchWithBasicAuth(url, "", username, password, kubeconfigPath) 1102 if err != nil { 1103 return false, err 1104 } 1105 return resp.StatusCode == http.StatusOK, nil 1106 } 1107 1108 func GetISMPolicy(policyName string) (ISMPolicy, error) { 1109 result := ISMPolicy{} 1110 kubeconfigPath, err := k8sutil.GetKubeConfigLocation() 1111 if err != nil { 1112 return result, err 1113 } 1114 username, password, err := getOpenSearchUsernamePassword(kubeconfigPath) 1115 if err != nil { 1116 return result, err 1117 } 1118 url := fmt.Sprintf("%s/_plugins/_ism/policies/%s", getOpenSearchURL(kubeconfigPath), policyName) 1119 resp, err := getOpenSearchWithBasicAuth(url, "", username, password, kubeconfigPath) 1120 if err != nil { 1121 return result, err 1122 } 1123 if resp.StatusCode == http.StatusOK { 1124 var ismPolicyJSON map[string]ISMPolicy 1125 json.Unmarshal(resp.Body, &ismPolicyJSON) 1126 return ismPolicyJSON["policy"], nil 1127 } 1128 return result, nil 1129 } 1130 1131 func GetRetentionPeriod(policyName string) (string, error) { 1132 ismPolicy, err := GetISMPolicy(policyName) 1133 if err != nil { 1134 return "", err 1135 } 1136 for _, state := range ismPolicy.States { 1137 if state.Name == "ingest" { 1138 for _, transition := range state.Transitions { 1139 if transition.StateName == "delete" { 1140 minIndexAge := transition.Conditions["min_index_age"] 1141 return minIndexAge, nil 1142 } 1143 } 1144 } 1145 } 1146 return "", nil 1147 } 1148 1149 func GetISMRolloverPeriod(policyName string) (string, error) { 1150 ismPolicy, err := GetISMPolicy(policyName) 1151 if err != nil { 1152 return "", err 1153 } 1154 for _, state := range ismPolicy.States { 1155 if state.Name == "ingest" { 1156 for _, action := range state.Actions { 1157 rolloverPeriod := action.Rollover.MinIndexAge 1158 return rolloverPeriod, nil 1159 } 1160 } 1161 } 1162 return "", nil 1163 } 1164 1165 func CheckForDataStream(name string) bool { 1166 url := getDataStreamURLFormat + name 1167 resp, err := doGetOpenSearchURL(url) 1168 if err != nil { 1169 Log(Error, fmt.Sprintf("Error getting Opensearch data streams: url=%s, error=%v", url, err)) 1170 return false 1171 } 1172 if resp.StatusCode != http.StatusOK { 1173 Log(Error, fmt.Sprintf("Error retrieving Opensearch data streams: url=%s, status=%d", url, resp.StatusCode)) 1174 return false 1175 } 1176 return true 1177 } 1178 1179 // OpensearchQuery describes an Opensearch Query 1180 type OpensearchQuery struct { 1181 Filters []Match 1182 MustNot []Match 1183 } 1184 1185 // Match describes a match_phrase in Opensearch Query 1186 type Match struct { 1187 Key string 1188 Value string 1189 } 1190 1191 const queryTemplate = `{ 1192 "size": 100, 1193 "sort": [ 1194 { 1195 "@timestamp": { 1196 "order": "desc", 1197 "unmapped_type": "boolean" 1198 } 1199 } 1200 ], 1201 "query": { 1202 "bool": { 1203 "filter": [ 1204 { 1205 "match_all": {} 1206 } 1207 {{range $filter := .Filters}} 1208 , 1209 { 1210 "match_phrase": { 1211 "{{$filter.Key}}": "{{$filter.Value}}" 1212 } 1213 } 1214 {{end}} 1215 ], 1216 "must_not": [ 1217 {{range $index, $mustNot := .MustNot}} 1218 {{if $index}},{{end}} 1219 { 1220 "match_phrase": { 1221 "{{$mustNot.Key}}": "{{$mustNot.Value}}" 1222 } 1223 } 1224 {{end}} 1225 ] 1226 } 1227 } 1228 } 1229 ` 1230 1231 func PutISMPolicy(policyData, policyName string) (*HTTPResponse, error) { 1232 kubeconfigPath, err := k8sutil.GetKubeConfigLocation() 1233 if err != nil { 1234 return nil, err 1235 } 1236 username, password, err := getOpenSearchUsernamePassword(kubeconfigPath) 1237 if err != nil { 1238 return nil, err 1239 } 1240 policyURL := fmt.Sprintf("%s/_plugins/_ism/policies/%s", getOpenSearchURL(kubeconfigPath), policyName) 1241 resp, err := putOpenSearchWithBasicAuth(policyURL, policyData, username, password, kubeconfigPath) 1242 return resp, err 1243 } 1244 1245 func CheckISMPolicy() (bool, error) { 1246 result := false 1247 counter := 0 1248 kubeconfigPath, err := k8sutil.GetKubeConfigLocation() 1249 if err != nil { 1250 return false, err 1251 } 1252 username, password, err := getOpenSearchUsernamePassword(kubeconfigPath) 1253 if err != nil { 1254 return false, err 1255 } 1256 policyURL := fmt.Sprintf("%s/_plugins/_ism/policies/", getOpenSearchURL(kubeconfigPath)) 1257 resp, err := getOpenSearchWithBasicAuth(policyURL, "", username, password, kubeconfigPath) 1258 if err != nil { 1259 return result, err 1260 } 1261 if resp.StatusCode == http.StatusOK { 1262 policies := &PolicyList{} 1263 err = json.Unmarshal(resp.Body, &policies) 1264 if err != nil { 1265 fmt.Println("error while processing", err) 1266 } 1267 for _, pl := range policies.Policies { 1268 for _, policy := range expectedSystemISMPolicies { 1269 if pl.Policy.PolicyID == policy { 1270 counter++ 1271 } 1272 } 1273 } 1274 if counter == len(expectedSystemISMPolicies) { 1275 return true, nil 1276 } 1277 } 1278 return false, nil 1279 }