-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11 from modeseven-os-climate/update-gha-wf
Chore: Update workflows from upstream repo
- Loading branch information
Showing
7 changed files
with
356 additions
and
119 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,19 +12,16 @@ jobs: | |
name: "Update DevOps tooling" | ||
runs-on: ubuntu-latest | ||
permissions: | ||
# IMPORTANT: mandatory to update content/actions/PRs | ||
# IMPORTANT: mandatory to create or update content/actions/pr | ||
contents: write | ||
actions: write | ||
pull-requests: write | ||
|
||
steps: | ||
- name: "Checkout primary repository" | ||
uses: actions/checkout@v4 | ||
with: | ||
# Note: Requires a specific/defined Personal Access Token | ||
token: ${{ secrets.ACTIONS_WORKFLOW }} | ||
|
||
- name: "Pull workflows from central repository" | ||
- name: "Pull devops content from repository" | ||
uses: actions/checkout@v4 | ||
with: | ||
repository: "os-climate/devops-toolkit" | ||
|
@@ -36,115 +33,307 @@ jobs: | |
GH_TOKEN: ${{ github.token }} | ||
# yamllint disable rule:line-length | ||
run: | | ||
### SHELL CODE START ### | ||
#SHELLCODESTART | ||
set -euo pipefail | ||
# set -x | ||
# Define variables | ||
DEVOPS_DIR=".devops" | ||
AUTOMATION_BRANCH="update-devops-tooling" | ||
REPO_DIR=$(git rev-parse --show-toplevel) | ||
# Ensure working from top-level of GIT repository | ||
CURRENT_DIR=$(pwd) | ||
if [ "$REPO_DIR" != "$CURRENT_DIR" ]; then | ||
echo "Changing directory to: $REPO_DIR" | ||
if ! (cd "$REPO_DIR"); then | ||
echo "Error: unable to change directory"; exit 1 | ||
fi | ||
fi | ||
GIT_ORIGIN=$(git config --get remote.origin.url) | ||
REPO_NAME=$(basename -s .git "$GIT_ORIGIN") | ||
EXCLUDE_FILE=".devops-exclusions" | ||
DEVOPS_REPO='[email protected]:os-climate/devops-toolkit.git' | ||
HEAD_BRANCH=$(git rev-parse --abbrev-ref HEAD) | ||
# Content folder defines the files and folders to update | ||
FILES="$DEVOPS_DIR/content/files.txt" | ||
FOLDERS="$DEVOPS_DIR/content/folders.txt" | ||
# Define a function to allow selective opt-out of devops tooling | ||
OPT_OUT=".devops-exclusions" | ||
# Define functions | ||
perform_folder_operation() { | ||
FS_PATH="$1" | ||
if [ -d "$DEVOPS_DIR"/"$FS_PATH" ]; then | ||
echo "Scanning target folder content at: $FS_PATH" | ||
return 0 | ||
else | ||
echo "Upstream folder NOT found: $FS_PATH [skipping]" | ||
return 1 | ||
fi | ||
} | ||
# Allows for selective opt-out components on a per-path basis | ||
perform_operation() { | ||
ELEMENT="$1" | ||
if [ ! -f "$OPT_OUT" ]; then | ||
# Opt-out file does not exist; all operations will be performed | ||
FS_PATH="$1" | ||
if [ ! -f "$DEVOPS_DIR"/"$FS_PATH" ]; then | ||
echo "Skipping missing upstream file at: $FS_PATH" | ||
return 1 | ||
fi | ||
# Elements excluded from processing return exit status 1 | ||
if [ ! -f "$EXCLUDE_FILE" ]; then | ||
return 0 | ||
elif [ "$FS_PATH" = "$EXCLUDE_FILE" ]; then | ||
# The exclusion file itself is never updated by automation | ||
return 1 | ||
elif (grep -Fxq "$FS_PATH" "$EXCLUDE_FILE" > /dev/null); then | ||
# Element listed; exclude from processing | ||
return 1 | ||
else | ||
if grep -Fxq "$ELEMENT" "$OPT_OUT" | ||
then | ||
# Element is excluded from processing | ||
# Element not found in exclusion file; process it | ||
return 0 | ||
fi | ||
} | ||
# Only updates file if it has changed | ||
selective_file_copy() { | ||
# Receives a single file path as argument | ||
SHA_SRC=$(sha1sum "$DEVOPS_DIR"/"$1" | awk '{print $1}') | ||
SHA_DST=$(sha1sum "$1" 2>/dev/null | awk '{print $1}' || :) | ||
if [ "$SHA_SRC" != "$SHA_DST" ]; then | ||
echo "Copying: $1" | ||
cp "$DEVOPS_DIR/$1" "$1" | ||
git add "$1" | ||
fi | ||
} | ||
check_pr_for_author() { | ||
AUTHOR="$1" | ||
printf "Checking for pull requests by: %s" "$AUTHOR" | ||
# Capture the existing PR number | ||
PR_NUM=$(gh pr list --state open -L 1 \ | ||
--author "$AUTHOR" --json number | \ | ||
grep "number" | sed "s/:/ /g" | awk '{print $2}' | \ | ||
sed "s/}//g" | sed "s/]//g") | ||
if [ -z "$PR_NUM" ]; then | ||
echo " [none]" | ||
return 1 | ||
else | ||
echo " [$PR_NUM]" | ||
echo "Running: gh pr checkout $PR_NUM" | ||
if (gh pr checkout "$PR_NUM"); then | ||
return 0 | ||
else | ||
# Element should be processed | ||
return 1 | ||
echo "Failed to checkout GitHub pull request" | ||
echo "Check errors/output for the cause" | ||
return 2 | ||
fi | ||
fi | ||
} | ||
echo "Removing remote branch if it exists: update-devops-tooling" | ||
git push origin --delete update-devops-tooling || : | ||
STRING=$(dd if=/dev/urandom bs=1k count=1 2>/dev/null | tr -dc 'a-zA-Z0-9' | head -c 10) | ||
git checkout -b "update-$STRING" | ||
check_prs() { | ||
# Define users to check for pre-existing pull requests | ||
AUTOMATION_USER="github-actions[bot]" | ||
if [[ -n ${GH_TOKEN+x} ]]; then | ||
GITHUB_USERS="$AUTOMATION_USER" | ||
else | ||
GITHUB_USERS=$(gh api user | jq -r '.login') | ||
# Check local user account first, if enumerated | ||
GITHUB_USERS+=" $AUTOMATION_USER" | ||
fi | ||
# Check for existing pull requests opened by this automation | ||
for USER in $GITHUB_USERS; do | ||
if (check_pr_for_author "$USER"); then | ||
return 0 | ||
else | ||
STATUS="$?" | ||
fi | ||
if [ "$STATUS" -eq 1 ]; then | ||
continue | ||
elif [ "$STATUS" -eq 2 ]; then | ||
echo "Failed to checkout pull request"; exit 1 | ||
fi | ||
done | ||
return 1 | ||
} | ||
# Check if script is running in GHA workflow | ||
in_github() { | ||
if [ -z ${GITHUB_RUN_ID+x} ]; then | ||
echo "Script is NOT running in GitHub" | ||
return 1 | ||
else | ||
echo "Script is running in GitHub" | ||
return 0 | ||
fi | ||
} | ||
# Configure GIT | ||
TEST=$(git config -l) | ||
# Check if user is logged into GitHub | ||
logged_in_github() { | ||
if (gh auth status); then | ||
echo "Logged in and authenticated to GitHb" | ||
return 0 | ||
else | ||
echo "Not logged into GitHub, some script operations unavailable" | ||
return 1 | ||
fi | ||
} | ||
# Main script entry point | ||
echo "Repository name and HEAD branch: $REPO_NAME [$HEAD_BRANCH]" | ||
# Ensure working from top-level of GIT repository | ||
CURRENT_DIR=$(pwd) | ||
if [ "$REPO_DIR" != "$CURRENT_DIR" ]; then | ||
echo "Changing directory to: $REPO_DIR" | ||
if ! (cd "$REPO_DIR"); then | ||
echo "Error: unable to change directory"; exit 1 | ||
fi | ||
fi | ||
# Stashing only used during development/testing | ||
# Check if there are unstaged changes | ||
# if ! (git diff --exit-code --quiet); then | ||
# echo "Stashing unstaged changes in current repository" | ||
# git stash -q | ||
# fi | ||
# Configure GIT environment only if NOT already configured | ||
# i.e. when running in a GitHub Actions workflow | ||
TEST=$(git config -l > /dev/null 2>&1) | ||
if [ -n "$TEST" ]; then | ||
git config user.name "github-actions[bot]" | ||
git config user.email \ | ||
"41898282+github-actions[bot]@users.noreply.github.com" | ||
fi | ||
FOLDERS=".github .github/workflows scripts" | ||
for FOLDER in ${FOLDERS}; do | ||
# Check to see if operation should be skipped | ||
if (perform_operation "$FOLDER"); then | ||
echo "Opted out of DevOps folder: $FOLDER" | ||
continue | ||
if ! (check_prs); then | ||
# No existing open pull requests found for this repository | ||
# Remove remote branch if it exists | ||
git push origin --delete "$AUTOMATION_BRANCH" > /dev/null 2>&1 || : | ||
git branch -D "$AUTOMATION_BRANCH" || : | ||
git checkout -b "$AUTOMATION_BRANCH" | ||
else | ||
# The -B flag swaps branch and creates it if NOT present | ||
git checkout -B "$AUTOMATION_BRANCH" | ||
fi | ||
# Only if NOT running in GitHub | ||
# (checkout is otherwise performed by earlier steps) | ||
if ! (in_github); then | ||
# Remove any stale local copy of the upstream repository | ||
if [ -d "$DEVOPS_DIR" ]; then | ||
rm -Rf "$DEVOPS_DIR" | ||
fi | ||
printf "Cloning DevOps repository into: %s" "$DEVOPS_DIR" | ||
if (git clone "$DEVOPS_REPO" "$DEVOPS_DIR" > /dev/null 2>&1); then | ||
echo " [success]" | ||
else | ||
# If necessary, create target folder | ||
if [ ! -d "$FOLDER" ]; then | ||
echo "Creating target folder: $FOLDER" | ||
mkdir "$FOLDER" | ||
fi | ||
# Update folder contents | ||
echo "Updating folder contents: $FOLDER" | ||
cp -a .devops/"$FOLDER"/. "$FOLDER" | ||
echo " [failed]"; exit 1 | ||
fi | ||
done | ||
fi | ||
# Process upstream DevOps repository content and update | ||
LOCATIONS="" | ||
# Populate list of files to be updated/sourced | ||
while read -ra LINE; | ||
do | ||
for FILE in "${LINE[@]}"; | ||
do | ||
LOCATIONS+="$FILE " | ||
done | ||
done < "$FILES" | ||
# Gather files from specified folders and append to locations list | ||
while read -ra LINE; | ||
do | ||
for FOLDER in "${LINE[@]}"; | ||
do | ||
# Check to see if this folder should be skipped | ||
if (perform_folder_operation "$FOLDER"); then | ||
# If necessary, create target folder | ||
if [ ! -d "$FOLDER" ]; then | ||
echo "Creating target folder: $FOLDER" | ||
mkdir "$FOLDER" | ||
fi | ||
# Add folder contents to list of file LOCATIONS | ||
FILES=$(cd "$DEVOPS_DIR/$FOLDER"; find . -maxdepth 1 -type f -exec basename {} \;) | ||
for LOCATION in $FILES; do | ||
# Also check if individual files in the folder are excluded | ||
if (perform_operation "$FOLDER/$LOCATION"); then | ||
LOCATIONS+=" $FOLDER/$LOCATION" | ||
fi | ||
done | ||
else | ||
echo "Opted out of folder: $FOLDER" | ||
continue | ||
fi | ||
done; | ||
done < "$FOLDERS" | ||
# Copy specified files into repository root | ||
FILES=".pre-commit-config.yaml .prettierignore .gitignore" | ||
for FILE in ${FILES}; do | ||
if (perform_operation "$FILE"); then | ||
echo "Opted out of DevOps file: $FILE" | ||
for LOCATION in ${LOCATIONS}; do | ||
if (perform_operation "$LOCATION"); then | ||
selective_file_copy "$LOCATION" | ||
else | ||
echo "Copying file: $FILE" | ||
cp .devops/"$FILE" "$FILE" | ||
echo "Not updating: $LOCATION" | ||
fi | ||
done | ||
# If no changes required, do not throw an error | ||
if [ -z "$(git status --porcelain)" ]; then | ||
echo "No updates/changes to commit"; exit 0 | ||
else | ||
# Set a flag for use by the next action/step | ||
fi | ||
# Temporarily disable exit on unbound variable | ||
set +eu +o pipefail | ||
# Next step is only performed if running as GitHub Action | ||
if [[ -n ${GH_TOKEN+x} ]]; then | ||
# Script is running in a GitHub actions workflow | ||
# Set outputs for use by the next actions/steps | ||
# shellcheck disable=SC2129 | ||
echo "changed=true" >> "$GITHUB_OUTPUT" | ||
echo "branchname=$AUTOMATION_BRANCH" >> "$GITHUB_OUTPUT" | ||
echo "headbranch=$HEAD_BRANCH" >> "$GITHUB_OUTPUT" | ||
# Move to the next workflow step to raise the PR | ||
git push --set-upstream origin "$AUTOMATION_BRANCH" | ||
exit 0 | ||
fi | ||
if [ -n "$GITHUB_TOKEN" ]; then | ||
git add . | ||
if ! (git commit -as -S -m "Chore: Update DevOps tooling from central repository [skip-ci]" \ | ||
-m "This commit created by automation/scripting" --no-verify); then | ||
echo "Commit failed; aborting"; exit 1 | ||
else | ||
git push --set-upstream origin update-devops-tooling | ||
# ToDo: need to verify if we are running in a GHA | ||
gh pr create --title \ | ||
"Chore: Pull DevOps tooling from upstream repository" \ | ||
--body 'Automated by a GitHub workflow: bootstrap.yaml' | ||
fi | ||
# If running shell code locally, continue to raise the PR | ||
# Reinstate exit on unbound variables | ||
set -euo pipefail | ||
git status | ||
if ! (git commit -as -S -m "Chore: Update DevOps tooling from central repository [skip ci]" \ | ||
-m "This commit created by automation/scripting" --no-verify); then | ||
echo "Commit failed; aborting"; exit 1 | ||
else | ||
echo "Script running in GitHub Actions workflow; proceeding to next step" | ||
# Push branch to remote repository | ||
git push --set-upstream origin "$AUTOMATION_BRANCH" | ||
# Create PR request | ||
gh pr create \ | ||
--title "Chore: Pull DevOps tooling from upstream repository" \ | ||
--body 'Automated by a GitHub workflow: bootstrap.yaml' | ||
fi | ||
### SHELL CODE END ### | ||
# echo "Unstashing unstaged changes, if any exist" | ||
# git stash pop -q || : | ||
#SHELLCODEEND | ||
- name: Create Pull Request | ||
if: steps.update-repository.outputs.changed == 'true' | ||
uses: peter-evans/create-pull-request@v5 | ||
env: | ||
GITHUB_TOKEN: ${{ github.token }} | ||
uses: peter-evans/create-pull-request@v6 | ||
# env: | ||
# GITHUB_TOKEN: ${{ github.token }} | ||
with: | ||
token: ${{ github.token }} | ||
commit-message: "Chore: Update DevOps tooling from central repository [skip-ci]" | ||
# Note: Requires a specific/defined Personal Access Token | ||
token: ${{ secrets.ACTIONS_WORKFLOW }} | ||
commit-message: "Chore: Update DevOps tooling from central repository [skip ci]" | ||
signoff: "true" | ||
branch: update-devops-tooling | ||
base: ${{ steps.update-repository.outputs.headbranch }} | ||
branch: ${{ steps.update-repository.outputs.branchname }} | ||
delete-branch: true | ||
title: "Chore: Update DevOps tooling from central repository [skip-ci]" | ||
title: "Chore: Update DevOps tooling from central repository [skip ci]" | ||
body: | | ||
Update repository with content from upstream: os-climate/devops-toolkit | ||
labels: | | ||
|
Oops, something went wrong.