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 }