istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/resource/version.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package resource 16 17 import ( 18 "fmt" 19 "sort" 20 "strconv" 21 "strings" 22 23 "istio.io/istio/pilot/pkg/model" 24 "istio.io/istio/pkg/test/framework/config" 25 ) 26 27 var _ config.Value = &RevVerMap{} 28 29 // RevVerMap maps installed revisions to their Istio versions. 30 type RevVerMap map[string]IstioVersion 31 32 func (rv *RevVerMap) SetConfig(mi any) error { 33 m, ok := mi.(config.Map) 34 if !ok { 35 return fmt.Errorf("revisions map: expected map but got slice") 36 } 37 out := make(RevVerMap) 38 for k := range m { 39 version := m.String(k) 40 v, err := NewIstioVersion(version) 41 if err != nil { 42 return fmt.Errorf("could not parse %s as version: %w", 43 version, err) 44 } 45 out[k] = v 46 } 47 *rv = out 48 return nil 49 } 50 51 // Set parses IstioVersions from a string flag in the form "a=1.5.6,b,c=1.4". 52 // If no version is specified for a revision assume latest, represented as "" 53 func (rv *RevVerMap) Set(value string) error { 54 m := make(map[string]IstioVersion) 55 rvPairs := strings.Split(value, ",") 56 for _, rv := range rvPairs { 57 s := strings.Split(rv, "=") 58 rev := s[0] 59 if len(s) == 1 { 60 m[rev] = "" 61 } else if len(s) == 2 { 62 ver := s[1] 63 v, err := NewIstioVersion(ver) 64 if err != nil { 65 return fmt.Errorf("could not parse %s as version: %w", 66 ver, err) 67 } 68 m[rev] = v 69 } else { 70 return fmt.Errorf("invalid revision<->version pairing specified: %q", rv) 71 } 72 } 73 *rv = m 74 return nil 75 } 76 77 func (rv *RevVerMap) String() string { 78 if rv == nil { 79 return "" 80 } 81 var rvPairs []string 82 for rev, ver := range *rv { 83 if ver == "" { 84 ver = "latest" 85 } 86 rvPairs = append(rvPairs, 87 fmt.Sprintf("%s=%s", rev, ver)) 88 } 89 return strings.Join(rvPairs, ",") 90 } 91 92 // Versions returns the Istio versions present in the given RevVerMap. 93 func (rv *RevVerMap) Versions() IstioVersions { 94 if rv == nil { 95 return nil 96 } 97 var vers []IstioVersion 98 for _, v := range *rv { 99 vers = append(vers, v) 100 } 101 return vers 102 } 103 104 // Default returns the revision with the newest `IstioVersion`, or in the case of a tie, the first 105 // alphabetically. 106 func (rv *RevVerMap) Default() string { 107 if rv == nil || len(*rv) == 0 { 108 return "" 109 } 110 max := rv.Maximum() 111 var candidates []string 112 for rev, ver := range *rv { 113 if ver.Compare(max) == 0 { 114 candidates = append(candidates, rev) 115 } 116 } 117 if len(candidates) == 0 { 118 panic("could not find revision with max IstioVersion") 119 } 120 sort.Strings(candidates) 121 if candidates[0] == "default" { 122 return "" 123 } 124 return candidates[0] 125 } 126 127 // Minimum returns the minimum version from the revision-version mapping. 128 func (rv *RevVerMap) Minimum() IstioVersion { 129 return rv.Versions().Minimum() 130 } 131 132 // Maximum returns the maximum version from the revision-version mapping. 133 func (rv *RevVerMap) Maximum() IstioVersion { 134 return rv.Versions().Maximum() 135 } 136 137 // AtLeast returns true if the minimum Istio version under test is at least the given version. 138 func (rv *RevVerMap) AtLeast(v IstioVersion) bool { 139 return rv.Versions().Minimum().Compare(v) >= 0 140 } 141 142 // IsMultiVersion returns whether the associated IstioVersions have multiple specified versions. 143 func (rv *RevVerMap) IsMultiVersion() bool { 144 return rv != nil && len(*rv) > 0 145 } 146 147 // TemplateMap creates a map of revisions and versions suitable for templating. 148 func (rv *RevVerMap) TemplateMap() map[string]string { 149 if rv == nil { 150 return nil 151 } 152 templateMap := make(map[string]string) 153 if len(*rv) == 0 { 154 // if there are no entries, generate a dummy value so that we don't render 155 // deployment template with empty loop 156 templateMap[""] = "" 157 return templateMap 158 } 159 for r, v := range *rv { 160 templateMap[r] = string(v) 161 } 162 return templateMap 163 } 164 165 // IstioVersion is an Istio version running within a cluster. 166 type IstioVersion string 167 168 // IstioVersions represents a collection of Istio versions running in a cluster. 169 type IstioVersions []IstioVersion 170 171 // NewIstioVersion creates an IstioVersion with validation. 172 func NewIstioVersion(s string) (IstioVersion, error) { 173 // empty version string sentinel value for latest 174 if s == "" { 175 return "", nil 176 } 177 parts := strings.Split(s, ".") 178 if len(parts) < 2 || len(parts) > 3 { 179 return "", fmt.Errorf("cannot parse version from %s", s) 180 } 181 for _, part := range parts { 182 if _, err := strconv.Atoi(part); err != nil { 183 return "", fmt.Errorf("cannot use %s as version part", part) 184 } 185 } 186 return IstioVersion(s), nil 187 } 188 189 // Compare compares two Istio versions. Returns -1 if version "v" is less than "other", 0 if the same, 190 // and 1 if "v" is greater than "other". 191 func (v IstioVersion) Compare(other IstioVersion) int { 192 ver := model.ParseIstioVersion(string(v)) 193 otherVer := model.ParseIstioVersion(string(other)) 194 return ver.Compare(otherVer) 195 } 196 197 // Minimum returns the minimum from a set of IstioVersions 198 // returns empty value if no versions. 199 func (v IstioVersions) Minimum() IstioVersion { 200 if len(v) == 0 { 201 return "" 202 } 203 min := v[0] 204 for i := 1; i < len(v); i++ { 205 ver := v[i] 206 if ver.Compare(min) < 0 { 207 min = ver 208 } 209 } 210 return min 211 } 212 213 // Maximum returns the maximum from a set of IstioVersions 214 // returns empty value if no versions. 215 func (v IstioVersions) Maximum() IstioVersion { 216 if len(v) == 0 { 217 return "" 218 } 219 max := v[0] 220 for i := 1; i < len(v); i++ { 221 ver := v[i] 222 if ver.Compare(max) > 0 { 223 max = ver 224 } 225 } 226 return max 227 }