diff options
| -rw-r--r-- | Dockerfile | 21 | ||||
| -rw-r--r-- | README.md | 22 | ||||
| -rw-r--r-- | docker-compose.yaml | 10 | ||||
| -rwxr-xr-x | rebuild_image.sh | 7 | ||||
| -rwxr-xr-x | restart_service.sh | 3 | ||||
| -rw-r--r-- | src/app.go | 50 | ||||
| -rw-r--r-- | src/common/common.go | 32 | ||||
| -rw-r--r-- | src/jobCheckin/jobCheckin.go | 114 |
8 files changed, 259 insertions, 0 deletions
diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8864e93 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM golang:latest + +LABEL MAINTAINER=AcuityAds-DevOps +ENV TZ=America/Toronto + +# Get a package manager +RUN go get github.com/golang/dep/cmd/dep + +WORKDIR /go/src/app + +# add source code +ADD src/ ./ + +RUN dep init + +# install dependencies +#RUN dep status +RUN dep ensure --vendor-only + +#RUN go build -o main . +CMD ["go", "run", "app.go"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..637b3db --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# dvo-api +This project is structured in a way so that development + execution is as easy as possible (using docker), while still making the code as modular as possible. +For eg., nothing extra needs to be done to import 3rd party libraries - simply add it to the imports in the applicable file, and docker will handle the rest duing the image build. + +## Project details +See the [confluence page](https://confluence.acuityads.com/display/ITOPS/Devops+Monitor+API). + +## General rules +1. Each new functionality (endpoint) should be a new package in `src/` under a package sub-dir with the **same name as the package itself**. +2. To maintain **styling consistency**, all *.go* files must be run through `go fmt` prior to making a PR. See https://blog.golang.org/gofmt for a guide. + +## Dependencies +1. docker + +## Running locally +1. Clone the repo etc. +2. Build and run: `docker-compose build && docker-compose up -d --force-recreate` + +## Update the K8S deployment +Suppose you have made code updates and want to deploy these updates into k8s, follow these steps: +1. Updated docker image (acuitydevops/dvo-api:latest) and push to dockerhub - rebuild_image.sh +2. Restart the deployment: `kubectl rollout restart deployment/dvo-api -n devops-scheduler`. Since we set the image pull setting to "Always" in the deployment spec, the updated image should be pulled in the pod recreation - restart_service.sh diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..8fc2619 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,10 @@ +version: "3" +services: + api: + build: . + container_name: 'dvo-api-container' + ports: + - 8093:5000 + network_mode: host + env_file: + - ../config/created-as-envs/dvo-api.properties diff --git a/rebuild_image.sh b/rebuild_image.sh new file mode 100755 index 0000000..954902f --- /dev/null +++ b/rebuild_image.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +docker rmi acuitydevops/dvo-api + +docker image build --tag acuitydevops/dvo-api . + +docker --config /root/.acuitydevops-docker push acuitydevops/dvo-api:latest diff --git a/restart_service.sh b/restart_service.sh new file mode 100755 index 0000000..4b5e099 --- /dev/null +++ b/restart_service.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +kubectl rollout restart deployment/dvo-api -n devops-scheduler diff --git a/src/app.go b/src/app.go new file mode 100644 index 0000000..0581bfb --- /dev/null +++ b/src/app.go @@ -0,0 +1,50 @@ +package main + +import ( + "app/common" + "app/jobCheckin" + "log" + "net/http" + "os" +) + +func pingHandler(w http.ResponseWriter, r *http.Request) { + common.JsonRespond(&w, http.StatusOK, "pong") + return +} + +func main() { + // define urls being served + pingEndpoint := "ping" + checkinEndpoint := "check-in" + + // create mux + mux := http.NewServeMux() + + // define handlers for endpoints here + mux.HandleFunc("/"+pingEndpoint, pingHandler) + mux.HandleFunc("/"+pingEndpoint+"/", pingHandler) + + // check-in handled by jobCheckin package + jobCheckinHandler := jobCheckin.MakeHTTPHandler(checkinEndpoint) + mux.Handle("/"+checkinEndpoint, jobCheckinHandler) + mux.Handle("/"+checkinEndpoint+"/", jobCheckinHandler) + + // add more routes... eg. /foo handled by foo package + // ... + + log.Println("Starting Restful services...") + log.Println("Using port:", os.Getenv("HTTP_LISTEN_PORT")) + + // start listening + err := http.ListenAndServe(":"+os.Getenv("HTTP_LISTEN_PORT"), mux) + log.Print(err) + errorHandler(err) +} + +func errorHandler(err error) { + if err != nil { + log.Println(err) + os.Exit(1) + } +} diff --git a/src/common/common.go b/src/common/common.go new file mode 100644 index 0000000..3e50632 --- /dev/null +++ b/src/common/common.go @@ -0,0 +1,32 @@ +// ONLY PUT FUNCTIONS / VARS THAT WILL BE USED BY MANY PACKAGES +package common + +import ( + "encoding/json" + "fmt" + "github.com/gorilla/mux" + "log" + "net/http" +) + +// Every respose MUST be a single string. +type response struct { + Mssg string +} + +// Builds and write json response given http status code, and message. +func JsonRespond(w *http.ResponseWriter, rc int, body string) { + res1D := &response{Mssg: body} + res1B, _ := json.Marshal(res1D) + (*w).WriteHeader(rc) + log.Printf("RC: %d | Message: %s", rc, body) + fmt.Fprintln(*w, string(res1B)) +} + +// Assign a handler function to an endpoint. +func MakeHTTPHandler(route string, h http.HandlerFunc) http.Handler { + r := mux.NewRouter() + r.HandleFunc("/"+route, h) + r.HandleFunc("/"+route+"/", h) + return r +} diff --git a/src/jobCheckin/jobCheckin.go b/src/jobCheckin/jobCheckin.go new file mode 100644 index 0000000..0a1dd1b --- /dev/null +++ b/src/jobCheckin/jobCheckin.go @@ -0,0 +1,114 @@ +package jobCheckin + +import ( + "app/common" + "encoding/json" + client "github.com/influxdata/influxdb1-client/v2" + "log" + "net/http" + "os" + "regexp" + "strings" +) + +type Job struct { + Source string +} + +// Validates the incomming request and writes an appropriate response. +// 405: when not POST request +// 401: when body is not given, or 'source' field missing +// 500: failed to complete +// 202: successfully complete +func jobCheckinHandler(w http.ResponseWriter, r *http.Request) { + log.Println("New request ===== ") + + if r.Method != "POST" { + common.JsonRespond(&w, http.StatusMethodNotAllowed, "This end-point only accepts POST requests.") + return + } + + var j Job + err := json.NewDecoder(r.Body).Decode(&j) + if err != nil { + common.JsonRespond(&w, http.StatusBadRequest, err.Error()) + return + } + if j.Source == "" { + common.JsonRespond(&w, http.StatusBadRequest, "no 'source' field in body. Try sending a json such as {'source': 'job_name'} in the body.") + return + } + log.Println("source:", j.Source) + + measurement, err := chekinJob(j.Source) + if err != nil { + common.JsonRespond(&w, http.StatusInternalServerError, "Error while writing to Influx.") + return + } + + common.JsonRespond(&w, http.StatusAccepted, measurement) + return +} + +// Write job check-in to influx. +func chekinJob(jobName string) (string, error) { + jobNameClean, err := sanitizeJobName(jobName) + if err != nil { + return "", err + } + + // Make client + c, err := client.NewHTTPClient(client.HTTPConfig{ + Addr: "http://" + os.Getenv("INFLUX_HOST") + ":" + os.Getenv("INFLUX_PORT"), + }) + if err != nil { + return "", err + } + defer c.Close() + + // Create a new point batch + bp, _ := client.NewBatchPoints(client.BatchPointsConfig{ + Database: os.Getenv("INFLUX_DB"), + Precision: "s", + RetentionPolicy: os.Getenv("INFLUX_RP"), + }) + + measurement := jobNameClean + "_checkins" + // Create a point and add to batch + tags := map[string]string{} + fields := map[string]interface{}{"check-in": 1} + pt, err := client.NewPoint(measurement, tags, fields) + if err != nil { + return "", err + } + bp.AddPoint(pt) + + // Write the batch + err = c.Write(bp) + if err != nil { + return "", err + } + return measurement, nil +} + +// Sanitize jobname - we only want letters, numbers, and underscores +func sanitizeJobName(jobName string) (string, error) { + // remove any non-alphanumeric chars, and non-space chars + reg1, err := regexp.Compile("[^ _a-zA-Z0-9]+") + if err != nil { + return "", nil + } + jobName = reg1.ReplaceAllString(jobName, "") + // replace spaces with _ + reg2, err := regexp.Compile("[ ]+") + if err != nil { + return "", nil + } + jobName = reg2.ReplaceAllString(jobName, "_") + return strings.ToLower(jobName), nil +} + +// Every new endpoint/package MUST implement this function like so... +func MakeHTTPHandler(endpoint string) http.Handler { + return common.MakeHTTPHandler(endpoint, jobCheckinHandler) +} |
