Hello World – COVID-19 and Golang

Hello World – COVID-19 and Golang

March 22, 2020 0 By Eric Shanks

There is a worldwide pandemic going on right now and it has disrupted practically everything. Many people are worried not only about their health and families health, but also their job situations. I feel incredibly fortunate that my employer seems intent on continuing to work through this situation and that I am already a remote worker most of the time.

My team was asked to of course take care of our families, but also to take this opportunity to learn something new. I took this respite from normal activities to try to learn how to do some basic Golang (Go) programming. I have a hard time focusing on a project sometimes when there are no specific goals in mind, so my “Hello World” attempt at programming in Golang was to grab the latest COVID-19 statistics and post them to slack once per day.

The results of my efforts were slack messages looking something like what you see below.

Setup

Slack Webhook

The first thing to do was to get a webhook from slack. Assuming you have permissions you can find the information on setting up your slack app and incoming webhooks from the slack documentation. https://api.slack.com/messaging/webhooks#create_a_webhook

I created my incoming webhook and pointed it at one of my slack channels. The webhook URL will be needed later.

Write Golang!

OK, this is really the part I was trying to learn. I’ll post my code below, and the link to that code is found on github if you are also interested.

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"time"
	"encoding/json"
	"bytes"
	"errors"
	"github.com/aws/aws-lambda-go/lambda"
)
type slackRequestBody struct {
    Text string `json:"text"`
}

func getInfections(endpoint string) string {

    resp, err := http.Get(endpoint)
    if err != nil {
        log.Fatalln(err)
    }

    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Fatalln(err)
    }
    
    return string(body)
}

func postToSlack(webhookURL string, msg string) error {
	slackBody, _ := json.Marshal(slackRequestBody{Text: msg})
	req, err := http.NewRequest(http.MethodPost, webhookURL, bytes.NewBuffer(slackBody))
	if err != nil {
		return err
	}

	req.Header.Add("content-Type", "application/json")

	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}

	buf := new(bytes.Buffer)
	buf.ReadFrom(resp.Body)
	if buf.String() != "ok" {
		return errors.New("Non-ok response returned from Slack")
	}

	return nil
}

func HandleRequest() (string, error) {
	//set slack webhook URL
	webhookUrl := "NEEDS_TO_BE_FILLED_IN"

    //Get today's date
    date := time.Now()
    today := date.Format("2006.01.02 15:04:05")

    //Get Global Infections
    globalEndpoint := "https://corona.lmao.ninja/all"
    worldInfections := getInfections(globalEndpoint)

	var data map[string]interface{}
	err := json.Unmarshal([]byte(worldInfections), &data)
	if err != nil {
		log.Fatal(err)
	}
	//for debugging
	//fmt.Println("These are the current COVID-19 numbers as of", today)
	//fmt.Println("World Cases :", data["cases"])
	//fmt.Println("World Deaths :", data["deaths"])
	//fmt.Println("World Recovered :", data["recovered"])
	//fmt.Println("Updated: ", data["updated"])
	
    usEndpoint := "https://corona.lmao.ninja/countries/usa"
    usInfections := getInfections(usEndpoint)

	var usData map[string]interface{}
	userr := json.Unmarshal([]byte(usInfections), &usData)
	if userr != nil {
		log.Fatal(userr)
	}

	//For Debugging
	//fmt.Println("US cases :", usData["cases"])
	//fmt.Println("US cases today:", usData["todayCases"])
	//fmt.Println("US deaths :", usData["deaths"])
	//fmt.Println("US deaths today:", usData["todayDeaths"])
	//fmt.Println("US recovered :", usData["recovered"])


	//Post to Slack - World
	err = postToSlack(webhookUrl, "These are the current COVID-19 numbers as of " + today + "\n" +
	"World Cases : " + fmt.Sprintf("%v", data["cases"]) + "\n" + 
	"World Deaths : " + fmt.Sprintf("%v", data["deaths"]) + "\n" + 
	"World Recovered : " + fmt.Sprintf("%v", data["recovered"]) + "\n" +
	"US Cases : " + fmt.Sprintf("%v", usData["cases"]) + "\n" +
	"US Cases today : " + fmt.Sprintf("%v", usData["todayCases"]) + "\n" +
	"US Deaths : " + fmt.Sprintf("%v", usData["deaths"]) + "\n" +
	"US Deaths today : " + fmt.Sprintf("%v", usData["todayDeaths"]) + "\n" +
	"US recovered : " + fmt.Sprintf("%v", usData["recovered"]) + "\n")

	return "Infections Printed", nil
}

func main() {
    //start lambda function
    lambda.Start(HandleRequest)
}Code language: PHP (php)

I don’t want to walk through the code line by line and explain what I learned in my introduction to Go, but in general the code grabs data from “https://corona.lmao.ninja” through a simple GET REST call. Then it formats a message and makes a POST REST call to our incoming webhook URL.

If you want to use the code to get updates, here’s what you do. Make sure you’ve got the Go prerequisites installed and configured. https://golang.org/doc/install#install

Update the webhook URL field in the code above (or from github) and save the file with a .go extension.

Within the directory where the go code is stored, run :

env GOOS=linux GOARCH=amd64 go build -o covid

The result of running the above command is a go executable that is ready to be used with AWS Lambda. Before we upload this binary as a Lambda function, we must zip it.

zip -j covid.zip covidCode language: CSS (css)

Create Lambda

The code is ready to be used with Lambda. Login to your AWS account and go to the Lambda service. Create a new function. In the function setup be sure to give the function a name and select GO for the language.

On the next screen be sure to upload the zip file created earlier and set the handler name to “covid”.

Configure your test event at the top of the same screen and pass an empty set. Save the function and run it as you wish.

I setup a CloudWatch Event to run the lambda function on a schedule so I got a new message each day with the updated numbers.

Summary

This code may not be the best GO code ever run and I’m sure it could be optimized, but was my project for learning the language. The result though, is some useful, albeit depressing, information that is delivered to my slack instance each day.

On a related subject, if you are interested in putting your computer cycles to work to help combat and learn about these and other illnesses, please take a look at the post submitted by VMware this week about how to setup a FoldingatHome appliance to aid researchers in finding cures to diseases such as Coronavirus.