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  }