2020-11-18 19:25:51 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2020-11-18 23:46:31 +01:00
|
|
|
"context"
|
2020-11-18 19:25:51 +01:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2020-11-18 23:46:31 +01:00
|
|
|
"io"
|
2020-11-18 19:25:51 +01:00
|
|
|
"log"
|
|
|
|
"net/http"
|
2020-11-18 23:46:31 +01:00
|
|
|
"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"
|
2020-11-18 19:25:51 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2020-11-18 23:46:31 +01:00
|
|
|
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
|
2020-11-18 19:25:51 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
2020-11-18 23:46:31 +01:00
|
|
|
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
|
|
|
|
}
|
2020-11-18 19:25:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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>
|
2020-11-19 00:09:20 +01:00
|
|
|
<form action="./cmd" method="post">
|
2020-11-18 19:25:51 +01:00
|
|
|
<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"
|
|
|
|
}
|
|
|
|
|
2020-11-18 23:46:31 +01:00
|
|
|
err := updateOpenPODVersion(newVersion, w)
|
2020-11-18 19:25:51 +01:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
2020-11-18 23:46:31 +01:00
|
|
|
fmt.Fprint(w, "\n\n\nSuccess")
|
2020-11-18 19:25:51 +01:00
|
|
|
}
|
|
|
|
|
2020-11-18 23:46:31 +01:00
|
|
|
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)
|
2020-11-18 19:25:51 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-11-18 23:46:31 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-11-18 19:25:51 +01:00
|
|
|
func main() {
|
|
|
|
http.HandleFunc("/", manageHandler)
|
|
|
|
http.HandleFunc("/cmd", cmdHandler)
|
|
|
|
|
2020-11-18 23:46:31 +01:00
|
|
|
bindAddr := fmt.Sprintf(":%s", PORT)
|
|
|
|
|
|
|
|
log.Println("Starting on", bindAddr)
|
|
|
|
log.Fatal(http.ListenAndServe(bindAddr, nil))
|
2020-11-18 19:25:51 +01:00
|
|
|
}
|