Table Of Content
This is a part 3 in a 3 part blog series about GitHub Actions.
Part 1: Github Actions 2.0 Is Here!!! – A first look and simple walkthrough of GitHub Actions 2.0 for CI/CD
Part 2: Git Actions 2.0-Let’s do something a little more involved – A deeper dive using GitHub Actions 2.0 for a more involved CI/CD pipeline including provisioining infrastructure using IaC and deploy schema changes in a SqlServer Database using a dacpac.
Part 3: Writing My First Custom Github Action – Walkthrough writing a custom GitHub Action.
Recently, I started playing with GitHub Actions 2.0 for CI/CD and so far it’s been surprisingly easy. It behaves pretty much the way I expect a yaml based task/action runner to behave. See my previous blog posts on doing a quick and dirty ci/cd pipeline for a .net core web app hosted in Azure App Service, and another where I do a much more involved CI/CD pipeline including provisioning infrastructure using IaC and also deploying database schema changes in my pipeline.
What I don’t know how to do is build my own Action to use in a workflow. But once I know how to do that, I’m pretty confident I’ll be able to do just about anything with Github Actions 2.0.
In my last blog, I deployed my database schema using sqlpackage.exe and a dacpac. However using sqlpackage.exe was kind of a pain. It was installed on the GitHub windows vm’s. But first I had to manually figure out the path to sqlpackage.exe, and then I had to figure out exactly how to use sqlpackage.exe (lots of googling!). I think I’ll build an action that will deploy my database schema changes via a dacpac. The action will hide all the nonsense involved so it’s super easy for a user to do a dacpac deploy. At the very least, this will make for a great learning experience!
Creating a new action
This brought me to a page where I filled in the name of the repository I wanted to create (dacpac-deploy), clicked the bright green Create repository from template button and…
Bam! It generated the repo in my account.
All the parts of the repo
Starting from the bottom of the template and working my way up:
- tsconfig.json – configuration for TypeScript
- package.json – node/npm configuration stuff
- package-lock.json – auto generated npm config file that describes all the libraries installed (or something like that, again… not a node expert. barely functional in node really)
- jest.config.js – unit test configuration
- action.yml – Ooooh, this is the important file. This file describes the custom action
- README.md – the readme
- LICENSE – duh
- .gitignore – all the files ignored by git
- src – folder containing the source code for the action. This is the folder for our code.
- docs – folder for your docs
- _tests_ folder for all your unit tests
- .github/workflows – folder for the workflows for this repository
The first thing I need to do is update the action.yml file. This file describes my action as well as defines what all my inputs are. Opening it up, I see
I update this with my action name, a description of my action and the author name. I will also need inputs for my connection string to my sql server database, the path to my dacpac file and another field to hold the optional command line arguments for sqlpackage.exe.
Next, I went into the package.json file and updated the name and description there.
And finally, it’s time to dive into code. The entry point for my code is the main.ts file under src. Right now it looks like this:
Github provides us with 5 libraries that help us do the things we need in an action.
In our sample code, we are using core to get the input variables. For my dacpac-deploy action, I need to get the input for my path to my dacpac file, my connection string to my sql server and any additional command line arguments. Right now, just to see if I can get things working, I’ll just get the input and then echo it to the screen. I updated main.ts to look like this:
Building and deploying my action
To “build” my action, the first thing I do is npm install from the command line at the root of my repo. This installs all the node libraries I need.
Next, I do a npm run build from the command line. This packages and “builds” .ts files into .js files and it puts them under the lib folder
And finally, I have to make sure my node_modules folder has production modules (not dev modules) so I delete the node_modules folder, and then run npm install –production from the command line. Now, I’m ready to check everything in, including the node_modules directory. The node_modules directory is in my .gitignore file so when I add this, I need to add a –f. From the command line, I issue the following commands:
git add *
git add node_modules –f
git commit –m “first take of my action”
Aaaand…. uh…. wait… I think I just wrote and published my first action. Let’s test it out. I quickly create a workflow that uses this action:
Check it in and my output is
SWEET!!!! I just created my first action!!!! Granted it doesn’t do much. It just takes 3 values as inputs and then echoes them out to the console. But its a start!
Time to write my action for real
I’m thinking this action should be pretty easy to write. I have the path to the dacpac, the connection string and additional command line parameters. I also know the path the sqplpackage.exe since I found where it was installed on the GitHub Windows VM in my last blog. All I need to do is call:
sqlpackage.exe /Action:Publish /SourceFile:<path to dacpac> /TargetConnectionString: <connection string> <additional command line args>
Easy peasy! I think what I’m going to do is create an object named DacpacDeployer which does all the heavy lifting. This way, my main.ts doesn’t do anything except instantiate the DacpacDeployer and then calls the deploy() method. By isolating all my logic in the DacpacDeployer task, this should make writing unit tests easier.
I get that this action might be overkill for literally just calling sqlpackage.exe with some parameters. It makes more sense to create an install-sqlpackage.exe action where I add the path to sqlpackage.exe to the path and that’s it. But this is just a learning exercise so I’ll create the dacpac-deploy action.
Here is my DacpacDeployer.ts object:
In my constructor I use the core library to get all my input values. I also get the workspace path from the environment variable GITHUB_WORKSPACE as I’ll need this to construct the full path to my dacpac later.
In my deploy method, I use the core library to add the path to sqlpackage.exe to the path. Then, I create my command line command string from all my variables. And finally, I use the exec library to call sqlpackage.exe with all the command line variables tacked on to it. Because I’m using the exec library, I need to add it to package.json
And here is my main.ts
Very little magic here. I import my DacPacDeployer, instantiate it and then call the deploy method.
To “build and deploy” my action, I call from the command line at the root of my action
- npm install
- npm run build
- delete my node_modules directory
- npm install –production
- git add *
- git add node_modules –f
- git commit –m “initial version of my dacpac deployer”
- git push
Allright! Let’s test my action, see if it works. In my MercuryWebCore repo, I create a simple deployDatabaseWorkflow.yml
Running the build my output is now
Holy crap! I totally built my own github action. That wasn’t hard at all.
Wrapping up this action
There’s a couple of things I need to do before calling this action finished. I need to write unit tests and I also need to write a good readme. This isn’t a blog about unit testing so I won’t dive into that. For the readme, the documentation recommends this:
So…. I update my readme with…
And… that’s a wrap. I just wrote an Action!
Let’s really get crazy
Ok, so writing a github action is really easy. Starting from the template makes everything super easy and the walkthroughs are good too. This one was a little too easy. I was hoping to have more of a challenge. Just to see how it would work, I wanted to see how actions would work if the tool I wanted to use didn’t exist on the vm.
From the documentation, GitHub provides a tool-cache library where you can download and cache tools that aren’t on the vm’s. To test it out, I’m going to pretend sqlpackage.exe is not on the windows vm.
My code now looks like this:
I use the tool-cache library to download my version of sqlpackage.exe (I zipped up the sqlpackage folder and stuck it in azure storage). I then use the tool-cache library to unzip the zip file I just downloaded and then using the core library, I add the path to the downloaded sqlpackage.exe to the PATH and then call this version. Because I’m using the tool-cache library, I need to add it to package.json
And now, when I run this Action I see
Bam!!!!! Everything just works!!!!
Github Actions 2.0 is pretty slick. It’s not complete and it’s definitely in beta but some things are already abundantly clear. It’s pretty simple to write CI/CD pipelines using actions. And creating custom actions is super easy as well. My example in this blog is kind of contrived, but it showed me that I can pretty much do whatever I need to in an Action. The power, flexibility and ease of writing custom actions is SICK!