github.com/vshn/k8ify@v1.1.2-0.20240502214202-6c9ed3ef0bf4/pkg/util/configutils.go (about) 1 package util 2 3 import ( 4 "fmt" 5 core "k8s.io/api/core/v1" 6 "maps" 7 "os" 8 "regexp" 9 "strconv" 10 "strings" 11 12 "github.com/docker/go-units" 13 "github.com/sirupsen/logrus" 14 "k8s.io/apimachinery/pkg/api/resource" 15 ) 16 17 var ( 18 reTrue = regexp.MustCompile("(?i)^true|yes|1$") 19 ) 20 21 // SubConfig extracts the keys that start with a given prefix from a given 22 // config map 23 // 24 // If there is a key that is EQUAL to the prefix, the entry identified by 25 // `defaultKey` will be updated. 26 // 27 // If there is a key that is equal to the prefix, AND an entry corresponding to 28 // `defaultKey`, the behavior is undefined! 29 func SubConfig(config map[string]string, prefix string, defaultKey string) map[string]string { 30 subConfig := make(map[string]string) 31 for key, value := range config { 32 if key == prefix { 33 subConfig[defaultKey] = value 34 } 35 if strings.HasPrefix(key, prefix+".") && len(key) > (len(prefix)+1) { 36 subKey := key[len(prefix)+1:] 37 subConfig[subKey] = value 38 } 39 } 40 return subConfig 41 } 42 43 // ConfigGetInt32 extracts the int32 value from the entry with the given key 44 // 45 // Returns `defaultValue` if either the entry does not exist, or is not a 46 // numeric value. 47 func ConfigGetInt32(config map[string]string, key string, defaultValue int32) int32 { 48 if valStr, ok := config[key]; ok { 49 if valInt, err := strconv.Atoi(valStr); err == nil { 50 return int32(valInt) 51 } 52 } 53 return defaultValue 54 } 55 56 // IsTruthy determines whether the given string is a representation of a "true" 57 // state. 58 // 59 // Concretly, it currently tests for "true", "yes" or "1", ignoring character 60 // cases. 61 func IsTruthy(s string) bool { 62 return reTrue.MatchString(s) 63 } 64 65 func GetBoolean(labels map[string]string, key string) bool { 66 if val, ok := labels[key]; ok { 67 return IsTruthy(val) 68 } 69 70 return false 71 } 72 73 func GetOptional(labels map[string]string, key string) *string { 74 if val, ok := labels[key]; ok { 75 return &val 76 } 77 78 return nil 79 } 80 81 // IsSingleton determine whether a resource (according to its labels) should be 82 // treated as a singleton. 83 func IsSingleton(labels map[string]string) bool { 84 return GetBoolean(labels, "k8ify.singleton") 85 } 86 87 // IsShared determines whether a volume is shared between replicas 88 func IsShared(labels map[string]string) bool { 89 return GetBoolean(labels, "k8ify.shared") 90 } 91 92 // StorageClass determines a storage class from a set of labels 93 func StorageClass(labels map[string]string) *string { 94 return GetOptional(labels, "k8ify.storageClass") 95 } 96 97 func StorageSizeRaw(labels map[string]string) *string { 98 return GetOptional(labels, "k8ify.size") 99 } 100 101 func Converter(labels map[string]string) *string { 102 return GetOptional(labels, "k8ify.converter") 103 } 104 105 func PartOf(labels map[string]string) *string { 106 return GetOptional(labels, "k8ify.partOf") 107 } 108 109 // StorageSize determines the requested storage size for a volume, or a 110 // fallback value. 111 func StorageSize(labels map[string]string, fallback string) resource.Quantity { 112 quantity := fallback 113 if q := StorageSizeRaw(labels); q != nil { 114 quantity = *q 115 } 116 117 size, err := units.RAMInBytes(quantity) 118 if err != nil { 119 logrus.Errorf("Invalid storage size: %q\n", quantity) 120 os.Exit(1) 121 } 122 123 return *resource.NewQuantity(size, resource.BinarySI) 124 } 125 126 func ServiceAccountName(labels map[string]string) string { 127 serviceAccountName := GetOptional(labels, "k8ify.serviceAccountName") 128 if serviceAccountName == nil { 129 return "" 130 } 131 return *serviceAccountName 132 } 133 134 func Annotations(labels map[string]string, kind string) map[string]string { 135 annotations := SubConfig(labels, "k8ify.annotations", "") 136 maps.Copy(annotations, SubConfig(labels, fmt.Sprintf("k8ify.%s.annotations", kind), "")) 137 delete(annotations, "") 138 return annotations 139 } 140 141 func ServiceType(labels map[string]string, port int32) core.ServiceType { 142 subConfig := SubConfig(labels, fmt.Sprintf("k8ify.exposePlain.%d", port), "") 143 if len(subConfig) == 0 { 144 return "" 145 } 146 if serviceType, ok := subConfig["type"]; ok { 147 // Go does not offer a way to list values of its "ENUMs", see https://github.com/golang/go/issues/19814 148 if serviceType == string(core.ServiceTypeClusterIP) { 149 return core.ServiceTypeClusterIP 150 } 151 if serviceType == string(core.ServiceTypeLoadBalancer) { 152 return core.ServiceTypeLoadBalancer 153 } 154 if serviceType == string(core.ServiceTypeExternalName) { 155 return core.ServiceTypeExternalName 156 } 157 if serviceType == string(core.ServiceTypeNodePort) { 158 return core.ServiceTypeNodePort 159 } 160 } 161 return core.ServiceTypeLoadBalancer 162 } 163 164 func ServiceExternalTrafficPolicy(labels map[string]string, port int32) core.ServiceExternalTrafficPolicy { 165 subConfig := SubConfig(labels, fmt.Sprintf("k8ify.exposePlain.%d", port), "") 166 if len(subConfig) == 0 { 167 return "" 168 } 169 if serviceType, ok := subConfig["externalTrafficPolicy"]; ok { 170 // Go does not offer a way to list values of its "ENUMs", see https://github.com/golang/go/issues/19814 171 if serviceType == string(core.ServiceExternalTrafficPolicyCluster) { 172 return core.ServiceExternalTrafficPolicyCluster 173 } 174 if serviceType == string(core.ServiceExternalTrafficPolicyLocal) { 175 return core.ServiceExternalTrafficPolicyLocal 176 } 177 } 178 return core.ServiceExternalTrafficPolicyLocal 179 } 180 181 func ServiceHealthCheckNodePort(labels map[string]string, port int32) int32 { 182 subConfig := SubConfig(labels, fmt.Sprintf("k8ify.exposePlain.%d", port), "") 183 if len(subConfig) == 0 { 184 return 0 185 } 186 healthCheckNodePort := ConfigGetInt32(subConfig, "healthCheckNodePort", 0) 187 if healthCheckNodePort > 65535 || healthCheckNodePort < 0 { 188 return 0 189 } 190 return healthCheckNodePort 191 }