Using GitHub Actions (missteps included)

Fresh off the back of the excellent Microsoft Build I wanted to try out the CICD capabilities directly inside GitHub; aka GitHub Actions (although ‘Actions’ aren’t specifically limited to this area).

Getting started is absolutely free, with 2000 workflow minutes per month available right off of the bat (increasing based on your account type). The process is mainly painless, to be honest; however, I’ve included all of the times I fell reeling into the pit in the hopes you’ll dodge the bullets.

What are GitHub Actions?

GitHub Actions are, in essence, ‘triggers’ that are fired when GitHub events are kicked into motion. They are for the building, testing and deployment of code alongside the automation of your coding workflows (think actions for assessing code quality or scanning your repository for secrets periodically). The ecosystem is growing; see the full marketplace here. I’ll be focusing on the build, test and deployment of a simple ASP.NET Core Web Application.

Getting Started

You’ll need:

  1. A GitHub account (free is perfectly fine).
  2. A repository to get you going (a private repo, again, totally works so no issues there).

Here is the starter configuration I’ve gone with:

Repository Setup
Repository Setup.

I’ll be working inside Visual Studio, hence the choice of the Visual Studio ‘.gitignore’. The repository is set to private and initialised with a ‘README’ so I can get on with cloning this straightway. I’m using the GitHub desktop client this time around but use whatever floats your boat (Fork, GitKraken, command line, etc.) will suffice.

Clone Setup
Clone Setup.

After cloning, using the URL from GitHub, I’ll get started with making the sample application in the newly created repository. I’m going to use the template application as we’re only interested in a demonstration of GitHub Actions. Inside Visual Studio create an ‘ASP.NET Core Web Application’ using the ‘Web Application’ template. I’ve configured the project to use HTTPS and am looking to target .NET Core 3.1.

Starter Web Application
Starter Web Application.
Starter Web Application Template
Starter Web Application Template.

At this point, run up the application and confirm it compiles and runs:

Web Application Running
Web Application Running.

Great stuff, we now have an application to build a workflow against. At this point, we’ll add an ‘xUnit Test Project’ and add one, fake test so we can include a test step in our workflow later on.

Sample Unit Tests
Sample Unit Tests.

Create a commit and push the code up to the master branch, we’re back off to the GitHub web interface.

Adding a GitHub Action

Back in the web interface, the following link will be available for creating our first action. I will say that, at this time, I would probably defer off the task of creating an action to the Azure ‘Deployment Center’ interface for reasons you’ll see later on (I’m sure this won’t be a long-standing issue though). But, to show you my full end-to-end experience here is how I got on.

Click the link shown here:

Creating an Action
Creating an Action.

At this point, you’ll receive suggestions as to what actions might be appropriate based on the type of your repository. Here, I selected the .NET Core option by clicking the ‘Set up this workflow’ button.

Set up a Workflow
Set up a Workflow.

Great, we’re well on our way to configuring up a build, test and deploy pipeline. The next screen that is presented allows you to name the GitHub Action configuration file (which takes the form of a YAML mark-up file), as well as browse the marketplace for other actions of interest. When finished, you can click the ‘Start commit’ button to create a branch/pull request or be rogue (as I am going to be) and commit directly to the master branch. 🙂

Notice how the new configuration file is created inside the ‘.github/workflows’ folder. Here is the ‘.yml’ file structure that I started with:

name: .NET Core

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.1.101
    - name: Install dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --configuration Release --no-restore
    - name: Test
      run: dotnet test --no-restore --verbosity normal
Configuring an Action
Configuring an Action.

This is where I made the first misstep, which may also catch you out depending on the directory structure of your target project. With my repository, the ‘solution’ file is nested within the ‘BearStarterWebApp’ folder, meaning the working directory needs amending slightly for the steps outlined in the YAML file. Here’s the error that I first received after committing this file, with the results accessible via the ‘Actions’ link in the header section of the repository:

Working Directory Error
Working Directory Error.

Thankfully, this isn’t too difficult to fix. I made the following modifications to the ‘dotnet-core.yml’ file so that all jobs run, by default, inside the nested ‘BearStarterWebApp’ directory. I also increased the version of .NET Core (inside the ‘actions/setup-dotnet@v1’ step) being used to ‘3.1.201‘.

name: .NET Core

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: BearStarterWebApp

    steps:
    - uses: actions/checkout@v2
    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.1.201
    - name: Install dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --configuration Release --no-restore
    - name: Test
      run: dotnet test --no-restore --verbosity normal

With these changes in tow, we have a bit more success:

Successful Build & Test
Successful Build & Test.

The next phase is to wrap this all up in a bow and chain everything through to a Web App running in Azure.

Setting up Auto-deployment to an Azure Web App

Let’s shift gears and head on over to the Azure Portal (you’ll need an account to follow on from here on out). On the ‘Home’ page select ‘Create a resource’ and search for and create a ‘Web App’:

Create Web App
Create Web App.

On the Web App configuration screen, you just need, for the purposes of this sample, to fill out the details on the ‘Basics’ tab only. You’ll need a subscription at this point. A resource group is used to logically group your, well, resources (I’ve created a new one for this sample). A word of warning, make sure you select the (if using Windows as I am) a new/existing App Service Plan that targets a ‘Free F1’ instance. Notice also that I am targeting .NET Core 3.1. Pick the region of your choice and we’re nearly there!

Azure Web App Configuration
Azure Web App Configuration.
F1 Free Resource
F1 Free Resource.

