k8s.io/test-infra@v0.0.0-20240520184403-27c6b4c223d8/releng/config-rotator/main.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "errors" 21 "flag" 22 "fmt" 23 "log" 24 "os" 25 "strings" 26 27 gyaml "gopkg.in/yaml.v2" 28 "sigs.k8s.io/yaml" 29 30 "sigs.k8s.io/prow/pkg/config" 31 ) 32 33 type options struct { 34 configFile string 35 oldVersion string 36 newVersion string 37 } 38 39 func cdToRootDir() error { 40 if bazelWorkspace := os.Getenv("BUILD_WORKSPACE_DIRECTORY"); bazelWorkspace != "" { 41 if err := os.Chdir(bazelWorkspace); err != nil { 42 return fmt.Errorf("failed to chdir to bazel workspace (%s): %w", bazelWorkspace, err) 43 } 44 return nil 45 } 46 return nil 47 } 48 49 func parseFlags() options { 50 o := options{} 51 flag.StringVar(&o.configFile, "config-file", "", "Path to the job config") 52 flag.StringVar(&o.oldVersion, "old", "", "Old version (beta, stable1, or stable2)") 53 flag.StringVar(&o.newVersion, "new", "", "New version (stable1, stable2, or stable3)") 54 flag.Parse() 55 return o 56 } 57 58 func validateOptions(o options) error { 59 if o.configFile == "" { 60 return errors.New("--config-file must be specified") 61 } 62 if o.newVersion == "" { 63 return errors.New("--new must be specified") 64 } 65 if o.oldVersion == "" { 66 return errors.New("--old must be specified") 67 } 68 return nil 69 } 70 71 func updateString(s, old, new string) string { 72 return strings.ReplaceAll(s, old, new) 73 } 74 75 func updateJobBase(j *config.JobBase, old, new string) { 76 j.Name = updateString(j.Name, old, new) 77 for i := range j.Spec.Containers { 78 c := &j.Spec.Containers[i] 79 for j := range c.Args { 80 c.Args[j] = updateGenericVersionMarker(c.Args[j]) 81 c.Args[j] = updateString(c.Args[j], old, new) 82 } 83 for j := range c.Command { 84 c.Command[j] = updateGenericVersionMarker(c.Command[j]) 85 c.Command[j] = updateString(c.Command[j], old, new) 86 } 87 } 88 } 89 90 func updateEverything(c *config.JobConfig, old, new string) { 91 for _, presubmits := range c.PresubmitsStatic { 92 for i := range presubmits { 93 updateJobBase(&presubmits[i].JobBase, old, new) 94 } 95 } 96 for _, postsubmits := range c.PostsubmitsStatic { 97 for i := range postsubmits { 98 updateJobBase(&postsubmits[i].JobBase, old, new) 99 } 100 } 101 for i := range c.Periodics { 102 p := &c.Periodics[i] 103 updateJobBase(&p.JobBase, old, new) 104 for k, v := range p.Annotations { 105 if k == "fork-per-release-periodic-interval" { 106 f := strings.Fields(v) 107 if len(f) > 0 { 108 p.Interval = f[0] 109 p.Annotations[k] = strings.Join(f[1:], " ") 110 } 111 } 112 } 113 for k, v := range p.Annotations { 114 if k == "fork-per-release-cron" { 115 f := strings.Split(v, ", ") 116 if len(f) > 0 { 117 p.Cron = f[0] 118 p.Annotations[k] = strings.Join(f[1:], ", ") 119 } 120 } 121 } 122 for j := range p.Tags { 123 p.Tags[j] = updateString(p.Tags[j], old, new) 124 } 125 } 126 } 127 128 func main() { 129 if err := cdToRootDir(); err != nil { 130 log.Fatalln(err) 131 } 132 o := parseFlags() 133 if err := validateOptions(o); err != nil { 134 log.Fatalln(err) 135 } 136 c, err := config.ReadJobConfig(o.configFile) 137 if err != nil { 138 log.Fatalf("Failed to load job config: %v\n", err) 139 } 140 updateEverything(&c, o.oldVersion, o.newVersion) 141 142 // We need to use FutureLineWrap because "fork-per-release-cron" is too long 143 // causing the annotation value to be split into two lines. 144 // We use gopkg.in/yaml here because sigs.k8s.io/yaml doesn't export this 145 // function. sigs.k8s.io/yaml uses gopkg.in/yaml under the hood. 146 gyaml.FutureLineWrap() 147 148 output, err := yaml.Marshal(map[string]interface{}{ 149 "presubmits": c.PresubmitsStatic, 150 "postsubmits": c.PostsubmitsStatic, 151 "periodics": c.Periodics, 152 }) 153 if err != nil { 154 log.Fatalf("Failed to marshal new presubmits: %v\n", err) 155 } 156 157 if err := os.WriteFile(o.configFile, output, 0666); err != nil { 158 log.Fatalf("Failed to write new presubmits: %v.\n", err) 159 } 160 } 161 162 // Version marker logic 163 164 const ( 165 markerDefault = "k8s-master" 166 markerBeta = "k8s-beta" 167 markerStableOne = "k8s-stable1" 168 markerStableTwo = "k8s-stable2" 169 markerStableThree = "k8s-stable3" 170 markerStableFour = "k8s-stable4" 171 ) 172 173 var allowedMarkers = []string{ 174 markerDefault, 175 markerBeta, 176 markerStableOne, 177 markerStableTwo, 178 markerStableThree, 179 markerStableFour, 180 } 181 182 func getMarker(s string) string { 183 var marker string 184 for _, m := range allowedMarkers { 185 if strings.Contains(s, m) { 186 marker = m 187 break 188 } 189 } 190 191 return marker 192 } 193 194 func updateGenericVersionMarker(s string) string { 195 var newMarker string 196 197 marker := getMarker(s) 198 switch marker { 199 case markerDefault: 200 newMarker = markerBeta 201 case markerBeta: 202 newMarker = markerStableOne 203 case markerStableOne: 204 newMarker = markerStableTwo 205 case markerStableTwo: 206 newMarker = markerStableThree 207 case markerStableThree: 208 newMarker = markerStableFour 209 default: 210 newMarker = marker 211 } 212 213 return updateString(s, marker, newMarker) 214 }