github.com/argoproj/argo-events@v1.9.1/common/util.go (about) 1 /* 2 Copyright 2018 BlackRock, Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package common 18 19 import ( 20 "context" 21 "crypto/tls" 22 "crypto/x509" 23 "encoding/json" 24 "fmt" 25 "hash/fnv" 26 "net/http" 27 "os" 28 "reflect" 29 "strings" 30 31 v1 "k8s.io/api/core/v1" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/client-go/kubernetes" 34 "k8s.io/client-go/rest" 35 "k8s.io/client-go/tools/clientcmd" 36 37 apicommon "github.com/argoproj/argo-events/pkg/apis/common" 38 ) 39 40 // GetClientConfig return rest config, if path not specified, assume in cluster config 41 func GetClientConfig(kubeconfig string) (*rest.Config, error) { 42 if kubeconfig != "" { 43 return clientcmd.BuildConfigFromFlags("", kubeconfig) 44 } 45 return rest.InClusterConfig() 46 } 47 48 // SendSuccessResponse sends http success response 49 func SendSuccessResponse(writer http.ResponseWriter, response string) { 50 writer.WriteHeader(http.StatusOK) 51 if _, err := writer.Write([]byte(response)); err != nil { 52 fmt.Printf("failed to write the response. err: %+v\n", err) 53 } 54 } 55 56 // SendErrorResponse sends http error response 57 func SendErrorResponse(writer http.ResponseWriter, response string) { 58 writer.WriteHeader(http.StatusBadRequest) 59 if _, err := writer.Write([]byte(response)); err != nil { 60 fmt.Printf("failed to write the response. err: %+v\n", err) 61 } 62 } 63 64 // SendInternalErrorResponse sends http internal error response 65 func SendInternalErrorResponse(writer http.ResponseWriter, response string) { 66 writer.WriteHeader(http.StatusInternalServerError) 67 if _, err := writer.Write([]byte(response)); err != nil { 68 fmt.Printf("failed to write the response. err: %+v\n", err) 69 } 70 } 71 72 // SendResponse sends http response with given status code 73 func SendResponse(writer http.ResponseWriter, statusCode int, response string) { 74 writer.WriteHeader(statusCode) 75 if _, err := writer.Write([]byte(response)); err != nil { 76 fmt.Printf("failed to write the response. err: %+v\n", err) 77 } 78 } 79 80 // Hasher hashes a string 81 func Hasher(value string) string { 82 h := fnv.New32a() 83 _, _ = h.Write([]byte(value)) 84 return fmt.Sprintf("%v", h.Sum32()) 85 } 86 87 // GetObjectHash returns hash of a given object 88 func GetObjectHash(obj metav1.Object) (string, error) { 89 b, err := json.Marshal(obj) 90 if err != nil { 91 return "", fmt.Errorf("failed to marshal resource") 92 } 93 return Hasher(string(b)), nil 94 } 95 96 // FormatEndpoint returns a formatted api endpoint 97 func FormatEndpoint(endpoint string) string { 98 if !strings.HasPrefix(endpoint, "/") { 99 return fmt.Sprintf("/%s", endpoint) 100 } 101 return endpoint 102 } 103 104 // FormattedURL returns a formatted url 105 func FormattedURL(url, endpoint string) string { 106 return fmt.Sprintf("%s%s", url, FormatEndpoint(endpoint)) 107 } 108 109 func ErrEventSourceTypeMismatch(eventSourceType string) string { 110 return fmt.Sprintf("event source is not type of %s", eventSourceType) 111 } 112 113 // GetSecretValue retrieves the secret value from the secret in namespace with name and key 114 func GetSecretValue(ctx context.Context, client kubernetes.Interface, namespace string, selector *v1.SecretKeySelector) (string, error) { 115 secret, err := client.CoreV1().Secrets(namespace).Get(ctx, selector.Name, metav1.GetOptions{}) 116 if err != nil { 117 return "", err 118 } 119 val, ok := secret.Data[selector.Key] 120 if !ok { 121 return "", fmt.Errorf("secret '%s' does not have the key '%s'", selector.Name, selector.Key) 122 } 123 return string(val), nil 124 } 125 126 // GetEnvFromSecret retrieves the value of envFrom.secretRef 127 // "${secretRef.name}_" is expected to be defined as "prefix" 128 func GetEnvFromSecret(selector *v1.SecretKeySelector) (string, bool) { 129 return os.LookupEnv(fmt.Sprintf("%s_%s", selector.Name, selector.Key)) 130 } 131 132 // GenerateEnvFromSecretSpec builds a "envFrom" spec with a secretKeySelector 133 func GenerateEnvFromSecretSpec(selector *v1.SecretKeySelector) v1.EnvFromSource { 134 return v1.EnvFromSource{ 135 Prefix: selector.Name + "_", 136 SecretRef: &v1.SecretEnvSource{ 137 LocalObjectReference: v1.LocalObjectReference{ 138 Name: selector.Name, 139 }, 140 }, 141 } 142 } 143 144 // GetSecretFromVolume retrieves the value of mounted secret volume 145 // "/argo-events/secrets/${secretRef.name}/${secretRef.key}" is expected to be the file path 146 func GetSecretFromVolume(selector *v1.SecretKeySelector) (string, error) { 147 filePath, err := GetSecretVolumePath(selector) 148 if err != nil { 149 return "", err 150 } 151 data, err := os.ReadFile(filePath) 152 if err != nil { 153 return "", fmt.Errorf("failed to get secret value of name: %s, key: %s, %w", selector.Name, selector.Key, err) 154 } 155 // Secrets edited by tools like "vim" always have an extra invisible "\n" in the end, 156 // and it's often neglected, but it makes differences for some of the applications. 157 return strings.TrimSuffix(string(data), "\n"), nil 158 } 159 160 // GetSecretVolumePath returns the path of the mounted secret 161 func GetSecretVolumePath(selector *v1.SecretKeySelector) (string, error) { 162 if selector == nil { 163 return "", fmt.Errorf("secret key selector is nil") 164 } 165 return fmt.Sprintf("/argo-events/secrets/%s/%s", selector.Name, selector.Key), nil 166 } 167 168 // GetConfigMapFromVolume retrieves the value of mounted config map volume 169 // "/argo-events/config/${configMapRef.name}/${configMapRef.key}" is expected to be the file path 170 func GetConfigMapFromVolume(selector *v1.ConfigMapKeySelector) (string, error) { 171 filePath, err := GetConfigMapVolumePath(selector) 172 if err != nil { 173 return "", err 174 } 175 data, err := os.ReadFile(filePath) 176 if err != nil { 177 return "", fmt.Errorf("failed to get configMap value of name: %s, key: %s, %w", selector.Name, selector.Key, err) 178 } 179 // Contents edied by tools like "vim" always have an extra invisible "\n" in the end, 180 // and it's often negleted, but it makes differences for some of the applications. 181 return strings.TrimSuffix(string(data), "\n"), nil 182 } 183 184 // GetConfigMapVolumePath returns the path of the mounted configmap 185 func GetConfigMapVolumePath(selector *v1.ConfigMapKeySelector) (string, error) { 186 if selector == nil { 187 return "", fmt.Errorf("configmap key selector is nil") 188 } 189 return fmt.Sprintf("/argo-events/config/%s/%s", selector.Name, selector.Key), nil 190 } 191 192 // GetEnvFromConfigMap retrieves the value of envFrom.configMapRef 193 // "${configMapRef.name}_" is expected to be defined as "prefix" 194 func GetEnvFromConfigMap(selector *v1.ConfigMapKeySelector) (string, bool) { 195 return os.LookupEnv(fmt.Sprintf("%s_%s", selector.Name, selector.Key)) 196 } 197 198 // GenerateEnvFromConfigMapSpec builds a "envFrom" spec with a configMapKeySelector 199 func GenerateEnvFromConfigMapSpec(selector *v1.ConfigMapKeySelector) v1.EnvFromSource { 200 return v1.EnvFromSource{ 201 Prefix: selector.Name + "_", 202 ConfigMapRef: &v1.ConfigMapEnvSource{ 203 LocalObjectReference: v1.LocalObjectReference{ 204 Name: selector.Name, 205 }, 206 }, 207 } 208 } 209 210 // GetTLSConfig returns a tls configuration for given cert and key or skips the certs if InsecureSkipVerify is true. 211 func GetTLSConfig(config *apicommon.TLSConfig) (*tls.Config, error) { 212 if config == nil { 213 return nil, fmt.Errorf("TLSConfig is nil") 214 } 215 216 if config.InsecureSkipVerify { 217 tlsConfig := &tls.Config{ 218 InsecureSkipVerify: true, 219 ClientAuth: 0, 220 } 221 return tlsConfig, nil 222 } 223 224 var caCertPath, clientCertPath, clientKeyPath string 225 var err error 226 if config.CACertSecret != nil { 227 caCertPath, err = GetSecretVolumePath(config.CACertSecret) 228 if err != nil { 229 return nil, err 230 } 231 } 232 233 if config.ClientCertSecret != nil { 234 clientCertPath, err = GetSecretVolumePath(config.ClientCertSecret) 235 if err != nil { 236 return nil, err 237 } 238 } 239 240 if config.ClientKeySecret != nil { 241 clientKeyPath, err = GetSecretVolumePath(config.ClientKeySecret) 242 if err != nil { 243 return nil, err 244 } 245 } 246 247 if len(caCertPath)+len(clientCertPath)+len(clientKeyPath) == 0 { 248 // None of 3 is configured 249 return nil, fmt.Errorf("invalid tls config, neither of caCertSecret, clientCertSecret and clientKeySecret is configured") 250 } 251 252 if len(clientCertPath)+len(clientKeyPath) > 0 && len(clientCertPath)*len(clientKeyPath) == 0 { 253 // Only one of clientCertSecret and clientKeySecret is configured 254 return nil, fmt.Errorf("invalid tls config, both of clientCertSecret and clientKeySecret need to be configured") 255 } 256 257 c := &tls.Config{} 258 if len(caCertPath) > 0 { 259 caCert, err := os.ReadFile(caCertPath) 260 if err != nil { 261 return nil, fmt.Errorf("failed to read ca cert file %s, %w", caCertPath, err) 262 } 263 pool := x509.NewCertPool() 264 pool.AppendCertsFromPEM(caCert) 265 c.RootCAs = pool 266 } 267 268 if len(clientCertPath) > 0 && len(clientKeyPath) > 0 { 269 clientCert, err := tls.LoadX509KeyPair(clientCertPath, clientKeyPath) 270 if err != nil { 271 return nil, fmt.Errorf("failed to load client cert key pair %s, %w", caCertPath, err) 272 } 273 c.Certificates = []tls.Certificate{clientCert} 274 } 275 return c, nil 276 } 277 278 // VolumesFromSecretsOrConfigMaps builds volumes and volumeMounts spec based on 279 // the obj and its children's secretKeyselector or configMapKeySelector 280 func VolumesFromSecretsOrConfigMaps(t reflect.Type, objs ...interface{}) ([]v1.Volume, []v1.VolumeMount) { 281 resultVolumes := []v1.Volume{} 282 resultMounts := []v1.VolumeMount{} 283 values := []interface{}{} 284 285 for _, obj := range objs { 286 values = append(values, findTypeValues(obj, t)...) 287 } 288 if len(values) == 0 { 289 return resultVolumes, resultMounts 290 } 291 292 switch t { 293 case SecretKeySelectorType: 294 for _, v := range values { 295 selector := v.(*v1.SecretKeySelector) 296 vol, mount := GenerateSecretVolumeSpecs(selector) 297 resultVolumes = append(resultVolumes, vol) 298 resultMounts = append(resultMounts, mount) 299 } 300 case ConfigMapKeySelectorType: 301 for _, v := range values { 302 selector := v.(*v1.ConfigMapKeySelector) 303 vol, mount := GenerateConfigMapVolumeSpecs(selector) 304 resultVolumes = append(resultVolumes, vol) 305 resultMounts = append(resultMounts, mount) 306 } 307 default: 308 } 309 return uniqueVolumes(resultVolumes), uniqueVolumeMounts(resultMounts) 310 } 311 312 // Find all the values obj's children matching provided type, type needs to be a pointer 313 func findTypeValues(obj interface{}, t reflect.Type) []interface{} { 314 result := []interface{}{} 315 value := reflect.ValueOf(obj) 316 findTypesRecursive(&result, value, t) 317 return result 318 } 319 320 func findTypesRecursive(result *[]interface{}, obj reflect.Value, t reflect.Type) { 321 if obj.Type() == t && obj.CanInterface() && !obj.IsNil() { 322 *result = append(*result, obj.Interface()) 323 } 324 switch obj.Kind() { 325 case reflect.Ptr: 326 objValue := obj.Elem() 327 // Check if it is nil 328 if !objValue.IsValid() { 329 return 330 } 331 findTypesRecursive(result, objValue, t) 332 case reflect.Interface: 333 objValue := obj.Elem() 334 // Check if it is nil 335 if !objValue.IsValid() { 336 return 337 } 338 findTypesRecursive(result, objValue, t) 339 case reflect.Struct: 340 for i := 0; i < obj.NumField(); i++ { 341 if obj.Field(i).CanInterface() { 342 findTypesRecursive(result, obj.Field(i), t) 343 } 344 } 345 case reflect.Slice: 346 for i := 0; i < obj.Len(); i++ { 347 findTypesRecursive(result, obj.Index(i), t) 348 } 349 case reflect.Map: 350 iter := obj.MapRange() 351 for iter.Next() { 352 findTypesRecursive(result, iter.Value(), t) 353 } 354 default: 355 return 356 } 357 } 358 359 // GenerateSecretVolumeSpecs builds a "volume" and "volumeMount"spec with a secretKeySelector 360 func GenerateSecretVolumeSpecs(selector *v1.SecretKeySelector) (v1.Volume, v1.VolumeMount) { 361 volName := strings.ReplaceAll("secret-"+selector.Name, "_", "-") 362 return v1.Volume{ 363 Name: volName, 364 VolumeSource: v1.VolumeSource{ 365 Secret: &v1.SecretVolumeSource{ 366 SecretName: selector.Name, 367 }, 368 }, 369 }, v1.VolumeMount{ 370 Name: volName, 371 ReadOnly: true, 372 MountPath: "/argo-events/secrets/" + selector.Name, 373 } 374 } 375 376 // GenerateConfigMapVolumeSpecs builds a "volume" and "volumeMount"spec with a configMapKeySelector 377 func GenerateConfigMapVolumeSpecs(selector *v1.ConfigMapKeySelector) (v1.Volume, v1.VolumeMount) { 378 volName := strings.ReplaceAll("cm-"+selector.Name, "_", "-") 379 return v1.Volume{ 380 Name: volName, 381 VolumeSource: v1.VolumeSource{ 382 ConfigMap: &v1.ConfigMapVolumeSource{ 383 LocalObjectReference: v1.LocalObjectReference{ 384 Name: selector.Name, 385 }, 386 }, 387 }, 388 }, v1.VolumeMount{ 389 Name: volName, 390 ReadOnly: true, 391 MountPath: "/argo-events/config/" + selector.Name, 392 } 393 } 394 395 func uniqueVolumes(vols []v1.Volume) []v1.Volume { 396 rVols := []v1.Volume{} 397 keys := make(map[string]bool) 398 for _, e := range vols { 399 if _, value := keys[e.Name]; !value { 400 keys[e.Name] = true 401 rVols = append(rVols, e) 402 } 403 } 404 return rVols 405 } 406 407 func uniqueVolumeMounts(mounts []v1.VolumeMount) []v1.VolumeMount { 408 rMounts := []v1.VolumeMount{} 409 keys := make(map[string]bool) 410 for _, e := range mounts { 411 if _, value := keys[e.Name]; !value { 412 keys[e.Name] = true 413 rMounts = append(rMounts, e) 414 } 415 } 416 return rMounts 417 } 418 419 // ElementsMatch returns true if the two provided string slices contain the same elements while avoiding duplications. 420 // WARN: this method avoids duplications. 421 func ElementsMatch(first []string, second []string) bool { 422 if len(first) == 0 && len(second) == 0 { 423 return true 424 } 425 if len(first) == 0 || len(second) == 0 { 426 return false 427 } 428 429 diff := make(map[string]int) 430 for _, str := range first { 431 diff[str] = 1 432 } 433 434 for _, str := range second { 435 if _, ok := diff[str]; !ok { 436 return false 437 } else { 438 diff[str] = 2 439 } 440 } 441 442 for _, v := range diff { 443 // 1: only exists in first 444 // 2: exists in both 445 if v < 2 { 446 return false 447 } 448 } 449 return true 450 } 451 452 // SliceContains checks if a string slice contains a specific string 453 func SliceContains(strSlice []string, targetStr string) bool { 454 for _, curr := range strSlice { 455 if curr == targetStr { 456 return true 457 } 458 } 459 return false 460 } 461 462 func GetImagePullPolicy() v1.PullPolicy { 463 imgPullPolicy := v1.PullAlways 464 if x := os.Getenv(EnvImagePullPolicy); x != "" { 465 imgPullPolicy = v1.PullPolicy(x) 466 } 467 return imgPullPolicy 468 } 469 470 func StructToMap(obj interface{}, output map[string]interface{}) error { 471 data, err := json.Marshal(obj) // Convert to a json string 472 if err != nil { 473 return err 474 } 475 476 return json.Unmarshal(data, &output) // Convert to a map 477 }