@@ -4,9 +4,14 @@ ENVIRONMENT_NAME=Shuffle
|
||||
|
||||
# Remote github config for first load
|
||||
SHUFFLE_DOWNLOAD_WORKFLOW_LOCATION=
|
||||
SHUFFLE_DOWNLOAD_WORKFLOW_USERNAME=
|
||||
SHUFFLE_DOWNLOAD_WORKFLOW_PASSWORD=
|
||||
SHUFFLE_DOWNLOAD_WORKFLOW_BRANCH=
|
||||
|
||||
SHUFFLE_APP_DOWNLOAD_LOCATION=https://github.com/frikky/shuffle-apps
|
||||
SHUFFLE_DOWNLOAD_AUTH_USERNAME=
|
||||
SHUFFLE_DOWNLOAD_AUTH_PASSWORD=
|
||||
APP_DOWNLOAD_LOCATION=https://github.com/frikky/shuffle-apps
|
||||
SHUFFLE_DOWNLOAD_AUTH_BRANCH=
|
||||
|
||||
# User config for first load. Username & PW: min length 3
|
||||
SHUFFLE_DEFAULT_USERNAME=
|
||||
@@ -14,7 +19,7 @@ SHUFFLE_DEFAULT_PASSWORD=
|
||||
SHUFFLE_DEFAULT_APIKEY=
|
||||
|
||||
# Local location of your app directory. Can't use ~/
|
||||
APP_HOTLOAD_LOCATION=./shuffle-apps
|
||||
SHUFFLE_APP_HOTLOAD_LOCATION=./shuffle-apps
|
||||
|
||||
# Other configs
|
||||
BACKEND_HOSTNAME=shuffle-backend
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o webapp .
|
||||
FROM alpine:latest as certs
|
||||
RUN apk --update add ca-certificates
|
||||
|
||||
from scratch
|
||||
FROM alpine:3.12
|
||||
|
||||
COPY --from=builder /app/ /app
|
||||
COPY --from=builder /app_sdk/ /app_sdk
|
||||
|
||||
@@ -804,7 +804,15 @@ class AppBase:
|
||||
#submatch = "([${]{2}([0-9a-zA-Z_-]+)(\[.*\])[}$]{2})"
|
||||
submatch = "([${]{2}([0-9a-zA-Z_-]+)(\[.*\])[}$]{2})"
|
||||
actualitem = re.findall(submatch, value, re.MULTILINE)
|
||||
print("Multicheck ", actualitem)
|
||||
try:
|
||||
if action["skip_multicheck"]:
|
||||
print("Skipping multicheck")
|
||||
actualitem = []
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
actionname = action["name"]
|
||||
#print("Multicheck ", actualitem)
|
||||
if len(actualitem) > 0:
|
||||
multiexecution = True
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
NAME=app_sdk
|
||||
VERSION=0.2.0
|
||||
VERSION=0.6.1
|
||||
|
||||
docker rmi frikky/shuffle:$NAME --force
|
||||
docker build . -t frikky/shuffle:$NAME -t frikky/$NAME:$VERSION
|
||||
docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force
|
||||
docker build . -t frikky/shuffle:$NAME -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/app_sdk:0.6.0
|
||||
|
||||
#docker push frikky/$NAME:$VERSION
|
||||
#docker push docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION
|
||||
#docker push ghcr.io/frikky/$NAME:$VERSION
|
||||
|
||||
docker push frikky/shuffle:$NAME
|
||||
docker push frikky/$NAME:$VERSION
|
||||
docker push ghcr.io/frikky/$NAME:$VERSION
|
||||
|
||||
@@ -459,7 +459,7 @@ func generateYaml(swagger *openapi3.Swagger, newmd5 string) (*openapi3.Swagger,
|
||||
// Jesus what a clusterfuck.
|
||||
// Handles parsing of categories from OpenApi3 custom field
|
||||
if val, ok := swagger.Info.ExtensionProps.Extensions["x-categories"]; ok {
|
||||
log.Printf("Categories: %#v", val)
|
||||
//log.Printf("Categories: %#v", val)
|
||||
j, err := json.Marshal(&val)
|
||||
if err == nil {
|
||||
if j[0] == 0x22 && j[len(j)-1] == 0x22 {
|
||||
|
||||
@@ -11,11 +11,12 @@ import (
|
||||
"fmt"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
network "github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
natting "github.com/docker/go-connections/nat"
|
||||
"github.com/go-git/go-billy/v5"
|
||||
|
||||
network "github.com/docker/docker/api/types/network"
|
||||
natting "github.com/docker/go-connections/nat"
|
||||
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
@@ -180,9 +181,10 @@ func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder strin
|
||||
// Dockerfile is inside the TAR itself. Not local context
|
||||
// docker build --build-arg http_proxy=http://my.proxy.url
|
||||
buildOptions := types.ImageBuildOptions{
|
||||
Remove: true,
|
||||
Tags: tags,
|
||||
BuildArgs: map[string]*string{},
|
||||
Remove: true,
|
||||
Tags: tags,
|
||||
BuildArgs: map[string]*string{},
|
||||
NetworkMode: "host",
|
||||
}
|
||||
|
||||
httpProxy := os.Getenv("HTTP_PROXY")
|
||||
@@ -242,9 +244,10 @@ func buildImage(tags []string, dockerfileFolder string) error {
|
||||
|
||||
dockerFileTarReader := bytes.NewReader(buf.Bytes())
|
||||
buildOptions := types.ImageBuildOptions{
|
||||
Remove: true,
|
||||
Tags: tags,
|
||||
BuildArgs: map[string]*string{},
|
||||
Remove: true,
|
||||
Tags: tags,
|
||||
BuildArgs: map[string]*string{},
|
||||
NetworkMode: "host",
|
||||
}
|
||||
|
||||
httpProxy := os.Getenv("HTTP_PROXY")
|
||||
@@ -639,6 +642,51 @@ func handleStartHookDocker(resp http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Checks if an image exists
|
||||
func imageCheckBuilder(images []string) error {
|
||||
log.Printf("[FIXME] ImageNames to check: %#v", images)
|
||||
return nil
|
||||
|
||||
ctx := context.Background()
|
||||
client, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
log.Printf("Unable to create docker client: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
allImages, err := client.ImageList(ctx, types.ImageListOptions{
|
||||
All: true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed creating imagelist: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
filteredImages := []types.ImageSummary{}
|
||||
for _, image := range allImages {
|
||||
found := false
|
||||
for _, repoTag := range image.RepoTags {
|
||||
if strings.Contains(repoTag, baseDockerName) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
filteredImages = append(filteredImages, image)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Continue fixing apps here
|
||||
// https://github.com/frikky/Shuffle/issues/135
|
||||
// 1. Find if app exists
|
||||
// 2. Create app if it doesn't
|
||||
//log.Printf("Apps: %#v", filteredImages)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func hookTest() {
|
||||
var hook Hook
|
||||
err := json.Unmarshal([]byte(webhook), &hook)
|
||||
|
||||
@@ -7,8 +7,9 @@ require (
|
||||
cloud.google.com/go/datastore v1.1.0
|
||||
cloud.google.com/go/pubsub v1.3.1
|
||||
cloud.google.com/go/storage v1.7.0
|
||||
github.com/Microsoft/go-winio v0.4.14 // indirect
|
||||
github.com/basgys/goxml2json v1.1.0
|
||||
github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82 // indirect
|
||||
github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/docker v1.13.1
|
||||
github.com/docker/go-connections v0.4.0
|
||||
@@ -28,7 +29,8 @@ require (
|
||||
google.golang.org/api v0.23.0
|
||||
google.golang.org/appengine v1.6.6
|
||||
google.golang.org/genproto v0.0.0-20200507105951-43844f6eee31
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1 // indirect
|
||||
google.golang.org/grpc v1.29.1
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86
|
||||
)
|
||||
|
||||
@@ -31,8 +31,11 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
|
||||
cloud.google.com/go/storage v1.7.0 h1:DzdLPI8Em+DEk7IzA2a10ivq3mxIEASC9GeNJ6FFt5Q=
|
||||
cloud.google.com/go/storage v1.7.0/go.mod h1:jGMIBwF+L/tL6WN/W5InNgYYu4HP0DvGB6rQ1mufWfs=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
@@ -141,10 +144,12 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
@@ -166,10 +171,13 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
|
||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
@@ -212,6 +220,7 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
@@ -219,6 +228,7 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -255,6 +265,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -321,9 +332,11 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200409170454-77362c5149f0/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d h1:lzLdP95xJmMpwQ6LUHwrc5V7js93hTiY7gkznu0BgmY=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
@@ -413,6 +426,7 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
|
||||
+42
-25
@@ -41,6 +41,7 @@ import (
|
||||
"github.com/go-git/go-billy/v5"
|
||||
"github.com/go-git/go-billy/v5/memfs"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
|
||||
// Random
|
||||
@@ -141,7 +142,7 @@ type UserLimits struct {
|
||||
|
||||
// Saves some data, not sure what to have here lol
|
||||
type UserAuth struct {
|
||||
Description string `json:"description" datastore:"description" yaml:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex" yaml:"description"`
|
||||
Name string `json:"name" datastore:"name" yaml:"name"`
|
||||
Workflows []string `json:"workflows" datastore:"workflows"`
|
||||
Username string `json:"username" datastore:"username"`
|
||||
@@ -210,7 +211,7 @@ type Translator struct {
|
||||
Src struct {
|
||||
Name string `json:"name" datastore:"name"`
|
||||
Value string `json:"value" datastore:"value"`
|
||||
Description string `json:"description" datastore:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex"`
|
||||
Required string `json:"required" datastore:"required"`
|
||||
Type string `json:"type" datastore:"type"`
|
||||
Schema struct {
|
||||
@@ -221,7 +222,7 @@ type Translator struct {
|
||||
Name string `json:"name" datastore:"name"`
|
||||
Value string `json:"value" datastore:"value"`
|
||||
Type string `json:"type" datastore:"type"`
|
||||
Description string `json:"description" datastore:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex"`
|
||||
Required string `json:"required" datastore:"required"`
|
||||
Schema struct {
|
||||
Type string `json:"type" datastore:"type"`
|
||||
@@ -284,7 +285,7 @@ type ApiYaml struct {
|
||||
Name string `json:"name" yaml:"name" required:"true datastore:"name"`
|
||||
Foldername string `json:"foldername" yaml:"foldername" required:"true datastore:"foldername"`
|
||||
Id string `json:"id" yaml:"id",required:"true, datastore:"id"`
|
||||
Description string `json:"description" datastore:"description" yaml:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex" yaml:"description"`
|
||||
AppVersion string `json:"app_version" yaml:"app_version",datastore:"app_version"`
|
||||
ContactInfo struct {
|
||||
Name string `json:"name" datastore:"name" yaml:"name"`
|
||||
@@ -293,10 +294,10 @@ type ApiYaml struct {
|
||||
Types []string `json:"types" datastore:"types" yaml:"types"`
|
||||
Input []struct {
|
||||
Name string `json:"name" datastore:"name" yaml:"name"`
|
||||
Description string `json:"description" datastore:"description" yaml:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex" yaml:"description"`
|
||||
InputParameters []struct {
|
||||
Name string `json:"name" datastore:"name" yaml:"name"`
|
||||
Description string `json:"description" datastore:"description" yaml:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex" yaml:"description"`
|
||||
Required string `json:"required" datastore:"required" yaml:"required"`
|
||||
Schema struct {
|
||||
Type string `json:"type" datastore:"type" yaml:"type"`
|
||||
@@ -304,7 +305,7 @@ type ApiYaml struct {
|
||||
} `json:"inputparameters" datastore:"inputparameters" yaml:"inputparameters"`
|
||||
OutputParameters []struct {
|
||||
Name string `json:"name" datastore:"name" yaml:"name"`
|
||||
Description string `json:"description" datastore:"description" yaml:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex" yaml:"description"`
|
||||
Required string `json:"required" datastore:"required" yaml:"required"`
|
||||
Schema struct {
|
||||
Type string `json:"type" datastore:"type" yaml:"type"`
|
||||
@@ -312,7 +313,7 @@ type ApiYaml struct {
|
||||
} `json:"outputparameters" datastore:"outputparameters" yaml:"outputparameters"`
|
||||
Config []struct {
|
||||
Name string `json:"name" datastore:"name" yaml:"name"`
|
||||
Description string `json:"description" datastore:"description" yaml:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex" yaml:"description"`
|
||||
Required string `json:"required" datastore:"required" yaml:"required"`
|
||||
Schema struct {
|
||||
Type string `json:"type" datastore:"type" yaml:"type"`
|
||||
@@ -321,10 +322,10 @@ type ApiYaml struct {
|
||||
} `json:"input" datastore:"input" yaml:"input"`
|
||||
Output []struct {
|
||||
Name string `json:"name" datastore:"name" yaml:"name"`
|
||||
Description string `json:"description" datastore:"description" yaml:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex" yaml:"description"`
|
||||
Config []struct {
|
||||
Name string `json:"name" datastore:"name" yaml:"name"`
|
||||
Description string `json:"description" datastore:"description" yaml:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex" yaml:"description"`
|
||||
Required string `json:"required" datastore:"required" yaml:"required"`
|
||||
Schema struct {
|
||||
Type string `json:"type" datastore:"type" yaml:"type"`
|
||||
@@ -332,7 +333,7 @@ type ApiYaml struct {
|
||||
} `json:"config" datastore:"config" yaml:"config"`
|
||||
InputParameters []struct {
|
||||
Name string `json:"name" datastore:"name" yaml:"name"`
|
||||
Description string `json:"description" datastore:"description" yaml:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex" yaml:"description"`
|
||||
Required string `json:"required" datastore:"required" yaml:"required"`
|
||||
Schema struct {
|
||||
Type string `json:"type" datastore:"type" yaml:"type"`
|
||||
@@ -340,7 +341,7 @@ type ApiYaml struct {
|
||||
} `json:"inputparameters" datastore:"inputparameters" yaml:"inputparameters"`
|
||||
OutputParameters []struct {
|
||||
Name string `json:"name" datastore:"name" yaml:"name"`
|
||||
Description string `json:"description" datastore:"description" yaml:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex" yaml:"description"`
|
||||
Required string `json:"required" datastore:"required" yaml:"required"`
|
||||
Schema struct {
|
||||
Type string `json:"type" datastore:"type" yaml:"type"`
|
||||
@@ -357,7 +358,7 @@ type Hooks struct {
|
||||
type Info struct {
|
||||
Url string `json:"url" datastore:"url"`
|
||||
Name string `json:"name" datastore:"name"`
|
||||
Description string `json:"description" datastore:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex"`
|
||||
}
|
||||
|
||||
// Actions to be done by webhooks etc
|
||||
@@ -665,7 +666,7 @@ func handleApiAuthentication(resp http.ResponseWriter, request *http.Request) (U
|
||||
if len(Userdata.Username) > 0 {
|
||||
return Userdata, nil
|
||||
} else {
|
||||
return Userdata, errors.New(fmt.Sprintf("User is invalid - no username found: %#v", Userdata))
|
||||
return Userdata, errors.New(fmt.Sprintf("User is invalid - no username found"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1697,10 +1698,11 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
||||
"success": true,
|
||||
"admin": %s,
|
||||
"tutorials": [],
|
||||
"id": "%s",
|
||||
"orgs": [{"name": "Shuffle", "id": "123", "role": "admin"}],
|
||||
"selected_org": {"name": "Shuffle", "id": "123", "role": "admin"},
|
||||
"cookies": [{"key": "session_token", "value": "%s", "expiration": %d}]
|
||||
}`, parsedAdmin, userInfo.Session, expiration.Unix())
|
||||
}`, parsedAdmin, userInfo.Id, userInfo.Session, expiration.Unix())
|
||||
|
||||
resp.WriteHeader(200)
|
||||
resp.Write([]byte(returnData))
|
||||
@@ -2505,7 +2507,7 @@ func handleCors(resp http.ResponseWriter, request *http.Request) bool {
|
||||
|
||||
resp.Header().Set("Vary", "Origin")
|
||||
resp.Header().Set("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, remember-me")
|
||||
resp.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE")
|
||||
resp.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, PATCH")
|
||||
resp.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
resp.Header().Set("Access-Control-Allow-Origin", allowedOrigins)
|
||||
|
||||
@@ -5456,7 +5458,7 @@ func echoOpenapiData(resp http.ResponseWriter, request *http.Request) {
|
||||
if err != nil {
|
||||
log.Printf("Api authentication failed in validate swagger: %s", err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
resp.Write([]byte(`{"success": false, "reason": "Failed authentication"}`))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -5502,6 +5504,12 @@ func echoOpenapiData(resp http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if newresp.StatusCode >= 400 {
|
||||
resp.WriteHeader(201)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, urlbody)))
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteHeader(200)
|
||||
resp.Write(urlbody)
|
||||
}
|
||||
@@ -5732,6 +5740,8 @@ func validateSwagger(resp http.ResponseWriter, request *http.Request) {
|
||||
err = gyaml.Unmarshal(body, &swagger)
|
||||
if err != nil {
|
||||
log.Printf("Yaml error: %s", err)
|
||||
} else {
|
||||
log.Printf("Found valid yaml!")
|
||||
}
|
||||
|
||||
resp.WriteHeader(422)
|
||||
@@ -6145,7 +6155,7 @@ func createFs(basepath, pathname string) (billy.Filesystem, error) {
|
||||
}
|
||||
|
||||
// Hotloads new apps from a folder
|
||||
func handleAppHotload(location string) error {
|
||||
func handleAppHotload(location string, forceUpdate bool) error {
|
||||
basepath := "base"
|
||||
fs, err := createFs(basepath, location)
|
||||
if err != nil {
|
||||
@@ -6162,7 +6172,7 @@ func handleAppHotload(location string) error {
|
||||
}
|
||||
|
||||
//log.Printf("Reading app folder: %#v", dir)
|
||||
err = iterateAppGithubFolders(fs, dir, "", "", false)
|
||||
err = iterateAppGithubFolders(fs, dir, "", "", forceUpdate)
|
||||
if err != nil {
|
||||
log.Printf("Err: %s", err)
|
||||
return err
|
||||
@@ -6340,13 +6350,14 @@ func runInit(ctx context.Context) {
|
||||
fs := memfs.New()
|
||||
storer := memory.NewStorage()
|
||||
|
||||
url := os.Getenv("APP_DOWNLOAD_LOCATION")
|
||||
url := os.Getenv("SHUFFLE_APP_DOWNLOAD_LOCATION")
|
||||
if len(url) == 0 {
|
||||
url = "https://github.com/frikky/shuffle-apps"
|
||||
}
|
||||
|
||||
username := os.Getenv("SHUFFLE_DOWNLOAD_AUTH_USERNAME")
|
||||
password := os.Getenv("SHUFFLE_DOWNLOAD_AUTH_PASSWORD")
|
||||
|
||||
cloneOptions := &git.CloneOptions{
|
||||
URL: url,
|
||||
}
|
||||
@@ -6357,6 +6368,11 @@ func runInit(ctx context.Context) {
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
branch := os.Getenv("SHUFFLE_DOWNLOAD_AUTH_BRANCH")
|
||||
if len(branch) > 0 {
|
||||
cloneOptions.ReferenceName = plumbing.ReferenceName(branch)
|
||||
}
|
||||
|
||||
log.Printf("Getting apps from %s", url)
|
||||
|
||||
r, err := git.Clone(storer, fs, cloneOptions)
|
||||
@@ -6376,9 +6392,9 @@ func runInit(ctx context.Context) {
|
||||
iterateAppGithubFolders(fs, dir, "", "", false)
|
||||
|
||||
// Hotloads locally
|
||||
location := os.Getenv("APP_HOTLOAD_FOLDER")
|
||||
location := os.Getenv("SHUFFLE_APP_HOTLOAD_FOLDER")
|
||||
if len(location) != 0 {
|
||||
handleAppHotload(location)
|
||||
handleAppHotload(location, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6416,9 +6432,9 @@ func runInit(ctx context.Context) {
|
||||
log.Printf("Error getting workflows: %s", err)
|
||||
} else {
|
||||
if len(workflows) == 0 {
|
||||
username := os.Getenv("SHUFFLE_DOWNLOAD_AUTH_USERNAME")
|
||||
password := os.Getenv("SHUFFLE_DOWNLOAD_AUTH_PASSWORD")
|
||||
err = loadGithubWorkflows(workflowLocation, username, password, "")
|
||||
username := os.Getenv("SHUFFLE_DOWNLOAD_WORKFLOW_USERNAME")
|
||||
password := os.Getenv("SHUFFLE_DOWNLOAD_WORKFLOW_PASSWORD")
|
||||
err = loadGithubWorkflows(workflowLocation, username, password, "", os.Getenv("SHUFFLE_DOWNLOAD_WORKFLOW_BRANCH"))
|
||||
if err != nil {
|
||||
log.Printf("Failed to upload workflows from github: %s", err)
|
||||
} else {
|
||||
@@ -6497,6 +6513,7 @@ func init() {
|
||||
r.HandleFunc("/api/v1/apps/run_hotload", handleAppHotloadRequest).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/apps/get_existing", loadSpecificApps).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/apps/download_remote", loadSpecificApps).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/apps/{appId}", updateWorkflowAppConfig).Methods("PATCH", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/apps/validate", validateAppInput).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/apps/{appId}", deleteWorkflowApp).Methods("DELETE", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/apps/{appId}/config", getWorkflowAppConfig).Methods("GET", "OPTIONS")
|
||||
|
||||
+203
-103
@@ -23,14 +23,14 @@ import (
|
||||
"google.golang.org/api/cloudfunctions/v1"
|
||||
schedulerpb "google.golang.org/genproto/googleapis/cloud/scheduler/v1"
|
||||
|
||||
newscheduler "github.com/carlescere/scheduler"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/go-git/go-billy/v5"
|
||||
"github.com/go-git/go-billy/v5/memfs"
|
||||
"github.com/go-git/go-git/v5"
|
||||
http2 "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
|
||||
|
||||
newscheduler "github.com/carlescere/scheduler"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
http2 "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
|
||||
//"github.com/gorilla/websocket"
|
||||
//"google.golang.org/appengine"
|
||||
//"google.golang.org/appengine/memcache"
|
||||
@@ -42,14 +42,10 @@ var localBase = "http://localhost:5001"
|
||||
var baseEnvironment = "onprem"
|
||||
|
||||
var cloudname = "cloud"
|
||||
|
||||
var defaultLocation = "europe-west2"
|
||||
var scheduledJobs = map[string]*newscheduler.Job{}
|
||||
|
||||
// To test out firestore before potential merge
|
||||
var shuffleTestProject = "shuffle-test-258209"
|
||||
var shuffleTestPath = "./shuffle-test-258209-5a2e8d7e508a.json"
|
||||
|
||||
//var upgrader = websocket.Upgrader{
|
||||
// ReadBufferSize: 1024,
|
||||
// WriteBufferSize: 1024,
|
||||
@@ -96,25 +92,26 @@ type AuthenticationUsage struct {
|
||||
// An app inside Shuffle
|
||||
// Source string `json:"source" datastore:"soure" yaml:"source"` - downloadlocation
|
||||
type WorkflowApp struct {
|
||||
Name string `json:"name" yaml:"name" required:true datastore:"name"`
|
||||
IsValid bool `json:"is_valid" yaml:"is_valid" required:true datastore:"is_valid"`
|
||||
ID string `json:"id" yaml:"id,omitempty" required:false datastore:"id"`
|
||||
Link string `json:"link" yaml:"link" required:false datastore:"link,noindex"`
|
||||
AppVersion string `json:"app_version" yaml:"app_version" required:true datastore:"app_version"`
|
||||
Generated bool `json:"generated" yaml:"generated" required:false datastore:"generated"`
|
||||
Downloaded bool `json:"downloaded" yaml:"downloaded" required:false datastore:"downloaded"`
|
||||
Sharing bool `json:"sharing" yaml:"sharing" required:false datastore:"sharing"`
|
||||
Verified bool `json:"verified" yaml:"verified" required:false datastore:"verified"`
|
||||
Activated bool `json:"activated" yaml:"activated" required:false datastore:"activated"`
|
||||
Tested bool `json:"tested" yaml:"tested" required:false datastore:"tested"`
|
||||
Owner string `json:"owner" datastore:"owner" yaml:"owner"`
|
||||
Hash string `json:"hash" datastore:"hash" yaml:"hash"` // api.yaml+dockerfile+src/app.py for apps
|
||||
PrivateID string `json:"private_id" yaml:"private_id" required:false datastore:"private_id"`
|
||||
Description string `json:"description" datastore:"description,noindex" required:false yaml:"description"`
|
||||
Environment string `json:"environment" datastore:"environment" required:true yaml:"environment"`
|
||||
SmallImage string `json:"small_image" datastore:"small_image,noindex" required:false yaml:"small_image"`
|
||||
LargeImage string `json:"large_image" datastore:"large_image,noindex" yaml:"large_image" required:false`
|
||||
ContactInfo struct {
|
||||
Name string `json:"name" yaml:"name" required:true datastore:"name"`
|
||||
IsValid bool `json:"is_valid" yaml:"is_valid" required:true datastore:"is_valid"`
|
||||
ID string `json:"id" yaml:"id,omitempty" required:false datastore:"id"`
|
||||
Link string `json:"link" yaml:"link" required:false datastore:"link,noindex"`
|
||||
AppVersion string `json:"app_version" yaml:"app_version" required:true datastore:"app_version"`
|
||||
SharingConfig string `json:"sharing_config" yaml:"sharing_config" datastore:"sharing_config"`
|
||||
Generated bool `json:"generated" yaml:"generated" required:false datastore:"generated"`
|
||||
Downloaded bool `json:"downloaded" yaml:"downloaded" required:false datastore:"downloaded"`
|
||||
Sharing bool `json:"sharing" yaml:"sharing" required:false datastore:"sharing"`
|
||||
Verified bool `json:"verified" yaml:"verified" required:false datastore:"verified"`
|
||||
Activated bool `json:"activated" yaml:"activated" required:false datastore:"activated"`
|
||||
Tested bool `json:"tested" yaml:"tested" required:false datastore:"tested"`
|
||||
Owner string `json:"owner" datastore:"owner" yaml:"owner"`
|
||||
Hash string `json:"hash" datastore:"hash" yaml:"hash"` // api.yaml+dockerfile+src/app.py for apps
|
||||
PrivateID string `json:"private_id" yaml:"private_id" required:false datastore:"private_id"`
|
||||
Description string `json:"description" datastore:"description,noindex" required:false yaml:"description"`
|
||||
Environment string `json:"environment" datastore:"environment" required:true yaml:"environment"`
|
||||
SmallImage string `json:"small_image" datastore:"small_image,noindex" required:false yaml:"small_image"`
|
||||
LargeImage string `json:"large_image" datastore:"large_image,noindex" yaml:"large_image" required:false`
|
||||
ContactInfo struct {
|
||||
Name string `json:"name" datastore:"name" yaml:"name"`
|
||||
Url string `json:"url" datastore:"url" yaml:"url"`
|
||||
} `json:"contact_info" datastore:"contact_info" yaml:"contact_info" required:false`
|
||||
@@ -125,18 +122,20 @@ type WorkflowApp struct {
|
||||
}
|
||||
|
||||
type WorkflowAppActionParameter struct {
|
||||
Description string `json:"description" datastore:"description,noindex" yaml:"description"`
|
||||
ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
|
||||
Name string `json:"name" datastore:"name" yaml:"name"`
|
||||
Example string `json:"example" datastore:"example" yaml:"example"`
|
||||
Value string `json:"value" datastore:"value" yaml:"value,omitempty"`
|
||||
Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"`
|
||||
Options []string `json:"options" datastore:"options" yaml:"options"`
|
||||
ActionField string `json:"action_field" datastore:"action_field" yaml:"actionfield,omitempty"`
|
||||
Variant string `json:"variant" datastore:"variant" yaml:"variant,omitempty"`
|
||||
Required bool `json:"required" datastore:"required" yaml:"required"`
|
||||
Configuration bool `json:"configuration" datastore:"configuration" yaml:"configuration"`
|
||||
Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"`
|
||||
Description string `json:"description" datastore:"description,noindex" yaml:"description"`
|
||||
ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
|
||||
Name string `json:"name" datastore:"name" yaml:"name"`
|
||||
Example string `json:"example" datastore:"example" yaml:"example"`
|
||||
Value string `json:"value" datastore:"value" yaml:"value,omitempty"`
|
||||
Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"`
|
||||
Options []string `json:"options" datastore:"options" yaml:"options"`
|
||||
ActionField string `json:"action_field" datastore:"action_field" yaml:"actionfield,omitempty"`
|
||||
Variant string `json:"variant" datastore:"variant" yaml:"variant,omitempty"`
|
||||
Required bool `json:"required" datastore:"required" yaml:"required"`
|
||||
Configuration bool `json:"configuration" datastore:"configuration" yaml:"configuration"`
|
||||
Tags []string `json:"tags" datastore:"tags" yaml:"tags"`
|
||||
Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"`
|
||||
SkipMulticheck bool `json:"skip_multicheck" datastore:"skip_multicheck" yaml:"skip_multicheck"`
|
||||
}
|
||||
|
||||
type SchemaDefinition struct {
|
||||
@@ -144,7 +143,7 @@ type SchemaDefinition struct {
|
||||
}
|
||||
|
||||
type WorkflowAppAction struct {
|
||||
Description string `json:"description" datastore:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex"`
|
||||
ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
|
||||
Name string `json:"name" datastore:"name"`
|
||||
Label string `json:"label" datastore:"label"`
|
||||
@@ -153,11 +152,12 @@ type WorkflowAppAction struct {
|
||||
Sharing bool `json:"sharing" datastore:"sharing"`
|
||||
PrivateID string `json:"private_id" datastore:"private_id"`
|
||||
AppID string `json:"app_id" datastore:"app_id"`
|
||||
Tags []string `json:"tags" datastore:"tags" yaml:"tags"`
|
||||
Authentication []AuthenticationStore `json:"authentication" datastore:"authentication,noindex" yaml:"authentication,omitempty"`
|
||||
Tested bool `json:"tested" datastore:"tested" yaml:"tested"`
|
||||
Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters"`
|
||||
ExecutionVariable struct {
|
||||
Description string `json:"description" datastore:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex"`
|
||||
ID string `json:"id" datastore:"id"`
|
||||
Name string `json:"name" datastore:"name"`
|
||||
Value string `json:"value" datastore:"value"`
|
||||
@@ -192,7 +192,7 @@ type WorkflowExecution struct {
|
||||
Workflow Workflow `json:"workflow" datastore:"workflow,noindex"`
|
||||
Results []ActionResult `json:"results" datastore:"results,noindex"`
|
||||
ExecutionVariables []struct {
|
||||
Description string `json:"description" datastore:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex"`
|
||||
ID string `json:"id" datastore:"id"`
|
||||
Name string `json:"name" datastore:"name"`
|
||||
Value string `json:"value" datastore:"value,noindex"`
|
||||
@@ -217,7 +217,7 @@ type Action struct {
|
||||
Name string `json:"name" datastore:"name"`
|
||||
Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters,noindex"`
|
||||
ExecutionVariable struct {
|
||||
Description string `json:"description" datastore:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex"`
|
||||
ID string `json:"id" datastore:"id"`
|
||||
Name string `json:"name" datastore:"name"`
|
||||
Value string `json:"value" datastore:"value,noindex"`
|
||||
@@ -235,7 +235,7 @@ type Action struct {
|
||||
// Added environment for location to execute
|
||||
type Trigger struct {
|
||||
AppName string `json:"app_name" datastore:"app_name"`
|
||||
Description string `json:"description" datastore:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex"`
|
||||
LongDescription string `json:"long_description" datastore:"long_description"`
|
||||
Status string `json:"status" datastore:"status"`
|
||||
AppVersion string `json:"app_version" datastore:"app_version"`
|
||||
@@ -249,6 +249,7 @@ type Trigger struct {
|
||||
Environment string `json:"environment" datastore:"environment"`
|
||||
TriggerType string `json:"trigger_type" datastore:"trigger_type"`
|
||||
Name string `json:"name" datastore:"name"`
|
||||
Tags []string `json:"tags" datastore:"tags" yaml:"tags"`
|
||||
Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters,noindex"`
|
||||
Position struct {
|
||||
X float64 `json:"x" datastore:"x"`
|
||||
@@ -294,20 +295,20 @@ type Workflow struct {
|
||||
ID string `json:"id" datastore:"id"`
|
||||
IsValid bool `json:"is_valid" datastore:"is_valid"`
|
||||
Name string `json:"name" datastore:"name"`
|
||||
Description string `json:"description" datastore:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex"`
|
||||
Start string `json:"start" datastore:"start"`
|
||||
Owner string `json:"owner" datastore:"owner"`
|
||||
Sharing string `json:"sharing" datastore:"sharing"`
|
||||
Org []Org `json:"org,omitempty" datastore:"org"`
|
||||
ExecutingOrg Org `json:"execution_org,omitempty" datastore:"execution_org"`
|
||||
WorkflowVariables []struct {
|
||||
Description string `json:"description" datastore:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex"`
|
||||
ID string `json:"id" datastore:"id"`
|
||||
Name string `json:"name" datastore:"name"`
|
||||
Value string `json:"value" datastore:"value"`
|
||||
} `json:"workflow_variables" datastore:"workflow_variables"`
|
||||
ExecutionVariables []struct {
|
||||
Description string `json:"description" datastore:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex"`
|
||||
ID string `json:"id" datastore:"id"`
|
||||
Name string `json:"name" datastore:"name"`
|
||||
Value string `json:"value" datastore:"value,noindex"`
|
||||
@@ -330,7 +331,7 @@ type Authentication struct {
|
||||
}
|
||||
|
||||
type AuthenticationParams struct {
|
||||
Description string `json:"description" datastore:"description" yaml:"description"`
|
||||
Description string `json:"description" datastore:"description,noindex" yaml:"description"`
|
||||
ID string `json:"id" datastore:"id" yaml:"id"`
|
||||
Name string `json:"name" datastore:"name" yaml:"name"`
|
||||
Example string `json:"example" datastore:"example" yaml:"example"`
|
||||
@@ -540,6 +541,7 @@ func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Reque
|
||||
return
|
||||
}
|
||||
|
||||
// FIXME: Add authentication?
|
||||
id := request.Header.Get("Org-Id")
|
||||
if len(id) == 0 {
|
||||
log.Printf("No Org-Id header set - confirm")
|
||||
@@ -626,7 +628,8 @@ func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Reque
|
||||
resp.Write([]byte("OK"))
|
||||
}
|
||||
|
||||
// FIXME: Authenticate this one (especially since we have a default: shuffle)
|
||||
// FIXME: Authenticate this one? Can org ID be auth enough?
|
||||
// (especially since we have a default: shuffle)
|
||||
func handleGetWorkflowqueue(resp http.ResponseWriter, request *http.Request) {
|
||||
cors := handleCors(resp, request)
|
||||
if cors {
|
||||
@@ -653,6 +656,8 @@ func handleGetWorkflowqueue(resp http.ResponseWriter, request *http.Request) {
|
||||
|
||||
if len(executionRequests.Data) == 0 {
|
||||
executionRequests.Data = []ExecutionRequest{}
|
||||
} else {
|
||||
log.Printf("[INFO] Executionrequests: %d", len(executionRequests.Data))
|
||||
}
|
||||
|
||||
newjson, err := json.Marshal(executionRequests)
|
||||
@@ -2142,7 +2147,7 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
||||
|
||||
//log.Printf("Execution data: %#v", execution)
|
||||
if len(execution.Start) == 36 {
|
||||
log.Printf("SHOULD START ON NODE %s", execution.Start)
|
||||
log.Printf("[INFO] Should start execution on node %s", execution.Start)
|
||||
workflowExecution.Start = execution.Start
|
||||
|
||||
found := false
|
||||
@@ -2153,12 +2158,12 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
||||
}
|
||||
|
||||
if !found {
|
||||
log.Printf("ACTION %s WAS NOT FOUND!", workflow.Start)
|
||||
log.Printf("[ERROR] ACTION %s WAS NOT FOUND!", workflow.Start)
|
||||
return WorkflowExecution{}, fmt.Sprintf("Startnode %s was not found in actions", workflow.Start), errors.New(fmt.Sprintf("Startnode %s was not found in actions", workflow.Start))
|
||||
}
|
||||
} else if len(execution.Start) > 0 {
|
||||
|
||||
log.Printf("START ACTION %s IS WRONG ID LENGTH %d!", execution.Start, len(execution.Start))
|
||||
log.Printf("[ERROR] START ACTION %s IS WRONG ID LENGTH %d!", execution.Start, len(execution.Start))
|
||||
return WorkflowExecution{}, fmt.Sprintf("Startnode %s was not found in actions", execution.Start), errors.New(fmt.Sprintf("Startnode %s was not found in actions", execution.Start))
|
||||
}
|
||||
|
||||
@@ -2288,10 +2293,10 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
||||
}
|
||||
|
||||
if len(workflowExecution.ExecutionSource) == 0 {
|
||||
log.Printf("No execution source specified. Setting to default")
|
||||
log.Printf("[INFO] No execution source (trigger) specified. Setting to default")
|
||||
workflowExecution.ExecutionSource = "default"
|
||||
} else {
|
||||
log.Printf("Execution source is %s for execution ID %s", workflowExecution.ExecutionSource, workflowExecution.ExecutionId)
|
||||
log.Printf("[INFO] Execution source is %s for execution ID %s", workflowExecution.ExecutionSource, workflowExecution.ExecutionId)
|
||||
}
|
||||
|
||||
workflowExecution.ExecutionVariables = workflow.ExecutionVariables
|
||||
@@ -2311,7 +2316,7 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
||||
if len(workflowExecution.Start) == 0 {
|
||||
workflowExecution.Start = workflowExecution.Workflow.Start
|
||||
}
|
||||
log.Printf("STARTNODE: %s", workflowExecution.Start)
|
||||
log.Printf("[INFO] New startnode: %s", workflowExecution.Start)
|
||||
|
||||
childNodes := findChildNodes(workflowExecution, workflowExecution.Start)
|
||||
|
||||
@@ -2419,6 +2424,7 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
||||
environments := []string{}
|
||||
|
||||
// Check if the actions are children of the startnode?
|
||||
imageNames := []string{}
|
||||
for _, action := range workflowExecution.Workflow.Actions {
|
||||
if action.Environment != cloudname {
|
||||
found := false
|
||||
@@ -2429,6 +2435,11 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the app exists?
|
||||
newName := action.AppName
|
||||
newName = strings.ReplaceAll(newName, " ", "-")
|
||||
imageNames = append(imageNames, fmt.Sprintf("%s:%s_%s", baseDockerName, newName, action.AppVersion))
|
||||
|
||||
if !found {
|
||||
environments = append(environments, action.Environment)
|
||||
}
|
||||
@@ -2437,6 +2448,12 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
||||
}
|
||||
}
|
||||
|
||||
err = imageCheckBuilder(imageNames)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed building the required images from %#v: %s", imageNames, err)
|
||||
return WorkflowExecution{}, "Failed building missing Docker images", err
|
||||
}
|
||||
|
||||
err = setWorkflowExecution(ctx, workflowExecution)
|
||||
if err != nil {
|
||||
log.Printf("Error saving workflow execution for updates %s: %s", topic, err)
|
||||
@@ -2448,7 +2465,7 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
||||
if onpremExecution {
|
||||
// FIXME - tmp name based on future companyname-companyId
|
||||
for _, environment := range environments {
|
||||
log.Printf("EXECUTION: %s should execute onprem with execution environment \"%s\"", workflowExecution.ExecutionId, environment)
|
||||
log.Printf("[INFO] Execution: %s should execute onprem with execution environment \"%s\"", workflowExecution.ExecutionId, environment)
|
||||
|
||||
executionRequest := ExecutionRequest{
|
||||
ExecutionId: workflowExecution.ExecutionId,
|
||||
@@ -2467,12 +2484,13 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
||||
}
|
||||
|
||||
//log.Printf("Execution request: %#v", executionRequest)
|
||||
|
||||
err = setWorkflowQueue(ctx, executionRequestWrapper, environment)
|
||||
if err != nil {
|
||||
log.Printf("Failed adding to db: %s", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Printf("[ERROR] Cloud not implemented yet")
|
||||
}
|
||||
|
||||
err = increaseStatisticsField(ctx, "workflow_executions", workflow.ID, 1)
|
||||
@@ -2535,7 +2553,7 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("STARTING EXEC OF %s!", fileId)
|
||||
log.Printf("[INFO] Starting execution of %s!", fileId)
|
||||
workflowExecution, executionResp, err := handleExecution(fileId, *workflow, request)
|
||||
|
||||
if err == nil {
|
||||
@@ -3047,26 +3065,6 @@ func getSpecificWorkflow(resp http.ResponseWriter, request *http.Request) {
|
||||
resp.Write(body)
|
||||
}
|
||||
|
||||
//func setWorkflowExecutionFS(ctx context.Context, reference string, workflowExecution WorkflowExecution) error {
|
||||
// if len(workflowExecution.ExecutionId) == 0 {
|
||||
// log.Printf("Workflowexeciton executionId can't be empty.")
|
||||
// return errors.New("ExecutionId can't be empty.")
|
||||
// }
|
||||
//
|
||||
// firestoreClient, err := firestore.NewClient(ctx, shuffleTestProject, option.WithCredentialsFile(shuffleTestPath))
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// executionRef := firestoreClient.Doc(reference)
|
||||
// _, err = executionRef.Set(ctx, workflowExecution)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// return nil
|
||||
//}
|
||||
|
||||
func setWorkflowExecution(ctx context.Context, workflowExecution WorkflowExecution) error {
|
||||
if len(workflowExecution.ExecutionId) == 0 {
|
||||
log.Printf("Workflowexeciton executionId can't be empty.")
|
||||
@@ -3320,7 +3318,6 @@ func deleteWorkflowApp(resp http.ResponseWriter, request *http.Request) {
|
||||
log.Printf("Deleting private app")
|
||||
var privateApps []WorkflowApp
|
||||
for _, item := range user.PrivateApps {
|
||||
log.Println(item.ID, fileId)
|
||||
if item.ID == fileId {
|
||||
continue
|
||||
}
|
||||
@@ -3336,16 +3333,15 @@ func deleteWorkflowApp(resp http.ResponseWriter, request *http.Request) {
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": true"}`)))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
log.Printf("Deleting public app")
|
||||
err = DeleteKey(ctx, "workflowapp", fileId)
|
||||
if err != nil {
|
||||
log.Printf("Failed deleting workflowapp")
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed deleting workflow app"}`)))
|
||||
return
|
||||
}
|
||||
log.Printf("Deleting public app")
|
||||
err = DeleteKey(ctx, "workflowapp", fileId)
|
||||
if err != nil {
|
||||
log.Printf("Failed deleting workflowapp")
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed deleting workflow app"}`)))
|
||||
return
|
||||
}
|
||||
|
||||
err = increaseStatisticsField(ctx, "total_apps_deleted", fileId, 1)
|
||||
@@ -3408,9 +3404,6 @@ func getWorkflowAppConfig(resp http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// log.Printf("%#v", parsedApi)
|
||||
// log.Printf("API LEN: %d, ID: %s", len(parsedApi.Body), fileId)
|
||||
|
||||
//log.Printf("Parsed API: %#v", parsedApi)
|
||||
if len(parsedApi.ID) > 0 {
|
||||
parsedApi.Success = true
|
||||
@@ -3629,6 +3622,90 @@ func getAppAuthentication(resp http.ResponseWriter, request *http.Request) {
|
||||
}`
|
||||
*/
|
||||
}
|
||||
func updateWorkflowAppConfig(resp http.ResponseWriter, request *http.Request) {
|
||||
cors := handleCors(resp, request)
|
||||
if cors {
|
||||
return
|
||||
}
|
||||
|
||||
user, userErr := handleApiAuthentication(resp, request)
|
||||
if userErr != nil {
|
||||
log.Printf("Api authentication failed in get all apps: %s", userErr)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
return
|
||||
}
|
||||
|
||||
location := strings.Split(request.URL.String(), "/")
|
||||
var fileId string
|
||||
if location[1] == "api" {
|
||||
if len(location) <= 4 {
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
return
|
||||
}
|
||||
|
||||
fileId = location[4]
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
app, err := getApp(ctx, fileId)
|
||||
if err != nil {
|
||||
log.Printf("Error getting app: %s (update app)", app.Name)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
return
|
||||
}
|
||||
|
||||
if user.Id != app.Owner && user.Role != "admin" {
|
||||
log.Printf("Wrong user (%s) for app %s in update app", user.Username, app.Name)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
log.Printf("Error with body read in update app: %s", err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
return
|
||||
}
|
||||
|
||||
type updatefields struct {
|
||||
Sharing bool `json:"sharing"`
|
||||
SharingConfig string `json:"sharing_config"`
|
||||
}
|
||||
|
||||
var tmpfields updatefields
|
||||
err = json.Unmarshal(body, &tmpfields)
|
||||
if err != nil {
|
||||
log.Printf("Error with unmarshal body in update app: %s\n%s", err, string(body))
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
return
|
||||
}
|
||||
|
||||
if tmpfields.Sharing != app.Sharing {
|
||||
app.Sharing = tmpfields.Sharing
|
||||
}
|
||||
|
||||
if tmpfields.SharingConfig != app.SharingConfig {
|
||||
app.SharingConfig = tmpfields.SharingConfig
|
||||
}
|
||||
|
||||
err = setWorkflowAppDatastore(ctx, *app, app.ID)
|
||||
if err != nil {
|
||||
log.Printf("Failed patching workflowapp: %s", err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Changed workflow app %s", app.ID)
|
||||
resp.WriteHeader(200)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
|
||||
}
|
||||
|
||||
func getWorkflowApps(resp http.ResponseWriter, request *http.Request) {
|
||||
cors := handleCors(resp, request)
|
||||
@@ -3691,12 +3768,12 @@ func getWorkflowApps(resp http.ResponseWriter, request *http.Request) {
|
||||
newapps := []WorkflowApp{}
|
||||
baseApps := []WorkflowApp{}
|
||||
|
||||
if len(user.PrivateApps) > 0 {
|
||||
newapps = append(newapps, user.PrivateApps...)
|
||||
}
|
||||
|
||||
for _, workflowapp := range workflowapps {
|
||||
if !workflowapp.Sharing {
|
||||
if !workflowapp.Activated && workflowapp.Generated {
|
||||
continue
|
||||
}
|
||||
|
||||
if workflowapp.Owner != user.Id && user.Role != "admin" && !workflowapp.Sharing {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -3716,6 +3793,22 @@ func getWorkflowApps(resp http.ResponseWriter, request *http.Request) {
|
||||
baseApps = append(baseApps, workflowapp)
|
||||
}
|
||||
|
||||
if len(user.PrivateApps) > 0 {
|
||||
found := false
|
||||
for _, item := range user.PrivateApps {
|
||||
for _, app := range newapps {
|
||||
if item.ID == app.ID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
newapps = append(newapps, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Double unmarshal because of user apps
|
||||
newbody, err := json.Marshal(newapps)
|
||||
//newbody, err := json.Marshal(workflowapps)
|
||||
@@ -4071,7 +4164,7 @@ func deployWebhookFunction(ctx context.Context, name, localization, applocation
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadGithubWorkflows(url, username, password, userId string) error {
|
||||
func loadGithubWorkflows(url, username, password, userId, branch string) error {
|
||||
fs := memfs.New()
|
||||
|
||||
if strings.Contains(url, "github") || strings.Contains(url, "gitlab") || strings.Contains(url, "bitbucket") {
|
||||
@@ -4088,6 +4181,10 @@ func loadGithubWorkflows(url, username, password, userId string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if len(branch) > 0 {
|
||||
cloneOptions.ReferenceName = plumbing.ReferenceName(branch)
|
||||
}
|
||||
|
||||
storer := memory.NewStorage()
|
||||
r, err := git.Clone(storer, fs, cloneOptions)
|
||||
if err != nil {
|
||||
@@ -4163,6 +4260,7 @@ func loadSpecificWorkflows(resp http.ResponseWriter, request *http.Request) {
|
||||
URL string `json:"url"`
|
||||
Field1 string `json:"field_1"`
|
||||
Field2 string `json:"field_2"`
|
||||
Field3 string `json:"field_3"`
|
||||
}
|
||||
//log.Printf("Body: %s", string(body))
|
||||
|
||||
@@ -4175,7 +4273,8 @@ func loadSpecificWorkflows(resp http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = loadGithubWorkflows(tmpBody.URL, tmpBody.Field1, tmpBody.Field2, user.Id)
|
||||
// Field3 = branch
|
||||
err = loadGithubWorkflows(tmpBody.URL, tmpBody.Field1, tmpBody.Field2, user.Id, tmpBody.Field3)
|
||||
if err != nil {
|
||||
log.Printf("Failed to update workflows: %s", err)
|
||||
resp.WriteHeader(401)
|
||||
@@ -4211,15 +4310,15 @@ func handleAppHotloadRequest(resp http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
location := os.Getenv("APP_HOTLOAD_FOLDER")
|
||||
location := os.Getenv("SHUFFLE_APP_HOTLOAD_FOLDER")
|
||||
if len(location) == 0 {
|
||||
resp.WriteHeader(500)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "APP_HOTLOAD_FOLDER not specified in .env"}`)))
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "SHUFFLE_APP_HOTLOAD_FOLDER not specified in .env"}`)))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Hotloading from %s", location)
|
||||
err = handleAppHotload(location)
|
||||
err = handleAppHotload(location, true)
|
||||
if err != nil {
|
||||
resp.WriteHeader(500)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed loading apps: %s"}`)))
|
||||
@@ -4372,6 +4471,7 @@ func iterateOpenApiGithub(fs billy.Filesystem, dir []os.FileInfo, extra string,
|
||||
if strings.Contains(filename, "yaml") || strings.Contains(filename, "yml") {
|
||||
//log.Printf("File: %s", filename)
|
||||
//log.Printf("Found file: %s", filename)
|
||||
log.Printf("OpenAPI app: %s", filename)
|
||||
tmpExtra := fmt.Sprintf("%s%s/", extra, file.Name())
|
||||
|
||||
fileReader, err := fs.Open(tmpExtra)
|
||||
@@ -4381,14 +4481,14 @@ func iterateOpenApiGithub(fs billy.Filesystem, dir []os.FileInfo, extra string,
|
||||
|
||||
readFile, err := ioutil.ReadAll(fileReader)
|
||||
if err != nil {
|
||||
log.Printf("Filereader error yaml: %s", err)
|
||||
log.Printf("Filereader error yaml for %s: %s", filename, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 1. This parses OpenAPI v2 to v3 etc, for use.
|
||||
parsedOpenApi, err := handleSwaggerValidation(readFile)
|
||||
if err != nil {
|
||||
log.Printf("Validation error: %s", err)
|
||||
log.Printf("Validation error for %s: %s", filename, err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
+13
-11
@@ -2,7 +2,7 @@ version: '3'
|
||||
services:
|
||||
frontend:
|
||||
#build: ./frontend
|
||||
image: frikky/shuffle:frontend
|
||||
image: ghcr.io/frikky/shuffle-frontend:0.7.1
|
||||
container_name: shuffle-frontend
|
||||
hostname: shuffle-frontend
|
||||
ports:
|
||||
@@ -17,22 +17,22 @@ services:
|
||||
- backend
|
||||
backend:
|
||||
#build: ./backend
|
||||
image: frikky/shuffle:backend
|
||||
image: ghcr.io/frikky/shuffle-backend:0.7.1
|
||||
container_name: shuffle-backend
|
||||
hostname: ${BACKEND_HOSTNAME}
|
||||
# Here for debugging:
|
||||
ports:
|
||||
ports:
|
||||
- "${BACKEND_PORT}:5001"
|
||||
networks:
|
||||
- shuffle
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ${APP_HOTLOAD_LOCATION}:/shuffle-apps
|
||||
environment:
|
||||
- ${SHUFFLE_APP_HOTLOAD_LOCATION}:/shuffle-apps
|
||||
environment:
|
||||
- DATASTORE_EMULATOR_HOST=shuffle-database:8000
|
||||
- APP_HOTLOAD_FOLDER=/shuffle-apps
|
||||
- SHUFFLE_APP_HOTLOAD_FOLDER=/shuffle-apps
|
||||
- ORG_ID=${ORG_ID}
|
||||
- APP_DOWNLOAD_LOCATION=${APP_DOWNLOAD_LOCATION}
|
||||
- SHUFFLE_APP_DOWNLOAD_LOCATION=${SHUFFLE_APP_DOWNLOAD_LOCATION}
|
||||
- SHUFFLE_DEFAULT_USERNAME=${SHUFFLE_DEFAULT_USERNAME}
|
||||
- SHUFFLE_DEFAULT_PASSWORD=${SHUFFLE_DEFAULT_PASSWORD}
|
||||
- SHUFFLE_DEFAULT_APIKEY=${SHUFFLE_DEFAULT_APIKEY}
|
||||
@@ -43,14 +43,16 @@ services:
|
||||
- database
|
||||
orborus:
|
||||
#build: ./functions/onprem/orborus
|
||||
image: frikky/shuffle:orborus
|
||||
image: ghcr.io/frikky/orborus:0.6.2
|
||||
container_name: shuffle-orborus
|
||||
hostname: shuffle-orborus
|
||||
networks:
|
||||
- shuffle
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- SHUFFLE_APP_SDK_VERSION=0.6.0
|
||||
- SHUFFLE_WORKER_VERSION=0.6.0
|
||||
- ORG_ID=${ORG_ID}
|
||||
- ENVIRONMENT_NAME=${ENVIRONMENT_NAME}
|
||||
- BASE_URL=http://${OUTER_HOSTNAME}:${BACKEND_PORT}
|
||||
@@ -61,7 +63,7 @@ services:
|
||||
restart: unless-stopped
|
||||
database:
|
||||
#build: ./backend/database
|
||||
image: frikky/shuffle:database
|
||||
image: ghcr.io/frikky/shuffle-database:1.0.0
|
||||
container_name: shuffle-database
|
||||
hostname: shuffle-database
|
||||
ports:
|
||||
|
||||
@@ -129,13 +129,14 @@ const App = (message, props) => {
|
||||
<Route exact path="/contact" render={props => <Contact isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/login" render={props => <LoginPage isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
||||
<Route exact path="/admin" render={props => <Admin isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
||||
<Route exact path="/admin/:key" render={props => <Admin isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
||||
<Route exact path="/settings" render={props => <SettingsPage isLoaded={isLoaded} userdata={userdata} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/AdminSetup" render={props => <AdminSetup isLoaded={isLoaded} userdata={userdata} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/webhooks" render={props => <Webhooks isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/webhooks/:key" render={props => <EditWebhook isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/schedules" render={props => <Schedules globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/dashboard" render={props => <Dashboard isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/apps" render={props => <Apps isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/apps" render={props => <Apps isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} userdata={userdata} {...props} />} />
|
||||
<Route exact path="/apps/new" render={props => <AppCreator isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/apps/edit/:appid" render={props => <AppCreator isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/schedules/:key" render={props => <EditSchedule globalUrl={globalUrl} {...props} />} />
|
||||
|
||||
@@ -49,6 +49,71 @@ const Admin = (props) => {
|
||||
const [selectedAuthenticationModalOpen, setSelectedAuthenticationModalOpen] = React.useState(false)
|
||||
const [showArchived, setShowArchived] = React.useState(false)
|
||||
|
||||
const getApps = () => {
|
||||
fetch(globalUrl+"/api/v1/workflows/apps", {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for apps :O!")
|
||||
}
|
||||
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
console.log("apps: ", responseJson)
|
||||
//setApps(responseJson)
|
||||
//setFilteredApps(responseJson)
|
||||
//if (responseJson.length > 0) {
|
||||
// setSelectedApp(responseJson[0])
|
||||
// if (responseJson[0].actions !== null && responseJson[0].actions.length > 0) {
|
||||
// setSelectedAction(responseJson[0].actions[0])
|
||||
// } else {
|
||||
// setSelectedAction({})
|
||||
// }
|
||||
//}
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
const categories = [
|
||||
{
|
||||
"name": "Ticketing",
|
||||
"apps": [
|
||||
"TheHive",
|
||||
"Service-Now",
|
||||
"SecureWorks",
|
||||
],
|
||||
"categories": ["tickets", "ticket", "ticketing"]
|
||||
},
|
||||
]
|
||||
/*
|
||||
"SIEM",
|
||||
"Active Directory",
|
||||
"Firewalls",
|
||||
"Proxies web",
|
||||
"SIEM",
|
||||
"SOAR",
|
||||
"Mail",
|
||||
"EDR",
|
||||
"AV",
|
||||
"MDM/MAM",
|
||||
"DNS",
|
||||
"Ticketing platform",
|
||||
"TIP",
|
||||
"Communication",
|
||||
"DDOS protection",
|
||||
"VMS",
|
||||
]
|
||||
*/
|
||||
|
||||
const alert = useAlert()
|
||||
|
||||
const deleteAuthentication = (data) => {
|
||||
@@ -905,6 +970,82 @@ const Admin = (props) => {
|
||||
</div>
|
||||
: null
|
||||
|
||||
const appCategoryView = curTab === 6 ?
|
||||
<div>
|
||||
<div style={{marginTop: 20, marginBottom: 20,}}>
|
||||
<h2 style={{display: "inline",}}>Categories</h2>
|
||||
<span style={{marginLeft: 25}}>
|
||||
Categories are the categories supported by Shuffle, which are mapped to apps and workflows
|
||||
</span>
|
||||
</div>
|
||||
<Divider style={{marginTop: 20, marginBottom: 20, backgroundColor: theme.palette.inputColor}}/>
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary="Category"
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Apps"
|
||||
style={{minWidth: 250, maxWidth: 250}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Workflows"
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Authentication"
|
||||
style={{minWidth: 150, maxWidth: 150, overflow: "hidden"}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Actions"
|
||||
style={{minWidth: 150, maxWidth: 150, overflow: "hidden"}}
|
||||
/>
|
||||
</ListItem>
|
||||
{categories.map(data => {
|
||||
if (data.apps.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={data.name}
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={""}
|
||||
style={{minWidth: 250, maxWidth: 250}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={""}
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={""}
|
||||
style={{minWidth: 150, maxWidth: 150, overflow: "hidden"}}
|
||||
/>
|
||||
<ListItemText
|
||||
style={{minWidth: 150, maxWidth: 150, overflow: "hidden"}}
|
||||
>
|
||||
<Button
|
||||
style={{}}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
console.log("Show apps with this category")
|
||||
}}
|
||||
>
|
||||
Find app ({data.apps.length})
|
||||
</Button>
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
)
|
||||
})}
|
||||
</List>
|
||||
</div>
|
||||
: null
|
||||
|
||||
const authenticationView = curTab === 1 ?
|
||||
<div>
|
||||
<div style={{marginTop: 20, marginBottom: 20,}}>
|
||||
@@ -1182,6 +1323,10 @@ const Admin = (props) => {
|
||||
getSchedules()
|
||||
}
|
||||
|
||||
if (newValue === 6) {
|
||||
console.log("Should get apps for categories.")
|
||||
}
|
||||
|
||||
setModalUser({})
|
||||
setCurTab(newValue)
|
||||
}
|
||||
@@ -1202,10 +1347,12 @@ const Admin = (props) => {
|
||||
<Tab label=<span><ScheduleIcon style={iconStyle} />Schedules</span> />
|
||||
{window.location.protocol == "http:" && window.location.port === "3000" ? <Tab label=<span><CloudIcon style={iconStyle} /> Hybrid</span>/> : null}
|
||||
{window.location.protocol == "http:" && window.location.port === "3000" ? <Tab label=<span><BusinessIcon style={iconStyle} /> Organizations</span>/> : null}
|
||||
{window.location.protocol === "http:" && window.location.port === "3000" ? <Tab label=<span><LockIcon style={iconStyle} />Categories</span>/> : null}
|
||||
</Tabs>
|
||||
<Divider style={{marginTop: 0, marginBottom: 10, backgroundColor: "rgb(91, 96, 100)"}} />
|
||||
<div style={{padding: 15}}>
|
||||
{authenticationView}
|
||||
{appCategoryView}
|
||||
{usersView}
|
||||
{environmentView}
|
||||
{schedulesView}
|
||||
|
||||
@@ -293,6 +293,7 @@ const AngularWorkflow = (props) => {
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for stream results :O!")
|
||||
stop()
|
||||
}
|
||||
|
||||
return response.json()
|
||||
@@ -302,6 +303,7 @@ const AngularWorkflow = (props) => {
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
stop()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -639,7 +641,7 @@ const AngularWorkflow = (props) => {
|
||||
}
|
||||
|
||||
if (executionText.length > 0) {
|
||||
alert.success("Starting execution with argument "+executionText)
|
||||
alert.success("Starting execution with an execution argument")
|
||||
} else {
|
||||
alert.success("Starting execution")
|
||||
}
|
||||
@@ -5037,6 +5039,7 @@ const AngularWorkflow = (props) => {
|
||||
<Tooltip color="secondary" title="Show executions" placement="top-start">
|
||||
<Button color="primary" style={{height: 50, marginLeft: 10, }} variant="outlined" onClick={() => {
|
||||
setExecutionModalOpen(true)
|
||||
getWorkflowExecution(props.match.params.key)
|
||||
}}>
|
||||
<DirectionsRunIcon />
|
||||
</Button>
|
||||
@@ -5167,8 +5170,10 @@ const AngularWorkflow = (props) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3>Execution Argument: </h3>
|
||||
{executionData.execution_argument}
|
||||
<h3>Execution Argument</h3>
|
||||
<div style={{maxHeight: 200, overflowY: "scroll",}}>
|
||||
{executionData.execution_argument}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -5272,14 +5277,7 @@ const AngularWorkflow = (props) => {
|
||||
</h2>
|
||||
</Breadcrumbs>
|
||||
<Divider style={{backgroundColor: "white", marginTop: 10, marginBottom: 10,}}/>
|
||||
<h2>Executing Workflow</h2>
|
||||
<FormControlLabel
|
||||
style={{color: "white", marginBottom: "0px", marginTop: "10px"}}
|
||||
label={<div style={{color: "white"}}>Show failed / skipped actions</div>}
|
||||
control={
|
||||
<Switch checked={showSkippedActions} onChange={() => {setShowSkippedActions(!showSkippedActions)}} />
|
||||
}
|
||||
/>
|
||||
<h2>Executing Workflow</h2>
|
||||
{executionData.status !== undefined && executionData.status.length > 0 ?
|
||||
<div>
|
||||
<b>Status: </b>{executionData.status}
|
||||
@@ -5292,7 +5290,7 @@ const AngularWorkflow = (props) => {
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
{executionData.completed_at !== undefined ?
|
||||
{executionData.completed_at !== undefined && executionData.completed_at !== null && executionData.completed_at > 0 ?
|
||||
<div>
|
||||
<b>Finished: </b>{new Date(executionData.completed_at*1000).toISOString()}
|
||||
</div>
|
||||
@@ -5308,7 +5306,19 @@ const AngularWorkflow = (props) => {
|
||||
parsedExecutionArgument()
|
||||
: null }
|
||||
<Divider style={{backgroundColor: "white", marginTop: 30, marginBottom: 30,}}/>
|
||||
{executionData.results !== undefined && executionData.results !== null && executionData.results.length > 1 && executionData.results.find(result => result.status === "SKIPPED" || result.status === "FAILURE") ?
|
||||
<FormControlLabel
|
||||
style={{color: "white", marginBottom: 10, }}
|
||||
label={<div style={{color: "white"}}>Show failed / skipped actions</div>}
|
||||
control={
|
||||
<Switch checked={showSkippedActions} onChange={() => {setShowSkippedActions(!showSkippedActions)}} />
|
||||
}
|
||||
/>
|
||||
:
|
||||
null
|
||||
}
|
||||
<div style={{display: "flex", marginTop: 10, marginBottom: 30,}}>
|
||||
|
||||
<b>Actions</b>
|
||||
<div>
|
||||
{executionData.status !== undefined && executionData.status !== "ABORTED" && executionData.status !== "FINISHED" && executionData.status !== "FAILURE" ? <CircularProgress style={{marginLeft: 20}}/> : null}
|
||||
|
||||
@@ -334,6 +334,7 @@ const AppCreator = (props) => {
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("Error: ", error.toString())
|
||||
alert.error(error.toString())
|
||||
});
|
||||
}
|
||||
@@ -405,11 +406,33 @@ const AppCreator = (props) => {
|
||||
securitySchemes = data.components.securitySchemes
|
||||
}
|
||||
|
||||
const allowedfunctions = [
|
||||
"GET",
|
||||
"CONNECT",
|
||||
"HEAD",
|
||||
"DELETE",
|
||||
"POST",
|
||||
"PATCH",
|
||||
"PUT",
|
||||
]
|
||||
|
||||
// FIXME - headers?
|
||||
var newActions = []
|
||||
var wordlist = {}
|
||||
for (let [path, pathvalue] of Object.entries(data.paths)) {
|
||||
for (let [method, methodvalue] of Object.entries(pathvalue)) {
|
||||
if (methodvalue === null) {
|
||||
alert.info("Skipped method "+method)
|
||||
continue
|
||||
}
|
||||
|
||||
if (!allowedfunctions.includes(method.toUpperCase())) {
|
||||
console.log(method, path)
|
||||
continue
|
||||
}
|
||||
|
||||
console.log("Method: ", method)
|
||||
console.log("Methodval: ", methodvalue)
|
||||
var newaction = {
|
||||
"name": methodvalue.summary,
|
||||
"description": methodvalue.description,
|
||||
@@ -446,8 +469,11 @@ const AppCreator = (props) => {
|
||||
// https://swagger.io/docs/specification/describing-parameters/
|
||||
// Need to split the data.
|
||||
} else if (parameter.in === "body") {
|
||||
console.log("BODY: ", parameter)
|
||||
newaction.body = parameter.example
|
||||
// FIXME: Add tracking for components
|
||||
// E.G: https://raw.githubusercontent.com/owentl/Shuffle/master/gosecure.yaml
|
||||
if (parameter.example !== undefined) {
|
||||
newaction.body = parameter.example
|
||||
}
|
||||
} else if (parameter.in === "header") {
|
||||
newaction.headers += `${parameter.name}=${parameter.example}\n`
|
||||
}
|
||||
@@ -934,7 +960,7 @@ const AppCreator = (props) => {
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
value={parameterName}
|
||||
helperText={<div style={{color:"white", marginBottom: "2px",}}>Can't be empty. Can't contain any of the following characters: !#$%&'^+-._~|]+$</div>}
|
||||
helperText={<span style={{color:"white", marginBottom: "2px",}}>Can't be empty. Can't contain any of the following characters: !#$%&'^+-._~|]+$</span>}
|
||||
onChange={e => setParameterName(e.target.value)}
|
||||
InputProps={{
|
||||
classes: {
|
||||
@@ -1005,7 +1031,7 @@ const AppCreator = (props) => {
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div style={{color: "#f85a3e", cursor: "pointer"}} onClick={() => {deletePathQuery(index)}}>
|
||||
<div style={{float: "right", color: "#f85a3e", cursor: "pointer"}} onClick={() => {deletePathQuery(index)}}>
|
||||
Delete
|
||||
</div>
|
||||
|
||||
@@ -1264,7 +1290,7 @@ const AppCreator = (props) => {
|
||||
<FormControl style={{backgroundColor: surfaceColor, color: "white",}}>
|
||||
<DialogTitle><div style={{color: "white"}}>New action</div></DialogTitle>
|
||||
<DialogContent>
|
||||
<Link target="_blank" to="/docs/apps#actions" style={{textDecoration: "none", color: "#f85a3e"}}>Learn more about actions</Link>
|
||||
<Link target="_blank" to="https://shuffler.io/docs/apps#actions" style={{textDecoration: "none", color: "#f85a3e"}}>Learn more about actions</Link>
|
||||
<div style={{marginTop: "15px"}}/>
|
||||
Name
|
||||
<TextField
|
||||
@@ -1542,7 +1568,7 @@ const AppCreator = (props) => {
|
||||
<div style={{color: "white"}}>
|
||||
<h2>Actions</h2>
|
||||
Actions are the tasks performed by an app. Read more about actions and apps
|
||||
<Link target="_blank" to="/docs/apps#actions" style={{textDecoration: "none", color: "#f85a3e"}}> here</Link>.
|
||||
<Link target="_blank" to="https://shuffler.io/docs/apps#actions" style={{textDecoration: "none", color: "#f85a3e"}}> here</Link>.
|
||||
<div>
|
||||
{loopActions}
|
||||
<Button color="primary" style={{marginTop: "20px", borderRadius: "0px"}} variant="outlined" onClick={() => {
|
||||
@@ -1567,7 +1593,7 @@ const AppCreator = (props) => {
|
||||
<div style={{color: "white"}}>
|
||||
<h2>Test</h2>
|
||||
Test an action to see whether it performs in an expected way.
|
||||
<Link target="_blank" to="/docs/apps#testing" style={{textDecoration: "none", color: "#f85a3e"}}> TBD: Click here to learn more about testing</Link>.
|
||||
<Link target="_blank" to="https://shuffler.io/docs/apps#testing" style={{textDecoration: "none", color: "#f85a3e"}}> TBD: Click here to learn more about testing</Link>.
|
||||
<div>
|
||||
Test :)
|
||||
</div>
|
||||
@@ -1636,7 +1662,7 @@ const AppCreator = (props) => {
|
||||
</Breadcrumbs>
|
||||
<Paper style={boxStyle}>
|
||||
<h2 style={{marginBottom: "10px", color: "white"}}>General information</h2>
|
||||
<Link target="_blank" to="/docs/apps#create_openapi_app" style={{textDecoration: "none", color: "#f85a3e"}}>Click here to learn more about app creation</Link>
|
||||
<Link target="_blank" to="https://shuffler.io/docs/apps#create_openapi_app" style={{textDecoration: "none", color: "#f85a3e"}}>Click here to learn more about app creation</Link>
|
||||
<div style={{color: "white", flex: "1", display: "flex", flexDirection: "row"}}>
|
||||
<Tooltip title="Click to edit the app's image" placement="bottom">
|
||||
<div style={{flex: "1", margin: 10, border: "1px solid #f85a3e", cursor: "pointer", backgroundColor: inputColor, maxWidth: 174, maxHeight: 174}} onClick={() => {upload.click()}}>
|
||||
@@ -1732,7 +1758,7 @@ const AppCreator = (props) => {
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
value={baseUrl}
|
||||
helperText={<div style={{color:"white", marginBottom: "2px",}}>Must start with http(s):// and CANT end with /. </div>}
|
||||
helperText={<span style={{color:"white", marginBottom: "2px",}}>Must start with http(s):// and CANT end with /. </span>}
|
||||
placeholder="https://api.example.com"
|
||||
onChange={e => setBaseUrl(e.target.value)}
|
||||
onBlur={(event) => {
|
||||
|
||||
+282
-106
@@ -112,6 +112,7 @@ const Apps = (props) => {
|
||||
const [openApiError, setOpenApiError] = React.useState("")
|
||||
const [field1, setField1] = React.useState("")
|
||||
const [field2, setField2] = React.useState("")
|
||||
const [sharingConfiguration, setSharingConfiguration] = React.useState("you")
|
||||
|
||||
const { start, stop } = useInterval({
|
||||
duration: 5000,
|
||||
@@ -134,6 +135,24 @@ const Apps = (props) => {
|
||||
}
|
||||
})
|
||||
|
||||
function sortByKey(array, key) {
|
||||
return array.sort(function(a, b) {
|
||||
var x = a[key];
|
||||
var y = b[key];
|
||||
|
||||
if (typeof x == "string")
|
||||
{
|
||||
x = (""+x).toLowerCase();
|
||||
}
|
||||
if (typeof y == "string")
|
||||
{
|
||||
y = (""+y).toLowerCase();
|
||||
}
|
||||
|
||||
return ((x < y) ? 1 : ((x > y) ? -1 : 0));
|
||||
});
|
||||
}
|
||||
|
||||
const appViewStyle = {
|
||||
color: "#ffffff",
|
||||
width: "100%",
|
||||
@@ -169,6 +188,8 @@ const Apps = (props) => {
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
responseJson = sortByKey(responseJson, "large_image")
|
||||
|
||||
setApps(responseJson)
|
||||
setFilteredApps(responseJson)
|
||||
if (responseJson.length > 0) {
|
||||
@@ -290,12 +311,18 @@ const Apps = (props) => {
|
||||
<Paper square key={data.id} style={paperAppStyle} onClick={() => {
|
||||
if (selectedApp.id !== data.id) {
|
||||
setSelectedApp(data)
|
||||
|
||||
console.log(data)
|
||||
if (data.actions !== undefined && data.actions !== null && data.actions.length > 0) {
|
||||
setSelectedAction(data.actions[0])
|
||||
} else {
|
||||
setSelectedAction({})
|
||||
}
|
||||
|
||||
console.log("Sharing: ", data.sharing_config)
|
||||
if (data.sharing) {
|
||||
setSharingConfiguration("everyone")
|
||||
}
|
||||
}
|
||||
}}>
|
||||
<Grid container style={{margin: 10, flex: "10"}}>
|
||||
@@ -314,9 +341,22 @@ const Apps = (props) => {
|
||||
{description}
|
||||
</Grid>
|
||||
</div>
|
||||
<Grid item style={{flex: "1", justifyContent: "center"}}>
|
||||
Sharing: {sharing}
|
||||
, Valid: {valid}
|
||||
<Grid item style={{flex: "1", justifyContent: "center", marginTop: 5}}>
|
||||
{data.tags === null || data.tags === undefined ? null : data.tags.map((tag, index) => {
|
||||
if (index >= 3) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Chip
|
||||
key={index}
|
||||
style={{height: 25, marginRight: 5, cursor: "pointer",}}
|
||||
label={tag}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -324,7 +364,7 @@ const Apps = (props) => {
|
||||
|
||||
{data.activated && data.private_id !== undefined && data.private_id.length > 0 && data.generated ?
|
||||
<Grid container style={{margin: "10px 10px 10px 10px", flex: "1"}} onClick={() => {downloadApp(data)}}>
|
||||
<Tooltip title={"Download"} style={{marginTop: "28px", width: "100%"}} aria-label={data.name}>
|
||||
<Tooltip title={"Download OpenAPI"} style={{marginTop: "28px", width: "100%"}} aria-label={data.name}>
|
||||
<CloudDownload />
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
@@ -363,6 +403,7 @@ const Apps = (props) => {
|
||||
const activateUrl = "/apps/new?id="+selectedApp.id
|
||||
|
||||
var downloadButton = selectedApp.activated && selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated ?
|
||||
<Tooltip title={"Download OpenAPI"}>
|
||||
<Button
|
||||
onClick={() => {downloadApp(selectedApp)}}
|
||||
variant="outlined"
|
||||
@@ -372,31 +413,53 @@ const Apps = (props) => {
|
||||
>
|
||||
<CloudDownload />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
: null
|
||||
|
||||
var editButton = selectedApp.activated && selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated ?
|
||||
<Link to={editUrl} style={{textDecoration: "none"}}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
component="label"
|
||||
color="primary"
|
||||
style={{marginTop: 10, marginRight: 10,}}
|
||||
>
|
||||
<EditIcon />
|
||||
</Button></Link> : null
|
||||
<Tooltip title={"Edit OpenAPI app"}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
component="label"
|
||||
color="primary"
|
||||
style={{marginTop: 10, marginRight: 10,}}
|
||||
>
|
||||
<EditIcon />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Link> : null
|
||||
|
||||
var activateButton = selectedApp.generated && !selectedApp.activated ?
|
||||
<Link to={activateUrl} style={{textDecoration: "none"}}>
|
||||
<Button
|
||||
variant="contained"
|
||||
component="label"
|
||||
color="primary"
|
||||
style={{marginTop: 10}}
|
||||
>
|
||||
Activate App
|
||||
</Button></Link> : null
|
||||
<div>
|
||||
<Link to={activateUrl} style={{textDecoration: "none"}}>
|
||||
<Button
|
||||
variant="contained"
|
||||
component="label"
|
||||
color="primary"
|
||||
style={{marginTop: 10}}
|
||||
>
|
||||
Activate App
|
||||
</Button>
|
||||
</Link>
|
||||
<Tooltip title={"Delete app"}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
component="label"
|
||||
color="primary"
|
||||
style={{marginLeft: 5, marginTop: 10}}
|
||||
onClick={() => {
|
||||
setDeleteModalOpen(true)
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
: null
|
||||
|
||||
var deleteButton = ((selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated) || (selectedApp.downloaded != undefined && selectedApp.downloaded == true)) && activateButton === null ?
|
||||
<Tooltip title={"Delete app"}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
component="label"
|
||||
@@ -407,7 +470,9 @@ const Apps = (props) => {
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</Button> : null
|
||||
</Button>
|
||||
</Tooltip>
|
||||
: null
|
||||
|
||||
var imageline = selectedApp.large_image === undefined || selectedApp.large_image.length === 0 ?
|
||||
<img alt={selectedApp.title} style={{width: 100, height: 100}} />
|
||||
@@ -450,6 +515,7 @@ const Apps = (props) => {
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
{/*
|
||||
<ReactJson
|
||||
src={JSON.parse(showResult)}
|
||||
theme="solarized"
|
||||
@@ -457,19 +523,25 @@ const Apps = (props) => {
|
||||
displayDataTypes={true}
|
||||
name={"Example return value"}
|
||||
/>
|
||||
*/}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{marginTop: 10}}>
|
||||
<b>Example return</b><div/>
|
||||
{selectedAction.returns.example}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
//fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"), {
|
||||
const userRoles = [
|
||||
"you",
|
||||
"everyone",
|
||||
]
|
||||
|
||||
//fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"),
|
||||
var baseInfo = newAppname.length > 0 ?
|
||||
<div>
|
||||
<div style={{display: "flex"}}>
|
||||
@@ -482,15 +554,25 @@ const Apps = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
{activateButton}
|
||||
{downloadButton}
|
||||
{editButton}
|
||||
{deleteButton}
|
||||
{props.userdata.role === "admin" || props.userdata.id === selectedApp.owner ?
|
||||
<div>
|
||||
{downloadButton}
|
||||
{editButton}
|
||||
{deleteButton}
|
||||
</div>
|
||||
: null}
|
||||
{selectedApp.tags !== undefined && selectedApp.tags !== null ?
|
||||
<div style={{display: "inline-block", marginLeft: 15, float: "right",}}>
|
||||
{selectedApp.tags.map(tag => {
|
||||
{selectedApp.tags.map((tag, index) => {
|
||||
if (index >= 3) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Chip
|
||||
style={{marginRight: 5, marginTop: 7, cursor: "pointer",}}
|
||||
key={index}
|
||||
style={{height: 25, marginRight: 5, marginTop: 7, cursor: "pointer",}}
|
||||
variant="outlined"
|
||||
label={tag}
|
||||
color="primary"
|
||||
/>
|
||||
@@ -498,78 +580,110 @@ const Apps = (props) => {
|
||||
})}
|
||||
</div>
|
||||
: null}
|
||||
<Divider style={{marginBottom: "10px", marginTop: "10px", backgroundColor: dividerColor}}/>
|
||||
{selectedApp.link.length > 0 ? <p><b>URL:</b> {selectedApp.link}</p> : null}
|
||||
<p><b>ID:</b> {selectedApp.id}</p>
|
||||
{selectedApp.privateId !== undefined && selectedApp.privateId.length > 0 ? <p><b>PrivateID:</b> {selectedApp.privateId}</p> : null}
|
||||
|
||||
<div style={{marginTop: 15, marginBottom: 15}}>
|
||||
<b>Actions</b>
|
||||
{selectedApp.actions !== null && selectedApp.actions.length > 0 ?
|
||||
{props.userdata.id === selectedApp.owner ?
|
||||
<div style={{marginTop: 15}}>
|
||||
{/*<p><b>ID:</b> {selectedApp.id}</p>*/}
|
||||
<b style={{marginRight: 15}}>Sharing:</b>
|
||||
<Select
|
||||
fullWidth
|
||||
value={selectedAction}
|
||||
value={sharingConfiguration}
|
||||
onChange={(event) => {
|
||||
setSelectedAction(event.target.value)
|
||||
setSharingConfiguration(event.target.value)
|
||||
alert.info("Changed sharing to "+event.target.value)
|
||||
|
||||
updateAppField(selectedApp.id, "sharing", !selectedApp.sharing)
|
||||
//setSelectedAction(event.target.value)
|
||||
}}
|
||||
style={{backgroundColor: inputColor, color: "white", height: "50px"}}
|
||||
style={{width: 150, backgroundColor: inputColor, color: "white", height: 35, marginleft: 10,}}
|
||||
SelectDisplayProps={{
|
||||
style: {
|
||||
marginLeft: 10,
|
||||
}
|
||||
}}
|
||||
>
|
||||
{selectedApp.actions.map(data => {
|
||||
var newActionname = data.label !== undefined && data.label.length > 0 ? data.label : data.name
|
||||
{userRoles.map(data => {
|
||||
return (
|
||||
<MenuItem key={data} style={{backgroundColor: inputColor, color: "white"}} value={data}>
|
||||
{data}
|
||||
|
||||
// ROFL FIXME - loop
|
||||
newActionname = newActionname.replace("_", " ")
|
||||
newActionname = newActionname.replace("_", " ")
|
||||
newActionname = newActionname.replace("_", " ")
|
||||
newActionname = newActionname.replace("_", " ")
|
||||
newActionname = newActionname.charAt(0).toUpperCase()+newActionname.substring(1)
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
</Select>
|
||||
</div>
|
||||
: null}
|
||||
{/*<p><b>Owner:</b> {selectedApp.owner}</p>*/}
|
||||
{selectedApp.privateId !== undefined && selectedApp.privateId.length > 0 ? <p><b>PrivateID:</b> {selectedApp.privateId}</p> : null}
|
||||
<Divider style={{marginBottom: 10, marginTop: 10, backgroundColor: dividerColor}}/>
|
||||
<div style={{padding: 20}}>
|
||||
{selectedApp.link.length > 0 ? <p><b>URL:</b> {selectedApp.link}</p> : null}
|
||||
<div style={{marginTop: 15, marginBottom: 15}}>
|
||||
<b>Actions</b>
|
||||
{selectedApp.actions !== null && selectedApp.actions.length > 0 ?
|
||||
<Select
|
||||
fullWidth
|
||||
value={selectedAction}
|
||||
onChange={(event) => {
|
||||
setSelectedAction(event.target.value)
|
||||
}}
|
||||
style={{backgroundColor: inputColor, color: "white", height: "50px"}}
|
||||
SelectDisplayProps={{
|
||||
style: {
|
||||
marginLeft: 10,
|
||||
}
|
||||
}}
|
||||
>
|
||||
{selectedApp.actions.map(data => {
|
||||
var newActionname = data.label !== undefined && data.label.length > 0 ? data.label : data.name
|
||||
|
||||
// ROFL FIXME - loop
|
||||
newActionname = newActionname.replace("_", " ")
|
||||
newActionname = newActionname.replace("_", " ")
|
||||
newActionname = newActionname.replace("_", " ")
|
||||
newActionname = newActionname.replace("_", " ")
|
||||
newActionname = newActionname.charAt(0).toUpperCase()+newActionname.substring(1)
|
||||
return (
|
||||
<MenuItem key={data.name} style={{backgroundColor: inputColor, color: "white"}} value={data}>
|
||||
{newActionname}
|
||||
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
</Select>
|
||||
:
|
||||
<div style={{marginTop: 10}}>
|
||||
There are no actions defined for this app.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
{selectedAction.parameters !== undefined && selectedAction.parameters !== null ?
|
||||
<div style={{marginTop: 15, marginBottom: 15}}>
|
||||
<b>Arguments</b>
|
||||
{selectedAction.parameters.map(data => {
|
||||
var itemColor = "#f85a3e"
|
||||
if (!data.required) {
|
||||
itemColor = "#ffeb3b"
|
||||
}
|
||||
|
||||
const circleSize = 10
|
||||
return (
|
||||
<MenuItem key={data.name} style={{backgroundColor: inputColor, color: "white"}} value={data}>
|
||||
{newActionname}
|
||||
<div style={{width: circleSize, height: circleSize, borderRadius: circleSize / 2, backgroundColor: itemColor, marginRight: "10px"}}/>
|
||||
{data.name}
|
||||
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
</Select>
|
||||
:
|
||||
<div style={{marginTop: 10}}>
|
||||
There are no actions defined for this app.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
{selectedAction.parameters !== undefined && selectedAction.parameters !== null ?
|
||||
<div style={{marginTop: 15, marginBottom: 15}}>
|
||||
<b>Arguments</b>
|
||||
{selectedAction.parameters.map(data => {
|
||||
var itemColor = "#f85a3e"
|
||||
if (!data.required) {
|
||||
itemColor = "#ffeb3b"
|
||||
}
|
||||
|
||||
const circleSize = 10
|
||||
return (
|
||||
<MenuItem key={data.name} style={{backgroundColor: inputColor, color: "white"}} value={data}>
|
||||
<div style={{width: circleSize, height: circleSize, borderRadius: circleSize / 2, backgroundColor: itemColor, marginRight: "10px"}}/>
|
||||
{data.name}
|
||||
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
: null}
|
||||
{selectedAction.description !== undefined && selectedAction.description !== null && selectedAction.description.length > 0 ?
|
||||
<div>
|
||||
<b>Action Description</b><div/>
|
||||
{selectedAction.description}
|
||||
</div>
|
||||
: null}
|
||||
<GetAppExample />
|
||||
{selectedAction.description !== undefined && selectedAction.description !== null && selectedAction.description.length > 0 ?
|
||||
<div>
|
||||
<b>Action Description</b><div/>
|
||||
{selectedAction.description}
|
||||
</div>
|
||||
: null}
|
||||
<GetAppExample />
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
@@ -582,8 +696,9 @@ const Apps = (props) => {
|
||||
<a href="https://shuffler.io/docs/apps" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">How it works</a>
|
||||
- <a href="https://github.com/frikky/security-openapis" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">Security API's</a>
|
||||
- <a href="https://apis.guru/browse-apis/" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI directory</a>
|
||||
- <a href="https://editor.swagger.io/" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI Validator</a>
|
||||
<div/>
|
||||
Apps interact with eachother in workflows. They are created with the app creator, using OpenAPI specification or manually in python. Use the links above to find potential apps you're looking for using OpenAPI or make one from scratch. There's 1000+ available.
|
||||
Apps interact with eachother in workflows. They are created with the app creator, using OpenAPI specification or manually in python. The links above are references to OpenAPI tools and other app repositories. There's ten thousands of them.
|
||||
<div/>
|
||||
<Divider style={{height: 1, backgroundColor: dividerColor, marginTop: 20, marginBottom: 20}} />
|
||||
<div style={{}}>
|
||||
@@ -884,6 +999,36 @@ const Apps = (props) => {
|
||||
});
|
||||
}
|
||||
|
||||
const updateAppField = (app_id, fieldname, fieldvalue) => {
|
||||
const data = {}
|
||||
data[fieldname] = fieldvalue
|
||||
|
||||
fetch(globalUrl+"/api/v1/apps/"+app_id, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
//setAppSearchLoading(false)
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
//console.log(responseJson)
|
||||
//alert.info(responseJson)
|
||||
if (responseJson.success) {
|
||||
alert.info("Success")
|
||||
} else {
|
||||
alert.error("Error updating app")
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
const runAppSearch = (searchterm) => {
|
||||
const data = {"search": searchterm}
|
||||
|
||||
@@ -916,7 +1061,7 @@ const Apps = (props) => {
|
||||
setValidation(true)
|
||||
|
||||
fetch(globalUrl+"/api/v1/get_openapi_uri", {
|
||||
method: 'POST',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
@@ -924,19 +1069,34 @@ const Apps = (props) => {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
setValidation(false)
|
||||
if (response.status !== 200) {
|
||||
return response.json()
|
||||
}
|
||||
|
||||
return response.text()
|
||||
})
|
||||
.then((responseText) => {
|
||||
validateOpenApi(responseText)
|
||||
setValidation(false)
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (typeof(responseJson) !== "string" && !responseJson.success) {
|
||||
console.log(responseJson.reason)
|
||||
if (responseJson.reason !== undefined) {
|
||||
setOpenApiError(responseJson.reason)
|
||||
} else {
|
||||
setOpenApiError("Undefined issue with OpenAPI validation")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validateOpenApi(responseJson)
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
setOpenApiError(error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
const escapeApiData = (apidata) => {
|
||||
console.log(apidata)
|
||||
//console.log(apidata)
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(apidata))
|
||||
} catch(error) {
|
||||
@@ -948,7 +1108,7 @@ const Apps = (props) => {
|
||||
return JSON.stringify(YAML.parse(apidata))
|
||||
} catch(error) {
|
||||
console.log("YAML DECODE ERROR - TRY SOMETHING ELSE?: "+error)
|
||||
setOpenApiError(error)
|
||||
setOpenApiError(error.toString())
|
||||
}
|
||||
|
||||
return ""
|
||||
@@ -962,6 +1122,7 @@ const Apps = (props) => {
|
||||
return
|
||||
}
|
||||
|
||||
setValidation(true)
|
||||
fetch(globalUrl+"/api/v1/validate_openapi", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -971,10 +1132,10 @@ const Apps = (props) => {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
setValidation(false)
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
setValidation(false)
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success) {
|
||||
setAppValidation(responseJson.id)
|
||||
} else {
|
||||
@@ -985,7 +1146,9 @@ const Apps = (props) => {
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
setValidation(false)
|
||||
alert.error(error.toString())
|
||||
setOpenApiError(error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1131,7 +1294,9 @@ const Apps = (props) => {
|
||||
const modalView = openApiModal ?
|
||||
<Dialog
|
||||
open={openApiModal}
|
||||
onClose={() => {setOpenApiModal(false)}}
|
||||
onClose={() => {
|
||||
setOpenApiModal(false)
|
||||
}}
|
||||
PaperProps={{
|
||||
style: {
|
||||
backgroundColor: surfaceColor,
|
||||
@@ -1155,21 +1320,26 @@ const Apps = (props) => {
|
||||
height: "50px",
|
||||
fontSize: "1em",
|
||||
},
|
||||
endAdornment: <Button style={{borderRadius: "0px", marginTop: "0px", height: "50px"}} variant="contained" disabled={openApi.length === 0} color="primary" onClick={() => {
|
||||
endAdornment: <Button style={{borderRadius: "0px", marginTop: "0px", height: "50px"}} variant="contained" disabled={openApi.length === 0 || appValidation.length > 0} color="primary"
|
||||
onClick={() => {
|
||||
setOpenApiError("")
|
||||
validateRemote()
|
||||
}}>Validate</Button>
|
||||
}}
|
||||
onChange={e => setOpenApi(e.target.value)}
|
||||
helperText={<div style={{color:"white", marginBottom: "2px",}}>Must point to a version 2 or 3 specification.</div>}
|
||||
onChange={e => {
|
||||
setOpenApi(e.target.value)
|
||||
}}
|
||||
helperText={<span style={{color:"white", marginBottom: "2px",}}>Must point to a version 2 or 3 OpenAPI specification.</span>}
|
||||
placeholder="OpenAPI URI"
|
||||
fullWidth
|
||||
/>
|
||||
{/*
|
||||
<div style={{marginTop: "15px"}}/>
|
||||
Example:
|
||||
<div />
|
||||
https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/uber.json
|
||||
<h4>or paste the yaml/JSON directly below</h4>
|
||||
*/}
|
||||
Or paste the YAML or JSON specification
|
||||
<TextField
|
||||
style={{backgroundColor: inputColor}}
|
||||
variant="outlined"
|
||||
@@ -1181,13 +1351,13 @@ const Apps = (props) => {
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
endAdornment: <Button style={{marginLeft: 10, borderRadius: "0px", marginTop: "0px"}} variant="contained" disabled={openApiData.length === 0} color="primary" onClick={() => {
|
||||
endAdornment: <Button style={{marginLeft: 10, borderRadius: "0px", marginTop: "0px"}} variant="contained" disabled={openApiData.length === 0 || appValidation.length > 0} color="primary" onClick={() => {
|
||||
setOpenApiError("")
|
||||
validateOpenApi(openApiData)
|
||||
}}>Validate data</Button>
|
||||
}}>Validate OpenAPI</Button>
|
||||
}}
|
||||
onChange={e => setOpenApiData(e.target.value)}
|
||||
helperText={<div style={{color:"white", marginBottom: "2px",}}>Must point to a version 2 or 3 specification.</div>}
|
||||
helperText={<span style={{color:"white", marginBottom: "2px",}}>Must point to a version 2 or 3 specification.</span>}
|
||||
placeholder="OpenAPI text"
|
||||
fullWidth
|
||||
/>
|
||||
@@ -1195,13 +1365,19 @@ const Apps = (props) => {
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{circularLoader}
|
||||
<Button style={{borderRadius: "0px"}} onClick={() => setOpenApiModal(false)} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button style={{borderRadius: "0px"}} disabled={appValidation.length === 0} onClick={() => {
|
||||
<Button style={{borderRadius: "0px"}} onClick={() => {
|
||||
setOpenApiModal(false)
|
||||
setAppValidation("")
|
||||
setOpenApiError("")
|
||||
setOpenApi("")
|
||||
setOpenApiData("")
|
||||
}} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="contained" style={{borderRadius: "0px"}} disabled={appValidation.length === 0} onClick={() => {
|
||||
redirectOpenApi()
|
||||
}} color="primary">
|
||||
Submit
|
||||
Continue
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</FormControl>
|
||||
|
||||
@@ -14,10 +14,12 @@ import MenuItem from '@material-ui/core/MenuItem';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import Switch from '@material-ui/core/Switch';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||
import CachedIcon from '@material-ui/icons/Cached';
|
||||
import GetAppIcon from '@material-ui/icons/GetApp';
|
||||
import AppsIcon from '@material-ui/icons/Apps';
|
||||
import EditIcon from '@material-ui/icons/Edit';
|
||||
import MoreVertIcon from '@material-ui/icons/MoreVert';
|
||||
import PlayArrowIcon from '@material-ui/icons/PlayArrow';
|
||||
@@ -200,7 +202,7 @@ const Workflows = (props) => {
|
||||
marginTop: "10px",
|
||||
overflow: "scroll",
|
||||
height: "90%",
|
||||
overflowX: "auto",
|
||||
overflowX: "hidden",
|
||||
overflowY: "auto",
|
||||
}
|
||||
|
||||
@@ -211,6 +213,8 @@ const Workflows = (props) => {
|
||||
marginTop: "5px",
|
||||
color: "white",
|
||||
backgroundColor: surfaceColor,
|
||||
borderRadius: 5,
|
||||
padding: 10,
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
}
|
||||
@@ -240,6 +244,8 @@ const Workflows = (props) => {
|
||||
setWorkflowExecutions(responseJson)
|
||||
} else {
|
||||
alert.info("Couldn't find executions for the workflow")
|
||||
setSelectedExecution({})
|
||||
setWorkflowExecutions([])
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -412,12 +418,29 @@ const Workflows = (props) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
}
|
||||
|
||||
const actions = data.actions !== null ? data.actions.length : 0
|
||||
var schedules = 0
|
||||
var webhooks = 0
|
||||
var webhookImg = ""
|
||||
var scheduleImg = ""
|
||||
if (data.triggers !== undefined && data.triggers !== null && data.triggers.length > 0) {
|
||||
for (var key in data.triggers) {
|
||||
if (data.triggers[key].app_name === "Webhook") {
|
||||
webhooks += 1
|
||||
webhookImg = data.triggers[key].large_image
|
||||
} else if (data.triggers[key].app_name === "Schedule") {
|
||||
schedules += 1
|
||||
scheduleImg = data.triggers[key].large_image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const imgSize = 25
|
||||
return (
|
||||
<Paper square style={paperAppStyle} onClick={(e) => {
|
||||
}}>
|
||||
<div style={{marginLeft: "10px", marginTop: "5px", marginBottom: "5px", width: boxWidth, backgroundColor: boxColor}}>
|
||||
</div>
|
||||
<Grid container style={{margin: "10px 10px 10px 10px", flex: 1}}>
|
||||
}}>
|
||||
<div style={{width: boxWidth, backgroundColor: boxColor}} />
|
||||
<Grid container style={{margin: "0px 10px 0px 10px", flex: 10}}>
|
||||
<Grid style={{display: "flex", flexDirection: "column", width: "100%"}}>
|
||||
<Grid item style={{flex: 1, display: "flex"}}>
|
||||
<div style={{flex: "10",}} onClick={() => {
|
||||
@@ -426,9 +449,11 @@ const Workflows = (props) => {
|
||||
getWorkflowExecution(data.id)
|
||||
}
|
||||
}}>
|
||||
<h3 style={{marginBottom: "0px", marginTop: "10px"}}>{data.name}</h3>
|
||||
<Typography variant="h6" style={{marginTop: 10, marginBottom: 0, }}>
|
||||
{data.name}
|
||||
</Typography>
|
||||
</div>
|
||||
<div style={{flex: "1"}}>
|
||||
<div style={{flex: 1, }}>
|
||||
<IconButton
|
||||
aria-label="more"
|
||||
aria-controls="long-menu"
|
||||
@@ -481,7 +506,7 @@ const Workflows = (props) => {
|
||||
getWorkflowExecution(data.id)
|
||||
}
|
||||
}}>
|
||||
<Grid item style={{flex: "1", justifyContent: "center", overflow: "hidden"}}>
|
||||
<Grid item style={{flex: "1", justifyContent: "center", overflow: "hidden", float: "bottom",}}>
|
||||
<Link to={"/workflows/"+data.id}>
|
||||
<Tooltip color="primary" title="Edit workflow" placement="bottom">
|
||||
<Button style={{}} color="secondary" variant="text" style={{marginRight: 10}} onClick={() => {}}>
|
||||
@@ -490,16 +515,24 @@ const Workflows = (props) => {
|
||||
</Tooltip>
|
||||
</Link>
|
||||
<Tooltip color="primary" title="Execute workflow" placement="bottom">
|
||||
<Button style={{}} color="secondary" variant="text" onClick={() => executeWorkflow(data.id)}>
|
||||
<PlayArrowIcon />
|
||||
</Button>
|
||||
<span>
|
||||
<Button style={{}} disabled={!data.is_valid} color="secondary" variant="text" onClick={() => executeWorkflow(data.id)}>
|
||||
<PlayArrowIcon />
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
{data.tags !== undefined ?
|
||||
data.tags.map(tag => {
|
||||
data.tags.map((tag, index) => {
|
||||
if (index >= 3) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Chip
|
||||
style={{marginRight: 5, marginTop: 2, cursor: "pointer",}}
|
||||
key={index}
|
||||
style={{height: 25, marginRight: 5, marginTop: 2, cursor: "pointer",}}
|
||||
label={tag}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
/>
|
||||
)
|
||||
@@ -509,6 +542,22 @@ const Workflows = (props) => {
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container style={{maxWidth: 35, marginRight: 10,}}>
|
||||
<Tooltip title={`Actions: ${data.actions.length}`} placement="right">
|
||||
<AppsIcon style={{width: imgSize, height: imgSize}} />
|
||||
</Tooltip>
|
||||
|
||||
{webhooks > 0 ?
|
||||
<Tooltip title={`Webhooks: ${webhooks}`} placement="right">
|
||||
<img alt={data.title} style={{width: imgSize, height: imgSize, marginTop: 5}} src={webhookImg} />
|
||||
</Tooltip>
|
||||
: null}
|
||||
{schedules > 0 ?
|
||||
<Tooltip title={`Schedules: ${schedules}`} placement="right">
|
||||
<img alt={data.title} style={{width: imgSize, height: imgSize, marginTop: 5}} src={scheduleImg} />
|
||||
</Tooltip>
|
||||
: null}
|
||||
</Grid>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
@@ -914,7 +963,6 @@ const Workflows = (props) => {
|
||||
setLoadWorkflowsModalOpen(false)
|
||||
}
|
||||
|
||||
console.log("WOrkflowtags: ", newWorkflowTags)
|
||||
const modalView = modalOpen ?
|
||||
<Dialog
|
||||
open={modalOpen}
|
||||
|
||||
@@ -2,13 +2,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
|
||||
|
||||
COPY orborus.go /app/orborus.go
|
||||
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o orborus .
|
||||
|
||||
from scratch
|
||||
FROM alpine:3.12
|
||||
RUN apk add --no-cache bash
|
||||
COPY --from=builder /app/ /
|
||||
|
||||
CMD ["./orborus"]
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
NAME=orborus
|
||||
VERSION=0.1.0
|
||||
VERSION=0.6.2
|
||||
|
||||
echo "Running docker build with $NAME:$VERSION"
|
||||
#docker rmi frikky/shuffle:$NAME --force
|
||||
docker build . -t frikky/shuffle:$NAME -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t frikky/$NAME:$VERSION
|
||||
docker build . -t frikky/shuffle:$NAME -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t frikky/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
||||
|
||||
docker push frikky/$NAME:$VERSION
|
||||
docker push frikky/shuffle:$NAME
|
||||
#docker push docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION
|
||||
#docker push frikky/$NAME:$VERSION
|
||||
#docker push frikky/shuffle:$NAME
|
||||
# docker push docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION
|
||||
docker push ghcr.io/frikky/$NAME:$VERSION
|
||||
|
||||
+102
-112
@@ -14,31 +14,33 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
network "github.com/docker/docker/api/types/network"
|
||||
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 shuffleNetwork = "" // Filled in init if found
|
||||
|
||||
var dockerApiVersion = os.Getenv("DOCKER_API_VERSION")
|
||||
var environment = os.Getenv("ENVIRONMENT_NAME")
|
||||
var orgId = os.Getenv("ORG_ID")
|
||||
|
||||
// Starts jobs in bulk, so this could be increased
|
||||
var sleepTime = 3
|
||||
|
||||
// Timeout if somethinc rashes
|
||||
//var workerTimeout = 600
|
||||
// Timeout if something rashes
|
||||
var workerTimeout = 300
|
||||
var appSdkVersion = os.Getenv("SHUFFLE_APP_SDK_VERSION")
|
||||
var workerVersion = os.Getenv("SHUFFLE_WORKER_VERSION")
|
||||
|
||||
//var baseimagename = "docker.pkg.github.com/frikky/shuffle"
|
||||
var baseimagename = "ghcr.io/frikky"
|
||||
|
||||
var orgId = os.Getenv("ORG_ID")
|
||||
var baseUrl = os.Getenv("BASE_URL")
|
||||
var environment = os.Getenv("ENVIRONMENT_NAME")
|
||||
var dockerApiVersion = os.Getenv("DOCKER_API_VERSION")
|
||||
var runningMode = strings.ToLower(os.Getenv("RUNNING_MODE"))
|
||||
|
||||
type ExecutionRequestWrapper struct {
|
||||
Data []ExecutionRequest `json:"data"`
|
||||
@@ -54,6 +56,7 @@ type ExecutionRequest struct {
|
||||
}
|
||||
|
||||
var dockercli *dockerclient.Client
|
||||
var containerId string
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
@@ -63,48 +66,40 @@ func init() {
|
||||
panic(fmt.Sprintf("Unable to create docker client: %s", err))
|
||||
}
|
||||
|
||||
// FIXME: Move this to global variables?
|
||||
containerIdentifier := "orborus"
|
||||
networkIdentifier := "shuffle"
|
||||
getThisContainerId()
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
containers, err := dockercli.ContainerList(ctx, types.ContainerListOptions{
|
||||
All: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Failed getting containers during init - running without network check: %s", err)
|
||||
// form id of current running container
|
||||
func getThisContainerId() {
|
||||
fCol := ""
|
||||
|
||||
// some adjusting based on current running mode
|
||||
switch runningMode {
|
||||
case "kubernetes":
|
||||
// cgroup will be like:
|
||||
// 11:net_cls,net_prio:/kubepods/besteffort/podf132b44d-cfcf-43f7-9906-79f58e268333/851466f8b5ed5aa0f265b1c95c6d2bafbc51a38dd5c5a1621b6e586572150009
|
||||
fCol = "5"
|
||||
log.Printf("[INFO] Running containerized in Kubernetes!")
|
||||
|
||||
case "docker":
|
||||
// cgroup will be like:
|
||||
// 12:perf_event:/docker/0f06810364f52a2cd6e80bfba27419cb8a29758a204cd676388f4913bb366f2b
|
||||
fCol = "3"
|
||||
log.Printf("[INFO] Running containerized in Docker!")
|
||||
|
||||
default:
|
||||
fCol = "3" // for backward-compatibility with production
|
||||
log.Printf("[WARNING] RUNNING_MODE not set, so I can't figure out the current container ID! Defaulting to Docker (not Kubernetes).")
|
||||
}
|
||||
|
||||
// Skip random containers. Only handle things related to Shuffle.
|
||||
for _, container := range containers {
|
||||
found := false
|
||||
|
||||
// Bad states - it might just be created sometimes, leading to now netowkr
|
||||
//if container.State == "restarting" || container.State == "paused" || container.State == "exited" || container.State == "dead" {
|
||||
// continue
|
||||
//}
|
||||
|
||||
for _, name := range container.Names {
|
||||
if !strings.Contains(strings.ToLower(name), containerIdentifier) {
|
||||
found = true
|
||||
continue
|
||||
}
|
||||
if fCol != "" {
|
||||
cmd := fmt.Sprintf("cat /proc/self/cgroup | grep memory | tail -1 | cut -d/ -f%s", fCol)
|
||||
out, err := exec.Command("bash", "-c", cmd).Output()
|
||||
if err == nil {
|
||||
containerId = strings.TrimSpace(string(out))
|
||||
} else {
|
||||
log.Printf("Failed getting container ID: %s", err)
|
||||
}
|
||||
|
||||
if found {
|
||||
for key, _ := range container.NetworkSettings.Networks {
|
||||
if strings.Contains(strings.ToLower(key), networkIdentifier) {
|
||||
shuffleNetwork = key
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(shuffleNetwork) > 0 {
|
||||
log.Printf("Found shuffle network \"%s\" for container %s", shuffleNetwork, containerIdentifier)
|
||||
} else {
|
||||
log.Printf("Running Shuffle without a docker network")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,40 +116,24 @@ func deployWorker(image string, identifier string, env []string) {
|
||||
},
|
||||
}
|
||||
|
||||
// Look for Shuffle network and set it
|
||||
networkConfig := &network.NetworkingConfig{}
|
||||
if len(shuffleNetwork) > 0 {
|
||||
log.Printf("Starting worker with network %s", shuffleNetwork)
|
||||
networkConfig = &network.NetworkingConfig{
|
||||
EndpointsConfig: map[string]*network.EndpointSettings{
|
||||
shuffleNetwork: {
|
||||
NetworkID: shuffleNetwork,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
env = append(env, fmt.Sprintf("DOCKER_NETWORK=%s", shuffleNetwork))
|
||||
// form container id and use it as network source if it's not empty
|
||||
if containerId != "" {
|
||||
log.Printf("[INFO] Found container ID %s", containerId)
|
||||
hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId))
|
||||
} else {
|
||||
log.Printf("Starting worker WITHOUT any specified network: %s", shuffleNetwork)
|
||||
log.Printf("[INFO] Empty self container id, continue without NetworkMode")
|
||||
}
|
||||
|
||||
// ROFL: https://docker-py.readthedocs.io/en/1.4.0/volumes/
|
||||
config := &container.Config{
|
||||
Image: image,
|
||||
Env: env,
|
||||
}
|
||||
|
||||
//test := &network.EndpointSettings{
|
||||
// Gateway: "helo",
|
||||
//}
|
||||
//NetworkID
|
||||
//if connect.EndpointConfig.NetworkID != "NetworkID" {
|
||||
|
||||
cont, err := dockercli.ContainerCreate(
|
||||
context.Background(),
|
||||
config,
|
||||
hostConfig,
|
||||
networkConfig,
|
||||
nil,
|
||||
nil,
|
||||
identifier,
|
||||
)
|
||||
@@ -166,7 +145,7 @@ func deployWorker(image string, identifier string, env []string) {
|
||||
|
||||
err = dockercli.ContainerStart(context.Background(), cont.ID, types.ContainerStartOptions{})
|
||||
if err != nil {
|
||||
log.Printf("Failed to start container in environment %s: %s", environment, err)
|
||||
log.Printf("[ERROR] Failed to start container in environment %s: %s", environment, err)
|
||||
return
|
||||
|
||||
//stats, err := cli.ContainerInspect(context.Background(), containerName)
|
||||
@@ -191,7 +170,7 @@ func deployWorker(image string, identifier string, env []string) {
|
||||
// }
|
||||
//}
|
||||
} else {
|
||||
log.Printf("Container %s was created under environment %s", cont.ID, environment)
|
||||
log.Printf("[INFO] Container %s was created under environment %s", cont.ID, environment)
|
||||
}
|
||||
|
||||
return
|
||||
@@ -205,7 +184,7 @@ func stopWorker(containername string) error {
|
||||
// })
|
||||
|
||||
if err := dockercli.ContainerStop(ctx, containername, nil); err != nil {
|
||||
log.Printf("Unable to stop container %s - running removal anyway, just in case: %s", containername, err)
|
||||
log.Printf("[ERROR] Unable to stop container %s - running removal anyway, just in case: %s", containername, err)
|
||||
}
|
||||
|
||||
removeOptions := types.ContainerRemoveOptions{
|
||||
@@ -214,7 +193,7 @@ func stopWorker(containername string) error {
|
||||
}
|
||||
|
||||
if err := dockercli.ContainerRemove(ctx, containername, removeOptions); err != nil {
|
||||
log.Printf("Unable to remove container: %s", err)
|
||||
log.Printf("[ERROR] Unable to remove container: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -223,32 +202,43 @@ func stopWorker(containername string) error {
|
||||
func initializeImages() {
|
||||
ctx := context.Background()
|
||||
|
||||
if appSdkVersion == "" {
|
||||
appSdkVersion = "0.6.0"
|
||||
log.Printf("[WARNING] SHUFFLE_APP_SDK_VERSION not defined. Defaulting to %s", appSdkVersion)
|
||||
}
|
||||
if workerVersion == "" {
|
||||
workerVersion = "0.6.0"
|
||||
log.Printf("[WARNING] SHUFFLE_WORKER_VERSION not defined. Defaulting to %s", workerVersion)
|
||||
}
|
||||
|
||||
// check whether theyre the same first
|
||||
//version := "0.1.0"
|
||||
// fmt.Sprintf("docker.pkg.github.com/frikky/shuffle/orborus:%s", version),
|
||||
// fmt.Sprintf("docker.pkg.github.com/frikky/shuffle/worker:%s", version),
|
||||
images := []string{
|
||||
fmt.Sprintf("docker.io/%s:app_sdk", baseimagename),
|
||||
fmt.Sprintf("docker.io/%s:worker", baseimagename),
|
||||
// fmt.Sprintf("docker.io/%s:app_sdk", baseimagename),
|
||||
// fmt.Sprintf("docker.io/%s:worker", baseimagename),
|
||||
|
||||
fmt.Sprintf("%s/worker:%s", baseimagename, workerVersion),
|
||||
fmt.Sprintf("%s/app_sdk:%s", baseimagename, appSdkVersion),
|
||||
fmt.Sprintf("frikky/shuffle:app_sdk"),
|
||||
}
|
||||
|
||||
pullOptions := types.ImagePullOptions{}
|
||||
for _, image := range images {
|
||||
log.Printf("[INFO] Pulling image %s", image)
|
||||
reader, err := dockercli.ImagePull(ctx, image, pullOptions)
|
||||
if err != nil {
|
||||
log.Printf("Failed getting %s: %s", image, err)
|
||||
log.Printf("[ERROR] Failed getting image %s: %s", image, err)
|
||||
continue
|
||||
}
|
||||
|
||||
io.Copy(os.Stdout, reader)
|
||||
log.Printf("Successfully downloaded and built %s", image)
|
||||
log.Printf("[INFO] Successfully downloaded and built %s", image)
|
||||
}
|
||||
}
|
||||
|
||||
// Initial loop etc
|
||||
func main() {
|
||||
go zombiecheck()
|
||||
log.Println("Setting up execution environment")
|
||||
log.Println("[INFO] Setting up execution environment")
|
||||
|
||||
//FIXME
|
||||
if baseUrl == "" {
|
||||
@@ -257,30 +247,30 @@ func main() {
|
||||
}
|
||||
|
||||
if orgId == "" {
|
||||
log.Printf("Org not defined. Set variable ORG_ID based on your org")
|
||||
log.Printf("[ERROR] Org not defined. Set variable ORG_ID based on your org")
|
||||
os.Exit(3)
|
||||
}
|
||||
|
||||
log.Printf("Running towards %s with Org %s", baseUrl, orgId)
|
||||
log.Printf("[INFO] Running towards %s with Org %s", baseUrl, orgId)
|
||||
httpProxy := os.Getenv("HTTP_PROXY")
|
||||
httpsProxy := os.Getenv("HTTPS_PROXY")
|
||||
|
||||
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)
|
||||
log.Printf("[INFO] 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
|
||||
log.Printf("--- Setting up Docker environment. Downloading worker and App SDK! ---")
|
||||
initializeImages()
|
||||
log.Printf("[INFO] Setting up Docker environment. Downloading worker and App SDK!")
|
||||
go initializeImages()
|
||||
|
||||
//workerName := "worker"
|
||||
//workerVersion := "0.1.0"
|
||||
//workerImage := fmt.Sprintf("docker.pkg.github.com/frikky/shuffle/%s:%s", workerName, workerVersion)
|
||||
workerImage := fmt.Sprintf("%s:worker", baseimagename)
|
||||
workerImage := fmt.Sprintf("%s/worker:%s", baseimagename, workerVersion)
|
||||
|
||||
log.Printf("--- Finished configuring docker environment ---\n")
|
||||
log.Printf("[INFO] Finished configuring docker environment")
|
||||
|
||||
// FIXME - time limit
|
||||
client := &http.Client{
|
||||
@@ -293,10 +283,10 @@ func main() {
|
||||
client = &http.Client{}
|
||||
} else {
|
||||
if len(httpProxy) > 0 {
|
||||
log.Printf("Running with HTTP proxy %s (env: HTTP_PROXY)", httpProxy)
|
||||
log.Printf("[INFO] Running with HTTP proxy %s (env: HTTP_PROXY)", httpProxy)
|
||||
}
|
||||
if len(httpsProxy) > 0 {
|
||||
log.Printf("Running with HTTPS proxy %s (env: HTTPS_PROXY)", httpsProxy)
|
||||
log.Printf("[INFO] Running with HTTPS proxy %s (env: HTTPS_PROXY)", httpsProxy)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,21 +298,21 @@ func main() {
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed making request builder: %s", err)
|
||||
log.Printf("[ERROR] 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)
|
||||
log.Printf("[INFO] Waiting for executions at %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)
|
||||
log.Printf("[WARNING] Failed making request: %s", err)
|
||||
zombiecounter += 1
|
||||
if zombiecounter*sleepTime > workerTimeout {
|
||||
go zombiecheck()
|
||||
@@ -335,7 +325,7 @@ func main() {
|
||||
// FIXME - add check for StatusCode
|
||||
if newresp.StatusCode != 200 {
|
||||
if hasStarted {
|
||||
log.Printf("Bad statuscode: %d", newresp.StatusCode)
|
||||
log.Printf("[WARNING] Bad statuscode: %d", newresp.StatusCode)
|
||||
}
|
||||
} else {
|
||||
hasStarted = true
|
||||
@@ -343,7 +333,7 @@ func main() {
|
||||
|
||||
body, err := ioutil.ReadAll(newresp.Body)
|
||||
if err != nil {
|
||||
log.Printf("Failed reading body: %s", err)
|
||||
log.Printf("[ERROR] Failed reading body: %s", err)
|
||||
zombiecounter += 1
|
||||
if zombiecounter*sleepTime > workerTimeout {
|
||||
go zombiecheck()
|
||||
@@ -356,7 +346,7 @@ func main() {
|
||||
var executionRequests ExecutionRequestWrapper
|
||||
err = json.Unmarshal(body, &executionRequests)
|
||||
if err != nil {
|
||||
log.Printf("Failed executionrequest in queue unmarshaling: %s", err)
|
||||
log.Printf("[WARNING] Failed executionrequest in queue unmarshaling: %s", err)
|
||||
sleepTime = 10
|
||||
zombiecounter += 1
|
||||
if zombiecounter*sleepTime > workerTimeout {
|
||||
@@ -368,7 +358,7 @@ func main() {
|
||||
}
|
||||
|
||||
if hasStarted && len(executionRequests.Data) > 0 {
|
||||
log.Printf("Body: %s", string(body))
|
||||
log.Printf("[INFO] Body: %s", string(body))
|
||||
// Type string `json:"type"`
|
||||
}
|
||||
|
||||
@@ -386,16 +376,16 @@ func main() {
|
||||
var toBeRemoved ExecutionRequestWrapper
|
||||
for _, execution := range executionRequests.Data {
|
||||
if len(execution.ExecutionArgument) > 0 {
|
||||
log.Printf("Argument: %#v", execution.ExecutionArgument)
|
||||
log.Printf("[INFO] Argument: %#v", execution.ExecutionArgument)
|
||||
}
|
||||
|
||||
if execution.Type == "schedule" {
|
||||
log.Printf("SOMETHING ELSE :O: %s", execution.Type)
|
||||
log.Printf("[INFO] SOMETHING ELSE :O: %s", execution.Type)
|
||||
continue
|
||||
}
|
||||
|
||||
if execution.Status == "ABORT" || execution.Status == "FAILED" {
|
||||
log.Printf("Executionstatus issue: ", execution.Status)
|
||||
log.Printf("[INFO] 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.
|
||||
@@ -418,7 +408,7 @@ func main() {
|
||||
|
||||
go deployWorker(workerImage, containerName, env)
|
||||
|
||||
log.Printf("%s is deployed and to be removed from queue.", execution.ExecutionId)
|
||||
log.Printf("[INFO] %s is deployed and to be removed from queue.", execution.ExecutionId)
|
||||
zombiecounter += 1
|
||||
toBeRemoved.Data = append(toBeRemoved.Data, execution)
|
||||
}
|
||||
@@ -429,7 +419,7 @@ func main() {
|
||||
|
||||
data, err := json.Marshal(toBeRemoved)
|
||||
if err != nil {
|
||||
log.Printf("Failed removal marshalling: %s", err)
|
||||
log.Printf("[WARNING] Failed removal marshalling: %s", err)
|
||||
time.Sleep(time.Duration(sleepTime) * time.Second)
|
||||
continue
|
||||
}
|
||||
@@ -441,7 +431,7 @@ func main() {
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed building confirm request: %s", err)
|
||||
log.Printf("[ERROR] Failed building confirm request: %s", err)
|
||||
time.Sleep(time.Duration(sleepTime) * time.Second)
|
||||
continue
|
||||
}
|
||||
@@ -451,14 +441,14 @@ func main() {
|
||||
|
||||
resultResp, err := client.Do(result)
|
||||
if err != nil {
|
||||
log.Printf("Failed making confirm request: %s", err)
|
||||
log.Printf("[ERROR] 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)
|
||||
log.Printf("[ERROR] Failed reading confirm body: %s", err)
|
||||
time.Sleep(time.Duration(sleepTime) * time.Second)
|
||||
continue
|
||||
}
|
||||
@@ -472,7 +462,7 @@ func main() {
|
||||
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))
|
||||
log.Printf("[INFO] NOT IMPLEMENTED: Should remove %d workflows from backend because they're executed!", len(toBeRemoved.Data))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,7 +473,7 @@ func main() {
|
||||
// FIXME - add this to remove exited workers
|
||||
// Should it check what happened to the execution? idk
|
||||
func zombiecheck() error {
|
||||
log.Println("Looking for old containers")
|
||||
log.Println("[INFO] Looking for old containers")
|
||||
ctx := context.Background()
|
||||
|
||||
containers, err := dockercli.ContainerList(ctx, types.ContainerListOptions{
|
||||
@@ -491,7 +481,7 @@ func zombiecheck() error {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed creating Containerlist: %s", err)
|
||||
log.Printf("[ERROR] Failed creating Containerlist: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -524,7 +514,7 @@ func zombiecheck() error {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("NAME: %s", name)
|
||||
log.Printf("[INFO] NAME: %s", name)
|
||||
|
||||
// Need to check time here too because a container can be removed the same instant as its created
|
||||
currenttime := time.Now().Unix()
|
||||
@@ -544,7 +534,7 @@ func zombiecheck() error {
|
||||
|
||||
// FIXME - add killing of apps with same execution ID too
|
||||
for _, containername := range stopContainers {
|
||||
log.Printf("Stopping and removing container %s", containerNames[containername])
|
||||
log.Printf("[INFO] Stopping and removing container %s", containerNames[containername])
|
||||
go dockercli.ContainerStop(ctx, containername, nil)
|
||||
removeContainers = append(removeContainers, containername)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ docker run \
|
||||
--env ENVIRONMENT_NAME="Shuffle" \
|
||||
--env BASE_URL=http://shuffle-backend:5001 \
|
||||
--env DOCKER_API_VERSION=1.42 \
|
||||
--env RUNNING_MODE="Docker" \
|
||||
--network "shuffle_shuffle" \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
frikky/shuffle:orborus
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
#from golang as builder
|
||||
#
|
||||
#RUN mkdir /app
|
||||
#WORKDIR /app
|
||||
#COPY worker.go /app/worker.go
|
||||
#
|
||||
#RUN go get github.com/docker/docker/api/types
|
||||
#RUN go get github.com/docker/docker/api/types/container
|
||||
#RUN go get -u github.com/docker/docker/client
|
||||
#
|
||||
#RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker .
|
||||
#
|
||||
from golang as builder
|
||||
|
||||
# THis is a workaround until I get docker/docker to build in a dockerfile
|
||||
# PS: This is tricky to google.
|
||||
# Might not work on some machines.
|
||||
from scratch
|
||||
#COPY --from=builder /app/ /
|
||||
COPY worker.bin /worker.bin
|
||||
WORKDIR /app
|
||||
|
||||
CMD ["./worker.bin"]
|
||||
RUN go get -u github.com/docker/docker/api/types
|
||||
RUN go get -u github.com/docker/docker/api/types/container
|
||||
RUN go get -u github.com/docker/docker/client
|
||||
|
||||
COPY worker.go /app/worker.go
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker .
|
||||
|
||||
FROM alpine:3.12
|
||||
RUN apk add --no-cache bash
|
||||
COPY --from=builder /app/ /
|
||||
|
||||
CMD ["./worker"]
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
NAME=worker
|
||||
VERSION=0.1.0
|
||||
VERSION=0.6.0
|
||||
|
||||
echo "Running docker build with $NAME:$VERSION"
|
||||
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker.bin .
|
||||
docker build . -t frikky/shuffle:$NAME -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t frikky/$NAME:$VERSION
|
||||
docker build . -t frikky/shuffle:$NAME -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
||||
|
||||
# Push both for now..
|
||||
docker push frikky/$NAME:$VERSION
|
||||
docker push frikky/shuffle:$NAME
|
||||
#docker push frikky/$NAME:$VERSION
|
||||
#docker push frikky/shuffle:$NAME
|
||||
#docker push docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION
|
||||
docker push ghcr.io/frikky/$NAME:$VERSION
|
||||
|
||||
@@ -11,12 +11,12 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
network "github.com/docker/docker/api/types/network"
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
@@ -25,6 +25,29 @@ var baseUrl = os.Getenv("BASE_URL")
|
||||
var baseimagename = "frikky/shuffle"
|
||||
var sleepTime = 2
|
||||
|
||||
var containerId string
|
||||
|
||||
// form container id of current running container
|
||||
func getThisContainerId() string {
|
||||
id := ""
|
||||
cmd := fmt.Sprintf("cat /proc/self/cgroup | grep memory | tail -1 | cut -d/ -f3")
|
||||
out, err := exec.Command("bash", "-c", cmd).Output()
|
||||
if err == nil {
|
||||
id = strings.TrimSpace(string(out))
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func init() {
|
||||
containerId = getThisContainerId()
|
||||
if len(containerId) == 0 {
|
||||
log.Printf("[ERROR] No container ID found.")
|
||||
} else {
|
||||
log.Printf("[INFO] Found container ID: %s", containerId)
|
||||
}
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Username string `datastore:"Username" json:"username"`
|
||||
Password string `datastore:"password,noindex" password:"password,omitempty"`
|
||||
@@ -336,7 +359,7 @@ type ExecutionRequestWrapper struct {
|
||||
func shutdown(executionId, workflowId string) {
|
||||
dockercli, err := dockerclient.NewEnvClient()
|
||||
if err != nil {
|
||||
log.Printf("Unable to create docker client: %s", err)
|
||||
log.Printf("[ERROR] Unable to create docker client: %s", err)
|
||||
os.Exit(3)
|
||||
}
|
||||
|
||||
@@ -373,18 +396,18 @@ func shutdown(executionId, workflowId string) {
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Println("Failed building request: %s", err)
|
||||
log.Println("[INFO] Failed building request: %s", err)
|
||||
}
|
||||
|
||||
// FIXME: Add an API call to the backend
|
||||
authorization := os.Getenv("AUTHORIZATION")
|
||||
if len(authorization) > 0 {
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", authorization))
|
||||
} else {
|
||||
log.Printf("[ERROR] No authorization specified for abort")
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
//req.Header.Add("Authorization", authorization)
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: nil,
|
||||
@@ -397,23 +420,24 @@ func shutdown(executionId, workflowId string) {
|
||||
client = &http.Client{}
|
||||
} else {
|
||||
if len(httpProxy) > 0 {
|
||||
log.Printf("Running with HTTP proxy %s (env: HTTP_PROXY)", httpProxy)
|
||||
log.Printf("[INFO] Running with HTTP proxy %s (env: HTTP_PROXY)", httpProxy)
|
||||
}
|
||||
if len(httpsProxy) > 0 {
|
||||
log.Printf("Running with HTTPS proxy %s (env: HTTPS_PROXY)", httpsProxy)
|
||||
log.Printf("[INFO] Running with HTTPS proxy %s (env: HTTPS_PROXY)", httpsProxy)
|
||||
}
|
||||
}
|
||||
_, err = client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("Failed abort request: %s", err)
|
||||
log.Printf("[INFO] Failed abort request: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("Finished shutdown.")
|
||||
log.Printf("[INFO] Finished shutdown.")
|
||||
os.Exit(3)
|
||||
}
|
||||
|
||||
// Deploys the internal worker whenever something happens
|
||||
func deployApp(cli *dockerclient.Client, image string, identifier string, env []string) error {
|
||||
// form basic hostConfig
|
||||
hostConfig := &container.HostConfig{
|
||||
LogConfig: container.LogConfig{
|
||||
Type: "json-file",
|
||||
@@ -421,40 +445,34 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env []
|
||||
},
|
||||
}
|
||||
|
||||
// form container id and use it as network source if it's not empty
|
||||
if containerId != "" {
|
||||
hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId))
|
||||
} else {
|
||||
log.Printf("[WARNING] Empty self container id, continue without NetworkMode")
|
||||
}
|
||||
|
||||
config := &container.Config{
|
||||
Image: image,
|
||||
Env: env,
|
||||
}
|
||||
|
||||
networkConfig := &network.NetworkingConfig{}
|
||||
shuffleNetwork := os.Getenv("DOCKER_NETWORK")
|
||||
if len(shuffleNetwork) > 0 {
|
||||
networkConfig = &network.NetworkingConfig{
|
||||
EndpointsConfig: map[string]*network.EndpointSettings{
|
||||
shuffleNetwork: {
|
||||
NetworkID: shuffleNetwork,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
cont, err := cli.ContainerCreate(
|
||||
context.Background(),
|
||||
config,
|
||||
hostConfig,
|
||||
networkConfig,
|
||||
nil,
|
||||
nil,
|
||||
identifier,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
log.Printf("Container error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
cli.ContainerStart(context.Background(), cont.ID, types.ContainerStartOptions{})
|
||||
fmt.Printf("\n")
|
||||
log.Printf("Container %s is created", cont.ID)
|
||||
log.Printf("[INFO] Container %s is created", cont.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -463,7 +481,7 @@ func removeContainer(containername string) error {
|
||||
|
||||
cli, err := dockerclient.NewEnvClient()
|
||||
if err != nil {
|
||||
log.Printf("Unable to create docker client: %s", err)
|
||||
log.Printf("[INFO] Unable to create docker client: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -882,9 +900,11 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W
|
||||
|
||||
err = deployApp(dockercli, image, identifier, env)
|
||||
if err != nil {
|
||||
log.Printf("Failed deploying %s from image %s: %s", identifier, image, err)
|
||||
log.Printf("Should send status and exit the entire thing?")
|
||||
//shutdown(workflowExecution.ExecutionId, workflowExecution.Workflow.ID)
|
||||
log.Printf("[ERROR] Failed deploying %s from image %s: %s", identifier, image, err)
|
||||
if strings.Contains(err.Error(), "No such image") {
|
||||
log.Printf("[ERROR] Image doesn't exist. Shutting down")
|
||||
shutdown(workflowExecution.ExecutionId, workflowExecution.Workflow.ID)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Adding visited (3): %s", action.Label)
|
||||
@@ -1072,7 +1092,7 @@ func runTestExecution(client *http.Client, workflowId, apikey string) (string, s
|
||||
return "", ""
|
||||
}
|
||||
|
||||
log.Printf("Body: %s", string(body))
|
||||
log.Printf("[INFO] Body: %s", string(body))
|
||||
var workflowExecution WorkflowExecution
|
||||
err = json.Unmarshal(body, &workflowExecution)
|
||||
if err != nil {
|
||||
@@ -1085,7 +1105,7 @@ func runTestExecution(client *http.Client, workflowId, apikey string) (string, s
|
||||
|
||||
// Initial loop etc
|
||||
func main() {
|
||||
log.Printf("Setting up worker environment")
|
||||
log.Printf("[INFO] Setting up worker environment")
|
||||
sleepTime := 5
|
||||
|
||||
client := &http.Client{
|
||||
@@ -1107,13 +1127,6 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
shuffleNetwork := os.Getenv("DOCKER_NETWORK")
|
||||
if len(shuffleNetwork) > 0 {
|
||||
log.Printf("Running with Docker network %s", shuffleNetwork)
|
||||
} else {
|
||||
log.Printf("No docker network specified for Worker.")
|
||||
}
|
||||
|
||||
// WORKER_TESTING_WORKFLOW should be a workflow ID
|
||||
authorization := ""
|
||||
executionId := ""
|
||||
@@ -1121,7 +1134,7 @@ func main() {
|
||||
shuffle_apikey := os.Getenv("WORKER_TESTING_APIKEY")
|
||||
if len(testing) > 0 && len(shuffle_apikey) > 0 {
|
||||
// Execute a workflow and use that info
|
||||
log.Printf("!! Running test environment for worker by executing workflow %s", testing)
|
||||
log.Printf("[WARNING] Running test environment for worker by executing workflow %s", testing)
|
||||
authorization, executionId = runTestExecution(client, testing, shuffle_apikey)
|
||||
|
||||
//os.Exit(3)
|
||||
@@ -1132,12 +1145,12 @@ func main() {
|
||||
}
|
||||
|
||||
if len(authorization) == 0 {
|
||||
log.Println("No AUTHORIZATION key set in env")
|
||||
log.Println("[INFO] No AUTHORIZATION key set in env")
|
||||
shutdown(executionId, "")
|
||||
}
|
||||
|
||||
if len(executionId) == 0 {
|
||||
log.Println("No EXECUTIONID key set in env")
|
||||
log.Println("[INFO] No EXECUTIONID key set in env")
|
||||
shutdown(executionId, "")
|
||||
}
|
||||
|
||||
@@ -1151,7 +1164,7 @@ func main() {
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Println("Failed making request builder")
|
||||
log.Println("[ERROR] Failed making request builder for backend")
|
||||
shutdown(executionId, "")
|
||||
}
|
||||
|
||||
@@ -1160,20 +1173,20 @@ func main() {
|
||||
// Removed request requirement from app_sdk
|
||||
newresp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("Failed request: %s", err)
|
||||
log.Printf("[ERROR] Failed request: %s", err)
|
||||
time.Sleep(time.Duration(sleepTime) * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(newresp.Body)
|
||||
if err != nil {
|
||||
log.Printf("Failed reading body: %s", err)
|
||||
log.Printf("[ERROR] Failed reading body: %s", err)
|
||||
time.Sleep(time.Duration(sleepTime) * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
if newresp.StatusCode != 200 {
|
||||
log.Printf("Err: %s\nStatusCode: %d", string(body), newresp.StatusCode)
|
||||
log.Printf("[ERROR] %s\nStatusCode: %d", string(body), newresp.StatusCode)
|
||||
time.Sleep(time.Duration(sleepTime) * time.Second)
|
||||
continue
|
||||
}
|
||||
@@ -1181,13 +1194,13 @@ func main() {
|
||||
var workflowExecution WorkflowExecution
|
||||
err = json.Unmarshal(body, &workflowExecution)
|
||||
if err != nil {
|
||||
log.Printf("Failed workflowExecution unmarshal: %s", err)
|
||||
log.Printf("[ERROR] Failed workflowExecution unmarshal: %s", err)
|
||||
time.Sleep(time.Duration(sleepTime) * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
if workflowExecution.Status == "FINISHED" || workflowExecution.Status == "SUCCESS" {
|
||||
log.Printf("Workflow %s is finished. Exiting worker.", workflowExecution.ExecutionId)
|
||||
log.Printf("[INFO] Workflow %s is finished. Exiting worker.", workflowExecution.ExecutionId)
|
||||
shutdown(executionId, workflowExecution.Workflow.ID)
|
||||
}
|
||||
|
||||
@@ -1195,15 +1208,14 @@ func main() {
|
||||
//log.Printf("Status: %s", workflowExecution.Status)
|
||||
err = handleExecution(client, req, workflowExecution)
|
||||
if err != nil {
|
||||
log.Printf("Workflow %s is finished: %s", workflowExecution.ExecutionId, err)
|
||||
log.Printf("[INFO] Workflow %s is finished: %s", workflowExecution.ExecutionId, err)
|
||||
shutdown(executionId, workflowExecution.Workflow.ID)
|
||||
}
|
||||
} else {
|
||||
log.Printf("Workflow %s has status %s. Exiting worker.", workflowExecution.ExecutionId, workflowExecution.Status)
|
||||
log.Printf("[INFO] Workflow %s has status %s. Exiting worker.", workflowExecution.ExecutionId, workflowExecution.Status)
|
||||
shutdown(executionId, workflowExecution.Workflow.ID)
|
||||
}
|
||||
|
||||
//log.Println(string(body))
|
||||
time.Sleep(time.Duration(sleepTime) * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,15 +101,15 @@ func getRunner(classname string) string {
|
||||
return fmt.Sprintf(`
|
||||
# Run the actual thing after we've checked params
|
||||
def run(request):
|
||||
action = request.get_json()
|
||||
action = request.get_json()
|
||||
print(action)
|
||||
print(type(action))
|
||||
authorization_key = action.get("authorization")
|
||||
current_execution_id = action.get("execution_id")
|
||||
|
||||
|
||||
if action and "name" in action and "app_name" in action:
|
||||
asyncio.run(%s.run(action), debug=True)
|
||||
return f'Attempting to execute function {action["name"]} in app {action["app_name"]}'
|
||||
return f'Attempting to execute function {action["name"]} in app {action["app_name"]}'
|
||||
else:
|
||||
return f'Invalid action'
|
||||
|
||||
@@ -610,6 +610,7 @@ func buildImage(client *client.Client, tags []string, dockerBuildCtxDir string)
|
||||
PullParent: true,
|
||||
Remove: true,
|
||||
Tags: tags,
|
||||
NetworkMode: "host",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user