GitHub Actions – Job to Copy a Workflow From One Repository to Another

This is a continuation of what I posted the other day.

In this exercise – I needed to be able to automatically copy workflow files from one repository to another.

There is this known reported issue that despite adding a GitHub Actions Workflow file – unless it’s on the main/master branch – you can’t use the workflow_dispatch popup on the UI so build can be manually triggered (if needed).

So I did some investigation and created an automation to copy a workflow to the main/master branch.

As a result, I studied the GitHub API and wrote up the below snippet – it does the following:

  • Disables Branch Protections for the Target Repository and Branch
  • Checks if the Workflow exists on the Target Repository
  • If True on “Update if Exists” – Copies the Workflow from the Source Repository to the Target
    • Useful if wanting to do bulk updates across many repositories but not wanting to overwrite an existing actions build that has already been created on the Target
  • Enables Branch Protections for the Target Repository and Branch

File Name – .github/workflows/copy_workflow.yaml

name: Copy Workflow from Source to Target Repository

on:
  workflow_dispatch:
    inputs:
      owner:
        description: 'Source/Target Organization'
        required: true
        default: 'my-organization-name'
      target_repo:
        description: 'Target Repository (Place to Copy Workflow File TO)'
        required: true
        default: 'ExampleRepositoryName'
      target_branch:
        description: 'Target Branch (Place to Copy Workflow File TO)'
        required: true
        default: 'main'
      source_repo:
        description: 'Source Repository (Place to Copy Workflow File FROM)'
        required: true
        default: 'ExampleRepositoryName'
      source_branch:
        description: 'Source Branch (Place to Copy Workflow File FROM)'
        required: true
        default: 'master'
      workflow_file:
        description: 'Workflow File to Copy'
        required: true
        default: 'build.yaml'
      update_if_exists:
        description: 'Update Workflow File if it already exists in the target repository'
        required: true
        default: 'false'

jobs:
  update_secrets:
    name: Copy Workflow from Source to Target Repository
    runs-on: ubuntu-latest
    steps:
        - name: Clone Dependencies - my-actions-repository
          uses: actions/checkout@v3
          with:
              token: ${{secrets.GIT_TOKEN}}
              repository: my-organization-name/my-actions-repository
              ref: main
              path: ${{github.workspace}}/my-actions-repository

        - name: Copy Workflow from Source to Target Repository
          shell: bash
          run: |
            chmod 700 ${{github.workspace}}/my-actions-repository/utility/copy-workflow/
            ${{github.workspace}}/my-actions-repository/utility/copy-workflow/copy-workflow.sh "${{inputs.owner}}" "${{inputs.target_repo}}" "${{inputs.target_branch}}" "${{inputs.source_repo}}" "${{inputs.source_branch}}" "${{inputs.workflow_file}}" "${{inputs.update_if_exists}}"
          env:
            GITHUB_TOKEN: ${{ secrets.GIT_TOKEN }}

File Name – utility/copy-workflow/copy-workflow.sh

#!/bin/bash

# Get command-line arguments
owner="$1"
target_repo="$2"
target_branch="$3"
source_repo="$4"
source_branch="$5"
workflow_file="$6"
update_if_exists="$7"

echo "Disabling Branch Protection Rules for $target_branch on $target_repo"

gh api repos/$owner/$target_repo/branches/$target_branch/protection \
  -H "Accept: application/vnd.github.v3+json" \
  -X DELETE || echo "Branch protection rules already disabled or don't exist"

echo "Copying workflow: $workflow_file from '$source_repo' ($source_branch) to '$target_repo' ($target_branch)"

# Check if the source repository exists
echo "Checking source repository: $source_repo"
check_source_repo=$(gh api -X GET "/repos/$owner/$source_repo" -H 'Accept: application/vnd.github.v3+json' 2>&1)
if [[ $? -ne 0 ]]; then
  echo "Error: Failed to retrieve information for source repository: $source_repo"
  echo "$check_source_repo"
  exit 1
fi

# Check if the target repository exists
echo "Checking target repository: $target_repo"
check_target_repo=$(gh api -X GET "/repos/$owner/$target_repo" -H 'Accept: application/vnd.github.v3+json' 2>&1)
if [[ $? -ne 0 ]]; then
  echo "Error: Failed to retrieve information for target repository: $target_repo"
  echo "$check_target_repo"
  exit 1
