Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 57 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,65 @@
### Builder Containeer ###

FROM golang:latest AS builder
# Copy the code from the host and compile it
RUN go get github.com/golang/dep/cmd/dep
WORKDIR $GOPATH/src/codecomp-backend
COPY . ./

# Need to copy only necessary files to avoid cache busting
COPY conf $GOPATH/src/codecomp-backend/conf
COPY controllers $GOPATH/src/codecomp-backend/controllers
COPY models $GOPATH/src/codecomp-backend/models
COPY responses $GOPATH/src/codecomp-backend/responses
COPY routers $GOPATH/src/codecomp-backend/routers
COPY utils $GOPATH/src/codecomp-backend/utils
COPY Gopkg.lock $GOPATH/src/codecomp-backend/Gopkg.lock
COPY Gopkg.toml $GOPATH/src/codecomp-backend/Gopkg.toml
COPY server.go $GOPATH/src/codecomp-backend/server.go

RUN dep ensure -v
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GODEBUG=http2debug=2 go build -o /codecomp-backend

FROM alpine
COPY --from=builder /codecomp-backend ./
CMD ["./codecomp-backend"]
### Main Container ###
FROM alpine:3.11

# ARG DEVELOPER_TOOLS=0

# GOX variables used for development version and
# ignored for non-development purposes.
ENV GOPATH /opt/go
ENV PATH /opt/go/bin:$PATH
# elf == compiled binary, other options are: "reflex"
ENV SERVER="elf"
ENV LISTEN_PORT="8080"
ENV LISTEN_ADDR="0.0.0.0"
# The 4 commands make directories for development with
# go installed and allow for mounting repo/source as volume
RUN addgroup -S app && \
adduser -S app -G app -D -h /opt/app && \
mkdir /opt/app/data && \
chown app:app /opt/app/data && \
mkdir -p ${GOPATH}/src ${GOPATH}/bin && \
chown -R app:app ${GOPATH} && \
mkdir /opt/go/src/codecomp-backend && \
chown -R app:app /opt/go/src/codecomp-backend

# Installing go defeats the purpose of the multi-stage build
# We will set a build argument to install go if building dev images
# RUN if [ "1" = "$DEVELOPER_TOOLS" ]; then apk add --update go git musl-dev gcc build-base; fi
RUN apk add --update go git musl-dev gcc build-base

COPY --from=builder /codecomp-backend /opt/app/codecomp-backend
COPY ./docker-run.sh /opt/docker-run.sh
RUN chmod +x /opt/docker-run.sh
COPY reflex.conf /reflex.conf

# Dropping privileges is a good practices inside containers
USER app

# As "app" user, install CompileDaemon to user's bin
# RUN if [ "1" = "$DEVELOPER_TOOLS" ]; then go get github.com/cespare/reflex; fi
RUN go get github.com/cespare/reflex

EXPOSE 8080

ENTRYPOINT ["/opt/docker-run.sh"]
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# CodeComp

CodeComp is a competive coding platform for developers and coders to test their skills in live competitions. It is currently in the early stages of development and following instructions are aimed at developers.

## About This Repo

This Repo contains the source code for the backend API

## Before Reading The Instructions

There is a docker-compose project in the works called codecomp-compose. This compose project encapsulates alot of this knowledge. **In general, it's better to leverage codecomp-compose than attempt to run the container in an isolated terminal session as there may be other services that are required for it to function appropriately.**

## Using The Docker Container

The Docker container contains a multi-stage build that first installs the dependencies, then builds the project. Whether the output of this build project is actually used depends on your command. The primary container pulls the executable from the builder image and places it in "/opt/app/codecomp-backend". The rest of the application (source) is stored in "/opt/go/src/codecomp-backend". In addition, golang and reflex is installed in the primary container by default. **These dependencies may be removed by default in later release and installed conditionally based on a build argument in order to save space**.

This means that you can do the following:

- Build and serve the binary as normal (statically with no changes)
- Sync the application source code and run the application with dynamic refreshes via reflex

## Building the Container

```
$ cd /path/to/codecomp-backend
$ docker build . -t white105/codecomp-backend:latest
```

## Mode 1: Running the Compiled Binary

This is the standard way to run the docker container and most similar to how it'll be run in production. To propagate changes, you have to rebuild the container.

```
$ docker run --rm -p 8080:8080 white105/codecomp-backend:latest
```

## Mode 2: Syncing the Source Code and Running via Reflex

This is the best way as of now to do development with golang and docker. It requires minimum rebuilds and the server automatically restarts when it detects changes. This build does not used the binary and instead uses the `go run` on the source code (via reflex).

To get syncing to work, it is important to mount the repo base at /opt/go/src/codecomp-backend.

```
$ docker run -v $(pwd):/opt/go/src/codecomp-backend -e SERVER=reflex -p 8080:8080 --rm white105/codecomp-backend:latest
```

### Environment Variables

