Skip to content

Commit 7991633

Browse files
author
Paul Fitzgerald
committed
feat(RELEASE-1675): auto generate README.md files for tasks/pipelines
Add readme_generator.sh, which generates README.md files for tasks and pipelines, as well as a check_readme.sh script and workflow to check that the README.md files in each task and pipeline match the output of readme-generator.sh. Also updated CONTRIBUTING.md to mention this new requirement. Add check in check_readme.sh to stop '.' from being added to the end of task/pipeline param descriptions. Signed-off-by: Paul Fitzgerald <pafitzge@redhat.com>
1 parent a1417a5 commit 7991633

5 files changed

Lines changed: 419 additions & 1 deletion

File tree

.github/scripts/check_readme.sh

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#!/bin/bash
2+
3+
# This script will verify that the README.md of all task and
4+
# pipeline directories provided matches the output of hack/readme_generator.sh
5+
# to ensure that README files are up to date.
6+
#
7+
# Task and pipeline directories are provided
8+
# either via README_ITEMS env var, or as arguments
9+
# when running the script.
10+
11+
CLI_README_ITEMS=()
12+
FAILED_ITEMS=()
13+
FAILED_PARAMS=()
14+
15+
show_help() {
16+
echo "Usage: $0 [item1] [item2] [...]"
17+
echo
18+
echo Flags:
19+
echo " --help: Show this help message"
20+
echo
21+
echo "Items are task or pipeline directories. They can be supplied"
22+
echo "either as arguments or via the README_ITEMS environment variable."
23+
echo
24+
echo "Examples:"
25+
echo " $0 tasks/managed/apply-mapping tasks/internal/*"
26+
echo
27+
echo " or"
28+
echo
29+
echo ' export README_ITEMS="tasks/managed/apply-mapping some/other/dir"'
30+
echo " $0"
31+
exit 1
32+
}
33+
34+
while [[ $# -gt 0 ]]; do
35+
case "$1" in
36+
--help)
37+
show_help
38+
;;
39+
--*)
40+
show_help
41+
;;
42+
*)
43+
CLI_README_ITEMS+=("$1")
44+
shift
45+
;;
46+
esac
47+
done
48+
49+
if [[ "${#CLI_README_ITEMS[@]}" -gt 0 ]]; then
50+
README_ITEMS=("${CLI_README_ITEMS[@]}")
51+
else
52+
read -r -a README_ITEMS <<< "${README_ITEMS[@]}"
53+
fi
54+
55+
if [ "${#README_ITEMS[@]}" -eq 0 ]
56+
then
57+
show_help
58+
fi
59+
60+
# Check that all directories exist. If not, fail
61+
for ITEM in "${README_ITEMS[@]}"
62+
do
63+
if [[ -d "$ITEM" ]]; then
64+
true
65+
else
66+
echo "Error: Invalid file or directory: $ITEM"
67+
exit 1
68+
fi
69+
done
70+
71+
for ITEM in "${README_ITEMS[@]}"
72+
do
73+
echo "Task/Pipeline item: $ITEM"
74+
ITEM_NAME=$(basename "$ITEM")
75+
ITEM_DIR=$(cut -d '/' -f -3 <<< "$ITEM")
76+
echo " Task/Pipeline name: $ITEM_NAME"
77+
78+
ITEM_PATH=${ITEM_DIR}/${ITEM_NAME}.yaml
79+
if [ ! -f "$ITEM_PATH" ]
80+
then
81+
echo " Error: Task/Pipeline file does not exist: $ITEM_PATH"
82+
exit 1
83+
fi
84+
85+
if [[ $(yq ".spec | has(\"description\")" "$ITEM_PATH") == "false" ]]; then
86+
echo " Error in $ITEM: Field '.spec.description' is missing in task"
87+
FAILED_ITEMS+=("$ITEM - no description")
88+
fi
89+
90+
# Check description doesn't have '.' or ','
91+
PARAMS=$(yq .spec.params "$ITEM_PATH")
92+
for ((i=0; i < $(yq length <<< "$PARAMS"); i++)); do
93+
# Get rid of newlines and remove trailing whitespace
94+
DESCRIPTION="$(yq .[$i].description <<< "$PARAMS" | tr '\n' ' ' | sed 's/[[:space:]]*$//')"
95+
NAME="$(yq .[$i].name <<< "$PARAMS" | tr '\n' ' ' | sed 's/[[:space:]]*$//')"
96+
97+
if [[ $(yq ".[$i] | has(\"description\")" <<< "$PARAMS") == "false" ]]; then
98+
echo " Error in $ITEM: Field '.param.[$i].description' ($NAME) is missing in task"
99+
FAILED_PARAMS+=("$ITEM_PATH: $NAME - missing description")
100+
elif [[ "${DESCRIPTION: -1}" =~ [.,] ]]; then
101+
echo " Error in $NAME: Description should not end with a '${DESCRIPTION: -1}'"
102+
FAILED_PARAMS+=("$ITEM_PATH: $NAME - invalid description")
103+
fi
104+
done
105+
106+
README_PATH=${ITEM_DIR}/README.md
107+
if [ ! -f "$README_PATH" ]
108+
then
109+
echo " Error: README does not exist in $ITEM_DIR"
110+
FAILED_ITEMS+=("$ITEM - no README.md")
111+
continue
112+
fi
113+
114+
if ! diff -u <(.github/scripts/readme_generator.sh --dry-run "$ITEM_DIR") "$README_PATH"; then
115+
echo " Error: README.md has not been updated. Please use hack/readme-generator.sh to" \
116+
"generate a new README.md to replace $ITEM/README.md"
117+
FAILED_ITEMS+=("$ITEM - outdated README.md")
118+
else
119+
echo " README.md for $ITEM_DIR is up to date"
120+
fi
121+
122+
done
123+
124+
if [[ "${#FAILED_PARAMS[@]}" -eq 0 ]]; then
125+
echo
126+
echo "All task/pipeline parameter descriptions are valid."
127+
else
128+
echo
129+
echo "Error: these task/pipeline parameter descriptions must be updated:"
130+
for PARAM in "${FAILED_PARAMS[@]}"; do
131+
echo " $PARAM"
132+
done
133+
134+
if [[ "${#FAILED_ITEMS[@]}" -eq 0 ]]; then
135+
exit 1
136+
fi
137+
fi
138+
139+
if [[ "${#FAILED_ITEMS[@]}" -eq 0 ]]; then
140+
echo
141+
echo "All README.md files are up to date"
142+
else
143+
echo
144+
echo "Error: these task/pipeline README.md files must be updated:"
145+
for ITEM in "${FAILED_ITEMS[@]}"; do
146+
echo " $ITEM"
147+
done
148+
echo
149+
if [[ "${#FAILED_PARAMS[@]}" -eq 0 ]]; then
150+
echo "Add missing descriptions to tasks/pipelines, then try running the" \
151+
"following command to fix this:"
152+
else
153+
echo "Fix any invalid or missing parameter descriptions and add missing descriptions" \
154+
"to tasks/pipelines, then try running the following command to fix this:"
155+
fi
156+
# Get unique items in array
157+
FAILED_ITEMS=($(printf "%s\n" "${FAILED_ITEMS[@]% - *}" | sort -u ))
158+
echo "./.github/scripts/readme_generator.sh" "${FAILED_ITEMS[@]}"
159+
exit 1
160+
fi
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
#!/usr/bin/env bash
2+
3+
# Automatically generates a README.md for tasks and pipelines.
4+
# Without the '--dry-run' flag, this will automatically replace the current task/pipeline README.md
5+
# A check will be run on each pull request to make sure the README.md matches this script's output.
6+
#
7+
# This script takes the '.spec.description' and '.spec.params' fields
8+
# from the associated task/pipeline to create the description and table in the README.md of this task/pipeline.
9+
#
10+
# If you wish to update the README.md description and table, please update the '.spec.description' or '.spec.table'
11+
# field in the Tekton task/pipeline and run this script, instead of changing it manually in the README.md.
12+
#
13+
# Usage: ./readme_generator.sh tasks/apply-mapping
14+
15+
show_help() {
16+
echo "Usage: $0 [--dry-run] [item1] [item2] [...]"
17+
echo
18+
echo Flags:
19+
echo " --help: Show this help message"
20+
echo " --dry-run: Print the updated README.md files without changing"
21+
echo " the current README.md in each task and pipeline"
22+
echo
23+
echo "Items are task or pipeline directories. They can be supplied"
24+
echo "either as arguments or via the README_ITEMS environment variable."
25+
echo
26+
echo "Examples:"
27+
echo " $0 tasks/managed/apply-mapping"
28+
echo " $0 --dry-run tasks/managed/apply-mapping"
29+
echo
30+
echo " or"
31+
echo
32+
echo ' export README_ITEMS="mydir/tasks/apply-mapping some/other/dir"'
33+
echo " $0"
34+
exit 1
35+
}
36+
37+
DRY_RUN=false
38+
CLI_README_ITEMS=()
39+
40+
while [[ $# -gt 0 ]]; do
41+
case "$1" in
42+
--dry-run)
43+
DRY_RUN=true
44+
shift
45+
;;
46+
--help)
47+
show_help
48+
;;
49+
--*)
50+
show_help
51+
;;
52+
*)
53+
CLI_README_ITEMS+=("$1")
54+
shift
55+
;;
56+
esac
57+
done
58+
59+
if [[ "${#CLI_README_ITEMS[@]}" -gt 0 ]]; then
60+
README_ITEMS=("${CLI_README_ITEMS[@]}")
61+
else
62+
read -r -a README_ITEMS <<< "${README_ITEMS[@]}"
63+
fi
64+
65+
if [ "${#README_ITEMS[@]}" -eq 0 ]
66+
then
67+
show_help
68+
fi
69+
70+
# Check that all directories exist. If not, fail
71+
for ITEM in "${README_ITEMS[@]}"
72+
do
73+
if [[ ! -d "$ITEM" ]]; then
74+
echo "Error: Invalid file or directory: $ITEM"
75+
exit 1
76+
fi
77+
78+
ITEM_NAME=$(basename "$ITEM")
79+
ITEM_DIR=$(cut -d '/' -f -3 <<< "$ITEM")
80+
81+
ITEM_PATH=${ITEM_DIR}/${ITEM_NAME}.yaml
82+
if [ ! -f "$ITEM_PATH" ]
83+
then
84+
echo "Error: Task/Pipeline file does not exist: $ITEM_PATH"
85+
exit 1
86+
fi
87+
done
88+
89+
# Creating after checking all directories exist to simplify cleanup
90+
TEMP_README=$(mktemp)
91+
92+
# Adds trailing whitespace/hyphens to table until num_spaces is reached
93+
spaces() { # (num_spaces, string)
94+
for ((i=0; i < $(($1-${#2})); i++)); do
95+
echo -n " "
96+
done
97+
}
98+
99+
dashes() { # (num_spaces, string)
100+
for ((i=0; i < $(($1-${#2})); i++)); do
101+
echo -n "-"
102+
done
103+
}
104+
105+
# Add table
106+
for ITEM in "${README_ITEMS[@]}"
107+
do
108+
ITEM_DIR=$(cut -d '/' -f -3 <<< "$ITEM")
109+
BASE_DIR=$(cut -d '/' -f 1 <<< "$ITEM")
110+
ITEM_NAME=$(basename "$ITEM")
111+
ITEM_PATH=${ITEM_DIR}/${ITEM_NAME}.yaml
112+
113+
# Don't print any debug messages when dry run is true
114+
$DRY_RUN || echo "Task/Pipeline item: $ITEM"
115+
$DRY_RUN || echo " Task/Pipeline name: $ITEM_NAME"
116+
117+
# Variables for description of README.md
118+
METADATA_NAME=$(yq .metadata.name "$ITEM_PATH")
119+
SPEC_DESCRIPTION=$(yq .spec.description "$ITEM_PATH")
120+
121+
# Variables for table
122+
PARAMS=$(yq .spec.params "$ITEM_PATH")
123+
124+
# Get the maximum length for each column of table
125+
LONGEST_NAME=0
126+
LONGEST_DESCRIPTION=0
127+
LONGEST_DEFAULT=13
128+
LONGEST_OPTIONAL=8
129+
130+
for ((i=0; i < $(yq length <<< "$PARAMS"); i++)); do
131+
# Get rid of newlines and remove trailing whitespace
132+
NAME="$(yq .[$i].name <<< "$PARAMS" | tr '\n' ' ' | sed 's/[[:space:]]*$//')"
133+
DESCRIPTION="$(yq .[$i].description <<< "$PARAMS" | tr '\n' ' ' | sed 's/[[:space:]]*$//')"
134+
DEFAULT="$(yq .[$i].default <<< "$PARAMS" | tr '\n' ' ' | sed 's/[[:space:]]*$//')"
135+
136+
if [[ ${#NAME} -gt $LONGEST_NAME ]]; then
137+
LONGEST_NAME=${#NAME}
138+
fi
139+
140+
if [[ ${#DESCRIPTION} -gt $LONGEST_DESCRIPTION ]]; then
141+
LONGEST_DESCRIPTION=${#DESCRIPTION}
142+
fi
143+
144+
if [[ ${#DEFAULT} -gt $LONGEST_DEFAULT ]]; then
145+
LONGEST_DEFAULT=${#DEFAULT}
146+
fi
147+
done
148+
149+
# create table and write contents to file
150+
{
151+
if [[ "$BASE_DIR" == "pipelines" && "$ITEM_NAME" != *-pipeline ]]; then
152+
echo "# $METADATA_NAME pipeline"
153+
elif [[ "$BASE_DIR" == "pipelines" && "$ITEM_NAME" == *-pipeline ]]; then
154+
echo "# ${METADATA_NAME%-pipeline} pipeline"
155+
else
156+
echo "# $METADATA_NAME"
157+
fi
158+
echo
159+
echo "$SPEC_DESCRIPTION"
160+
echo
161+
echo "## Parameters"
162+
echo
163+
164+
# Print first row of table
165+
echo "| Name$(spaces "$LONGEST_NAME" "Name")" \
166+
"| Description$(spaces "$LONGEST_DESCRIPTION" "Description")" \
167+
"| Optional$(spaces "$LONGEST_OPTIONAL" "Optional")" \
168+
"| Default value$(spaces "$LONGEST_DEFAULT" "Default value") |"
169+
170+
# Print second row of table
171+
echo -n "|-$(dashes "$LONGEST_NAME" "")-|-$(dashes "$LONGEST_DESCRIPTION" "")"
172+
echo "-|-$(dashes "$LONGEST_OPTIONAL" "")-|-$(dashes "$LONGEST_DEFAULT" "")-|"
173+
174+
# Print remaining rows of table
175+
for ((i=0; i < $(yq length <<< "$PARAMS"); i++)); do
176+
# Get rid of newlines and remove trailing whitespace
177+
NAME="$(yq .[$i].name <<< "$PARAMS" | tr '\n' ' ' | sed 's/[[:space:]]*$//')"
178+
DESCRIPTION="$(yq .[$i].description <<< "$PARAMS" | tr '\n' ' ' | sed 's/[[:space:]]*$//')"
179+
DEFAULT="$(yq .[$i].default <<< "$PARAMS" | tr '\n' ' ' | sed 's/[[:space:]]*$//')"
180+
181+
# Check that default doesn't exist
182+
if [[ $(yq ".[$i] | has(\"default\")" <<< "$PARAMS") == "false" ]]; then
183+
echo "| $NAME$(spaces "$LONGEST_NAME" "$NAME")" \
184+
"| $DESCRIPTION$(spaces "$LONGEST_DESCRIPTION" "$DESCRIPTION")" \
185+
"| No$(spaces "$LONGEST_OPTIONAL" "No")" \
186+
"| -$(spaces "$LONGEST_DEFAULT" "-") |"
187+
else
188+
# Special case to show default empty strings as "" in table
189+
if [[ -z "$DEFAULT" ]]; then
190+
DEFAULT="\"\""
191+
fi
192+
193+
echo "| $NAME$(spaces "$LONGEST_NAME" "$NAME")" \
194+
"| $DESCRIPTION$(spaces "$LONGEST_DESCRIPTION" "$DESCRIPTION")" \
195+
"| Yes$(spaces "$LONGEST_OPTIONAL" "Yes")" \
196+
"| $DEFAULT$(spaces "$LONGEST_DEFAULT" "$DEFAULT") |"
197+
fi
198+
done
199+
} > "$TEMP_README"
200+
201+
if [[ $DRY_RUN == "true" ]]; then
202+
cat "$TEMP_README"
203+
else
204+
cat "$TEMP_README" > "${ITEM_DIR}/README.md"
205+
echo " README.md for $ITEM_DIR updated"
206+
fi
207+
done
208+
209+
# Cleanup
210+
if [ -v TEMP_README ]; then
211+
rm -f "$TEMP_README"
212+
fi

.github/workflows/lint.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,25 @@ jobs:
111111
run: .github/scripts/tkn_check_compute_resources.sh
112112
env:
113113
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
114+
check-readme:
115+
name: Check README.md files
116+
runs-on: ubuntu-latest
117+
steps:
118+
- name: Checkout code
119+
uses: actions/checkout@v4
120+
- name: Get changed dirs
121+
id: changed-dirs
122+
uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v45.0.6
123+
with:
124+
files: |
125+
tasks/*/**
126+
pipelines/*/**
127+
dir_names: "true"
128+
dir_names_max_depth: "3"
129+
- name: Check README.md files
130+
if: |
131+
steps.changed-dirs.outputs.any_changed == 'true'
132+
run: .github/scripts/check_readme.sh
133+
env:
134+
README_ITEMS: >-
135+
${{ steps.changed-dirs.outputs.all_changed_files }}

0 commit comments

Comments
 (0)