Initial open source commit

This commit is contained in:
frikky
2020-05-11 19:17:35 +02:00
commit f3587ac821
205 changed files with 63293 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
from golang as builder
RUN mkdir /app
WORKDIR /app
COPY orborus.go /app/orborus.go
RUN go get github.com/docker/docker/api/types github.com/docker/docker/api/types/container github.com/docker/docker/client
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o orborus .
from scratch
COPY --from=builder /app/ /
CMD ["./orborus"]
+4
View File
@@ -0,0 +1,4 @@
docker rmi frikky/shuffle:orborus --force
docker build . -t frikky/shuffle:orborus
docker push frikky/shuffle:orborus
+425
View File
@@ -0,0 +1,425 @@
package main
/*
Orborus exists to listen for new workflow executions and deploy workers.
*/
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
dockerclient "github.com/docker/docker/client"
//network "github.com/docker/docker/api/types/network"
//natting "github.com/docker/go-connections/nat"
)
var baseUrl = os.Getenv("BASE_URL")
var baseimagename = "frikky/shuffle"
var dockerApiVersion = os.Getenv("DOCKER_API_VERSION")
var environment = os.Getenv("ENVIRONMENT_NAME")
var orgId = os.Getenv("ORG_ID")
var workerTimeout = 600
type ExecutionRequestWrapper struct {
Data []ExecutionRequest `json:"data"`
}
type ExecutionRequest struct {
ExecutionId string `json:"execution_id"`
WorkflowId string `json:"workflow_id"`
Authorization string `json:"authorization"`
ExecutionArgument string `json:"execution_argument"`
Environments []string `json:"environments"`
Status string `json:"status"`
}
// Deploys the internal worker whenever something happens
func deployWorker(cli *dockerclient.Client, image string, identifier string, env []string) error {
// Binds is the actual "-v" volume.
hostConfig := &container.HostConfig{
LogConfig: container.LogConfig{
Type: "json-file",
Config: map[string]string{},
},
Binds: []string{
"/var/run/docker.sock:/var/run/docker.sock:rw",
},
}
// ROFL: https://docker-py.readthedocs.io/en/1.4.0/volumes/
config := &container.Config{
Image: image,
Env: env,
}
//Volumes: map[string]struct{}{
// "/var/run/docker.sock": {},
//},
cont, err := cli.ContainerCreate(
context.Background(),
config,
hostConfig,
nil,
identifier,
)
if err != nil {
log.Println(err)
return err
}
cli.ContainerStart(context.Background(), cont.ID, types.ContainerStartOptions{})
log.Printf("Container %s is created", cont.ID)
return nil
}
func stopWorker(containername string) error {
ctx := context.Background()
cli, err := dockerclient.NewEnvClient()
if err != nil {
log.Println("Unable to create docker client")
return err
}
// containers, err := cli.ContainerList(ctx, types.ContainerListOptions{
// All: true,
// })
if err := cli.ContainerStop(ctx, containername, nil); err != nil {
log.Printf("Unable to stop container %s - running removal anyway, just in case: %s", containername, err)
}
removeOptions := types.ContainerRemoveOptions{
RemoveVolumes: true,
Force: true,
}
if err := cli.ContainerRemove(ctx, containername, removeOptions); err != nil {
log.Printf("Unable to remove container: %s", err)
}
return nil
}
func initializeImages(dockercli *dockerclient.Client) {
ctx := context.Background()
// check whether theyre the same first
images := []string{
fmt.Sprintf("docker.io/%s:app_sdk", baseimagename),
fmt.Sprintf("docker.io/%s:worker", baseimagename),
}
pullOptions := types.ImagePullOptions{}
for _, image := range images {
reader, err := dockercli.ImagePull(ctx, image, pullOptions)
if err != nil {
log.Printf("Failed getting %s", image)
continue
}
io.Copy(os.Stdout, reader)
log.Printf("Successfully downloaded and built %s", image)
}
}
// Initial loop etc
func main() {
zombiecheck()
log.Println("Setting up execution environment")
//FIXME
if baseUrl == "" {
baseUrl = "https://shuffler.io"
//baseUrl = "http://localhost:5001"
}
if orgId == "" {
log.Printf("Org not defined. Set variable ORG_ID based on your org")
os.Exit(3)
}
log.Printf("Running towards %s with Org %s", baseUrl, orgId)
if environment == "" {
environment = "onprem"
log.Printf("Defaulting to environment name %s. Set environment variable ENVIRONMENT_NAME to change. This should be the same as in the frontend action.", environment)
}
// FIXME - during init, BUILD and/or LOAD worker and app_sdk
// Build/load app_sdk so it can be loaded as 127.0.0.1:5000/walkoff_app_sdk
dockercli, err := dockerclient.NewEnvClient()
if err != nil {
fmt.Println("Unable to create docker client")
os.Exit(3)
}
log.Printf("--- Setting up Docker environment. Downloading worker and App SDK! ---")
initializeImages(dockercli)
workerImage := fmt.Sprintf("%s:worker", baseimagename)
log.Printf("--- Finished configuring docker environment ---\n")
// FIXME - time limit
sleepTime := 10
client := &http.Client{}
fullUrl := fmt.Sprintf("%s/api/v1/workflows/queue", baseUrl)
req, err := http.NewRequest(
"GET",
fullUrl,
nil,
)
if err != nil {
log.Printf("Failed making request builder: %s", err)
os.Exit(3)
}
zombiecounter := 0
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Org-Id", orgId)
log.Printf("Getting data from %s", fullUrl)
hasStarted := false
for {
//log.Printf("Prerequest")
newresp, err := client.Do(req)
//log.Printf("Postrequest")
if err != nil {
log.Printf("Failed making request: %s", err)
zombiecounter += 1
if zombiecounter*sleepTime > workerTimeout {
zombiecheck()
zombiecounter = 0
}
time.Sleep(time.Duration(sleepTime) * time.Second)
continue
}
// FIXME - add check for StatusCode
if newresp.StatusCode != 200 {
if hasStarted {
log.Printf("Bad statuscode: %d", newresp.StatusCode)
}
} else {
hasStarted = true
}
body, err := ioutil.ReadAll(newresp.Body)
if err != nil {
log.Printf("Failed reading body: %s", err)
zombiecounter += 1
if zombiecounter*sleepTime > workerTimeout {
zombiecheck()
zombiecounter = 0
}
time.Sleep(time.Duration(sleepTime) * time.Second)
continue
}
var executionRequests ExecutionRequestWrapper
err = json.Unmarshal(body, &executionRequests)
if err != nil {
log.Printf("Failed executionrequest in queue unmarshaling: %s", err)
sleepTime = 10
zombiecounter += 1
if zombiecounter*sleepTime > workerTimeout {
zombiecheck()
zombiecounter = 0
}
time.Sleep(time.Duration(sleepTime) * time.Second)
continue
}
if hasStarted && len(executionRequests.Data) > 0 {
log.Println(string(body))
}
if len(executionRequests.Data) == 0 {
zombiecounter += 1
if zombiecounter*sleepTime > workerTimeout {
zombiecheck()
zombiecounter = 0
}
time.Sleep(time.Duration(sleepTime) * time.Second)
continue
}
// New, abortable version. Should check executionid and remove everything else
var toBeRemoved ExecutionRequestWrapper
for _, execution := range executionRequests.Data {
log.Println(execution.ExecutionArgument)
if execution.Status == "ABORT" || execution.Status == "FAILED" {
log.Printf("Executionstatus issue: ", execution.Status)
}
// Now, how do I execute this one?
// FIXME - if error, check the status of the running one. If it's bad, send data back.
containerName := fmt.Sprintf("worker-%s", execution.ExecutionId)
env := []string{
fmt.Sprintf("AUTHORIZATION=%s", execution.Authorization),
fmt.Sprintf("EXECUTIONID=%s", execution.ExecutionId),
fmt.Sprintf("DOCKER_API_VERSION=%s", dockerApiVersion),
fmt.Sprintf("ENVIRONMENT_NAME=%s", environment),
fmt.Sprintf("BASE_URL=%s", baseUrl),
}
err = deployWorker(dockercli, workerImage, containerName, env)
if err != nil {
stats, err := dockercli.ContainerInspect(context.Background(), containerName)
if err != nil {
log.Printf("Failed checking worker %s", execution.ExecutionId)
continue
}
containerStatus := stats.ContainerJSONBase.State.Status
if containerStatus != "running" {
log.Printf("Status of %s is %s. Should be running. Will reset", containerName, containerStatus)
err = stopWorker(containerName)
if err != nil {
log.Printf("Failed stopping worker %s", execution.ExecutionId)
continue
}
err = deployWorker(dockercli, workerImage, containerName, env)
if err != nil {
log.Printf("Failed executing worker %s in state %s", execution.ExecutionId, containerStatus)
}
} else {
// Should basically never hit here rofl
log.Printf("ERROR: I HAVE NO IDEA WHAT WENT WRONG. CHECK %s", containerName)
}
}
log.Printf("%s is deployed and to being removed from queue.", execution.ExecutionId)
zombiecounter += 1
toBeRemoved.Data = append(toBeRemoved.Data, execution)
}
// Removes handled workflows (worker is made)
if len(toBeRemoved.Data) > 0 {
confirmUrl := fmt.Sprintf("%s/api/v1/workflows/queue/confirm", baseUrl)
data, err := json.Marshal(toBeRemoved)
if err != nil {
log.Printf("Failed removal marshalling: %s", err)
time.Sleep(time.Duration(sleepTime) * time.Second)
continue
}
result, err := http.NewRequest(
"POST",
confirmUrl,
bytes.NewBuffer([]byte(data)),
)
if err != nil {
log.Printf("Failed building confirm request: %s", err)
time.Sleep(time.Duration(sleepTime) * time.Second)
continue
}
result.Header.Add("Content-Type", "application/json")
result.Header.Add("Org-Id", orgId)
resultResp, err := client.Do(result)
if err != nil {
log.Printf("Failed making confirm request: %s", err)
time.Sleep(time.Duration(sleepTime) * time.Second)
continue
}
body, err := ioutil.ReadAll(resultResp.Body)
if err != nil {
log.Printf("Failed reading confirm body: %s", err)
time.Sleep(time.Duration(sleepTime) * time.Second)
continue
}
log.Println(string(body))
// FIXME - remove these
//log.Println(string(body))
//log.Println(resultResp)
if len(toBeRemoved.Data) == len(executionRequests.Data) {
log.Println("Should remove ALL!")
} else {
log.Printf("NOT IMPLEMENTED: Should remove %d workflows from backend because they're executed!", len(toBeRemoved.Data))
}
}
time.Sleep(time.Duration(sleepTime) * time.Second)
}
}
// FIXME - add this to remove exited workers
// Should it check what happened to the execution? idk
func zombiecheck() error {
log.Println("Running zombiecheck")
ctx := context.Background()
dockercli, err := dockerclient.NewEnvClient()
if err != nil {
log.Println("Unable to create docker client")
return err
}
containers, err := dockercli.ContainerList(ctx, types.ContainerListOptions{
All: true,
})
stopContainers := []string{}
removeContainers := []string{}
for _, container := range containers {
for _, name := range container.Names {
// FIXME - add name_version_uid_uid regex check as well
if !strings.HasPrefix(name, "/worker") {
continue
}
if container.State != "running" {
removeContainers = append(removeContainers, container.ID)
}
// stopcontainer & removecontainer
currenttime := time.Now().Unix()
if container.State == "running" && currenttime-container.Created > int64(workerTimeout) {
stopContainers = append(stopContainers, container.ID)
}
}
}
// FIXME - add killing of apps with same execution ID too
for _, containername := range stopContainers {
if err := dockercli.ContainerStop(ctx, containername, nil); err != nil {
log.Printf("Unable to stop container: %s", err)
} else {
log.Printf("Stopped container %s", containername)
}
}
removeOptions := types.ContainerRemoveOptions{
RemoveVolumes: true,
Force: true,
}
for _, containername := range removeContainers {
if err := dockercli.ContainerRemove(ctx, containername, removeOptions); err != nil {
log.Printf("Unable to remove container: %s", err)
} else {
log.Printf("Removed container %s", containername)
}
}
return nil
}
+5
View File
@@ -0,0 +1,5 @@
docker run \
--env ORG_ID=$ORG_ID \
--env BASE_URL=$BASE_URL \
-v /var/run/docker.sock:/var/run/docker.sock \
frikky/shuffle:orborus