Cloudy with a Chance of Git Pulls

Cloudy with a Chance of Git Pulls

·

16 min read

How to use Open Weather API with GitHub Actions

This tutorial covers creating a custom GitHub Action to generate automated Weather Forecasts as seen below (and here). It relies on the Open Weather API and Nodejs. Once you understand the basic steps involves here, you will be able to apply them to automate all nature of things.

Wellington
title
19.23°C
Clouds: 90%
Humidity: 73%
Wind: 10.29 m/s
Pressure: 1001hpa

Actions are often an important part of continuous integration and development process. As developers, we enjoy being as lazy as possible. We can do this, by automating tedious, dull and repetitive tasks. This reduces the human resources cost and improves the efficiency of your work. The more you can automate in your day to day life, the more time you have to spend on what is meaningful. My boss once told me that I am the laziest worker he had ever seen, but he said he meant that as the highest form of praise, to describe how efficiently I worked. Or perhaps, he has just trying to be nice.

The repository relies on four main files:

We will go into detail about the implementation details of each of those below. But these are the basic components required to build our own custom GitHub action.

Setting us Node

We use nodejs to run our action on a virtual server provided by GitHub. For this script to work, we need to create a node project:

npm init
# Press <enter> until the the prompts stop

Install the necessary packages:

npm i @vercel/ncc node-fetch fs @actions/core

Then append our packages.json to include the following script:

"scripts": {
    "build": "ncc build src/action.js -o dist",
    ...
  },

Before pushing this repository to GitHub make sure to include a .gitignore file with node_modules in at the root level of your directory:

node_modules

This ignores the very large and unused directory node_modules, which stores the code for all the libraries we just imported. It is not needed by our GitHub action to run.

README.md

An example of a README file for this action is shown below.