```
SERVER:
This value can be set to "elf" (default) or "reflex". The "elf" server tells docker to execute the compiled binary at /opt/app/codecomp-backend. The "reflex" value instructs docker to leverage reflex to continually watch the source code at /opt/go/src/codecomp-backend for changes. When changes are detected, the app is restarted and run via 'go run'.

LISTEN_PORT:
This value tells the mux server which port to listen on. Inside the container, this the port you should forward to. For example, if LISTEN_PORT is 8080, but 8080 is in use on the host, you can forward the host's port 9090 to 8080 by using '-p 9090:8080'.

LISTEN_ADDR:
This is the bind address. It is either '0.0.0.0' or '127.0.0.1' (most likely). When running with docker, it should always be '0.0.0.0'. When running on the host (without docker), it is better to use '127.0.0.1' for security reasons.
```
40 changes: 40 additions & 0 deletions docker-run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/bin/sh

# enable errors
set -e;

# allow setting $SERVER to "cmd" to pass commands
# through entrypoint
if [ "cmd" != "$SERVER" ]; then

# reflex will watch the source code (/opt/go/src/codecomp-backend)
# for changes and restart go when detected
if [ "reflex" == "$SERVER" ]; then
if [ ! -f /usr/bin/go ]; then
echo "Go binary not found at /usr/bin/go";
echo "Did you build the image with DEVELOPER_TOOLS=1?"
echo "Unable to run reflex server. Quiting";
exit 1;
fi
cd /opt/go/src/codecomp-backend;
echo "Getting Dependencies";
# pull dependencies just in case
go get;
echo "Starting reflex server";
exec reflex -c /reflex.conf

# elf == Executable and Linkable Format
# runs the compiled target from the first stage
# image
elif [ "elf" == "$SERVER" ]; then
echo "Starting mux server";
exec /opt/app/codecomp-backend;
else
echo "Unkown \$SERVER Option: $SERVER";
exit 1;
fi

else
cd /opt/go/src/codecomp-backend;
exec "$@";
fi
1 change: 1 addition & 0 deletions reflex.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-r '(\.go$|go\.mod)' -s go run .
73 changes: 63 additions & 10 deletions server.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,77 @@
package main

import (
"codecomp-backend/routers"
"fmt"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"io/ioutil"
"log"
"net/http"
"os"
"strings"

"github.com/gorilla/handlers"
"github.com/gorilla/mux"
)

func handleRequests() {
// TODO: move to utils
func strcat(strs ...string) string {
var sb strings.Builder
for _, str := range strs {
sb.WriteString(str)
}
return sb.String()
}

// TODO: move to utils
func getenv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return fallback
}

func buildCORSHandler(routers *mux.Router) http.Handler {
corsHeaders := handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization"})
corsMethods := handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"})
corsOrigins := handlers.AllowedOrigins([]string{"*"})
cors := handlers.CORS(corsHeaders, corsMethods, corsOrigins)(routers)
return cors
}

func serve(handler http.Handler) error {
bind := getenv("LISTEN_ADDR", "0.0.0.0")
port := getenv("LISTEN_PORT", "8080")
listen := strcat(bind, ":", port)
fmt.Printf("Binding to address - %s\n", listen)
fmt.Println("Listening and Serving")

return http.ListenAndServe(listen, handler)
}

// need to handle 404s better
func buildRouters() *mux.Router {
myRouter := mux.NewRouter().StrictSlash(true)
myRouter.HandleFunc("/login", login).Methods("POST")
myRouter.HandleFunc("/register", register).Methods("POST")
myRouter.HandleFunc("/code", register).Methods("POST")
routers := routers.Init()
log.Fatal(http.ListenAndServe(":8080", handlers.CORS(handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization"}), handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}), handlers.AllowedOrigins([]string{"*"}))(routers)))
myRouter.HandleFunc("/api/v1/login", login).Methods("POST")
myRouter.HandleFunc("/api/v1/register", register).Methods("POST")
myRouter.HandleFunc("/api/v1/code", register).Methods("POST")
return myRouter
}

func buildHandler(router *mux.Router) http.Handler {
return buildCORSHandler(router)
}

// TODO: clean this up, add graceful shutdown
func handleRequests() {
fmt.Println("Rest API v1.0 - CodeComp")
fmt.Println("Building Routers")
myRouters := buildRouters()
fmt.Println("Building Handlers")
handler := buildHandler(myRouters)
// routers := routers.Init()
log.Fatal(serve(handler))
}

func main() {
fmt.Println("Rest API v2.0 - Mux Routers")
handleRequests()
}

Expand All @@ -39,6 +90,8 @@ func login(w http.ResponseWriter, r *http.Request) {
}

func runCode(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)

fmt.Println("you just hit this endpoint yo")
reqBody, _ := ioutil.ReadAll(r.Body)
fmt.Println("request body", string(reqBody))
Expand Down