diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..13a08a40d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: + pull_request: + branches: [ main ] + push: + branches-ignore: [ main ] + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.16.x' + + - name: Build (frontend) + working-directory: frontend + run: go build ./... + + - name: Test (frontend) + working-directory: frontend + run: go test -v ./... diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..ca2467a1d --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,38 @@ +name: publish +on: + push: + branches: [ main ] + workflow_dispatch: + +env: + BACKEND_IMAGE: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/sfc-backend + FRONTEND_IMAGE: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/sfc-frontend + SHORT_SHA: ${{ github.sha }} + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + + - name: Build and push backend + uses: docker/build-push-action@v6 + with: + context: ./backend + push: true + tags: | + ${{ env.BACKEND_IMAGE }}:latest + ${{ env.BACKEND_IMAGE}}:${{ env.SHORT_SHA }} + + - name: Build and push frontend + uses: docker/build-push-action@v6 + with: + context: ./frontend + push: true + tags: | + ${{ env.FRONTEND_IMAGE }}:latest + ${{ env.FRONTEND_IMAGE}}:${{ env.SHORT_SHA }} diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 000000000..7680a3005 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,12 @@ +FROM golang:1.19 +WORKDIR /app + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +RUN go build -o backend . + +EXPOSE 9000 + +CMD [ "./backend" ] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..0a09d0e3e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,17 @@ +version: "3.9" + +services: + backend: + build: + context: ./backend + + frontend: + build: + context: ./frontend + environment: + - BACKEND_DNS=backend + - BACKEND_PORT=9000 + ports: + - "8080:8080" + depends_on: + - backend diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 000000000..6a8b26be5 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,12 @@ +FROM golang:1.19 +WORKDIR /app + +COPY go.mod ./ +RUN go mod download + +COPY . . +RUN go build -o frontend . + +EXPOSE 8080 + +CMD [ "./frontend" ] diff --git a/frontend/main.go b/frontend/main.go index bede472b4..0d8969204 100644 --- a/frontend/main.go +++ b/frontend/main.go @@ -1,19 +1,19 @@ package main import ( - "io" - "fmt" - "encoding/json" - "html/template" - "net/http" - "log" - "time" - "bytes" - "math/rand" + "bytes" + "encoding/json" + "fmt" + "html/template" + "io" + "log" + "math/rand" + "net/http" + "time" ) -var BACKEND_DNS=getEnv("BACKEND_DNS", "localhost") -var BACKEND_PORT=getEnv("BACKEND_PORT", "9000") +var BACKEND_DNS = getEnv("BACKEND_DNS", "localhost") +var BACKEND_PORT = getEnv("BACKEND_PORT", "9000") type fortune struct { ID string `json:"id" redis:"id"` @@ -21,85 +21,85 @@ type fortune struct { } type newFortune struct { - Message string `json:"message"` + Message string `json:"message"` } // use a custom client, because we don't do blocking operations wihout timeouts var myClient = &http.Client{Timeout: 10 * time.Second} func HealthzHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - io.WriteString(w, "healthy") + w.WriteHeader(http.StatusOK) + io.WriteString(w, "healthy") } func main() { - http.HandleFunc("/healthz", HealthzHandler) + http.HandleFunc("/healthz", HealthzHandler) - http.HandleFunc("/api/random", func (w http.ResponseWriter, r *http.Request) { - resp, err := myClient.Get(fmt.Sprintf("http://%s:%s/fortunes/random", BACKEND_DNS, BACKEND_PORT)) - if err != nil { - log.Fatalln(err) - fmt.Fprint(w, err) - return - } + http.HandleFunc("/api/random", func(w http.ResponseWriter, r *http.Request) { + resp, err := myClient.Get(fmt.Sprintf("http://%s:%s/fortunes/random", BACKEND_DNS, BACKEND_PORT)) + if err != nil { + log.Fatalln(err) + fmt.Fprint(w, err) + return + } - f := new(fortune) - json.NewDecoder(resp.Body).Decode(f) + f := new(fortune) + json.NewDecoder(resp.Body).Decode(f) - fmt.Fprint(w, f.Message) - return - }) + fmt.Fprint(w, f.Message) + return + }) - http.HandleFunc("/api/all", func (w http.ResponseWriter, r *http.Request) { - resp, err := myClient.Get(fmt.Sprintf("http://%s:%s/fortunes", BACKEND_DNS, BACKEND_PORT)) - if err != nil { - log.Fatalln(err) - fmt.Fprint(w, err) - return - } + http.HandleFunc("/api/all", func(w http.ResponseWriter, r *http.Request) { + resp, err := myClient.Get(fmt.Sprintf("http://%s:%s/fortunes", BACKEND_DNS, BACKEND_PORT)) + if err != nil { + log.Fatalln(err) + fmt.Fprint(w, err) + return + } - fortunes := new([]fortune) - json.NewDecoder(resp.Body).Decode(fortunes) + fortunes := new([]fortune) + json.NewDecoder(resp.Body).Decode(fortunes) - tmpl, err := template.ParseFiles("./templates/fortunes.html") + tmpl, err := template.ParseFiles("./templates/fortunes.html") - if err != nil { - log.Fatalln(err) - fmt.Fprint(w, err) - return - } + if err != nil { + log.Fatalln(err) + fmt.Fprint(w, err) + return + } - tmpl.Execute(w, fortunes) - return - }) + tmpl.Execute(w, fortunes) + return + }) - http.HandleFunc("/api/add", func (w http.ResponseWriter, r *http.Request) { + http.HandleFunc("/api/add", func(w http.ResponseWriter, r *http.Request) { - if r.Method != "POST" { - http.Error(w, "Use POST", http.StatusMethodNotAllowed) - return - } + if r.Method != "POST" { + http.Error(w, "Use POST", http.StatusMethodNotAllowed) + return + } - f := new(newFortune) - json.NewDecoder(r.Body).Decode(f) + f := new(newFortune) + json.NewDecoder(r.Body).Decode(f) - var postUrl = fmt.Sprintf("http://%s:%s/fortunes", BACKEND_DNS, BACKEND_PORT) - var jsonStr = []byte(fmt.Sprintf(`{"id": "%d", "message": "%s"}`, rand.Intn(10000), f.Message)) + var postUrl = fmt.Sprintf("http://%s:%s/fortunes", BACKEND_DNS, BACKEND_PORT) + var jsonStr = []byte(fmt.Sprintf(`{"id": "%d", "message": "%s"}`, rand.Intn(10000), f.Message)) - _, err := myClient.Post(postUrl, "application/json", bytes.NewBuffer(jsonStr)) - if err != nil { - log.Fatalln(err) - fmt.Fprint(w, err) - return - } + _, err := myClient.Post(postUrl, "application/json", bytes.NewBuffer(jsonStr)) + if err != nil { + log.Fatalln(err) + fmt.Fprint(w, err) + return + } - fmt.Fprint(w, "Cookie added!") + fmt.Fprint(w, "Cookie added!") - return - }) + return + }) - http.Handle("/", http.FileServer(http.Dir("./static"))) - err := http.ListenAndServe(":8080", nil) - fmt.Println("%v", err) + http.Handle("/", http.FileServer(http.Dir("./static"))) + err := http.ListenAndServe(":8080", nil) + fmt.Printf("%v", err) }