A Custom GitHub Action
Git Submodules are a powerful way to manage dependencies in your projects. However, they can be difficult to manage manually. GitHub Actions and Octokit can help to streamline the management of Git Submodules.
GitHub Actions are a way to automate tasks in your GitHub repository. Octokit is a library that allows you to interact with GitHub from code. By using GitHub Actions and Octokit, you can create a workflow that automatically updates your submodules whenever you push to your repository.
This tutorial will show you how to streamline Git Submodules with GitHub Actions and Octokit.
Understanding the Problem π
Managing a repository that incorporates multiple submodules can present significant challenges when it comes to keeping track of changes and pipelines. Submodules are separate repositories embedded within a main repository, allowing for modular code organization and reusability.
However, as the number of submodules increases, manually monitoring their individual changes and pipeline statuses becomes increasingly burdensome and error-prone.
While submodules offer numerous advantages, they also introduce specific challenges that developers must understand and address effectively. In this section, we will explore some key problems that arise when managing a repository with multiple submodules and provide insights on how to mitigate them. Below are such problems :
1. Dependency Management π»
One of the primary challenges when working with submodules is ensuring proper dependency management. Each submodule may have its own dependencies and version requirements, which can lead to conflicts and compatibility issues.
Tracking and synchronizing dependencies across multiple submodules becomes crucial to maintain a stable and functional codebase.
2. Repository Cloning and Initialization π
When cloning a repository with submodules, additional steps are required to initialize and fetch the submodule content. This process can be cumbersome, particularly for new team members or when dealing with a large number of submodules.
Failure to properly initialize submodules can result in broken or incomplete codebases, leading to debugging and deployment challenges.
3. Version Control and Updates π«
Managing versions and updates across submodules can become intricate. Each submodule may have its own release cycle and versioning scheme. Coordinating updates, bug fixes, and feature enhancements across multiple submodules can be time-consuming and error-prone.
Additionally, ensuring that the main repository and submodules remain in sync requires careful planning and execution.
4. Collaboration and Communication π
When multiple developers are working on a repository with submodules, effective collaboration and communication become paramount. Without proper coordination, conflicts can arise when developers modify the same submodule simultaneously. Clear guidelines and communication channels are essential to avoid conflicts and streamline collaborative efforts.
To address these challenges, a solution is needed to automate the process of tracking changes and pipeline statuses across submodules. The next section will introduce a GitHub action specifically designed to manage, track, automate, and streamline repository management and provide an efficient and reliable solution.
Whatβs GitHub Action? π€·
GitHub actions have revolutionized the way developers collaborate on projects hosted on the GitHub platform. These actions allow users to automate various tasks, enhance workflows, and streamline development processes. The fun part of this tool is that you can create a custom Action that automates module tracking and management.
Overview of GitHub OctoKit
Octokit is a GitHub API client library for JavaScript. It allows you to interact with GitHub from code, and it provides a number of methods for interacting with items from GitHub.
Some of the items that you can compute from GitHub with Octokit include :
- Repositories : You can get information about repositories, such as their name, description, and stars.
- Issues : You can get information about issues, such as their title, description, and comments.
- Pull requests : You can get information about pull requests, such as their title, description, and commits.
- Commits : You can get information about commits, such as their message, author, and date.
To compute items from GitHub with Octokit, you first need to create a client object. You can do this by calling the octokit.client()
method. Once you have a client object, you can use the methods that are provided by Octokit to compute the items that you need.
For example, to get information about a repository, you would use the octokit.getRepository()
method. This method takes the name of the repository as its argument, and it returns a Repository object. The Repository object contains information about the repository, such as its name, description, and stars.
GitLab, Azure, GitHub provide native CI/CD support. These platforms provide a centralized location within organizations to view all workflows.
To enhance this functionality further and customize it according to your specific needs, we can leverage OctoKit and build a custom GitHub Actions solution. In the next section, weβll be moving to the fun part (i.e setup, coding, and implementation π)
Setting Up the Custom GitHub Action π·
GitHub Actions allow you to automate various tasks and workflows within your GitHub repositories using yaml
files.
In this guide, we will walk you through the process of setting up a custom GitHub Action with code samples and hook up your actions to octokit
so you can perform any tasks that your creativity comes up with!
lets get started ! π
Step 1 : Create a New Workflow File
- Open your GitHub repository in your preferred code editor or directly on the GitHub website. You can access the repository used in this content here.
- Navigate to the
.github/workflows
or create directory within the root directory if itβs not there already. - Create a new file within the
.github/workflows
and name itcustom-action.yml
.
Step 2 : Define the Workflow
- Open the
custom-action.yml
file. - Add the following code to define the workflow :
name: Custom Action
on:
pull_request:
branches:
- main
This configuration specifies that the custom action will be triggered whenever a pull request is made to the main
branch. You can modify the `branches` field to target a different branch if needed.
Step 3 : Configure the Job π·ββοΈ
Add the following code to configure the job within the workflow :
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: 14
- name: Run Custom Action
uses: ./
env:
root_token: ${{ secrets.ROOT_TOKEN }}
ci_token: ${{ secrets.CI_TOKEN }}
target_owner: YourTargetOwner
target_submodules: |
submodule1
submodule2
target_branch: YourTargetBranch
target_workflow: YourTargetWorkflow
make sure to replace YourTargetOwner
, YourTargetBranch
, YourTargetWorkflow
, and other placeholders with your actual values.
Step 4 : Add the Custom Action Code
- Create a new file in your repository's root directory and name it
main.ts
. - Then add your custom action code. You can use the code sample below to get started.
Copy and paste the code into your main.ts
file :
import * as core from '@actions/core'
import * as github from "@actions/github"
import { Octokit, } from "octokit";
async function getTargetPrData(targetOwner: string, targetRepo: string, targetBranch: string) {
const api = new Octokit({
auth: core.getInput('root_token'),
});
const { data } = await api.rest.pulls.list({
owner: targetOwner,
repo: targetRepo,
head: targetOwner + ":" + targetBranch,
sort: "updated",
state: "all"
})
if (data.length === 0) return null;
const prData = data[0]
return prData;
}
async function removePreviousBotComment() {
const api = new Octokit({
auth: core.getInput('ci_token'),
});
const { data } = await api.rest.issues.listComments({
owner: github.context.issue.owner,
repo: github.context.issue.repo,
issue_number: github.context.issue.number
})
let comment_id;
for (let comment of data) {
if (comment.body.includes("action info")) {
comment_id = comment.id;
break;
}
}
if (comment_id === undefined) return;
await api.rest.issues.deleteComment({
owner: github.context.issue.owner,
repo: github.context.issue.repo,
comment_id
})
}
async function pushCommentOnPr(targetPrDataList: {
title: string;
html_url: string;
state: string;
owner: string;
repo: string;
branch: string;
}[]) {
const api = new Octokit({
auth: core.getInput('ci_token'),
});
let body = `# action info \n`
for (let targetPrData of targetPrDataList) {
body += `## [${targetPrData.repo} - ${targetPrData.title}](${targetPrData.html_url})\n**pull request status**: ${targetPrData.state}\n**pipeline status**: }/badge.svg?branch=${targetPrData.branch})\n`
}
await api.rest.issues.createComment({
owner: github.context.issue.owner,
repo: github.context.issue.repo,
issue_number: github.context.issue.number,
body
})
}
async function run(): Promise<void> {
try {
const owner = core.getInput('target_owner');
const submodules = core.getMultilineInput("target_submodules")
const targetBranch = core.getInput("target_branch")
const targetPrDataList = []
for (let submodule of submodules) {
const targetPrData = await getTargetPrData(owner, submodule, targetBranch)
if (targetPrData) targetPrDataList.push({ ...targetPrData, owner: owner, repo: submodule, branch: targetBranch })
}
await removePreviousBotComment()
await pushCommentOnPr(targetPrDataList)
} catch (error) {
core.setFailed(error.message);
}
}
run()
Below is a breakdown of what the code above does :
1. Imports βοΈ
import * as core from '@actions/core'
: Imports the core module from the @actions/core package. It provides functions to interact with GitHub Actions' core features.
import * as github from "@actions/github"
: Imports the github module from the @actions/github package. It provides access to the GitHub context and related functionalities.
import { Octokit } from "octokit"
: Imports the Octokit class from the "octokit" package. It is a GitHub REST API client for Node.js.
2. Functions π¦
getTargetPrData(targetOwner, targetRepo, targetBranch)
- retrieves the data of the target pull request.
- It creates an instance of the Octokit class using the provided root_token from the GitHub repository secrets.
- It uses the api.rest.pulls.list() method to fetch the pull requests matching the given parameters (owner, repo, head, sort, and state).
- If the data array is empty, it returns null. Otherwise, it returns the first pull request data from the array.
removePreviousBotComment()
- removes the previous bot comment from the GitHub issue.
- It creates an instance of the Octokit class using the provided ci_token from the GitHub repository secrets.
- It uses the api.rest.issues.listComments() method to fetch the comments of the current GitHub issue.
- It iterates over the comments and checks if any comment's body includes the string "action info".
- If a matching comment is found, it stores its ID and breaks the loop.
- If a comment ID is found, it uses the api.rest.issues.deleteComment() method to delete the comment.
pushCommentOnPr(targetPrDataList)
- pushes a comment on the pull request with relevant information.
- It creates an instance of the Octokit class using the provided ci_token from the GitHub repository secrets.
- It generates a body string for the comment, including details about the target pull request.
- It iterates over the targetPrDataList array, adding information about each pull request to the body string.
- Finally, it uses the api.rest.issues.createComment() method to create the comment on the GitHub issue.
run()
- This is the main function that orchestrates the custom GitHub Action.
- It utilizes the core module to retrieve inputs such as target_owner, target_submodules, target_branch, etc.
- It calls the getTargetPrData() function for each submodule, gathers the data, and adds it to the targetPrDataList.
- It then removes the previous bot comment using the removePreviousBotComment() function.
- Finally, it pushes a new comment on the pull request with the relevant information using the pushCommentOnPr() function.
3. Execution β‘οΈ
run()
- This executes the run() function, kicking off the custom GitHub Action.
- It wraps the function call within a try/catch block to handle any potential errors.
- If an error occurs, it sets the failure message using core.setFailed().
Before committing and pushing your codes, create a package.json
file and insert the code below into it :
{
"name": "action-submodules",
"version": "0.0.0",
"private": true,
"description": "TypeScript template action",
"main": "lib/main.js",
"scripts": {
"build": "tsc",
"format": "prettier --write '**/*.ts'",
"format-check": "prettier --check '**/*.ts'",
"package": "ncc build --source-map --license licenses.txt",
"all": "npm run build && npm run format && npm run lint && npm run package && npm test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/actions/typescript-action.git"
},
"keywords": [
"actions",
"node",
"setup"
],
"author": "",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/github": "^5.1.1",
"octokit": "^2.0.14"
},
"devDependencies": {
"@types/node": "^18.15.13",
"@vercel/ncc": "^0.36.1",
"js-yaml": "^4.1.0",
"prettier": "^2.8.7",
"typescript": "^5.0.4"
}
}
run npm init
to initialize and get the dependencies and packages in our package.json
file
Step 5 : Commit and Push π
- Save all the changes you made to the files.
- Commit the changes and push them to your repository.
Step 6 : Configure Secrets π€«
- Go to your GitHub repository's page.
- Click on "Settings"
- Click on "Secrets"
- Click on "New repository secret"
- Create two secrets with the following names and corresponding values :
-
ROOT_TOKEN
: Your GitHub personal access token with the necessary permissions.-
CI_TOKEN
: Another GitHub personal access token with appropriate permissions.
Step 7 : Test the Custom Action π§ͺ
- Create or modify a pull request on the
main
branch to trigger the custom action. - Wait for the workflow to run.
- Check the pull request for a new comment that includes the information about the target pull request's data and pipeline status.
Congratulations ! You have successfully set up a custom GitHub Action using the provided code sample.
You can further customize the action and workflow according to your specific requirements by modifying the code and workflow configuration.
the final render should look something like this :
this project is also available from the zip below :
Conclusion β
In this tutorial, we have seen how to streamline Git Submodules with custom GitHub Actions and Octokit. This allows us to automatically update our submodules whenever we push to our repository. This can save us a lot of time and effort, and it can also help to ensure that our submodules are always up-to-date.
If you are using Git Submodules in your projects, I encourage you to try out this tutorial. It is a simple way to improve the way you manage your submodules.