github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/k8s/names.go (about) 1 package k8s 2 3 import ( 4 "fmt" 5 "strings" 6 7 "k8s.io/apimachinery/pkg/runtime/schema" 8 ) 9 10 const fmtduplicateYAMLDetectedError = "Duplicate YAML Entity: %s has been detected across one or more resources. Only one specification per entity can be applied to the cluster; to ensure expected behavior, remove the duplicate specifications." 11 12 func DuplicateYAMLDetectedError(duplicatedYaml string) string { 13 return fmt.Sprintf(fmtduplicateYAMLDetectedError, duplicatedYaml) 14 } 15 16 func UniqueNames(entities []K8sEntity, minComponents int) []string { 17 meta := make([]EntityMeta, len(entities)) 18 for i := range entities { 19 meta[i] = entities[i] 20 } 21 return UniqueNamesMeta(meta, minComponents) 22 } 23 24 // Calculates names for workloads by using the shortest uniquely matching identifiers 25 func UniqueNamesMeta(es []EntityMeta, minComponents int) []string { 26 ret := make([]string, len(es)) 27 // how many resources potentially map to a given name 28 counts := make(map[string]int) 29 // count how many entities want each potential name 30 for _, e := range es { 31 for _, name := range potentialNames(e, minComponents) { 32 counts[name]++ 33 } 34 } 35 36 // for each entity, take the shortest name that is uniquely wanted by that entity 37 for i, e := range es { 38 names := potentialNames(e, minComponents) 39 40 for _, name := range names { 41 if counts[name] == 1 { 42 ret[i] = name 43 break 44 } 45 } 46 if ret[i] == "" { 47 // If we hit this case, this means we have two resources with the same 48 // name/kind/namespace/group This usually means the user is trying to 49 // deploy the same resource twice. Kubernetes will not treat these as 50 // unique. 51 // 52 // Ideally, we should use the k8s object index to remove these before they 53 // get to this point. This only happens if the user has specified 54 // k8s_yaml(allow_duplicates). 55 // 56 // But for now, append the index to the name to make it unique 57 ret[i] = fmt.Sprintf("%s:%d", names[len(names)-1], i) 58 } 59 } 60 61 return ret 62 } 63 64 // FragmentsToEntities maps all possible fragments (e.g. foo, foo:secret, foo:secret:default) to the k8s entity or entities that they correspond to 65 func FragmentsToEntities(es []K8sEntity) map[string][]K8sEntity { 66 ret := make(map[string][]K8sEntity, len(es)) 67 68 for _, e := range es { 69 names := potentialNames(e, 1) 70 for _, name := range names { 71 if a, ok := ret[name]; ok { 72 ret[name] = append(a, e) 73 } else { 74 ret[name] = []K8sEntity{e} 75 } 76 } 77 } 78 79 return ret 80 } 81 82 // returns a list of potential names, in order of preference 83 func potentialNames(e EntityMeta, minComponents int) []string { 84 gvk := e.GVK() 85 86 // Empty string is synonymous with the core group 87 // Poorly documented, but check it out here https://kubernetes.io/docs/reference/access-authn-authz/authorization/#review-your-request-attributes 88 group := gvk.Group 89 if group == "" { 90 group = "core" 91 } 92 93 components := []string{ 94 e.Name(), 95 gvk.Kind, 96 e.Namespace().String(), 97 group, 98 } 99 var ret []string 100 for i := minComponents - 1; i < len(components); i++ { 101 ret = append(ret, strings.ToLower(SelectorStringFromParts(components[:i+1]))) 102 } 103 return ret 104 } 105 106 type EntityMeta interface { 107 Name() string 108 Namespace() Namespace 109 GVK() schema.GroupVersionKind 110 }