Table of contents
GitHub Actions is a powerful automation tool that enables developers to automate various software development workflows, such as building, testing, and deploying code. We won't be doing all that, in this tutorial, we'll demonstrate how to create a Github Action that updates your profile README every day at a specified time using cron. If you just want to use the action here's the link: Blog_It. Let's get started.
Rust App
We could have used any language but we're going to use Rust 🦀 (why? because it's awesome). Let's first create a CLI rust application that will fetch posts from the RSS feed and update a file. We'll take all the arguments from the user using clap.
use chrono::DateTime;
use clap::Parser;
use regex::Regex;
use reqwest::blocking::Client;
use rss::Channel;
/// Simple program to greet a person
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// RSS url to fetch blogs from
#[arg(short, long)]
url: String,
/// Number of times to greet
#[arg(short, long, default_value_t = 5)]
max_number_of_posts: u8,
#[arg(short, long)]
target_md: String
}
fn main() {
let args = Args::parse();
let url = args.url;
let max_number_of_posts = args.max_number_of_posts;
let target_md = args.target_md;
let client = Client::new();
let response = client.get(url).send().unwrap();
let rss = response.bytes().unwrap();
let channel = Channel::read_from(&rss[..]).unwrap();
let mut content = String::new();
for (_index, item) in channel
.items()
.iter()
.enumerate()
.take(max_number_of_posts.into())
{
let pub_date = DateTime::parse_from_rfc2822(item.pub_date().unwrap())
.unwrap()
.format("%Y-%m-%d")
.to_string();
content.push_str(&format!(
"[{}]({}) - {}\n\n",
item.title().unwrap(),
item.link().unwrap(),
pub_date
));
}
let re = Regex::new(r"(?s)<!-- blogs starts -->.*<!-- blogs ends -->").unwrap();
let readme = std::fs::read_to_string(target_md.clone()).unwrap();
let new_readme = re.replace_all(
&readme,
&format!("<!-- blogs starts -->\n{}\n<!-- blogs ends -->", content),
);
std::fs::write(target_md, new_readme.as_bytes()).unwrap();
}
Workflows
We'll have two workflows, one to build our executable whenever there's a change and one to run it on our cron schedule.
Cron
Cron is a time-based job scheduler used in Unix-based systems. It allows you to run commands or scripts at specified times. In GitHub Actions, you can schedule actions using cron syntax. Check out the syntax:
* * * * *
- - - - -
| | | | |
| | | | ----- Day of week (0 - 7) (Sunday = both 0 and 7)
| | | ------- Month (1 - 12)
| | --------- Day of month (1 - 31)
| ----------- Hour (0 - 23)
------------- Minute (0 - 59)
If you're confused, don't worry, you'll know what it means soon enough.
name: Update Profile README Everyday
on:
schedule:
- cron: '0 0 * * *'
jobs:
update_readme:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Update README
run: |
echo "Updating profile README"
# Command to update the README here
In this example, the cron syntax 0 0 * * *
specifies that the workflow should run every day at midnight (UTC). similarly, 32 13 1 12 2
specifies that it should run at 13:32 on 1st day-of-month and Tuesday in December. This website is really helpful, crontab.guru.
To create the GitHub Action, we need to create a couple of new workflows in our GitHub repository. To do this, go to your repository, click on the Actions tab, and then click on the New workflow button.
Build Workflow
It'll run on every push on "main" branch, on every PR merge on "main" branch and if triggered manually (
workflow_dispatch
).We'll checkout our repository.
Build an optimized executable binary.
Upload it as an artifact.
name: Build on every update
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
jobs:
build_binary:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Build release
run: cargo build --bin blog_puller --release --verbose
- uses: actions/upload-artifact@v3
with:
name: blog_puller_bin
path: target/release/blog_puller
Update Workflow
It'll run every day at 00:00 UTC and if triggered manually (
workflow_dispatch
).We'll checkout our repository.
Download the executable binary that we upload as an artifact.
Add execute permissions.
Run the executable.
Check if there were any changes in our repo files, if so, commit changes in "README.md" and push.
name: Update Profile README Everyday
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
jobs:
update_readme:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Download binary
uses: dawidd6/action-download-artifact@v2
with:
workflow: build.yml
name: blog_puller_bin
- name: Add permissions
run: chmod 777 blog_puller
- name: Run
run: ./blog_puller
- name: Commit and push if README changed
run: |-
git diff
git config --global user.email "actions@users.noreply.github.com"
git config --global user.name "README-bot"
git diff --quiet || (git add README.md && git commit -m "Updated README")
git push
Results
Once the later workflow has been successfully executed. The blogs will be visible on the profile README between tags <!-- blogs starts -->
and <!-- blogs ends -->
. Here's the complete example repo.
To get notified when the action is released or for similar articles follow me on Twitter at AzanulZ or connect with me on LinkedIn, Azanul Haque.