fi

# Retrieve the workflow file from the source repository
echo "Retrieving workflow file: $workflow_file"
workflow_file_info=$(gh api -X GET "/repos/$owner/$source_repo/contents/.github/workflows/$workflow_file" -H 'Accept: application/vnd.github.v3+json' 2>&1)
if [[ $? -ne 0 ]]; then
  echo "Failed to retrieve workflow file: $workflow_file"
  echo "$workflow_file_info"
  exit 1
fi

workflow_content=$(echo "$workflow_file_info" | jq -r '.content')
workflow_sha=$(echo "$workflow_file_info" | jq -r '.sha')

# Check if the workflow file already exists in the target repository
echo "Checking if workflow file exists in target repository: $workflow_file"
existing_workflow=$(gh api -X GET "/repos/$owner/$target_repo/contents/.github/workflows/$workflow_file" -H 'Accept: application/vnd.github.v3+json' 2>&1)
exists=$?
if [[ $exists -eq 0 ]]; then
  existing_workflow_sha=$(echo "$existing_workflow" | jq -r '.sha')
  echo "Workflow file exists in the target repository. Grabbing the Sha: $existing_workflow_sha"
else
  echo "Workflow file does not exist in the target repository."
fi

# Update - Skip if Exists
if [[ $exists -eq 0 && "$update_if_exists" = false ]]; then
  # Update the workflow in the target repository
  echo "Skipping Update of Existing Workflow: $workflow_file"
fi

# Update - Update if Exists
if [[ $exists -eq 0 && "$update_if_exists" = true ]]; then
  # Update the workflow in the target repository
  echo "Updating workflow in target repository: $workflow_file"
  update_workflow=$(gh api -X PUT "/repos/$owner/$target_repo/contents/.github/workflows/$workflow_file" \
    -H 'Accept: application/vnd.github.v3+json' \
    -f branch="$target_branch" \
    -f content="$workflow_content" \
    -f message="Update workflow: $workflow_file" \
    -f sha="$existing_workflow_sha" 2>&1)
fi

# Create
if [[ $exists -eq 1 ]]; then
  # Create the workflow in the target repository
  echo "Creating workflow in target repository: $workflow_file"
  create_workflow=$(gh api -X PUT "/repos/$owner/$target_repo/contents/.github/workflows/$workflow_file" \
    -H 'Accept: application/vnd.github.v3+json' \
    -f branch="$target_branch" \
    -f content="$workflow_content" \
    -f message="Copy workflow: $workflow_file" 2>&1)
fi

if [[ $? -ne 0 ]]; then
  echo "Failed to copy workflow: $workflow_file"
  if [[ $exists -eq 0 && "$update_if_exists" = true ]]; then
    echo "$update_workflow"
  else
    echo "$create_workflow"
  fi
  exit 1
fi

# Notify the user that the workflow has been copied
echo "Workflow Logic Completed: $workflow_file from '$source_repo' ($source_branch) to '$target_repo' ($target_branch)."

echo "Enabling Branch Protection Rules for $target_branch on $target_repo"

PAYLOAD='{
    "required_status_checks": null,
    "enforce_admins": true,
    "required_pull_request_reviews": {
        "dismissal_restrictions": {
            "users": [],
            "teams": ["my_team_name"]
        },
        "dismiss_stale_reviews": false,
        "require_code_owner_reviews": false,
        "required_approving_review_count": 1,
        "require_last_push_approval": false,
        "bypass_pull_request_allowances": {
            "users": ["service_account_username"],
            "teams": []
        }
    },
    "restrictions": {
        "users": [],
        "teams": ["my_team_name"],
        "apps": []
    },
    "required_linear_history": false,
    "allow_force_pushes": false,
    "allow_deletions": false,
    "block_creations": true,
    "required_conversation_resolution": true,
    "lock_branch": false,
    "allow_fork_syncing": true
}'

echo "$PAYLOAD" | gh api repos/$owner/$target_repo/branches/$target_branch/protection \
  -H "Accept: application/vnd.github.v3+json" \
  -X PUT \
  --silent \
  --input - || (echo "Failed to enable branch protection rules for $target_branch on $target_repo" && exit 1)

Leave a comment