Skip to content

Commit 7d5a266

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_param_punctuation.sh and workflow to stop '.' from being added to the end of task/pipeline param descriptions. Signed-off-by: Paul Fitzgerald <pafitzge@redhat.com>
1 parent f7724b5 commit 7d5a266

4 files changed

Lines changed: 384 additions & 0 deletions

File tree

.github/scripts/check_readme.sh

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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_PARAMS=()
13+
14+
show_help() {
15+
echo "Usage: $0 [item1] [item2] [...]"
16+
echo
17+
echo Flags:
18+
echo " --help: Show this help message"
19+
echo
20+
echo "Items are task or pipeline directories. They can be supplied"
21+
echo "either as arguments or via the README_ITEMS environment variable."
22+
echo
23+
echo "Examples:"
24+
echo " $0 tasks/managed/apply-mapping"
25+
echo
26+
echo " or"
27+
echo
28+
echo ' export README_ITEMS="mydir/tasks/apply-mapping some/other/dir"'
29+
echo " $0"
30+
exit 1
31+
}
32+
33+
while [[ $# -gt 0 ]]; do
34+
case "$1" in
35+
--help)
36+
show_help
37+
;;
38+
--*)
39+
show_help
40+
;;
41+
*)
42+
CLI_README_ITEMS+=("$1")
43+
shift
44+
;;
45+
esac
46+
done
47+
48+
if [[ "${#CLI_README_ITEMS[@]}" -gt 0 ]]; then
49+
README_ITEMS=("${CLI_README_ITEMS[@]}")
50+
else
51+
read -r -a README_ITEMS <<< "$README_ITEMS"
52+
fi
53+
54+
if [ "${#README_ITEMS[@]}" -eq 0 ]
55+
then
56+
show_help
57+
fi
58+
59+
# Check that all directories exist. If not, fail
60+
for ITEM in "${README_ITEMS[@]}"
61+
do
62+
if [[ -d "$ITEM" ]]; then
63+
true
64+
else
65+
echo "Error: Invalid file or directory: $ITEM"
66+
exit 1
67+
fi
68+
done
69+
70+
for ITEM in "${README_ITEMS[@]}"
71+
do
72+
echo "Task/Pipeline item: $ITEM"
73+
ITEM_NAME=$(basename "$ITEM")
74+
ITEM_DIR=$(echo "$ITEM" | cut -d '/' -f -3)
75+
echo " Task/Pipeline name: $ITEM_NAME"
76+
77+
ITEM_PATH=${ITEM_DIR}/${ITEM_NAME}.yaml
78+
if [ ! -f "$ITEM_PATH" ]
79+
then
80+
echo "Error: Task/Pipeline file does not exist: $ITEM_PATH"
81+
exit 1
82+
fi
83+
84+
README_PATH=${ITEM_DIR}/README.md
85+
if [ ! -f "$README_PATH" ]
86+
then
87+
echo "Error: README does not exist in $ITEM_DIR"
88+
exit 1
89+
fi
90+
91+
if ! diff -u <(hack/readme_generator.sh --dry-run "$ITEM_DIR") "$README_PATH"; then
92+
echo " Error: README.md has not been updated. Please use hack/readme-generator.sh to" \
93+
"generate a new README.md to replace $ITEM/README.md"
94+
FAILED_ITEMS+=("$ITEM")
95+
else
96+
echo " README.md for $ITEM_DIR is up to date"
97+
fi
98+
99+
# Check description doesn't have '.' or ','
100+
PARAMS=$(yq .spec.params "$ITEM_PATH")
101+
for ((i=0; i < $(yq length <<< "$PARAMS"); i++)); do
102+
# Get rid of newlines and remove trailing whitespace
103+
DESCRIPTION="$(yq .[$i].description <<< "$PARAMS" | tr '\n' ' ' | sed 's/[[:space:]]*$//')"
104+
if [[ "${DESCRIPTION: -1}" =~ [.,] ]]; then
105+
NAME="$(yq .[$i].name <<< "$PARAMS" | tr '\n' ' ' | sed 's/[[:space:]]*$//')"
106+
echo " Error in $NAME: Description should not end with a '${DESCRIPTION: -1}'"
107+
FAILED_PARAMS+=("$ITEM_PATH: $NAME")
108+
fi
109+
done
110+
111+
done
112+
113+
if [[ "${#FAILED_PARAMS[@]}" -eq 0 ]]; then
114+
echo
115+
echo "All task/pipeline parameter descriptions are valid."
116+
else
117+
echo
118+
echo "Error: these task/pipeline parameter descriptions must be updated:"
119+
for PARAM in "${FAILED_PARAMS[@]}"; do
120+
echo " $PARAM"
121+
done
122+
123+
if [[ "${#FAILED_ITEMS[@]}" -eq 0 ]]; then
124+
exit 1
125+
fi
126+
fi
127+
128+
if [[ "${#FAILED_ITEMS[@]}" -eq 0 ]]; then
129+
echo
130+
echo "All README.md files are up to date"
131+
else
132+
echo
133+
echo "Error: these task/pipeline README.md files must be updated:"
134+
for ITEM in "${FAILED_ITEMS[@]}"; do
135+
echo " $ITEM"
136+
done
137+
echo
138+
echo "Try running the following command to fix this, as, well as fixing any invalid parameter descriptions:"
139+
echo "./hack/readme_generator.sh ${FAILED_ITEMS[@]}"
140+
exit 1
141+
fi

.github/workflows/lint.yaml

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

CONTRIBUTING.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,15 @@ Here is an example
125125
cpu: 250m
126126
```
127127
128+
### Keeping Documentation Up to Date
129+
130+
Whenever a task or pipeline is changed, please run the `./hack/readme_generator.sh` script with the
131+
changed task/pipeline directories as arguments to update the README.md description and table included with the
132+
table.
133+
134+
A check defined in `.github/scripts/check_readme.sh` is run on each pull request to ensure that the README.md files in each task/pipeline are up to date.
135+
For more information, check the `./hack/readme_generator.sh` and `./github/scripts/check_readme.sh` scripts.
136+
128137
### Modes for Running Pipelines
129138

130139
Note: There are currently 2 modes that may be used when running pipelines:

hack/readme_generator.sh

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=$(echo "$ITEM" | cut -d '/' -f -3)
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=$(echo "$ITEM" | cut -d '/' -f -3)
109+
BASE_DIR=$(echo "$ITEM" | cut -d '/' -f 1)
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

0 commit comments

Comments
 (0)