Click ‘Review + create’ to continue ensuring all of the settings are correct (Runtime stack/App Service Plan, etc.) and then click ‘Create’ to provision the new web application. The deployment of this will take a few moments after which you’ll be able to click the ‘Go to resource’ button:

Go to Resource Link
Go to Resource Link.

You should now be able to browse to the newly created application:

Staging Web Application
Staging Web Application.

Awesome, our web application is running but of course is not tied to our source code in GitHub in any way…yet. This can be achieved via the tooling available in the ‘Deployment Center’ module, using the ‘GitHub’ CICD option (you’ll have to allow Azure permissions to make changes to your repository by following the prompts during your first run through).

Deployment Center
Deployment Center.

Next up, select the ‘GitHub Actions (Preview)’ option and select the details of your account (‘Organization’), ‘Repository’, target ‘Branch’ along with the ‘Runtime Stack’ and ‘Version’:

Build Provider Selection
Build Provider Selection.

NOTE: Things went slightly awry for me here so read this just for the ‘experience’ and also to correct me in the comments below. To see how I got this working, skip to the ‘A Working Build Provider’ section.

Whether I was doing something subtly wrong or this was just a preview issue (I can’t be 100 percent sure) I couldn’t get the ‘Configure using existing workflow definition’ to function correctly. My guess is that this is supposed to modify/retrofit the existing ‘.yml’ file in the target repository with the additional ‘steps’ to deploy code to Azure.

Build Provider Configuration
Build Provider Configuration.

Tangent alert! As I write this post I have a theory to test. When using the options specified above the resulting configuration was looking for a file called ‘master_bear-starterwebapp.yml’ in my repository (that was not created by default and, of course, didn’t exist, to begin with). Going along with the idea that this ‘naming’ is by convention I decided to change the name of the configuration file in my repository (via a quick commit) from ‘dotnet-core.yml’ to ‘master_bear-starterwebapp.yml’. Then, after that was committed, I created the Build Provider as above.

The result was….no dice! If anyone reading this has any thoughts on this then feel free to let me know in the comments below. It could well be (and reading the messaging again could support this) that this isn’t how it is supposed to work at all, with you needing to always manually adjust your configuration file. A misunderstanding on my part is the likely outcome here! Either way, it’s not too difficult to do this, as seen next.

A Working Build Provider

Instead, at this point, I created the build provider using the ‘Add or overwrite workflow definition’ option:

Build Provider Configuration New
Build Provider Configuration New.

As you guessed, this creates a brand new GitHub Action definition file in the repository (which, as I stated earlier, is why it may make sense to just use the Azure route to creating this kind of configuration, for now at least).

New Configuration File
New Configuration File.

The content of the configuration file that it creates for you looks like this:

# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# More GitHub Actions for Azure: https://github.com/Azure/actions

name: Build and deploy ASP.Net Core app to Azure Web App - bear-starterwebapp

on:
  push:
    branches:
      - master

jobs:
  build-and-deploy:
    runs-on: windows-latest

    steps:
    - uses: actions/checkout@master

    - name: Set up .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: '3.1.102'

    - name: Build with dotnet
      run: dotnet build --configuration Release

    - name: dotnet publish
      run: dotnet publish -c Release -o ${{env.DOTNET_ROOT}}/myapp

    - name: Deploy to Azure Web App
      uses: azure/webapps-deploy@v1
      with:
        app-name: 'bear-starterwebapp'
        slot-name: 'production'
        publish-profile: ${{ [SOME_SECRET] }}
        package: ${{env.DOTNET_ROOT}}/myapp 

The components added are the ‘Publish’ and ‘Deploy to Azure Web App’ steps. However, we’ve lost the ‘Working Directory’ and ‘Test’ changes we made previously, alongside an ‘action trigger’ point (we want a build triggered on ‘push’ and ‘pull request’ to master).

The Resolution (small amount of tinkering)

Keeping the ‘master_bear-starterwebapp.yml’ in tow I amalgamated this version of the configuration with my previously generated one from GitHub; I’ve opted to still run this against ‘ubuntu-latest’. This gave me the following, working set up:

# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# More GitHub Actions for Azure: https://github.com/Azure/actions

name: Build and deploy Web App

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: BearStarterWebApp
 
    steps:
    - uses: actions/checkout@v2
    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.1.201
    - name: Install dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --configuration Release --no-restore
    - name: Test
      run: dotnet test --no-restore --verbosity normal
    - name: Publish
      run: dotnet publish -c Release -o ${{env.DOTNET_ROOT}}/myapp
    - name: Deploy to Azure
      uses: azure/webapps-deploy@v1
      with:
        app-name: 'bear-starterwebapp'
        slot-name: 'production'
        publish-profile: ${{ [SOME_SECRET] }}
        package: ${{env.DOTNET_ROOT}}/myapp 

As the original ‘dotnet-core.yml’ was sparking off an addition workflow I removed it in favour of the modified, Azure injected, file. As you can see this has triggered our build, test deploy pipeline and shows in Azure as a deployment:

GitHub Action Workflow Logs
GitHub Action Workflow Logs.
Azure Deployment Logs
Azure Deployment Logs.

Navigating to our web applications URL shows us this has been successful, happy days:

Web Application Deployed
Web Application Deployed.

At this stage, I made a quick change to the main ‘index.cshtml’ page by changing the header and adding some content. After pushing this commit the GitHub Action Workflow is triggered, with the whole process (for this trivial example) taking around one minute. Here’s our Web Application deployed and updated!

Web Application Updated
Web Application Updated.

There you have it, a very basic sense of how you can use GitHub Actions for deployment. I hope this proves useful and enjoyable, please feel free to leave comments below. Until the next time have fun coding!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.