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  }