Browse Source

Dockerize project and make better dev environment

master
stew3254 3 years ago
parent
commit
faa7a9a4a7
  1. 16
      .env-template
  2. 3
      .vscode/settings.json
  3. 10
      Dockerfile
  4. 19
      build.sh
  5. 23
      docker-compose.yml
  6. 1
      drop.sql
  7. 5
      go.mod
  8. 2
      go.sum
  9. 16
      html/css/index.css
  10. 32
      html/css/sidenav.css
  11. 10
      html/index.html
  12. 44
      html/index.tmpl
  13. 4
      setup.sql
  14. 29
      src/db.go
  15. 1
      src/hook.go
  16. 259
      src/server.go

16
.env-template

@ -0,0 +1,16 @@
# Copy this file to .env and put in the correct values
# MySQL info
MYSQL_ROOT_PASSWORD=
MYSQL_USER=
MYSQL_PASSWORD=
# DB info for Captain
DB=
DB_PORT=
# Web server info
LISTEN=
PORT=
DB_ATTEMPTS=
DB_CONNECTION_TIMEOUT=

3
.vscode/settings.json

@ -0,0 +1,3 @@
{
"python.pythonPath": "/usr/bin/python"
}

10
Dockerfile

@ -0,0 +1,10 @@
FROM golang:latest
LABEL maintainer="Ryan Stewart <stewarrt@clarkson.edu>"
# USER www-data
WORKDIR /srv/website
EXPOSE 5000
COPY .env *sql go.mod go.sum ./
RUN go mod download
COPY src/* src/
RUN go build -o main src/*
CMD ./main

19
build.sh

@ -0,0 +1,19 @@
#!/bin/sh
# full path to the image
app="$1"
if [ -z $app ]; then
app="/srv/main"
fi
# image name
name="$2"
if [ -z $name ]; then
name="compile_container"
fi
docker build -t $name -f Dockerfile.comp .
docker container create --name temp $name
docker container cp temp:$app bin
docker container rm temp
docker-compose up --build -d

23
docker-compose.yml

@ -0,0 +1,23 @@
# Use root/example as user/password credentials
version: '3.7'
services:
db:
container_name: captain_db
image: mariadb
restart: always
env_file: .env
ports:
- "3306:3306"
captain:
container_name: captain_hook
build: .
restart: unless-stopped
env_file: .env
ports:
- "5000:5000"
depends_on:
- "db"
volumes:
- ./html:/srv/website/html

1
drop.sql

@ -0,0 +1 @@
DROP TABLE IF EXISTS HOOKS;

5
go.mod

@ -0,0 +1,5 @@
module captain
go 1.14
require github.com/go-sql-driver/mysql v1.5.0

2
go.sum

@ -0,0 +1,2 @@
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=

16
html/css/index.css

@ -0,0 +1,16 @@
/* Style page content */
.main {
margin-left: 160px; /* Same as the width of the sidebar */
padding: 0px 20px;
}
.main h2 {
margin-top: 5vh;
margin-bottom: 2vh;
}
/* Debug */
.center {
margin-left: 160px; /* Same as the width of the sidebar */
position: absolute center;
}

32
html/css/sidenav.css

@ -0,0 +1,32 @@
/* The sidebar menu */
.sidenav {
height: 100%; /* Full-height: remove this if you want "auto" height */
width: 160px; /* Set the width of the sidebar */
position: fixed; /* Fixed Sidebar (stay in place on scroll) */
z-index: 1; /* Stay on top */
top: 0; /* Stay at the top */
left: 0;
background-color: #111; /* Black */
overflow-x: hidden; /* Disable horizontal scroll */
padding-top: 20px;
}
/* The navigation menu links */
.sidenav a {
padding: 6px 8px 6px 16px;
text-decoration: none;
font-size: 25px;
color: #818181;
display: block;
}
/* When you mouse over the navigation links, change their color */
.sidenav a:hover {
color: #f1f1f1;
}
/* On smaller screens, where height is less than 450px, change the style of the sidebar (less padding and a smaller font size) */
@media screen and (max-height: 450px) {
.sidenav {padding-top: 15px;}
.sidenav a {font-size: 18px;}
}

10
html/index.html

@ -1,10 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>Test Web Hook Server</h1>
</body>
</html>

44
html/index.tmpl

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSS only -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="/css/sidenav.css">
<link rel="stylesheet" type="text/css" href="/css/index.css">
</head>
<body>
<div class="sidenav container" >
<a href="/hook/create/">Create</a>
<a href="/hook/delete/">Delete</a>
</div>
<div class="test"></div>
<div class="main container-fluid">
<div id="header">
<h2>Captain Hook</h2>
<p>
A webhook server written in Go.
</p>
</div>
{{if . -}}
<h4>Currently available hooks</h4>
<ul>
{{range . -}}
<li>{{.Name}}</li>
{{- end}}
</ul>
{{- else}}
<p><strong>No hooks have been created</strong></p>
{{- end}}
</div>
<div class="center">Testing</div>
<!-- Bootstrap JS -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
<!-- <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> -->
</body>
</html>

