openportal/main.go

237 lines
5.2 KiB
Go

package main
import (
"context"
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"time"
"github.com/docker/go-connections/nat"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
)
var (
Unauthorized = errors.New("unauthorized")
Unauthenticated = errors.New("unauthenticated")
UnknownAuth = errors.New("unknown authentication method")
ContainerNotFound = errors.New("openpod not found")
USER = "DUMMY_USER"
PASSWORD = "DUMMY_PASSWORD"
PORT = "8081"
dockerClient *client.Client
baseImage = "openpod/open-pod"
timeout = time.Minute
)
func init() {
var err error
dockerClient, err = client.NewEnvClient()
if err != nil {
log.Fatalln(err)
}
user, ok := os.LookupEnv("OPENPORTAL_USER")
if !ok {
log.Print("WARNING: DEFAULT USER")
} else {
USER = user
}
pass, ok := os.LookupEnv("OPENPORTAL_PASS")
if !ok {
log.Print("WARNING: DEFAULT PASSWORD")
} else {
PASSWORD = pass
}
port, ok := os.LookupEnv("OPENPORTAL_PORT")
if !ok {
log.Print("WARNING: DEFAULT PORT")
} else {
PORT = port
}
}
func verifyAuthentication(r *http.Request) error {
user, password, ok := r.BasicAuth()
if !ok {
return Unauthenticated
}
if user != USER || password != PASSWORD {
return Unauthorized
}
return nil
}
func loginMiddleware(w http.ResponseWriter, r *http.Request) error {
err := verifyAuthentication(r)
switch err {
case Unauthorized:
http.Error(w, fmt.Sprint(err), http.StatusUnauthorized)
ip := r.Header.Get("X-Forwarded-For")
if ip == "" {
ip = r.RemoteAddr
}
log.Print("Failed auth from: ", ip)
return Unauthorized
case Unauthenticated:
w.Header().Set("www-authenticate", "Basic realm=\"OPENPOD\"")
http.Error(w, fmt.Sprint(err), http.StatusUnauthorized)
return Unauthenticated
}
return nil
}
func manageHandler(w http.ResponseWriter, r *http.Request) {
err := loginMiddleware(w, r)
if err != nil {
return
}
fmt.Fprint(w, `<html>
<head>
<title>OpenPOD Management</title>
</head>
<body>
<h1>OpenPOD Management</h1>
<div>
<form action="./cmd" method="post">
<label for="version_tag">New version:</label>
<input type="text" id="version_tag" name="version_tag">
<input type="submit" value="Submit">
</form>
</div>
</body>
</html>`)
}
func cmdHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
r.ParseForm()
newVersion := r.FormValue("version_tag")
if newVersion == "" {
newVersion = "latest"
}
err := updateOpenPODVersion(newVersion, w)
if err != nil {
http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
return
}
fmt.Fprint(w, "\n\n\nSuccess")
}
func dockerPull(version string, out io.Writer) error {
ctx := context.Background()
image := fmt.Sprintf("docker.io/%s:%s", baseImage, version)
log.Println("Image to be pulled:", image)
reader, err := dockerClient.ImagePull(ctx, image, types.ImagePullOptions{})
if err != nil {
return err
}
io.Copy(out, reader)
return nil
}
func dockerStart(version string) error {
ctx := context.Background()
hostConfig := &container.HostConfig{
PortBindings: nat.PortMap{
"8080/tcp": []nat.PortBinding{
{
HostIP: "0.0.0.0",
HostPort: "8080",
},
},
},
}
config := &container.Config{
Image: fmt.Sprintf("docker.io/%s:%s", baseImage, version),
Env: []string{
"APP_HOST=localhost",
"APP_PORT=8080",
"APP_SCHEME=https",
"SECRET_KEY_BASE=\"SWl0xVj8AVXoc2G0eUk6VfeOd/lppjkaKbiHWs4ucxAUJ8+wzAEa4bMo0ZVjtVVk\"",
},
ExposedPorts: nat.PortSet{
"8080/tcp": struct{}{},
},
}
resp, err := dockerClient.ContainerCreate(ctx, config, hostConfig, nil, "openpod")
if err != nil {
return err
}
return dockerClient.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
}
func dockerStop() error {
ctx := context.Background()
containers, err := dockerClient.ContainerList(ctx, types.ContainerListOptions{All: true})
if err != nil {
return err
}
for _, container := range containers {
if isIn("/openpod", container.Names) {
err = dockerClient.ContainerStop(ctx, container.ID, &timeout)
if err != nil {
return err
}
return dockerClient.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{Force: true})
}
}
return ContainerNotFound
}
func isIn(t string, l []string) bool {
for _, el := range l {
if el == t {
return true
}
}
return false
}
func updateOpenPODVersion(newVersion string, out io.Writer) error {
var err error
log.Println("New OpenPOD version:", newVersion)
err = dockerPull(newVersion, out)
if err != nil {
log.Print("Pull failed")
return err
}
err = dockerStop()
switch err {
case nil:
case ContainerNotFound:
log.Println("WARNING: OpenPOD was not running")
default:
log.Println("Stop failed")
return err
}
err = dockerStart(newVersion)
if err != nil {
log.Println("Start failed:", err)
}
return err
}
func main() {
http.HandleFunc("/", manageHandler)
http.HandleFunc("/cmd", cmdHandler)
bindAddr := fmt.Sprintf(":%s", PORT)
log.Println("Starting on", bindAddr)
log.Fatal(http.ListenAndServe(bindAddr, nil))
}