k8s.io/test-infra@v0.0.0-20240520184403-27c6b4c223d8/gencred/merge_kubeconfig_secret.py (about) 1 #!/usr/bin/env python3 2 3 # Copyright 2020 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 """Merges a kubeconfig file into a kubeconfig file in a k8s secret giving 18 precedence to the secret.""" 19 20 # Requirements: kubectl (pointed at the correct cluster) and base64 21 # Example usage: 22 # ./merge_kubeconfig_secret.py \ 23 # --name=mykube \ 24 # --namespace=myspace \ 25 # --src-key=kube-old \ 26 # --dest-key=kube-new \ 27 # kubeconfig.yaml 28 29 import argparse 30 import os 31 import re 32 import subprocess 33 import sys 34 import tempfile 35 import time 36 37 reAutoKey = re.compile('^config-(\\d{8})$') 38 39 def call(cmd, **kwargs): 40 print('>>> %s' % cmd) 41 return subprocess.run( 42 cmd, 43 check=True, 44 shell=True, 45 stderr=sys.stderr, 46 stdout=subprocess.PIPE, 47 timeout=10,#seconds 48 universal_newlines=True, 49 **kwargs, 50 ) 51 52 def main(args): 53 print(args) 54 validateArgs(args) 55 print('Ensuring the kubeconfig current-context is not set.') 56 call('kubectl config unset current-context') 57 58 if args.auto: 59 # We need to determine the dest key automatically. 60 args.dest_key = time.strftime('config-%Y%m%d') 61 if not args.src_key: 62 # Also try to automatically determine the src key. 63 cmd = 'kubectl --context="%s" get secret --namespace "%s" "%s" -o go-template="{{range \\$key, \\$content := .data}}{{\\$key}};{{end}}"' % (args.context, args.namespace, args.name) #pylint: disable=line-too-long 64 keys = call(cmd).stdout.rstrip(";").split(";") 65 matches = [key for key in keys if reAutoKey.match(key)] 66 matches.sort(reverse=True) 67 if len(matches) == 0: 68 raise ValueError('The %s/%s secret does not contain any keys matching the "config-20200730" format. Please try again with --src-key set to the most recent key. Existing keys: %s' % (args.namespace, args.name, keys)) #pylint: disable=line-too-long 69 70 args.src_key = matches[0] 71 # Only enable pruning if we won't overwrite the source key. 72 # This ensures that a second update on the same day will still have a 73 # key to roll back to if needed. 74 args.prune = args.src_key != args.dest_key 75 print('Automatic mode: --src-key=%s --dest-key=%s' % (args.src_key, args.dest_key)) 76 77 with tempfile.TemporaryDirectory() as tmpdir: 78 orig = '%s/original' % (tmpdir) 79 merged = '%s/merged' % (tmpdir) 80 # Copy the current secret contents into a temp file. 81 cmd = 'kubectl --context="%s" get secret --namespace "%s" "%s" -o go-template="{{index .data \\"%s\\"}}" | base64 -d > %s' % (args.context, args.namespace, args.name, args.src_key, orig) #pylint: disable=line-too-long 82 call(cmd) 83 84 # Merge the existing and new kubeconfigs into another temp file. 85 env = os.environ.copy() 86 env['KUBECONFIG'] = '%s:%s' % (orig, args.kubeconfig_to_merge) 87 call( 88 'kubectl config view --raw > %s' % (merged), 89 env=env, 90 ) 91 92 # Update the secret with the merged config. 93 if args.prune: 94 # Pruning was request. Remove all keys except for dest and src (if different from dest). 95 srcflag = '' 96 if args.src_key != args.dest_key: 97 srcflag = '--from-file="%s=%s"' % (args.src_key, orig) 98 call('kubectl --context="%s" create secret generic --namespace "%s" "%s" --from-file="%s=%s" %s --dry-run -oyaml | kubectl --context="%s" replace -f -' % (args.context, args.namespace, args.name, args.dest_key, merged, srcflag, args.context)) #pylint: disable=line-too-long 99 else: 100 content = '' 101 with open(merged, 'r') as mergedFile: 102 yamlPad = ' ' 103 content = yamlPad + mergedFile.read() 104 content = content.replace('\n', '\n' + yamlPad) 105 call('kubectl --context="%s" patch --namespace "%s" "secret/%s" --patch "stringData:\n %s: |\n%s\n"' % (args.context, args.namespace, args.name, args.dest_key, content)) #pylint: disable=line-too-long 106 107 print('Successfully updated secret "%s/%s". The new kubeconfig is under the key "%s".' % (args.namespace, args.name, args.dest_key)) #pylint: disable=line-too-long 108 print('Don\'t forget to update any deployments or podspecs that use the secret to reference the updated key!') #pylint: disable=line-too-long 109 110 def validateArgs(args): 111 if args.auto: 112 if args.dest_key: 113 raise ValueError("--dest-key must be omitted when --auto is used.") 114 else: 115 if not args.src_key or not args.dest_key: 116 raise ValueError("--src-key and --dest-key are required unless --auto is used.") 117 118 119 if __name__ == '__main__': 120 parser = argparse.ArgumentParser(description='Merges the provided kubeconfig file into a kubeconfig file living in a kubernetes secret in order to add new cluster contexts to the secret. Requires kubectl and base64.') #pylint: disable=line-too-long 121 parser.add_argument( 122 '--context', 123 help='The kubectl context of the cluster containing the secret.', 124 required=True, 125 ) 126 parser.add_argument( 127 '--name', 128 help='The name of the k8s secret containing the kubeconfig file to add to.', 129 default='kubeconfig', 130 ) 131 parser.add_argument( 132 '--namespace', 133 help='The namespace containing the kubeconfig k8s secret to add to.', 134 default='default', 135 ) 136 parser.add_argument( 137 '--src-key', 138 help='The key of the source kubeconfig file in the k8s secret.', 139 ) 140 parser.add_argument( 141 '--dest-key', 142 help='The destination key of the merged kubeconfig file in the k8s secret.', 143 ) 144 parser.add_argument( 145 'kubeconfig_to_merge', 146 help='Filepath of the kubeconfig file to merge into the kubeconfig secret.', 147 ) 148 parser.add_argument( 149 '--prune', 150 action='store_true', 151 help='Remove all secret keys besides the source and dest. This should be used periodically to delete old kubeconfigs and keep the secret size under control.', #pylint: disable=line-too-long 152 ) 153 parser.add_argument( 154 '--auto', 155 action='store_true', 156 help='Automatically determine --dest-key and optionally --src-key assuming keys are of the form "config-20200730". Pruning is enabled.', #pylint: disable=line-too-long 157 ) 158 159 main(parser.parse_args())