Developing my first GitHub Action

Github actions have already shown a lot of promise and some distance to travel before it can stand next to its big brother ‘Azure DevOps’. When I say ‘big brother’, they’re both technically Microsoft Products now.

Azure DevOps for historical reasons was tagged as Azure native and Azure only. But, it’s not really an Azure only and I’m aware of organisations using Azure DevOps to manage their infrastructure. Don’t believe me… time to take the red pill and read about the AWS Toolkit for Microsoft Azure DevOps

Common sense prevailing, it’s right to market GitHub as the true multi-cloud champion instead of Azure DevOps (previously known as Microsoft VSTS,TFS). Which is what Microsoft seems to be doing with the addition of number features from areas such as Azure Pipelines and Visual Studio Codespaces to make the product more robust and enterprise friendly.

In this post I’m going to explain how to create a custom action for Azure, using my experience building an action as a part of https://githubhackathon.com

I’m in the Winners List :)

- My Action repository
- My Action in Marketplace

Actions documentation is pretty concise and helps you to get started quickly. Below links cover the basics for you to get started;

There are two types of actions “JavaScript and Docker Container”. While JavaScript directly runs on the runner, allowing for faster execution, the Docker Container allows for a consistent and reliable unit without having to worry about tools and dependencies.

I went for the reliability and decided to live with the extra time taken to build the container.

Setup

To make ‘GitHub Actions’ ready, you need to tick off few things as a part of your github repository;

  • inputs — input variables
  • outputs — output variables which can be used later in the workflow
  • branding — required for publishing to the marketplace
  • runs — using: 'docker' with image: 'Dockerfile' and the inputs set as args: ...

For my first attempt tried to start with a vanilla alpine and quickly realised that I’m trying to re-invent the wheel. Best starting ground for azure is to use the official azure cli container.

Below is the working Dockerfile content of my action;

# Using latest might cause issues with breaking changes.
# FROM mcr.microsoft.com/azure-cli:latest
# https://hub.docker.com/_/microsoft-azure-cli
# https://mcr.microsoft.com/v2/azure-cli/tags/list
FROM mcr.microsoft.com/azure-cli:2.8.0
# Copies your code file from your action repository to the filesystem path `/` of the container.
COPY entrypoint.sh /entrypoint.sh
# Set the missing exec permission, just in case if you're on on a *nix.
RUN chmod +x ./entrypoint.sh
# Enable dig to find the runner's public IP
RUN apk update && apk add --no-cache bind-tools && rm -rf /var/cache/apk/*
# Code file to execute when the docker container starts up (`entrypoint.sh`)
ENTRYPOINT ["/entrypoint.sh"]
  • Consideration #1

Don’t use FROM mcr.microsoft.com/azure-cli:latest. Versions change pretty fast and you wouldn’t want your workflow to break due to uncontrollable changes. Refer to the list of tags and use the latest at the point of creation and rollout updates overtime. I’ve started with 2.2.0 and moved to 2.8.0 at the point current update to the post.

E.g. FROM mcr.microsoft.com/azure-cli:2.8.0

  • Consideration #2

Make sure your entrypiont.sh file is executable. Use RUN chmod +x ./entrypoint.sh in your Dockerfile

What

My action is capable of two things.

E.g. Deploy to a protected Web App running on an ASE or an Azure VM.

Let’s look at what the entrypoint.sh does. Comments should tell you all about it!

#!/bin/sh -l# Ensure the workflow fails on error
set -e
_azure_credentails=$1
_rule_priority_start=$2
_rule_priority_end=$(($2+$3))
_rule_port=$4
_rule_id_for_removal=$5
_rule_nsg_resource_group=$6
_rule_nsg=$7
_rule_public_ip=$(dig +short myip.opendns.com @resolver1.opendns.com) # Get runner's public IP
# break the azure credientials JSON
_client_id=$(echo $_azure_credentails | jq -r '.clientId')
_client_secret=$(echo $_azure_credentails | jq -r '.clientSecret')
_tenant_id=$(echo $_azure_credentails | jq -r '.tenantId')
_subscription_id=$(echo $_azure_credentails | jq -r '.subscriptionId')
# Login to azure using service principal
az login --service-principal -u $_client_id -p $_client_secret --tenant $_tenant_id
# Select the subscription
az account set --subscription $_subscription_id
if [ $_rule_id_for_removal ]
then
# removing the rule
echo "Removing rule $_rule_id_for_removal"
az network nsg rule delete -g $_rule_nsg_resource_group --nsg-name $_rule_nsg -n $_rule_id_for_removal
echo "::set-output name=rule_name::$_rule_id_for_removal"
else
# adding the rule
echo _rule_port: $_rule_port
echo _rule_priority_start: $_rule_priority_start
echo _rule_priority_end: $_rule_priority_end
_rule_priority=$(shuf -i $_rule_priority_start-$_rule_priority_end -n 1)
_rule_name=manage-nsg-github-actions-$_rule_priority
echo "Adding rule.... $_rule_name"
az network nsg rule create -g $_rule_nsg_resource_group --nsg-name $_rule_nsg \
-n $_rule_name --priority $_rule_priority \
--source-address-prefixes $_rule_public_ip --source-port-ranges '*' \
--destination-address-prefixes '*' --destination-port-ranges '*' \
--access Allow --protocol '*' \
--description 'Allow from IP address of github actions hosted runner temporarily'
# output
echo "::set-output name=rule_name::$_rule_name"
fi

Publish

  • Publishing is easy. Go to the releases and tick the box to “Publish this Action to the GitHub Marketplace”
  • Define a tag which allows the users to define which version of the action to use.
  • Select your categories.
  • Give a Title and a Description for the release.
  • Hit “Publish Release”
  • Make sure to tag the unstable releases as “This is a pre-release” to let the users know that it’s not ready for consumption.

Test

Testing is easy. All you need is a workflow that runs on: push for the relevant branches and test the action.

Note: Instead of using a specific release I’m using the branch to ensure that I’m testing the latest code on the branch. uses: venura9/manage-nsg@master

name: run_test_masteron:
push:
branches:
- master
jobs:
test:
runs-on: ubuntu-latest
name: Run GitHub Action Tests
steps:
- name: dig +short myip.opendns.com @resolver1.opendns.com
run: dig +short myip.opendns.com @resolver1.opendns.com
- name: Add NSG Rule
uses: venura9/manage-nsg@master
id: rule
with:
azure-credentials: $
rule-nsg-resource-group-name: ManageNsg
rule-nsg-name: ManageNsg
- name: Print Created NSG Rule Name
run: echo "Rule Name $"
- name: Remove NSG Rule
uses: venura9/manage-nsg@master
with:
azure-credentials: $
rule-id-for-removal: $
rule-nsg-resource-group-name: ManageNsg
rule-nsg-name: ManageNsg

Local Actions

You can test the action even before publishing using the local actions. This feature allows for private actions that are specific to a repository.

jobs:
my_first_job:
steps:
- name: Check out repository
uses: actions/checkout@v2
- name: Use local my-action
uses: ./.github/actions/my-action

Trying to run stuff on someone else’s `Tin` & do it better next time around.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store