ClusterAPI.imageBuilder/hack/image-new-kube.py

193 lines
7.4 KiB
Python
Raw Normal View History

2023-02-22 20:24:42 +00:00
#!/usr/bin/env python3
# Copyright 2019 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import re
import requests
import sys
import tarfile
from io import BytesIO
KUBE_SRC = "https://dl.k8s.io"
KUBE_RESOLVED_SEM = "kubernetes_semver"
KUBE_RESOLVED_SRC = "kubernetes_http_source"
KUBE_RESOLVED_VER = "kubernetes_version"
# KubeVersionResolver is used for resolving Kubernetes version strings to the
# actual version and URL or package string that may be used to deploy
# Kubernetes.
class KubeVersionResolver(object):
# Resolve accepts a Kubernetes version string and returns a dictionary with
# information that can be used to deploy the provided version.
def Resolve(self, version):
if version == '':
raise Exception('version is required')
result = {
KUBE_RESOLVED_SEM: version,
KUBE_RESOLVED_SRC: 'pkg',
KUBE_RESOLVED_VER: version,
}
# When version is 'latest' then the returned dictionary points
# to the latest package for Kubernetes.
if version == 'latest':
return result
# Otherwise check to see if the provided version matches a
# managed package format. Technically the else clause could be
# descoped one level, but by placing the logic in the scope of
# the else clause, the scope of 'match' is isolated from the
# rest of this function.
else:
match = re.match(r'^(\d+\.\d+.\d+)\-\d+$', version)
if match:
version = match.groups(1)[0]
result[KUBE_RESOLVED_SEM] = f'v{version}'
return result
url = ''
if re.match(r'(?i)^https?:', version):
url = version
elif re.match(r'^v?\d+(?:\.\d+){0,3}(?:[.+-].+)?$', version):
if not version.startswith('v'):
version = f'v{version}'
url = f'{KUBE_SRC}/release/{version}'
elif re.match(r'^(ci|release)/.+$', version):
url = self.__resolve_build_url(version)
else:
raise Exception(f'Invalid Kubernetes version: {version}')
result[KUBE_RESOLVED_SRC] = url
version = self.__read_version_from_kube_tarball(url)
result[KUBE_RESOLVED_SEM] = version
result[KUBE_RESOLVED_VER] = version
return result
def __resolve_build_url(self, buildID):
url = f'{KUBE_SRC}/{buildID}'
# If the URL doesn't end with '.txt' then see if the URL is already valid.
if not url.endswith('.txt'):
# If there is a kubernetes tarball available at the root of the URL
# then it is already a valid URL.
try:
r = requests.head(url, allow_redirects=True)
if r.status_code >= 200 and r.status_code <= 299:
return url
except:
pass
# The URL wasn't valid, so add '.txt' to the end and let's see if the
# URL points to a valid build.
url = f'{url}.txt'
# Do an HTTP GET on the txt file to get the actual Kubernetes version.
version = requests.get(url).text
version = version.strip()
if buildID.startswith('ci/'):
version = f'ci/{version}'
url = f'{KUBE_SRC}/{version}'
return url
def __read_version_from_kube_tarball(self, url):
url = f'{url}/kubernetes.tar.gz'
r = requests.get(url)
if not r.status_code == 200:
raise Exception(f'HTTP GET {url} failed: {r.status_code}')
b = BytesIO(r.content)
t = tarfile.open(fileobj=b, mode='r')
v = t.extractfile('kubernetes/version')
return v.read().strip().decode('utf-8')
if __name__ == '__main__':
import argparse
import textwrap
parser = argparse.ArgumentParser(
formatter_class=argparse.RawTextHelpFormatter,
description='Generates new Kubernetes image config',
epilog=textwrap.dedent(r'''
THE VERSION STRING
====================================================================
The version string not only determines what version of Kubernetes
will be installed, but also *how* Kubernetes is installed.
PLACEHOLDERS
====================================================================
The following placeholders are used in the examples below:
BASE_URI https://dl.k8s.io
K8S_TGZ kubernetes.tar.gz
PACKAGE MANAGER INSTALLATION
====================================================================
If the version string matches the pattern "^\d+\.\d+.\d+\-\d+$",
ex. 1.14.0-0, then Kubernetes is installed using the system's
package manager, such as yum or apt.
MANUAL INSTALLATION
====================================================================
If the string does not match the above pattern then the following
logic is used to resolve the string VALUE to a valid Kubernetes
version along with the URLs for the Kubernetes artifiacts required
for installation:
1. If the VALUE begins with "http:" or "https:" then the value is
treated as a URL and the Kubernetes version is obtained by reading
the "kuberentes/version" file from the URL "VALUE/K8S_TGZ".
2. If the VALUE matches a semantic version then the value is
treated as a release build and "BASE_URI/release/SEMVER"
is processed like the URL in step one.
3. If the VALUE begins with "ci/" then:
a. If VALUE does not end with ".txt" then a HEAD request is used
to check the existence of "BASE_URI/VALUE/K8S_TGZ":
i. If the HEAD request is successful then the URL is processed
like the one in step one.
ii. If the HEAD request fails then ".txt" is added to the end
of the URL and is processed by step 3b.
b. If VALUE *does* end with ".txt" then a GET request is used to
read "BASE_URI/VALUE" in order to get the dereferenced
version string. Then "BASE_URI/ci/DEREF" is processed
like the URL in step one.
4. If the VALUE begins with "release/" then the VALUE is processed
like step three, without the "ci/" prefix
The resolved URL is used to install Kuberentes from the set of
pre-built container images and binaries.
'''))
parser.add_argument('version',
nargs=1,
help='A Kubernetes version string')
args = parser.parse_args()
resolver = KubeVersionResolver()
result = resolver.Resolve(args.version[0])
data = json.dumps(result, indent=2)
print(data)