Get GitHub's service status using Golang on IBM Cloud
Tags: github, go, beginners, cloud
Published: 2020-10-22 16:11:37

How many times have you had a service go down leaving you searching your Twitter feed to find out if it's really down or if it's just you?

Golang has a knack and good reputation for many things but one that stands out like a sore thumb is the low memory footprint.

For this project, it is perfect for a lightweight HTTP server and client. The low data usage makes it compatible for _most_ cloud providers free tier accounts and it compiles to binaries that are cross-platform. This means you can deploy your server to the cloud and share your client binary and it will run on all systems.

In this article I will show you how to create the following elements and connect them:
1) A cloud-hosted Golang HTTP server
2) A Golang client to consume the HTTP server data
3) How to host the server in IBM Cloud Foundry
4) How to build and install the client binary onto your own machine

![Data Flow](https://dev-to-uploads.s3.amazonaws.com/i/24d3v3jz0jsmzn2njvoy.png)

This is really fun because it can be adjusted for other services really easily, after all it is just providing and consuming JSON data!

What will you need for this?
1) IBM Cloud Lite account
2) IBM Cloud CLI + Cloud Foundry CLI
3) GitHub account + git installed (optional)
4) Golang installed/setup on your machine

# Pre-req set up
1) Sign up to [IBM Cloud](https://www.ibm.com/uk-en/cloud/free?cm_mmc=Inpersondirected-_-Audience+Developer_Developer+Conversation-_-WW_WW-_-Oct2020-liam_hampton-get_github_s_service_status_using_golang_on_ibm_cloud___dev-global-devadvgrp-london&cm_mmca1=000039JL&cm_mmca2=10010797)
2) Login/signup to [GitHub](https://github.com/). This is optional should you wish to push your code to a repository or clone mine. Install [git](https://git-scm.com/doc) to interact with GitHub
3) Golang installation can be tricky but the [official docs](https://golang.org/doc/) are where it's at!
4) Install the [IBM Cloud CLI](https://cloud.ibm.com/docs/cli?topic=cli-install-ibmcloud-cli) + Cloud Foundry CLI (use command `ibmcloud cf install` once you have the IBM Cloud CLI installed)

# Create a Go HTTP server
First, we are going to create a server to act as a stepping stone endpoint that the client will call instead of going straight to the GitHub API. For this, it might seem a little overkill but if you were to expand on this project it would be very useful.

First, let's make a simple server and get it into IBM Cloud Foundry, OR you can fork / clone [this one from my github](https://github.com/liamchampton/gohttpserver)

In a terminal, navigate into `$HOME/go/src/github.com` and create your project folder. Call it `github-status-server`. If you are copying from GitHub, make sure you clone the repository into this directory.

Note - if you do not have this folder tree already (go/src/github.com), then create it and make sure go is installed on your system correctly. To test your installation and check the installed version, enter the command `go version` into a terminal window.

`cd` into this new project folder and enter the command `go mod init` (in some cases you may need to specify the exact project path after `init`). This will initialise the project to use go modules to help with dependency tracking.

Make a new file in this directory and call it `main.go`.

Open this project in your preferred editor. I use VSCode or GoLand.

In `main.go` you will need the following code (if you are copying from GitHub, you will just need to amend the code to suit):

```golang
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
)

// Response struct will pick out the elements of JSON we want to display
type Response struct {
	Components []struct {
		Name        string `json:"name"`
		Description string `json:"description"`
		Status      string `json:"status"`
	}
	Status struct {
		Description string `json:"description"`
	}
}

// Setup the HTTP server in one housing function
func SetUpRoutes() {
	// Create the route handler listening on '/'
	http.HandleFunc("/", Home)
	fmt.Println("Starting server on port 8080")

	// Start the server
	http.ListenAndServe(":8080", nil)
}

func main() {
	SetUpRoutes()
}

func Home(w http.ResponseWriter, r *http.Request) {
	fmt.Println("Calling GitHub API for Data")

	// GitHub API
	response, err := http.Get("https://kctbh9vrtdwd.statuspage.io/api/v2/summary.json")

	if err != nil {
		fmt.Print(err)
		os.Exit(1)
	}

	// Pull out the body of the response
	responseData, err := ioutil.ReadAll(response.Body)
	if err != nil {
		log.Fatal(err)
	}

	defer response.Body.Close()

	// Unmarshal the reponse data into the struct specified at the top
	var responseObject Response
	json.Unmarshal(responseData, &responseObject)

	// Turn unmarshalled json into a byte array for the write stream
	responseBodyBytes := new(bytes.Buffer)
	json.NewEncoder(responseBodyBytes).Encode(responseObject)
	byteArray := []byte(responseBodyBytes.Bytes())
	w.Write(byteArray)
}

```
Tada! We have got a simple HTTP server that will return only the part of the GitHub status API that we have stated in the struct. You can adjust this to taste.

To run this and see it in action, all you have to do is enter the command `go run main.go` into a terminal from within the root of the project directory. You may be asked to allow a network connection (at least on Mac you might).

If you go to `localhost:8080` in your web browser you should see the following JSON output:

```json
{"Components":[{"name":"Git Operations","description":"Performance of git clones, pulls, pushes, and associated operations","status":"operational"},{"name":"API Requests","description":"Requests for GitHub APIs","status":"operational"},{"name":"Webhooks","description":"Real time HTTP callbacks of user-generated and system events","status":"operational"},{"name":"Visit www.githubstatus.com for more information","description":"","status":"operational"},{"name":"Issues","description":"Requests for Issues on GitHub.com","status":"operational"},{"name":"Pull Requests","description":"Requests for Pull Requests on GitHub.com","status":"operational"},{"name":"GitHub Actions","description":"Workflows, Compute and Orchestration for GitHub Actions","status":"operational"},{"name":"GitHub Packages","description":"API requests and webhook delivery for GitHub Packages","status":"operational"},{"name":"GitHub Pages","description":"Frontend application and API servers for Pages builds","status":"operational"}],"Status":{"description":"All Systems Operational"}}
```
This looks gross, I know! But only the client app will consume this so you don't really need it to be readable. By all means, feel free to make it readable, nicely formatted JSON.. make it a personal challenge.

# Push the server into IBM Cloud Foundry - its so easy!

Make sure you are signed up to [IBM Cloud](https://www.ibm.com/uk-en/cloud/free?cm_mmc=Inpersondirected-_-Audience+Developer_Developer+Conversation-_-WW_WW-_-Oct2020-liam_hampton-get_github_s_service_status_using_golang_on_ibm_cloud___dev-global-devadvgrp-london&cm_mmca1=000039JL&cm_mmca2=10010797) and have the [CLI](https://cloud.ibm.com/docs/cli?topic=cli-install-ibmcloud-cli) installed.

Once it is installed, make sure you are logged into IBM Cloud via your terminal. You can log in with the simple command `ibmcloud login` or if you have a federated ID use the command `ibmcloud login --sso`. [Signup](https://www.ibm.com/uk-en/cloud/free?cm_mmc=Inpersondirected-_-Audience+Developer_Developer+Conversation-_-WW_WW-_-Oct2020-liam_hampton-get_github_s_service_status_using_golang_on_ibm_cloud___dev-global-devadvgrp-london&cm_mmca1=000039JL&cm_mmca2=10010797) if you don't already have an account.

After you have logged in, make sure you also have the Cloud Foundry CLI installed. To install it, just use the command `ibmcloud cf install`. This will allow you to interact with Cloud Foundry quick and easily through the terminal.

Now all you need to do is target Cloud Foundry. Again, this is easily done with the command `ibmcloud target --cf`. Now you are able to interact with IBM Cloud Foundry.

It's time to push the app up. To do this, use the command `ibmcloud cf push github-status -m 128mb`

Broken down, `ibmcloud cf push` will connect to Cloud Foundry through IBM Cloud and push up your code. Using a build pack and the name you give it, in this case I called it `github-status`, it builds and creates a service for you. The `-m 128mb` just states the amount of memory you wish to allocate to this service. Since it is a basic HTTP server you do not need much memory at all.

# Create a Go (or a node, python, swift etc..) Client for local system use

This is the magic of microservices, they don't all have to be the same language, they just need a common way to chat to one another. For this example I am using Go so you can see how easy it is for you to use it from anywhere on your local system environment.

Just like before, in a terminal navigate into `$HOME/go/src/github.com` and create your project folder. Call it `get-github-status`.

`cd` into this new project folder and enter the command `go mod init` (in some cases you may need to specify the exact project path after `init`). This will initialise the project to use go modules to help with dependency tracking.

You guessed it.. make a new file in this directory and call it `main.go` and open this project in your preferred editor.

In `main.go` you will need the following code (you may notice it is very similar to the server code). The main difference is that this will make a single request when you want it to, not just sit running like the server does. Make sure you change the endpoint link in the first line of the `main()` function to that of your Cloud Foundry route. This will be found in the terminal output after your push up to the cloud. 

My example is below:

![Cloud Foundry Route](https://dev-to-uploads.s3.amazonaws.com/i/xixmvu8ikoj9ug9bkxba.png)

```golang
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
)

type Response struct {
	Components []struct {
		Name        string `json:"name"`
		Description string `json:"description"`
		Status      string `json:"status"`
	}
	Status struct {
		Description string `json:"description"`
	}
}

func main() {

	// Calling the hosted HTTP server
	// Edit this with your route
	response, err := http.Get("https://YOU-ROUTE-ENDPOINT")

	// Check the request didn't return an error
	if err != nil {
		fmt.Print(err)
		os.Exit(1)
	}

	// Pull out the body of the response
	responseData, err := ioutil.ReadAll(response.Body)
	if err != nil {
		log.Fatal(err)
	}

	defer response.Body.Close()

	// Unmarshal the data into the format given in the struct
	var responseObject Response
	json.Unmarshal(responseData, &responseObject)

	// Pretty print the JSON making it easier to read
	responseBodyBytes := new(bytes.Buffer)
	json.NewEncoder(responseBodyBytes).Encode(responseObject)
	byteArray := []byte(responseBodyBytes.Bytes())
	byteBuffer := &bytes.Buffer{}
	if err := json.Indent(byteBuffer, byteArray, "", "  "); err != nil {
		panic(err)
	}
	fmt.Println(byteBuffer.String())
}
```

Wahoo :tada:

You now have a server providing data and a client to consume it.

Run the client locally using the command `go run main.go` and you should hopefully see the following output:

```bash
{
  "Components": [
    {
      "name": "Git Operations",
      "description": "Performance of git clones, pulls, pushes, and associated operations",
      "status": "operational"
    },
    {
      "name": "API Requests",
      "description": "Requests for GitHub APIs",
      "status": "operational"
    },
    {
      "name": "Webhooks",
      "description": "Real time HTTP callbacks of user-generated and system events",
      "status": "operational"
    },
    {
      "name": "Visit www.githubstatus.com for more information",
      "description": "",
      "status": "operational"
    },
    {
      "name": "Issues",
      "description": "Requests for Issues on GitHub.com",
      "status": "operational"
    },
    {
      "name": "Pull Requests",
      "description": "Requests for Pull Requests on GitHub.com",
      "status": "operational"
    },
    {
      "name": "GitHub Actions",
      "description": "Workflows, Compute and Orchestration for GitHub Actions",
      "status": "operational"
    },
    {
      "name": "GitHub Packages",
      "description": "API requests and webhook delivery for GitHub Packages",
      "status": "operational"
    },
    {
      "name": "GitHub Pages",
      "description": "Frontend application and API servers for Pages builds",
      "status": "operational"
    }
  ],
  "Status": {
    "description": "All Systems Operational"
  }
}
```

Providing you have set up the Golang environment correctly on your system, you should now be able to build/install a binary of the client. This will allow you to call the program from wherever you are on your system.

An example use case - you are developing a really cool app and you need to push your code up to GitHub but you are getting an error in your terminal. Well, now you can use your chosen command to call this program instead of having to search the internet for a service status :wink:

To do this, you simply use the command `go install`. This will build a binary and store it within the directory `go/src/bin`. Providing this is on your `PATH` you can call it from anywhere. 

Official docs specify this. To see the full explanation use the command `go help install` in your terminal from within your project.
```text
usage: go install [-i] [build flags] [packages]

Install compiles and installs the packages named by the import paths.

Executables are installed in the directory named by the GOBIN environment
variable, which defaults to $GOPATH/bin or $HOME/go/bin if the GOPATH
environment variable is not set. Executables in $GOROOT
are installed in $GOROOT/bin or $GOTOOLDIR instead of $GOBIN.
etc...
```

Here is my example of me calling it from my `$HOME` directory:
```bash
➜  ~ get-github-status
{
  "Components": [
    {
      "name": "Git Operations",
      "description": "Performance of git clones, pulls, pushes, and associated operations",
      "status": "operational"
    },
etc...
```

If you have issues calling the binary from outside of this directory, use the following command and try again:
`export PATH=$PATH:$(go env GOPATH)/bin`

And there you have it, a simple example of 2 microservices running in a hybrid like scenario. Neither one is dependant on the other. You can switch them up and modify this to fit your needs.

For example, the following code is for a python client:
```python
import requests
import json

r = requests.get('https://YOU-ROUTE-ENDPOINT')

pretty_json = json.loads(r.text)
print(json.dumps(pretty_json, indent=2))
```

If you have any questions or want to see more content like this, feel free to reach out!

[GitHub](https://github.com/liamchampton)
[Twitter](https://twitter.com/liamchampton)
[Instagram](https://www.instagram.com/liamhampton/)
[LinkedIn](www.linkedin.com/in/liam-conroy-hampton)

Alternatively, you can see what my team get up to and our other projects on our website [IBM Developer UK](https://ibmdeveloperuk.github.io)