github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/lib/catalogsource/image_template.go (about) 1 package catalogsource 2 3 import ( 4 "fmt" 5 "regexp" 6 "strconv" 7 "strings" 8 "sync" 9 10 "github.com/sirupsen/logrus" 11 versionutil "k8s.io/apimachinery/pkg/util/version" 12 "k8s.io/apimachinery/pkg/version" 13 14 "github.com/operator-framework/api/pkg/operators/v1alpha1" 15 ) 16 17 const ( 18 19 // regex capture group names 20 21 // capGrpKubeMajorV is a capture group name for a kube major version 22 capGrpKubeMajorV = "kubemajorv" 23 // capGrpKubeMinorV is a capture group name for a kube minor version 24 capGrpKubeMinorV = "kubeminorv" 25 // capGrpKubePatchV is a capture group name for a kube patch version 26 capGrpKubePatchV = "kubepatchv" 27 28 // static templates 29 30 // TemplKubeMajorV is a template that represents the kube major version 31 TemplKubeMajorV = "{kube_major_version}" 32 // TemplKubeMinorV is a template that represents the kube minor version 33 TemplKubeMinorV = "{kube_minor_version}" 34 // TemplKubePatchV is a template that represents the kube patch version 35 TemplKubePatchV = "{kube_patch_version}" 36 37 // templateMissing represents a value that could not be obtained from the cluster 38 templateMissing = "missing" 39 40 // catalogImageTemplateAnnotation is OLM annotation. The annotation value that corresponds 41 // to this key is used as means to adjust a catalog source image, where templated 42 // values are replaced with values found in the cluster 43 CatalogImageTemplateAnnotation = "olm.catalogImageTemplate" 44 ) 45 46 // templateNameToReplacementValuesMap is storage for templates and their resolved values 47 // The values are initialized to variable "templateMissing" 48 var templateNameToReplacementValuesMap = map[string]string{} 49 50 // templateMutex is a package scoped mutex for synchronizing access to templateNameToReplacementValuesMap 51 var templateMutex sync.RWMutex 52 53 func init() { 54 // Handle known static templates 55 initializeIfNeeded(TemplKubeMajorV) 56 initializeIfNeeded(TemplKubeMinorV) 57 initializeIfNeeded(TemplKubePatchV) 58 } 59 60 // initializeIfNeeded sets the map to a "missing" value if its not already present 61 func initializeIfNeeded(templateKey string) { 62 templateMutex.Lock() 63 defer templateMutex.Unlock() 64 65 // have we encountered this template already? 66 if _, ok := templateNameToReplacementValuesMap[templateKey]; !ok { 67 // this is a new template, so default to missing value 68 templateNameToReplacementValuesMap[templateKey] = templateMissing 69 } 70 } 71 72 // resetMaps is only useful for test cases 73 func resetMaps() { 74 templateMutex.Lock() 75 templateNameToReplacementValuesMap = map[string]string{} 76 templateMutex.Unlock() 77 78 initializeIfNeeded(TemplKubeMajorV) 79 initializeIfNeeded(TemplKubeMinorV) 80 initializeIfNeeded(TemplKubePatchV) 81 } 82 83 type regexEntry struct { 84 captureGroup string 85 template string 86 } 87 88 func (r *regexEntry) String() string { 89 return fmt.Sprintf(`(?P<%s>%s)`, r.captureGroup, r.template) 90 } 91 92 type regexEntries []regexEntry 93 94 func (r regexEntries) String() string { 95 regexEntryAsString := []string{} 96 for _, regexEntry := range r { 97 regexEntryAsString = append(regexEntryAsString, regexEntry.String()) 98 } 99 result := strings.Join(regexEntryAsString, "|") 100 return result 101 } 102 103 var regexList = regexEntries{ 104 {capGrpKubeMajorV, TemplKubeMajorV}, 105 {capGrpKubeMinorV, TemplKubeMinorV}, 106 {capGrpKubePatchV, TemplKubePatchV}, 107 } 108 109 var regexImageTemplates = regexp.MustCompile(regexList.String()) 110 111 // ReplaceTemplates takes a catalog image reference containing templates (i.e. catalogImageTemplate) 112 // and attempts to replace the templates with actual values (if available). 113 // The return value processedCatalogImageTemplate represents the catalog image reference after processing. 114 // Callers of this function should check the unresolvedTemplates return value to determine 115 // if all values were properly resolved (i.e. empty slice means all items were resolved, and presence 116 // of a value in the slice means that the template was either not found in the cache or its value has not been 117 // fetched yet). Providing an empty catalogImageTemplate results in empty processedCatalogImageTemplate and 118 // zero length unresolvedTemplates 119 func ReplaceTemplates(catalogImageTemplate string) (processedCatalogImageTemplate string, unresolvedTemplates []string) { 120 templateMutex.RLock() 121 defer templateMutex.RUnlock() 122 123 // init to empty slice 124 unresolvedTemplates = []string{} 125 126 // templateReplacer function determines the replacement value for the given template 127 var templateReplacer = func(template string) string { 128 replacement, ok := templateNameToReplacementValuesMap[template] 129 if ok { 130 // found a template, but check if the value is missing and keep record of 131 // what templates were unresolved 132 if replacement == templateMissing { 133 unresolvedTemplates = append(unresolvedTemplates, template) 134 } 135 return replacement 136 } else { 137 // probably should not get here, but in case there is no match, 138 // just return template unchanged, but keep record of 139 // what templates were unresolved 140 unresolvedTemplates = append(unresolvedTemplates, template) 141 return template 142 } 143 } 144 145 // if image is present, perform template substitution 146 if catalogImageTemplate != "" { 147 processedCatalogImageTemplate = regexImageTemplates.ReplaceAllStringFunc(catalogImageTemplate, templateReplacer) 148 } 149 return 150 } 151 152 // GetCatalogTemplateAnnotation is a helper function to extract the catalog image template annotation. 153 // Returns empty string if value not set, otherwise returns annotation. 154 func GetCatalogTemplateAnnotation(catalogSource *v1alpha1.CatalogSource) string { 155 if catalogSource == nil { 156 return "" 157 } 158 if catalogImageTemplate, ok := catalogSource.GetAnnotations()[CatalogImageTemplateAnnotation]; !ok { 159 return "" 160 } else { 161 return catalogImageTemplate 162 } 163 } 164 165 func UpdateKubeVersion(serverVersion *version.Info, logger *logrus.Logger) { 166 templateMutex.Lock() 167 defer templateMutex.Unlock() 168 169 if serverVersion == nil { 170 logger.Warn("no server version provided") 171 return 172 } 173 174 // need to use the gitversion from version.info.String() because minor version is not always Uint value 175 // and patch version is not returned as a first class field 176 semver, err := versionutil.ParseSemantic(serverVersion.String()) 177 if err != nil { 178 logger.WithError(err).Error("unable to parse kube server version") 179 return 180 } 181 182 templateNameToReplacementValuesMap[TemplKubeMajorV] = strconv.FormatUint(uint64(semver.Major()), 10) 183 templateNameToReplacementValuesMap[TemplKubeMinorV] = strconv.FormatUint(uint64(semver.Minor()), 10) 184 templateNameToReplacementValuesMap[TemplKubePatchV] = strconv.FormatUint(uint64(semver.Patch()), 10) 185 }