github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/core/assumes/sat_checker.go (about) 1 // Copyright 2021 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package assumes 5 6 import ( 7 chassumes "github.com/juju/charm/v12/assumes" 8 "github.com/juju/errors" 9 ) 10 11 // A link to a web page with additional information about features, 12 // the Juju versions that support them etc. 13 const featureDocsURL = "https://juju.is/docs/olm/supported-features" 14 15 // satisfyExpr checks whether the feature set contents satisfy the provided 16 // "assumes" expression. The function can process either feature or composite 17 // expressions. 18 func satisfyExpr(fs FeatureSet, expr chassumes.Expression, exprTreeDepth int) error { 19 switch expr := expr.(type) { 20 case chassumes.FeatureExpression: 21 return satisfyFeatureExpr(fs, expr) 22 case chassumes.CompositeExpression: 23 return satisfyCompositeExpr(fs, expr, exprTreeDepth) 24 default: 25 return errors.NotSupportedf("assumes expression type %q", expr.Type()) 26 } 27 } 28 29 // satisfyExpr checks whether the feature set contents satisfy the provided 30 // "assumes" feature expression. 31 // 32 // The expression is matched if the feature set contains the required feature 33 // name and any of the following conditions is true: 34 // a) The feature set entry OR the assumes expression does not specify a version. 35 // 2. Both the feature set entry AND the assumes expression specify versions 36 // AND the required version constraint (>= or <) is satisfied. 37 func satisfyFeatureExpr(fs FeatureSet, expr chassumes.FeatureExpression) error { 38 supported, defined := fs.Get(expr.Name) 39 if !defined { 40 featDescr := UserFriendlyFeatureDescriptions[expr.Name] 41 return featureError( 42 expr.Name, featDescr, 43 "charm requires feature %q but model does not support it", expr.Name, 44 ) 45 } 46 47 // If the "assumes" feature expression does not specify a version or the 48 // provided feature omits its version, then the expression is always 49 // satisfied. 50 if expr.Version == nil || supported.Version == nil { 51 return nil 52 } 53 54 // Compare versions 55 var satisfied bool 56 switch expr.Constraint { 57 case chassumes.VersionGTE: 58 satisfied = supported.Version.Compare(*expr.Version) >= 0 59 case chassumes.VersionLT: 60 satisfied = supported.Version.Compare(*expr.Version) < 0 61 } 62 63 if satisfied { 64 return nil 65 } 66 67 var featDescr = supported.Description 68 if featDescr == "" { 69 // The feature set should always have a feature description. 70 // Try the fallback descriptions if it is missing. 71 featDescr = UserFriendlyFeatureDescriptions[featDescr] 72 } 73 return featureError( 74 expr.Name, featDescr, 75 "charm requires feature %q (version %s %s) but model currently supports version %s", 76 expr.Name, expr.Constraint, expr.Version, supported.Version, 77 ) 78 } 79 80 // satisfyCompositeExpr checks whether the feature set contents satisfy the 81 // provided "assumes" composite expression. 82 // 83 // For an any-of kind of expression, the sub-expression evaluation will be 84 // short-circuited when the first satisfied sub-expression is found. For all-of 85 // kind of expressions, all sub-expressions must be matched. 86 // 87 // If the expression cannot be satisfied, the function returns a multi-error 88 // value listing any detected conflicts. 89 func satisfyCompositeExpr(fs FeatureSet, expr chassumes.CompositeExpression, exprTreeDepth int) error { 90 var errList = make([]error, 0, len(expr.SubExpressions)) 91 for _, subExpr := range expr.SubExpressions { 92 err := satisfyExpr(fs, subExpr, exprTreeDepth+1) 93 if err == nil && expr.ExprType == chassumes.AnyOfExpression { 94 // We can short-circuit the check if this is an any-of 95 // expression and we found a matching subexpression. 96 return nil 97 } else if err != nil { 98 errList = append(errList, err) 99 } 100 } 101 102 if len(errList) == 0 { 103 return nil 104 } 105 106 // The root of the expression tree is always an implicit all-of 107 // expression. To improve UX, we should avoid using the switch statement 108 // below which introduces yet another indentation level and instead 109 // emit a top-level descriptive message. 110 if exprTreeDepth == 0 { 111 return requirementsNotSatisfied("Charm feature requirements cannot be met:", errList) 112 } 113 114 switch expr.Type() { 115 case chassumes.AllOfExpression: 116 return notSatisfiedError("charm requires all of the following:", errList...) 117 case chassumes.AnyOfExpression: 118 return notSatisfiedError("charm requires at least one of the following:", errList...) 119 default: 120 return notSatisfiedError("charm requires "+string(expr.Type())+" of the following:", errList...) 121 } 122 }