github.com/verrazzano/verrazzano@v1.7.1/application-operator/controllers/metricstrait/metricstrait_utils.go (about)

     1  // Copyright (c) 2020, 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 metricstrait
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"regexp"
    10  	"strings"
    11  
    12  	gabs "github.com/Jeffail/gabs/v2"
    13  	"github.com/crossplane/oam-kubernetes-runtime/pkg/oam"
    14  	vzapi "github.com/verrazzano/verrazzano/application-operator/apis/oam/v1alpha1"
    15  	"github.com/verrazzano/verrazzano/application-operator/constants"
    16  	vznav "github.com/verrazzano/verrazzano/application-operator/controllers/navigation"
    17  	k8score "k8s.io/api/core/v1"
    18  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    20  	"sigs.k8s.io/controller-runtime/pkg/client"
    21  	"sigs.k8s.io/yaml"
    22  )
    23  
    24  // updateStringMap updates a string key value pair in a map.
    25  // strMap is the map to be updated.  It may be nil.
    26  // key is the key to add to the map
    27  // value is the value to add to the map
    28  // Returns the provided or a new map if strMap was nil
    29  func updateStringMap(strMap map[string]string, key string, value string) map[string]string {
    30  	if strMap == nil {
    31  		strMap = map[string]string{}
    32  	}
    33  	strMap[key] = value
    34  	return strMap
    35  }
    36  
    37  // copyStringMapEntries copies key value pairs from one map to another.
    38  // target is the map key value pairs are copied into
    39  // source is the map key value pairs are copied from
    40  // keys are a list of keys to copy from the source to the target map
    41  // Returns the target map or a new map if the target was nil
    42  func copyStringMapEntries(target map[string]string, source map[string]string, keys ...string) map[string]string {
    43  	if target == nil {
    44  		target = map[string]string{}
    45  	}
    46  	for _, key := range keys {
    47  		value, found := source[key]
    48  		if found {
    49  			target[key] = value
    50  		}
    51  	}
    52  	return target
    53  }
    54  
    55  // parseYAMLString parses a string into a internal representation.
    56  // s is the YAML formatted string to parse.
    57  // Returns an unstructured representation of the input YAML string.
    58  // Returns and error if parsing fails.
    59  func parseYAMLString(s string) (*gabs.Container, error) {
    60  	prometheusJSON, _ := yaml.YAMLToJSON([]byte(s))
    61  	return gabs.ParseJSON(prometheusJSON)
    62  }
    63  
    64  // writeYAMLString writes unstructured data to a YAML formatted string.
    65  // c is the unstructured representation
    66  // Returns a YAML format string version of the input
    67  // Returns an error if the unstructured cannot be converted to a YAML string.
    68  func writeYAMLString(c *gabs.Container) (string, error) {
    69  	bytes, err := yaml.JSONToYAML(c.Bytes())
    70  	if err != nil {
    71  		return "", err
    72  	}
    73  	return string(bytes), nil
    74  }
    75  
    76  // getNamespaceFromObjectMetaOrDefault extracts the namespace name from object metadata.
    77  // meta is the object metadata to extract the namespace name from.
    78  // Returns the namespace name of "default" if the namespace is the empty string.
    79  func getNamespaceFromObjectMetaOrDefault(meta metav1.ObjectMeta) string {
    80  	name := meta.Namespace
    81  	if name == "" {
    82  		return "default"
    83  	}
    84  	return name
    85  }
    86  
    87  // mergeTemplateWithContext merges a map of string into a string template.
    88  // template is the string to merge the values from the context map into.
    89  // context is a map of string to be merged into the template.
    90  // Returns a string with all values from the context merged into the template.
    91  func mergeTemplateWithContext(template string, context map[string]string) string {
    92  	for key, value := range context {
    93  		template = strings.ReplaceAll(template, key, value)
    94  	}
    95  	return template
    96  }
    97  
    98  // GetSupportedWorkloadType returns workload type corresponding to input API version and kind
    99  // that is supported by MetricsTrait.
   100  func GetSupportedWorkloadType(apiVerKind string) string {
   101  	// Match any version of Group=weblogic.oracle and Kind=Domain
   102  	if matched, _ := regexp.MatchString("^weblogic.oracle/.*\\.Domain$", apiVerKind); matched {
   103  		return constants.WorkloadTypeWeblogic
   104  	}
   105  	// Match any version of Group=coherence.oracle and Kind=Coherence
   106  	if matched, _ := regexp.MatchString("^coherence.oracle.com/.*\\.Coherence$", apiVerKind); matched {
   107  		return constants.WorkloadTypeCoherence
   108  	}
   109  
   110  	// Match any version of Group=coherence.oracle and Kind=VerrazzanoHelidonWorkload or
   111  	// In the case of Helidon, the workload isn't currently being unwrapped
   112  	if matched, _ := regexp.MatchString("^oam.verrazzano.io/.*\\.VerrazzanoHelidonWorkload$", apiVerKind); matched {
   113  		return constants.WorkloadTypeGeneric
   114  	}
   115  
   116  	// Match any version of Group=core.oam.dev and Kind=ContainerizedWorkload
   117  	if matched, _ := regexp.MatchString("^core.oam.dev/.*\\.ContainerizedWorkload$", apiVerKind); matched {
   118  		return constants.WorkloadTypeGeneric
   119  	}
   120  
   121  	// Match any version of Group=apps and Kind=Deployment
   122  	if matched, _ := regexp.MatchString("^apps/.*\\.Deployment$", apiVerKind); matched {
   123  		return constants.WorkloadTypeGeneric
   124  	}
   125  
   126  	return ""
   127  }
   128  
   129  // createJobOrServiceMonitorName creates a Prometheus scrape configmap job name from a trait.
   130  // Format is {oam_app}_{cluster}_{namespace}_{oam_comp}
   131  func createJobOrServiceMonitorName(trait *vzapi.MetricsTrait, portNum int) (string, error) {
   132  	namespace := getNamespaceFromObjectMetaOrDefault(trait.ObjectMeta)
   133  	app, found := trait.Labels[oam.LabelAppName]
   134  	if !found {
   135  		return "", fmt.Errorf("metrics trait missing application name label")
   136  	}
   137  	comp, found := trait.Labels[oam.LabelAppComponent]
   138  	if !found {
   139  		return "", fmt.Errorf("metrics trait missing component name label")
   140  	}
   141  	portStr := ""
   142  	if portNum > 0 {
   143  		portStr = fmt.Sprintf("_%d", portNum)
   144  	}
   145  
   146  	finalName := fmt.Sprintf("%s_%s_%s%s", app, namespace, comp, portStr)
   147  	// Check for Kubernetes name length requirement
   148  	if len(finalName) > 63 {
   149  		finalName = fmt.Sprintf("%s_%s%s", app, namespace, portStr)
   150  		if len(finalName) > 63 {
   151  			return finalName[:63], nil
   152  		}
   153  	}
   154  	return finalName, nil
   155  }
   156  
   157  // createJobOrServiceMonitorName creates a valid Prometheus ServiceMonitor name from a trait,
   158  // replacing underscores with dashes in name to appease Kubernetes requirements
   159  // Format is {oam_app}_{cluster}_{namespace}_{oam_comp}
   160  func createServiceMonitorName(trait *vzapi.MetricsTrait, portNum int) (string, error) {
   161  	sname, err := createJobOrServiceMonitorName(trait, portNum)
   162  	if err != nil {
   163  		return "", err
   164  	}
   165  	return strings.Replace(sname, "_", "-", -1), nil
   166  }
   167  
   168  // getPortSpecs returns a complete set of port specs from the trait and the trait defaults
   169  func getPortSpecs(trait *vzapi.MetricsTrait, traitDefaults *vzapi.MetricsTraitSpec) []vzapi.PortSpec {
   170  	ports := trait.Spec.Ports
   171  	if len(ports) == 0 {
   172  		// create a port spec from the existing port
   173  		ports = []vzapi.PortSpec{{Port: trait.Spec.Port, Path: trait.Spec.Path}}
   174  	} else {
   175  		// if there are existing ports and a port/path setting, add the latter to the ports
   176  		if trait.Spec.Port != nil {
   177  			// add the port to the ports
   178  			path := trait.Spec.Path
   179  			if path == nil {
   180  				path = traitDefaults.Path
   181  			}
   182  			portSpec := vzapi.PortSpec{
   183  				Port: trait.Spec.Port,
   184  				Path: path,
   185  			}
   186  			ports = append(ports, portSpec)
   187  		}
   188  	}
   189  	return ports
   190  }
   191  
   192  func isEnabled(trait *vzapi.MetricsTrait) bool {
   193  	return trait.Spec.Enabled == nil || *trait.Spec.Enabled
   194  }
   195  
   196  // useHTTPSForScrapeTarget returns true if https with Istio certs should be used for scrape target. Otherwise return false, use http
   197  func useHTTPSForScrapeTarget(ctx context.Context, c client.Client, trait *vzapi.MetricsTrait) (bool, error) {
   198  	if trait.Spec.WorkloadReference.Kind == "VerrazzanoCoherenceWorkload" || trait.Spec.WorkloadReference.Kind == "Coherence" {
   199  		return false, nil
   200  	}
   201  	// Get the namespace resource that the MetricsTrait is deployed to
   202  	namespace := &k8score.Namespace{}
   203  	if err := c.Get(ctx, client.ObjectKey{Namespace: "", Name: trait.Namespace}, namespace); err != nil {
   204  		return false, err
   205  	}
   206  	value, ok := namespace.Labels["istio-injection"]
   207  	if ok && value == "enabled" {
   208  		return true, nil
   209  	}
   210  	return false, nil
   211  }
   212  
   213  // fetchSourceCredentialsSecretIfRequired fetches the metrics endpoint authentication credentials if a secret is provided.
   214  func fetchSourceCredentialsSecretIfRequired(ctx context.Context, trait *vzapi.MetricsTrait, traitDefaults *vzapi.MetricsTraitSpec, workload *unstructured.Unstructured, cli client.Client) (*k8score.Secret, error) {
   215  	secretName := trait.Spec.Secret
   216  	// If no secret name explicitly provided use the default secret name.
   217  	if secretName == nil && traitDefaults != nil {
   218  		secretName = traitDefaults.Secret
   219  	}
   220  	// If neither an explicit or default secret name provided do not fetch a secret.
   221  	if secretName == nil {
   222  		return nil, nil
   223  	}
   224  	// Use the workload namespace for the secret to fetch.
   225  	secretNamespace, found, err := unstructured.NestedString(workload.Object, "metadata", "namespace")
   226  	if err != nil {
   227  		return nil, fmt.Errorf("failed to determine namespace for secret %s: %w", *secretName, err)
   228  	}
   229  	if !found {
   230  		return nil, fmt.Errorf("failed to find namespace for secret %s", *secretName)
   231  	}
   232  	// Fetch the secret.
   233  	secretKey := client.ObjectKey{Namespace: secretNamespace, Name: *secretName}
   234  	secretObj := k8score.Secret{}
   235  	err = cli.Get(ctx, secretKey, &secretObj)
   236  	if err != nil {
   237  		return nil, fmt.Errorf("failed to fetch secret %v: %w", secretKey, err)
   238  	}
   239  	return &secretObj, nil
   240  }
   241  
   242  // isWLSWorkload returns true if the unstructured object is a Weblogic Workload
   243  func isWLSWorkload(workload *unstructured.Unstructured) (bool, error) {
   244  	apiVerKind, err := vznav.GetAPIVersionKindOfUnstructured(workload)
   245  	if err != nil {
   246  		return false, err
   247  	}
   248  	// Match any version of APIVersion=weblogic.oracle and Kind=Domain
   249  	if matched, _ := regexp.MatchString("^weblogic.oracle/.*\\.Domain$", apiVerKind); matched {
   250  		return true, nil
   251  	}
   252  	return false, nil
   253  }