4
setup.sql

@ -0,0 +1,4 @@
CREATE TABLE IF NOT EXISTS HOOKS
(
ID varchar(64) PRIMARY KEY
);

29
src/db.go

@ -0,0 +1,29 @@
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"io/ioutil"
)
func runSQL(db *sql.DB, name string) error {
b, err := ioutil.ReadFile(name)
if err != nil {
return err
}
statement, err := db.Prepare(string(b))
if err != nil {
return err
}
statement.Exec()
return nil
}
func createDB(db *sql.DB) error {
return runSQL(db, "setup.sql")
}
func dropDB(db *sql.DB) error {
return runSQL(db, "drop.sql")
}

1
src/hook.go

@ -6,6 +6,7 @@ import (
"net/http"
)
type HookHandler interface {
ServeHTTP(http.ResponseWriter, *http.Request)
}

259
src/server.go

@ -2,24 +2,62 @@ package main
import (
"database/sql"
"fmt"
"io/ioutil"
"fmt"
"time"
// "github.com/joho/godotenv"
"html/template"
"log"
"net/http"
// "sort"
"strings"
_ "github.com/go-sql-driver/mysql"
"os"
"strconv"
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB
//Show the index
// Check errors and fail if they're bad
func check(err error) {
if err != nil {
log.Fatal(err)
}
}
// Get all the currently available hooks
func getHooks() []Hook {
rows, err := db.Query("SELECT ID FROM HOOKS")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
var hooks []Hook
for rows.Next() {
var hook Hook
err = rows.Scan(&hook.Name)
if err != nil {
log.Fatal(err)
}
hooks = append(hooks, hook)
}
return hooks
}
// Show the index
func index(w http.ResponseWriter, r *http.Request) {
log.Println(r.URL.String())
if r.URL.String() == "/" {
http.ServeFile(w, r, "../html/index.html")
t := template.Must(template.New("index.tmpl").ParseFiles("html/index.tmpl"))
for i, j := range getHooks() {
log.Printf("%d: %s\n", i, j.Name)
}
err := t.Execute(w, getHooks())
if err != nil {
log.Fatal(err)
}
} else {
http.ServeFile(w, r, "../html/404.html")
http.ServeFile(w, r, "html/404.html")
}
}
@ -28,73 +66,63 @@ func runHook(w http.ResponseWriter, r *http.Request, hook Hook) {
fmt.Fprintln(w, hook.Name)
}
//Handles all of the hooks
// Handles all of the hooks
func hookHandler(w http.ResponseWriter, r *http.Request) {
log.Println(r.URL.String())
name := r.URL.String()[len("/hook/"):]
row := db.QueryRow("SELECT EXISTS(SELECT id FROM HOOKS WHERE HOOKS.id = ?)", name)
row := db.QueryRow("SELECT EXISTS(SELECT id FROM HOOKS WHERE HOOKS.id = ?)", name)
var res int
row.Scan(&res)
if res == 0 {
http.ServeFile(w, r, "../html/404.html")
http.ServeFile(w, r, "html/404.html")
return
}
hook := Hook{
Name: name,
params: nil,
actions: nil,
}
hook := Hook{
Name: name,
params: nil,
actions: nil,
}
runHook(w, r, hook)
}
//Shows all of the hooks
// Shows all of the hooks
func showHooks(w http.ResponseWriter, r *http.Request) {
log.Println(r.URL.String())
if r.URL.String() != "/hooks/" {
http.ServeFile(w, r, "../html/404.html")
http.ServeFile(w, r, "html/404.html")
return
}
rows, err := db.Query("SELECT ID FROM HOOKS")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
hookNames := ""
var name string
for rows.Next() {
err = rows.Scan(&name)
if err != nil {
log.Fatal(err)
}
hookNames += name + "\n"
}
fmt.Fprint(w, hookNames)
hooks := getHooks()
allHooks := ""
for _, h := range hooks {
allHooks += h.Name + "\n"
}
fmt.Fprint(w, allHooks)
}
//Creates a new hook
// Creates a new hook
func createHook(w http.ResponseWriter, r *http.Request) {
log.Println(r.URL.String())
//Doesn't actually work yet
// Doesn't actually work yet
if r.URL.String() == "/hook/create/" {
http.ServeFile(w, r, "../html/create.html")
} else {
//Create a hook
name := r.URL.String()[len("/hook/create/"):]
statement, err := db.Prepare("INSERT IGNORE INTO HOOKS(id) VALUES(?)")
if err != nil {
log.Fatal(err)
fmt.Fprintln(w, err)
return
}
defer statement.Close()
statement.Exec(name)
fmt.Fprintln(w, name)
http.ServeFile(w, r, "html/create.html")
return
}
// Create a hook
name := r.URL.String()[len("/hook/create/"):]
statement, err := db.Prepare("INSERT IGNORE INTO HOOKS(id) VALUES(?)")
if err != nil {
log.Fatal(err)
fmt.Fprintln(w, err)
return
}
defer statement.Close()
statement.Exec(name)
fmt.Fprintln(w, name)
}
//Deletes all hooks
// Delete one hook
func deleteHook(w http.ResponseWriter, r *http.Request) {
log.Println(r.URL.String())
name := r.URL.String()[len("/hook/delete/"):]
@ -118,7 +146,7 @@ func deleteHook(w http.ResponseWriter, r *http.Request) {
return
}
if n == 0 {
http.ServeFile(w, r, "../html/404.html")
http.ServeFile(w, r, "html/404.html")
return
}
fmt.Fprintln(w, name)
@ -127,45 +155,108 @@ func deleteHook(w http.ResponseWriter, r *http.Request) {
// Hooks = nil
}
// func init() {
// // Loads values from .env into the system
// if err := godotenv.Load(); err != nil {
// log.Println("No .env file found")
// }
// }
func main() {
//Get user credentials
password, err := ioutil.ReadFile(".password")
if err != nil {
log.Fatal(err)
}
connectString := strings.TrimSpace(string(password))
//Open the db and initialize the table if it doesn't exist
db, err = sql.Open("mysql", connectString + "@tcp(localhost:3306)/captain")
if err != nil {
log.Fatal(err)
}
defer db.Close()
var statement *sql.Stmt
//// DEBUG: Making sure table is good
// statement, err = db.Prepare("DROP TABLE IF EXISTS HOOKS")
// if err != nil {
// log.Fatal(err)
// }
// statement.Exec()
statement, err = db.Prepare(`CREATE TABLE IF NOT EXISTS HOOKS(
ID varchar(64) PRIMARY KEY
)`)
if err != nil {
log.Fatal(err)
}
statement.Exec()
//Handle hooks
// Check if a value exists and fail if it doesn't
checkExists := func(exists bool, msg string) {
if !exists {
log.Fatal(msg)
}
}
// Get user credentials
user, exists := os.LookupEnv("MYSQL_USER")
checkExists(exists, "Couldn't find database user")
password, exists := os.LookupEnv("MYSQL_PASSWORD")
checkExists(exists, "Couldn't find database password")
// Get database params
dbServer, exists := os.LookupEnv("MYSQL_SERVER")
checkExists(exists, "Couldn't find database server")
dbPort, exists := os.LookupEnv("MYSQL_PORT")
checkExists(exists, "Couldn't find database port")
dbName, exists := os.LookupEnv("MYSQL_DATABASE")
checkExists(exists, "Couldn't find database name")
connectionString := fmt.Sprintf(
"%s:%s@tcp(%s:%s)/%s",
user,
password,
dbServer,
dbPort,
dbName,
)
// Check how many times to try the db before quitting
attemptsStr, exists := os.LookupEnv("DB_ATTEMPTS")
if !exists {
attemptsStr = "5"
}
attempts, err := strconv.Atoi(attemptsStr)
if err != nil {
attempts = 5
}
timeoutStr, exists := os.LookupEnv("DB_CONNECTION_TIMEOUT")
if !exists {
timeoutStr = "5"
}
timeout, err := strconv.Atoi(timeoutStr)
if err != nil {
timeout = 5
}
for i := 1; i <= attempts; i++ {
db, err = sql.Open("mysql", connectionString)
if err != nil && i != attempts {
log.Printf(
"WARNING: Could not connect to db on attempt %d. Trying again in %d seconds.\n",
attempts,
timeout,
)
} else if err != nil {
log.Fatalf("Could not connect to db after %d attempts\n", attempts)
}
time.Sleep(time.Duration(timeout) * time.Second)
}
log.Println("Connection to db succeeded!")
defer db.Close()
// Open the db and initialize the table if it doesn't exist
db, err = sql.Open("mysql", connectionString)
//// DEBUG: Making sure table is good
// if err := dropDB(db); err != nil {
// log.Fatal(err)
// }
// Create database if it doesn't exist
if err := createDB(db); err != nil {
log.Fatal(err)
}
// Get listen address and port
addr, exists := os.LookupEnv("LISTEN")
checkExists(exists, "Couldn't find listen address")
port, exists := os.LookupEnv("PORT")
checkExists(exists, "Couldn't find port")
// Handle hooks
http.HandleFunc("/hook/delete/", deleteHook)
http.HandleFunc("/hook/create/", createHook)
http.HandleFunc("/hook/", hookHandler)
http.HandleFunc("/hooks/", showHooks)
http.HandleFunc("/", index)
http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("html/css"))))
//Start the server
// Start the server
log.Println("Starting server")
log.Fatal(http.ListenAndServe("127.0.0.1:8000", nil))
log.Fatal(http.ListenAndServe(addr+":"+port, nil))
}
Loading…
Cancel
Save