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  }