280 lines
9.6 KiB
Python
Executable File
280 lines
9.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
##
|
|
# This script was tested with Python 3.7.4, Go 1.14.4+, and PyYAML 5.1.2
|
|
# installed in a virtual environment.
|
|
# This script assumes you have the Python package manager 'pip' installed.
|
|
#
|
|
# This script updates the generated reference documentation.
|
|
# See https://kubernetes.io/docs/contribute/generate-ref-docs/kubernetes-components/
|
|
# for further details.
|
|
#
|
|
# This script checks to make sure Go and PyYAML have been installed.
|
|
# The reference docs are generated by a Go command so the Go binary must be
|
|
# in your PATH.
|
|
#
|
|
# A temp "work_dir" is created and is the path where repos will be cloned.
|
|
# The work_dir is printed out so you can remove it
|
|
# when you no longer need the contents.
|
|
# This work_dir will temporarily become the GOPATH.
|
|
#
|
|
# To execute the script from the website/update-imported-docs directory:
|
|
# ./update-imported-docs.py <config_file> <k8s_release>
|
|
# Config files:
|
|
# reference.yml use this to update the reference docs
|
|
# release.yml use this to auto-generate/import release notes
|
|
# K8S_RELEASE: provide a valid release tag such as, 1.17.0
|
|
##
|
|
|
|
import argparse
|
|
import glob
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import platform
|
|
|
|
error_msgs = []
|
|
|
|
# pip should be installed when Python is installed, but just in case...
|
|
if not (shutil.which('pip') or shutil.which('pip3')):
|
|
error_msgs.append(
|
|
"Install pip so you can install PyYAML. https://pip.pypa.io/en/stable/installation")
|
|
|
|
reqs = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze'])
|
|
installed_packages = [r.decode().split('==')[0] for r in reqs.split()]
|
|
if 'PyYAML' not in installed_packages:
|
|
error_msgs.append(
|
|
"Please ensure the PyYAML package is installed; see https://pypi.org/project/PyYAML")
|
|
else:
|
|
import yaml
|
|
|
|
if not shutil.which('go'):
|
|
error_msgs.append(
|
|
"Go must be installed. See https://golang.org/doc/install")
|
|
|
|
|
|
def process_links(content, remote_prefix, sub_path):
|
|
"""Process markdown links found in the docs."""
|
|
|
|
def analyze(match_obj):
|
|
ankor = match_obj.group('ankor')
|
|
target = match_obj.group('target')
|
|
if not (target.startswith("https://") or
|
|
target.startswith("mailto:") or
|
|
target.startswith("#")):
|
|
if target.startswith("/"):
|
|
target_list = remote_prefix, target[1:]
|
|
target = "/".join(target_list)
|
|
else:
|
|
target_list = remote_prefix, sub_path, target
|
|
target = "/".join(target_list)
|
|
|
|
return "[%s](%s)" % (ankor, target)
|
|
|
|
# Links are in the form '[text](url)'
|
|
link_regex = re.compile(r"\[(?P<ankor>.*)\]\((?P<target>.*)\)")
|
|
content = re.sub(link_regex, analyze, content)
|
|
|
|
h1_regex = re.compile("^(# .*)?\n")
|
|
content = re.sub(h1_regex, "", content)
|
|
|
|
return content
|
|
|
|
|
|
def process_kubectl_links(content):
|
|
"""Update markdown links found in the SeeAlso section of kubectl page.
|
|
Example:[kubectl annotate](/docs/reference/generated/kubectl/kubectl-commands#annotate)
|
|
"""
|
|
|
|
def analyze(match_obj):
|
|
ankor = match_obj.group('ankor')
|
|
target = match_obj.group('target')
|
|
if target.endswith(".md") and target.startswith("kubectl"):
|
|
ankor_list = ankor.split("kubectl ")
|
|
target = "/docs/reference/generated/kubectl/kubectl-commands" + "#" + \
|
|
ankor_list[1]
|
|
return "[%s](%s)" % (ankor, target)
|
|
|
|
# Links are in the form '[text](url)'
|
|
link_regex = re.compile(r"\[(?P<ankor>.*)\]\((?P<target>.*?)\)")
|
|
content = re.sub(link_regex, analyze, content)
|
|
|
|
return content
|
|
|
|
|
|
def process_file(src, dst, repo_path, repo_dir, root_dir, gen_absolute_links):
|
|
"""Process a file element.
|
|
|
|
:param src: A string containing the relative path of a source file. The
|
|
string may contain wildcard characters such as '*' or '?'.
|
|
:param dst: The path for the destination file. The string can be a
|
|
directory name or a file name.
|
|
:param repo_path:
|
|
:param repo_dir:
|
|
:param root_dir:
|
|
:param gen_absolute_links:
|
|
"""
|
|
pattern = os.path.join(repo_dir, repo_path, src)
|
|
dst_path = os.path.join(root_dir, dst)
|
|
|
|
for src in glob.glob(pattern):
|
|
# we don't dive into subdirectories
|
|
if not os.path.isfile(src):
|
|
print("[Error] skipping non-regular path {}".format(src))
|
|
continue
|
|
|
|
content = ""
|
|
try:
|
|
with open(src, "r") as srcFile:
|
|
content = srcFile.read()
|
|
except Exception as ex:
|
|
print("[Error] failed in reading source file: ".format(ex))
|
|
continue
|
|
|
|
dst = dst_path
|
|
if dst_path.endswith("/"):
|
|
base_name = os.path.basename(src)
|
|
dst = os.path.join(dst, base_name)
|
|
|
|
try:
|
|
print("Writing doc: " + dst)
|
|
with open(dst, "w") as dstFile:
|
|
if gen_absolute_links:
|
|
src_dir = os.path.dirname(src)
|
|
remote_prefix = repo_path + "/tree/master"
|
|
content = process_links(content, remote_prefix, src_dir)
|
|
if dst.endswith("kubectl.md"):
|
|
print("Processing kubectl links")
|
|
content = process_kubectl_links(content)
|
|
dstFile.write(content)
|
|
except Exception as ex:
|
|
print("[Error] failed in writing target file {}: {}".format(dst, ex))
|
|
continue
|
|
|
|
|
|
def parse_input_args():
|
|
"""
|
|
Parse command line argument
|
|
'config_file' is the first argument; it should be one of the YAML
|
|
files in this same directory
|
|
'k8s_release' is the second argument; provide the release version
|
|
:return: parsed argument
|
|
"""
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('config_file', type=str,
|
|
help="reference.yml to generate reference docs; "
|
|
"release.yml to generate release notes")
|
|
parser.add_argument('k8s_release', type=str,
|
|
help="k8s release version, ex: 1.17.0"
|
|
)
|
|
return parser.parse_args()
|
|
|
|
|
|
def main():
|
|
"""The main entry of the program."""
|
|
if len(error_msgs) > 0:
|
|
for msg in error_msgs:
|
|
print(msg + "\n")
|
|
return -2
|
|
|
|
# first parse input argument
|
|
in_args = parse_input_args()
|
|
config_file = in_args.config_file
|
|
print("config_file is {}".format(config_file))
|
|
|
|
# second parse input argument
|
|
k8s_release = in_args.k8s_release
|
|
print("k8s_release is {}".format(k8s_release))
|
|
|
|
# if release string does not contain patch num, add zero
|
|
if len(k8s_release) == 4:
|
|
k8s_release = k8s_release + ".0"
|
|
print("k8s_release updated to {}".format(k8s_release))
|
|
|
|
curr_dir = os.path.dirname(os.path.abspath(__file__))
|
|
print("curr_dir {}".format(curr_dir))
|
|
root_dir = os.path.realpath(os.path.join(curr_dir, '..'))
|
|
print("root_dir {}".format(root_dir))
|
|
|
|
try:
|
|
config_data = yaml.full_load(open(config_file, 'r'))
|
|
except Exception as ex:
|
|
# to catch when a user specifies a file that does not exist
|
|
print("[Error] failed in loading config file - {}".format(str(ex)))
|
|
return -2
|
|
|
|
os.chdir(root_dir)
|
|
|
|
# create the temp work_dir
|
|
try:
|
|
print("Making temp work_dir")
|
|
work_dir = tempfile.mkdtemp(
|
|
dir='/tmp' if platform.system() == 'Darwin' else tempfile.gettempdir()
|
|
)
|
|
except OSError as ose:
|
|
print("[Error] Unable to create temp work_dir {}; error: {}"
|
|
.format(work_dir, ose))
|
|
return -2
|
|
|
|
print("Working dir {}".format(work_dir))
|
|
|
|
for repo in config_data["repos"]:
|
|
if "name" not in repo:
|
|
print("[Error] repo missing name")
|
|
continue
|
|
repo_name = repo["name"]
|
|
|
|
if "remote" not in repo:
|
|
print("[Error] repo {} missing repo path".format(repo_name))
|
|
continue
|
|
repo_remote = repo["remote"]
|
|
|
|
remote_regex = re.compile(r"^https://(?P<prefix>.*)\.git$")
|
|
matches = remote_regex.search(repo_remote)
|
|
if not matches:
|
|
print("[Error] repo path for {} is invalid".format(repo_name))
|
|
continue
|
|
|
|
repo_path = os.path.join("src", matches.group('prefix'))
|
|
|
|
os.chdir(work_dir)
|
|
print("Cloning repo {}".format(repo_name))
|
|
cmd = "git clone --depth=1 -b {0} {1} {2}".format(
|
|
repo["branch"], repo_remote, repo_path)
|
|
res = subprocess.call(cmd, shell=True)
|
|
if res != 0:
|
|
print("[Error] failed in cloning repo {}".format(repo_name))
|
|
continue
|
|
|
|
os.chdir(repo_path)
|
|
if "generate-command" in repo:
|
|
gen_cmd = repo["generate-command"]
|
|
gen_cmd = "export K8S_RELEASE=" + k8s_release + "\n" + \
|
|
"export GOPATH=" + work_dir + "\n" + \
|
|
"export K8S_ROOT=" + work_dir + \
|
|
"/src/k8s.io/kubernetes" + "\n" + \
|
|
"export K8S_WEBROOT=" + root_dir + "\n" + gen_cmd
|
|
print("Generating docs for {} with {}".format(repo_name, gen_cmd))
|
|
res = subprocess.call(gen_cmd, shell=True)
|
|
if res != 0:
|
|
print("[Error] failed in generating docs for {}".format(
|
|
repo_name))
|
|
continue
|
|
|
|
os.chdir(root_dir)
|
|
for f in repo["files"]:
|
|
process_file(f['src'], f['dst'], repo_path, work_dir, root_dir,
|
|
"gen-absolute-links" in repo)
|
|
|
|
print("Completed docs update. Now run the following command to commit:\n\n"
|
|
" git add .\n"
|
|
" git commit -m <comment>\n"
|
|
" git push\n"
|
|
" delete temp dir {} when done ".format(work_dir))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|