# Weather Forecast
![Weather](https://github.com/woodRock/expert-chainsaw/workflows/Weather/badge.svg)

Tumeric single-origin coffee taiyaki, literally craft beer enamel pin plaid chia direct trade 90's distillery retro vaporware. PBR&B hexagon blue bottle banh mi mumblecore synth, pitchfork actually vegan church-key bicycle rights iceland occupy street art. Normcore disrupt you probably haven't heard of them, crucifix single-origin coffee cornhole listicle. Distillery iPhone enamel pin direct trade venmo quinoa jianbing four loko bespoke unicorn.

## Forecast
<!-- FEED-START -->
<!-- FEED-END -->

It is the page that is displayed to the user when they first visit the URL for a GitHub repo. It uses the Markdown markup language to produce styled text. The Markdown spec even allows us to include HTML snippets (i.e., divs or comments). It is important to note, that to avoid security vulnerabilities such a XSS (Cross-Site Scripting), GitHub does not allow JavaScript to embedded into their statically hosted README files. We will come back to why this is important soon.

At the top of this file, we have a title, # Weather Forecast, this is similar to the <h1> tag from HTML, where the number of hashes # denotes the weight of the header (i.e., ## equals <h2>, ### equals <h3>, etc ... ). Also worth noting, Hipster Ipsum can be used to create amusing placeholder text as seen above.

We then have an embedded Action's workflow badge. These can be generated by clicking the Actions tab on the repository. Selecting a workflow, in our case weather. Expanding the three dots ... option in the top right, and clicking create status badge. This automatically generates the embedded HTML necessary to be added to our Markdown file. Programmers love these badges, and adorn them to their repositories as badges of honour, so welcome to the club!

We then have a secondary header for our weather forecast ## Forecast, followed by an HTML comment. This comment is hidden when viewing the README on the URL, but it is there when we edit the document. Notice the tags FEED-START and FEED-END. This let our action.js script (to be discussed later) know where we want to append our weather forecast. We use these tags so that we do not overwrite the existing content of the README, each time a new forecast is given.

action.yml

This is the YAML file for our action. It describes the necessary meta-data, such that we can publish it to the GitHub market place, and the inputs that are required for the action to function.

name: "Weather Forecast Action"
description: "Display weather forecast for any area in a project README"
author: "woodrock"

inputs:
  GITHUB_TOKEN:
    description: "GitHub token"
    required: true
  OPEN_WEATHER_TOKEN:
    description: "Open Weather API Token"
    required: true
  CITY:
    description: "City to get the forecast for"
    required: true

runs:
  using: "node12"
  main: "dist/index.js"

Here we have three inputs:

  • GITHUB_TOKEN
  • OPEN_WEATHER_TOKEN
  • CITY

These are environment variables that are used by our scripts. The first two provide access to the APIs, so we can generate the necessary content. The GITHUB_TOKEN is automatically generated by the repository, so we do not need to worry about that.

The OPEN_WEATHER_TOKEN is an API key from the Open Weather API. You will need to register an account for this site, should you wish to recreate this action. Once registered, click on your username on the top right of the navigation bar, then select 'My API Keys'. You can either use the default key provided by your account or create an isolated key, to be used specifically by this application. Copy that key into your clipboard.

Now we need to create a GitHub secret. This is an environment variable stored securely by GitHub so that we don't expose sensitive information, such as our API Key, to the public. Return to the repository. Click Settings. On the navigation bar to the left, select secrets. Select New Repository Secret, call it OPEN_WEATHER_TOKEN and copy and past you Open Weather API key as its value.

We specify the city in the weather.yml. This is included so that this action can be reused for different locations. Now, it can be used to forecast the weather anywhere in the world, as opposed to hard-coding a city value in our action.js. Generic solutions in the software are more maintainable as they encourage re-use without the need to refactor.

action.js

The script that performs the work is shown below. It is located in the /src directory.

const fetch = require("node-fetch");
const fs = require("fs");
const core = require("@actions/core");

const FILE_NAME = "./README.md";
const ENCODING = "utf8";
const TAG_OPEN = `<!-- FEED-START -->`;
const TAG_CLOSE = `<!-- FEED-END -->`;

async function fetchWeather() {
  const API_KEY = core.getInput("OPEN_WEATHER_TOKEN");
  const city = core.getInput("CITY");
  const URL = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&mode=html`;
  return fetch(URL)
    .then((response) => response.text())
    .then((html) => html);
}

async function run() {
  const readme = fs.readFileSync(FILE_NAME, ENCODING);
  const indexBefore = readme.indexOf(TAG_OPEN) + TAG_OPEN.length;
  const indexAfter = readme.indexOf(TAG_CLOSE);
  const before = readme.substring(0, indexBefore);
  const after = readme.substring(indexAfter);
  const input = (await fetchWeather()).replace(/<script.*>.*<\/script>/ims, "");
  const edited = `
${before}
${input}
${after}`;
  fs.writeFileSync(FILE_NAME, edited.trim());
}

try {
  run();
} catch (error) {
  console.log(error);
}

At the top, we import all the packages we need. We use fetch to make an API call using javascript asynchronous functions. fs allows us to perform FileIO operations, such as reading and writing to a README.md. @actions/core allows us to request access to the repository secrets, our OPEN_WEATHER_TOKEN, and the CITY environment variable.

The fetchWeather() method, requests the Open Weather API token, and performs an API call using fetch(). We use string templating to structure a custom call to this API:

`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&mode=html`

We specify the city and the API key and request that the response from the API in an HTML format. So that we can simply embed this into our README. The weather API returns an HTML object, that includes <script> tags to neatly render the response. However, as mentioned previously, we cannot embed JavaScript into a static README hosted by GitHub. So instead, we strip the HTML of the <script> tags and the content inside them. This is done using the replace() method:

await fetchWeather().replace(/<script.*>.*<\/script>/ims, "")

Then we perform some arithmetic, to find where our FEED-START and FEED-END tags are, and append the embedded weather forecast within these tags. Without overwriting any of the existing document outside these tags.

This script must be built with by running the following command in your terminal:

npm run build

This ensures that our action a compiled version of our current program.

weather.yml

This is the GitHub Workflow that runs our script. It is located in the .github/workflows directory.

name: "Weather"

on:
  workflow_dispatch:
  schedule:
    - cron: "*/30 * * * *" # Runs every 30 minutes

jobs:
  update_blogs:
    name: "Update Blogs"
    runs-on: ubuntu-latest
    steps:
      - name: "📥 Fetching Repository Contents"
        uses: actions/checkout@main
      - name: "🌨️ Fetching Weather Forecast"
        uses: ./
        with:
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
          OPEN_WEATHER_TOKEN: ${{secrets.OPEN_WEATHER_TOKEN}}
          CITY: "Wellington, New Zealand"
      - name: "🛠️ Push changes to GitHub"
        run:
            git config --global user.email readme-bot@example.com && git config --global user.name readme-bot;
            git diff --quiet && git diff --staged --quiet || git commit -am '[BOT] Update Readme' && git push;

We use the workflow_dispatch so we can trigger our script to be run automatically on certain intervals. We specify that interval using cron. This is a time-based job scheduler from Unix machines. Here */30 * * * * we set the workflow to be run once every 30 minutes, every hour, every month, every day of the week.

Our workflow has three jobs. The first uses actions/checkout@main to clone our repository to the virtual machine running the workflow. The second run our script using the node12 docker image we specified in the action.yml. With the main script being dist/action.js. We provide access repository secretes, the GITHUB_TOKEN and OPEN_WEATHER_TOKEN, under the with section. We can specify the CITY of our choice here, in this case, I specify my home city Wellington, New Zealand.

Finally, we push our changes to the repository. The workflow is set to have a GitHub user with the name readme-bot. This makes it clear that updates to the README in our repository are being done by a service account, not an actual user. This is an important distinction, for separating intentional changes by users, from automated ones by our GitHub Action. To reinforce this idea we include [BOT] in the commit message as well.

Conclusion

And we are done. This is how you build an automated weather forecasting bot using GitHub Actions. The principles learnt here can be applied to any other API of your choice. Here is a list just to name a few, the possibilities are endless. But after completing this tutorial, you should feel comfortable implementing APIs of your own. Each free GitHub account has access to 2000 Workflow minutes, so don't let them go to waste.

Appendix

To see the whole thing in action, please check out the original repository, feel free to fork this and build your amazing Actions.