k8s.io/test-infra@v0.0.0-20240520184403-27c6b4c223d8/releng/prepare_release_branch.py (about) 1 #!/usr/bin/env python3 2 3 # Copyright 2019 The Kubernetes Authors. 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 17 # pylint can't figure out sh.bazel 18 # pylint: disable=no-member 19 20 import os 21 import sys 22 import glob 23 import re 24 25 import sh 26 import ruamel.yaml as yaml 27 28 import requests 29 30 TEST_CONFIG_YAML = "test_config.yaml" 31 JOB_CONFIG = "../config/jobs" 32 BRANCH_JOB_DIR = "../config/jobs/kubernetes/sig-release/release-branch-jobs" 33 34 max_config_count = 5 35 min_config_count = 3 36 37 suffixes = ['beta', 'stable1', 'stable2', 'stable3', 'stable4'] 38 39 class ToolError(Exception): 40 pass 41 42 def get_config_files(branch_path): 43 print("Retrieving config files...") 44 files = glob.glob(os.path.join(branch_path, "*.yaml")) 45 return files 46 47 def has_correct_amount_of_configs(branch_path): 48 print("Checking config count...") 49 files = get_config_files(branch_path) 50 if len(files) < min_config_count or len(files) > max_config_count: 51 print( 52 "Expected between %s and %s yaml files in %s, but found %s" % ( 53 min_config_count, 54 max_config_count, 55 branch_path, 56 len(files), 57 ) 58 ) 59 return False 60 return True 61 62 def check_version(branch_path): 63 print("Checking latest version...") 64 files = get_config_files(branch_path) 65 if not has_correct_amount_of_configs(branch_path): 66 raise ToolError("Incorrect release config count. Cannot continue.") 67 basenames = [os.path.splitext(os.path.basename(x))[0] for x in files] 68 numbers = sorted([list(map(int, x.split('.'))) for x in basenames]) 69 lowest = numbers[0] 70 for i, num in enumerate(numbers): 71 if num[1] != lowest[1] + i: 72 raise ToolError("Branches are not sequential.") 73 return numbers[-1] 74 75 76 def delete_stale_branch(branch_path, current_version): 77 print("Deleting stale branch...") 78 filename = '%d.%d.yaml' % (current_version[0], current_version[1] - 3) 79 filepath = os.path.join(branch_path, filename) 80 81 if os.path.exists(filepath): 82 os.unlink(filepath) 83 else: 84 print("the branch config (%s) does not exist" % filename) 85 86 87 def rotate_files(rotator_bin, branch_path, current_version): 88 print("Rotating files...") 89 for i in range(max_config_count - 1): 90 filename = '%d.%d.yaml' % (current_version[0], current_version[1] - i) 91 from_suffix = suffixes[i] 92 to_suffix = suffixes[i+1] 93 sh.Command(rotator_bin)( 94 old=from_suffix, 95 new=to_suffix, 96 config_file=os.path.join(branch_path, filename), 97 _fg=True) 98 99 100 def fork_new_file(forker_bin, branch_path, prowjob_path, current_version, go_version): 101 print("Forking new file...") 102 next_version = (current_version[0], current_version[1] + 1) 103 filename = '%d.%d.yaml' % (next_version[0], next_version[1]) 104 sh.Command(forker_bin)( 105 job_config=os.path.abspath(prowjob_path), 106 output=os.path.abspath(os.path.join(branch_path, filename)), 107 version='%d.%d' % next_version, 108 go_version=go_version, 109 _fg=True) 110 111 112 def update_generated_config(path, latest_version): 113 print("Updating test_config.yaml...") 114 with open(path, 'r') as f: 115 config = yaml.round_trip_load(f) 116 117 v = latest_version 118 for i, s in enumerate(suffixes): 119 vs = "%d.%d" % (v[0], v[1] + 1 - i) 120 markers = config['k8sVersions'][s] 121 markers['version'] = vs 122 for j, arg in enumerate(markers['args']): 123 markers['args'][j] = re.sub( 124 r'latest(-\d+\.\d+)?', 'latest-%s' % vs, arg) 125 126 node = config['nodeK8sVersions'][s] 127 for k, arg in enumerate(node['args']): 128 node['args'][k] = re.sub( 129 r'master|release-\d+\.\d+', 'release-%s' % vs, arg) 130 node['prowImage'] = node['prowImage'].rpartition('-')[0] + '-' + vs 131 132 with open(path, 'w') as f: 133 yaml.round_trip_dump(config, f) 134 135 136 def regenerate_files(generate_tests_bin, test_config): 137 print("Regenerating files...") 138 sh.Command(generate_tests_bin)( 139 yaml_config_path=test_config, 140 _fg=True) 141 142 def go_version_kubernetes_master(): 143 resp = requests.get( 144 'https://raw.githubusercontent.com/kubernetes/kubernetes/master/.go-version') 145 resp.raise_for_status() 146 data = resp.content.decode("utf-8") 147 return data 148 149 def main(): 150 if os.environ.get('BUILD_WORKSPACE_DIRECTORY'): 151 print("Please run me via make rule!") 152 print("make -C releng prepare-release-branch") 153 sys.exit(1) 154 rotator_bin = sys.argv[1] 155 forker_bin = sys.argv[2] 156 generate_tests_bin = sys.argv[3] 157 158 cur_file_dir = os.path.dirname(__file__) 159 branch_job_dir_abs = os.path.join(cur_file_dir, BRANCH_JOB_DIR) 160 161 version = check_version(branch_job_dir_abs) 162 print("Current version: %d.%d" % (version[0], version[1])) 163 164 go_version = go_version_kubernetes_master() 165 print("Current Go Version: %s" % go_version) 166 167 files = get_config_files(branch_job_dir_abs) 168 if len(files) > max_config_count: 169 print("There should be a maximum of %s release branch configs." % max_config_count) 170 print("Deleting the oldest config before rotation...") 171 172 delete_stale_branch(branch_job_dir_abs, version) 173 174 rotate_files(rotator_bin, branch_job_dir_abs, version) 175 176 fork_new_file(forker_bin, branch_job_dir_abs, 177 os.path.join(cur_file_dir, JOB_CONFIG), version, go_version) 178 179 update_generated_config(os.path.join(cur_file_dir, TEST_CONFIG_YAML), version) 180 181 regenerate_files(generate_tests_bin, os.path.join(cur_file_dir, TEST_CONFIG_YAML)) 182 183 184 if __name__ == "__main__": 185 main()