Merge branch 'launch' into launch

This commit is contained in:
Frikky
2021-10-31 22:23:06 +01:00
committed by GitHub
24 changed files with 929 additions and 2659 deletions
+19
View File
@@ -0,0 +1,19 @@
# Security Policy
## Supported Versions
Shuffle is currently still in beta, but we aim to support older version with critical severity issues, but do advise you to stay up to date with Major versions.
| Version | Supported |
| ------- | ------------------ |
| >=0.9.0 | :white_check_mark: |
| < 0.9.0 | :x: |
## Reporting a Vulnerability
Reporting a vulnerability can either be done to (frikky@shuffler.io)[mailto:frikky@shuffler.io] or [through the contact page on our website](https://shuffler.io/contact)
Security.txt: https://shuffler.io/.well_known/security.txt
When a >medium severity vulnerability is discovered, expect it to be fixed ASAP - please nag us until it is. Security is a top priority, and we expect you to keep us accountable.
In the case it makes sense, we'll further create a security advisory, and publish a new CVE for your new glorious finding.
+176 -55
View File
@@ -4,16 +4,18 @@ import sys
import re import re
import time import time
import json import json
import liquid
import logging import logging
import requests
import urllib.parse
import http.client
import urllib3 import urllib3
import hashlib import hashlib
from liquid import Liquid
import liquid
import zipfile import zipfile
import requests
import http.client
import urllib.parse
from io import BytesIO from io import BytesIO
from liquid import Liquid
runtime = os.getenv("SHUFFLE_SWARM_CONFIG", "")
class AppBase: class AppBase:
__version__ = None __version__ = None
@@ -27,7 +29,7 @@ class AppBase:
# apikey is for the user / org # apikey is for the user / org
# authorization is for the specific workflow # authorization is for the specific workflow
self.url = os.getenv("CALLBACK_URL", "https://shuffler.io") self.url = os.getenv("CALLBACK_URL", "https://shuffler.io")
self.base_url = os.getenv("BASE_URL", "https://shuffler.io") self.base_url = os.getenv("BASE_URL", "https://shuffler.io")
self.action = os.getenv("ACTION", "") self.action = os.getenv("ACTION", "")
self.original_action = os.getenv("ACTION", "") self.original_action = os.getenv("ACTION", "")
@@ -51,8 +53,10 @@ class AppBase:
try: try:
self.action = json.loads(self.action) self.action = json.loads(self.action)
self.original_action = json.loads(self.action) self.original_action = json.loads(self.action)
except: except Exception as e:
self.logger.info("[WARNING] Failed parsing action as JSON") self.logger.info(f"[WARNING] Failed parsing action as JSON (init): {e}. NOT important if running apps with webserver")
#print(f"ACTION: {self.action}")
if len(self.base_url) == 0: if len(self.base_url) == 0:
self.base_url = self.url self.base_url = self.url
@@ -80,14 +84,14 @@ class AppBase:
# I wonder if this actually works # I wonder if this actually works
self.logger.info(f"[DEBUG] Before last stream result") self.logger.info(f"[DEBUG] Before last stream result")
url = "%s%s" % (self.base_url, stream_path) url = "%s%s" % (self.base_url, stream_path)
#self.logger.info("[INFO] URL (URL): %s" % url) self.logger.info("[INFO] URL FOR RESULT (URL): %s" % url)
try: try:
ret = requests.post(url, headers=headers, json=action_result) ret = requests.post(url, headers=headers, json=action_result)
#self.logger.info(f"[DEBUG] Result: {ret.status_code}") #self.logger.info(f"[DEBUG] Result: {ret.status_code}")
#if ret.status_code != 200: #if ret.status_code != 200:
# self.logger.info(f"[DEBUG] Shuffle Response: {ret.text}") # self.logger.info(f"[DEBUG] Shuffle Response: {ret.text}")
self.logger.info(f"[DEBUG] Successful request: Status= {ret.status_code} & Response= {ret.text}") self.logger.info(f"""[DEBUG] Successful request result request: Status= {ret.status_code} & Response= {ret.text}. Action status: {action_result["status"]}""")
except requests.exceptions.ConnectionError as e: except requests.exceptions.ConnectionError as e:
self.logger.info(f"[DEBUG] Unexpected ConnectionError happened: {e}") self.logger.info(f"[DEBUG] Unexpected ConnectionError happened: {e}")
return return
@@ -520,8 +524,10 @@ class AppBase:
} }
self.send_result(self.action_result, {"Content-Type": "application/json", "Authorization": "Bearer %s" % self.authorization}, "/api/v1/streams") self.send_result(self.action_result, {"Content-Type": "application/json", "Authorization": "Bearer %s" % self.authorization}, "/api/v1/streams")
exit() if runtime != "run":
#return exit()
else:
return
else: else:
#subparams = new_params #subparams = new_params
#self.logger.info(f"NEW PARAMS: {new_params}") #self.logger.info(f"NEW PARAMS: {new_params}")
@@ -849,8 +855,8 @@ class AppBase:
self.logger.info("IDS TO RETURN: %s" % file_ids) self.logger.info("IDS TO RETURN: %s" % file_ids)
return file_ids return file_ids
async def execute_action(self, action): #async def execute_action(self, action):
def execute_action(self, action):
# !!! Let this line stay - its used for some horrible codegeneration / stitching !!! # # !!! Let this line stay - its used for some horrible codegeneration / stitching !!! #
#STARTCOPY #STARTCOPY
stream_path = "/api/v1/streams" stream_path = "/api/v1/streams"
@@ -880,19 +886,19 @@ class AppBase:
} }
if len(self.action) == 0: if len(self.action) == 0:
self.logger.info("ACTION env not defined") self.logger.info("[WARNING] ACTION env not defined")
self.action_result["result"] = "Error in setup ENV: ACTION not defined" self.action_result["result"] = "Error in setup ENV: ACTION not defined"
self.send_result(self.action_result, headers, stream_path) self.send_result(self.action_result, headers, stream_path)
return return
if len(self.authorization) == 0: if len(self.authorization) == 0:
self.logger.info("AUTHORIZATION env not defined") self.logger.info("[WARING] AUTHORIZATION env not defined")
self.action_result["result"] = "Error in setup ENV: AUTHORIZATION not defined" self.action_result["result"] = "Error in setup ENV: AUTHORIZATION not defined"
self.send_result(self.action_result, headers, stream_path) self.send_result(self.action_result, headers, stream_path)
return return
if len(self.current_execution_id) == 0: if len(self.current_execution_id) == 0:
self.logger.info("EXECUTIONID env not defined") self.logger.info("[WARNING] EXECUTIONID env not defined")
self.action_result["result"] = "Error in setup ENV: EXECUTIONID not defined" self.action_result["result"] = "Error in setup ENV: EXECUTIONID not defined"
self.send_result(self.action_result, headers, stream_path) self.send_result(self.action_result, headers, stream_path)
return return
@@ -918,7 +924,7 @@ class AppBase:
# Verify whether there are any parameters with ACTION_RESULT required # Verify whether there are any parameters with ACTION_RESULT required
# If found, we get the full results list from backend # If found, we get the full results list from backend
fullexecution = {} fullexecution = {}
if len(self.full_execution) == 0: if isinstance(self.full_execution, str) and len(self.full_execution) == 0:
self.logger.info("[DEBUG] NO EXECUTION - LOADING!") self.logger.info("[DEBUG] NO EXECUTION - LOADING!")
try: try:
tmpdata = { tmpdata = {
@@ -937,8 +943,8 @@ class AppBase:
fullexecution = ret.json() fullexecution = ret.json()
else: else:
try: try:
self.logger.info("Error: Data: ", ret.json()) self.logger.info("[DEBUG] Error: Data: ", ret.json())
self.logger.info("Error with status code for results. Crashing because ACTION_RESULTS or WORKFLOW_VARIABLE can't be handled. Status: %d" % ret.status_code) self.logger.info("[DEBUG] Error with status code for results. Crashing because ACTION_RESULTS or WORKFLOW_VARIABLE can't be handled. Status: %d" % ret.status_code)
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
pass pass
@@ -946,11 +952,12 @@ class AppBase:
self.send_result(self.action_result, headers, stream_path) self.send_result(self.action_result, headers, stream_path)
return return
except requests.exceptions.ConnectionError as e: except requests.exceptions.ConnectionError as e:
self.logger.info("Connectionerror: %s" % e) self.logger.info("[DEBUG] FullExec Connectionerror: %s" % e)
self.action_result["result"] = "Connection error during startup: %s" % e self.action_result["result"] = "Connection error during startup: %s" % e
self.send_result(self.action_result, headers, stream_path) self.send_result(self.action_result, headers, stream_path)
return return
else: else:
self.logger.info(f"[DEBUG] Setting execution to default value with type {type(self.full_execution)}")
try: try:
fullexecution = json.loads(self.full_execution) fullexecution = json.loads(self.full_execution)
except json.decoder.JSONDecodeError as e: except json.decoder.JSONDecodeError as e:
@@ -1645,7 +1652,11 @@ class AppBase:
self.send_result(self.action_result, headers, stream_path) self.send_result(self.action_result, headers, stream_path)
self.logger.info(f"[ERROR] Sent FAILURE response to backend due to : {e}") self.logger.info(f"[ERROR] Sent FAILURE response to backend due to : {e}")
os.exit()
if runtime == "run":
return template
else:
os.exit()
return template return template
@@ -2005,11 +2016,11 @@ class AppBase:
except KeyError: except KeyError:
continue continue
self.logger.info("Relevant conditions: %s" % branch["conditions"]) self.logger.info("[DEBUG] Relevant conditions: %s" % branch["conditions"])
successful_conditions = [] successful_conditions = []
failed_conditions = [] failed_conditions = []
for condition in branch["conditions"]: for condition in branch["conditions"]:
self.logger.info("Getting condition value of %s" % condition) self.logger.info("[DEBUG] Getting condition value of %s" % condition)
# Parse all values first here # Parse all values first here
sourcevalue = condition["source"]["value"] sourcevalue = condition["source"]["value"]
@@ -2105,7 +2116,7 @@ class AppBase:
if " " in actionname: if " " in actionname:
actionname.replace(" ", "_", -1) actionname.replace(" ", "_", -1)
#print(action)
#if action.generated: #if action.generated:
# actionname = actionname.lower() # actionname = actionname.lower()
@@ -2113,13 +2124,14 @@ class AppBase:
try: try:
func = getattr(self, actionname, None) func = getattr(self, actionname, None)
if func == None: if func == None:
self.logger.debug(f"Failed executing {actionname} because func is None.") self.logger.debug(f"[DEBUG] Failed executing {actionname} because func is None.")
self.action_result["status"] = "FAILURE" self.action_result["status"] = "FAILURE"
self.action_result["result"] = "Function %s doesn't exist." % actionname self.action_result["result"] = "Function %s doesn't exist." % actionname
elif callable(func): elif callable(func):
try: try:
if len(action["parameters"]) < 1: if len(action["parameters"]) < 1:
result = await func() #result = await func()
result = func()
else: else:
# Potentially parse JSON here # Potentially parse JSON here
# FIXME - add potential authentication as first parameter(s) here # FIXME - add potential authentication as first parameter(s) here
@@ -2460,7 +2472,7 @@ class AppBase:
# This part has fucked over so many random JSON usages because of weird paranthesis parsing # This part has fucked over so many random JSON usages because of weird paranthesis parsing
value = parse_wrapper_start(value, self) value = parse_wrapper_start(value, self)
self.logger.info("[DEBUG] Post return: %s" % value) #self.logger.info("[DEBUG] Post return: %s" % value)
#self.logger.info("POST data value: %s" % value) #self.logger.info("POST data value: %s" % value)
params[parameter["name"]] = value params[parameter["name"]] = value
@@ -2551,7 +2563,8 @@ class AppBase:
#newres = "" #newres = ""
while True: while True:
try: try:
newres = await func(**params) #newres = await func(**params)
newres = func(**params)
break break
except TypeError as e: except TypeError as e:
newres = "" newres = ""
@@ -2626,7 +2639,8 @@ class AppBase:
self.logger.info("[INFO] Running WITHOUT outer loop (looping)") self.logger.info("[INFO] Running WITHOUT outer loop (looping)")
json_object = False json_object = False
results = await self.run_recursed_items(func, multi_parameters, {}) #results = await self.run_recursed_items(func, multi_parameters, {})
results = self.run_recursed_items(func, multi_parameters, {})
if isinstance(results, dict) or isinstance(results, list): if isinstance(results, dict) or isinstance(results, list):
json_object = True json_object = True
@@ -2821,34 +2835,141 @@ class AppBase:
logger = logging.getLogger(f"{cls.__name__}") logger = logging.getLogger(f"{cls.__name__}")
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
#self.logger.info("Started execution: %s!!" % cls)
#self.logger.info("Action: %s" % action)
#if isinstance(cls, object):
# self.action = cls
app = cls(redis=None, logger=logger, console_logger=logger) ##############################################
if isinstance(action, str):
print("[DEBUG] Normal execution. Action is a string.")
elif isinstance(action, object):
print("[DEBUG] OBJECT execution. Action is NOT a string.")
app.action = action
try: exposed_port = os.getenv("SHUFFLE_APP_EXPOSED_PORT", "")
app.authorization = action["authorization"] logger.info(f"[DEBUG] \"{runtime}\" - run indicates microservices. Port: \"{exposed_port}\"")
app.current_execution_id = action["execution_id"] if runtime == "run" and exposed_port != "":
except: # Base port is 33334. Exposed port may differ based on discovery from Worker
pass port = int(exposed_port)
logger.info(f"[DEBUG] Starting webserver on port {port} (same as exposed port)")
from flask import Flask, request
from waitress import serve
import asyncio
flask_app = Flask(__name__)
@flask_app.route("/api/v1/run", methods=["POST"])
#async def execute():
def execute():
if request.method == "POST":
#print(request.get_json(force=True))
#print("DATA: ", request.data)
requestdata = {}
try:
requestdata = json.loads(request.data)
except Exception as e:
return {
"success": False,
"reason": f"Invalid Action data {e}",
}
#logger.info(f"[DEBUG] Datatype: {type(requestdata)}: {requestdata}")
try: # Remaking class for each request
app.url = action["url"] #print(f"APP: {app}")
except:
pass app = cls(redis=None, logger=logger, console_logger=logger)
try:
#asyncio.run(AppBase.run(action=requestdata), debug=True)
#value = json.dumps(value)
try:
app.full_execution = json.dumps(requestdata["workflow_execution"])
except Exception as e:
logger.info(f"Failed parsing full execution from workflow_execution: {e}")
try:
app.action = requestdata["action"]
except:
logger.info("Failed parsing action")
try: try:
app.base_url = action["base_url"] app.authorization = requestdata["authorization"]
except: app.current_execution_id = requestdata["execution_id"]
pass except:
logger.info("Failed parsing auth and exec id")
# BASE URL (backend)
try:
app.url = requestdata["url"]
logger.info(f"BACKEND URL: {app.url}")
except:
logger.info("Failed parsing url")
# URL (worker)
try:
app.base_url = requestdata["base_url"]
logger.info(f"WORKER URL: {app.base_url}")
except:
logger.info("Failed parsing base url")
#await
app.execute_action(app.action)
logger.info("\n\n[DEBUG] Done awaiting app action running\n\n")
except Exception as e:
return {
"success": False,
"reason": f"Problem in execution {e}",
}
return {
"success": True,
"reason": "App successfully finished",
}
else:
return {
"success": False,
"reason": f"HTTP method {request.method} not allowed",
}
logger.info(f"[DEBUG] Serving on port {port}")
serve(
flask_app,
host="0.0.0.0",
port=port,
threads=8,
channel_timeout=30,
expose_tracebacks=True,
asyncore_use_poll=True,
)
#######################
else: else:
self.logger.info("ACTION TYPE (unhandled): %s" % type(action)) # Has to start like this due to imports in other apps
# Move it outside everything?
app = cls(redis=None, logger=logger, console_logger=logger)
logger.info(f"[DEBUG] Action: {action}")
if isinstance(action, str):
logger.info("[DEBUG] Normal execution (env var). Action is a string.")
elif isinstance(action, object):
logger.info("[DEBUG] OBJECT execution (cloud). Action is NOT a string.")
app.action = action
await app.execute_action(app.action) try:
app.authorization = action["authorization"]
app.current_execution_id = action["execution_id"]
except:
pass
# BASE URL (worker)
try:
app.url = action["url"]
except:
pass
# Callback URL (backend)
try:
app.base_url = action["base_url"]
except:
pass
else:
self.logger.info("ACTION TYPE (unhandled): %s" % type(action))
#await app.execute_action(app.action)
app.execute_action(app.action)
#app.run(host="0.0.0.0", port=33334)
if __name__ == "__main__":
import asyncio
asyncio.run(AppBase.run(), debug=True)
+2 -1
View File
@@ -3,7 +3,7 @@
### DEFAULT ### DEFAULT
NAME=shuffle-app_sdk NAME=shuffle-app_sdk
VERSION=0.9.26 VERSION=0.9.30
docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force
docker build . -f Dockerfile -t frikky/shuffle:app_sdk -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION -t ghcr.io/frikky/$NAME:nightly docker build . -f Dockerfile -t frikky/shuffle:app_sdk -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION -t ghcr.io/frikky/$NAME:nightly
@@ -16,6 +16,7 @@ docker build . -f Dockerfile -t frikky/shuffle:app_sdk -t frikky/$NAME:$VERSION
docker push frikky/shuffle:app_sdk docker push frikky/shuffle:app_sdk
docker push ghcr.io/frikky/$NAME:$VERSION docker push ghcr.io/frikky/$NAME:$VERSION
docker push ghcr.io/frikky/$NAME:nightly docker push ghcr.io/frikky/$NAME:nightly
docker push ghcr.io/frikky/$NAME:latest
#### BLACKARCH ### #### BLACKARCH ###
NAME=shuffle-app_sdk_kali NAME=shuffle-app_sdk_kali
+3
View File
@@ -2,3 +2,6 @@ urllib3==1.26.5
requests==2.25.1 requests==2.25.1
MarkupSafe==2.0.1 MarkupSafe==2.0.1
liquidpy==0.7.2 liquidpy==0.7.2
flask[async]==2.0.2
waitress==2.0.0
#flask==1.1.2
+2 -2
View File
@@ -2,7 +2,7 @@ module main
go 1.15 go 1.15
replace github.com/shuffle/shuffle-shared => ../../../../git/shuffle-shared //replace github.com/shuffle/shuffle-shared => ../../../../git/shuffle-shared
//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi //replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
//replace github.com/frikky/go-elasticsearch => ../../../../git/go-elasticsearch //replace github.com/frikky/go-elasticsearch => ../../../../git/go-elasticsearch
@@ -21,7 +21,7 @@ require (
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/h2non/filetype v1.1.1 github.com/h2non/filetype v1.1.1
github.com/satori/go.uuid v1.2.0 github.com/satori/go.uuid v1.2.0
github.com/shuffle/shuffle-shared v0.1.18 github.com/shuffle/shuffle-shared v0.1.27
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
google.golang.org/api v0.58.0 google.golang.org/api v0.58.0
+3 -3
View File
@@ -3084,7 +3084,7 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User) {
basePath, err := shuffle.BuildStructure(swagger, newmd5) basePath, err := shuffle.BuildStructure(swagger, newmd5)
if err != nil { if err != nil {
log.Printf("Failed to build base structure: %s", err) log.Printf("[WARNING] Failed to build base structure: %s", err)
resp.WriteHeader(500) resp.WriteHeader(500)
resp.Write([]byte(`{"success": false, "reason": "Failed building baseline structure"}`)) resp.Write([]byte(`{"success": false, "reason": "Failed building baseline structure"}`))
return return
@@ -3103,7 +3103,7 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User) {
// Can't overwrite existing normal app // Can't overwrite existing normal app
workflowApps, err := shuffle.GetPrioritizedApps(ctx, user) workflowApps, err := shuffle.GetPrioritizedApps(ctx, user)
if err != nil { if err != nil {
log.Printf("Failed getting all workflow apps from database to verify: %s", err) log.Printf("[WARNING] Failed getting all workflow apps from database to verify: %s", err)
resp.WriteHeader(401) resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "reason": "Failed to verify existence"}`)) resp.Write([]byte(`{"success": false, "reason": "Failed to verify existence"}`))
return return
@@ -3123,7 +3123,7 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User) {
err = shuffle.DumpApi(basePath, api) err = shuffle.DumpApi(basePath, api)
if err != nil { if err != nil {
log.Printf("Failed dumping yaml: %s", err) log.Printf("[WARNING] Failed dumping yaml: %s", err)
resp.WriteHeader(500) resp.WriteHeader(500)
resp.Write([]byte(`{"success": false, "reason": "Failed dumping yaml"}`)) resp.Write([]byte(`{"success": false, "reason": "Failed dumping yaml"}`))
return return
+1 -2
View File
@@ -39,7 +39,6 @@ services:
#- opensearch #Not necessary because dependancy is handled within the backend itself instead #- opensearch #Not necessary because dependancy is handled within the backend itself instead
#- database #- database
orborus: orborus:
#build: ./functions/onprem/orborus
image: ghcr.io/frikky/shuffle-orborus:nightly image: ghcr.io/frikky/shuffle-orborus:nightly
container_name: shuffle-orborus container_name: shuffle-orborus
hostname: shuffle-orborus hostname: shuffle-orborus
@@ -49,7 +48,7 @@ services:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
environment: environment:
- SHUFFLE_APP_SDK_VERSION=0.8.97 - SHUFFLE_APP_SDK_VERSION=0.8.97
- SHUFFLE_WORKER_VERSION=latest - SHUFFLE_WORKER_VERSION=0.9.30
- ORG_ID=${ORG_ID} - ORG_ID=${ORG_ID}
- ENVIRONMENT_NAME=${ENVIRONMENT_NAME} - ENVIRONMENT_NAME=${ENVIRONMENT_NAME}
- BASE_URL=http://${OUTER_HOSTNAME}:${BACKEND_PORT} - BASE_URL=http://${OUTER_HOSTNAME}:${BACKEND_PORT}
+1 -1
View File
@@ -2320,7 +2320,7 @@ const Admin = (props) => {
: :
<Tooltip title={"Go to workflow"} style={{}} aria-label={"Download"}> <Tooltip title={"Go to workflow"} style={{}} aria-label={"Download"}>
<span> <span>
<a style={{textDecoration: "none", color: "#f85a3e"}} href={`/workflows/${file.workflow_id}`} target="_blank"> <a rel="noopener noreferrer" style={{textDecoration: "none", color: "#f85a3e"}} href={`/workflows/${file.workflow_id}`} target="_blank">
<IconButton disabled={file.workflow_id === "global"}> <IconButton disabled={file.workflow_id === "global"}>
<OpenInNewIcon style={{color: file.workflow_id !== "global" ? "white" : "grey",}} /> <OpenInNewIcon style={{color: file.workflow_id !== "global" ? "white" : "grey",}} />
</IconButton> </IconButton>
+2 -1
View File
@@ -1723,6 +1723,7 @@ const AngularWorkflow = (props) => {
} }
workflow.start = parentNode.data('id') workflow.start = parentNode.data('id')
setLastSaved(true)
parentNode.data('isStartNode', true) parentNode.data('isStartNode', true)
} }
@@ -7296,7 +7297,7 @@ const AngularWorkflow = (props) => {
} }
const executionModal = const executionModal =
<Drawer anchor={"right"} open={executionModalOpen} onClose={() => setExecutionModalOpen(false)} style={{resize: "both", overflow: "auto", zIndex: 10005}} PaperProps={{style: {resize: "both", overflow: "auto", minWidth: 400, maxWidth: 400, backgroundColor: "#1F2023", color: "white", fontSize: 18, zIndex: 10005}}}> <Drawer anchor={"right"} open={executionModalOpen} onClose={() => setExecutionModalOpen(false)} style={{resize: "both", overflow: "auto", zIndex: 10005}} PaperProps={{style: {resize: "both", overflow: "auto", minWidth: 420, maxWidth: 420, backgroundColor: "#1F2023", color: "white", fontSize: 18, zIndex: 10005}}}>
{executionModalView === 0 ? {executionModalView === 0 ?
<div style={{padding: 25, }}> <div style={{padding: 25, }}>
<Breadcrumbs aria-label="breadcrumb" separator="›" style={{color: "white", fontSize: 16}}> <Breadcrumbs aria-label="breadcrumb" separator="›" style={{color: "white", fontSize: 16}}>
+7 -1
View File
@@ -426,10 +426,16 @@ const AppCreator = (props) => {
return newitem return newitem
} }
const base64_decode = (str) => {
return decodeURIComponent(atob(str).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''))
}
// Sets the data up as it should be at later points // Sets the data up as it should be at later points
// This is the data FROM the database, not what's being saved // This is the data FROM the database, not what's being saved
const parseIncomingOpenapiData = (data) => { const parseIncomingOpenapiData = (data) => {
const parsedapp = data.openapi === undefined ? data : JSON.parse(atob(data.openapi)) const parsedapp = data.openapi === undefined ? data : JSON.parse(base64_decode(data.openapi))
data = parsedapp.body === undefined ? parsedapp : parsedapp.body data = parsedapp.body === undefined ? parsedapp : parsedapp.body
var jsonvalid = false var jsonvalid = false
+6 -6
View File
@@ -695,7 +695,7 @@ const Apps = (props) => {
</Select> </Select>
: null } : null }
{isCloud ? {isCloud ?
<a href={"https://shuffler.io/apps/"+selectedApp.id} style={{textDecoration: "none", color: "#f85a3e"}} target="_blank"> <a rel="noopener noreferrer" href={"https://shuffler.io/apps/"+selectedApp.id} style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">
<IconButton style={{top: -10, right: 0, position: "absolute", color: "#f85a3e"}} > <IconButton style={{top: -10, right: 0, position: "absolute", color: "#f85a3e"}} >
<OpenInNewIcon style={{}} /> <OpenInNewIcon style={{}} />
</IconButton> </IconButton>
@@ -848,10 +848,10 @@ const Apps = (props) => {
<Paper square style={uploadViewPaperStyle}> <Paper square style={uploadViewPaperStyle}>
<div style={{width: "100%", margin: 25}}> <div style={{width: "100%", margin: 25}}>
<h2>App Creator</h2> <h2>App Creator</h2>
<a href="https://shuffler.io/docs/apps" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">How it works</a> <a rel="noopener noreferrer" href="https://shuffler.io/docs/apps" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">How it works</a>
&nbsp;- <a href="https://github.com/frikky/security-openapis" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">Security API's</a> &nbsp;- <a rel="noopener noreferrer" href="https://github.com/frikky/security-openapis" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">Security API's</a>
&nbsp;- <a href="https://github.com/APIs-guru/openapi-directory/tree/main/APIs" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI directory</a> &nbsp;- <a rel="noopener noreferrer" href="https://github.com/APIs-guru/openapi-directory/tree/main/APIs" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI directory</a>
&nbsp;- <a href="https://editor.swagger.io/" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI Validator</a> &nbsp;- <a rel="noopener noreferrer" href="https://editor.swagger.io/" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI Validator</a>
<div/> <div/>
<Typography variant="body2" color="textSecondary"> <Typography variant="body2" color="textSecondary">
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 thousands of them. 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 thousands of them.
@@ -1061,7 +1061,7 @@ const Apps = (props) => {
<Paper square style={uploadViewPaperStyle}> <Paper square style={uploadViewPaperStyle}>
<Typography style={{margin: 10, }}> <Typography style={{margin: 10, }}>
<span> <span>
<a href={"https://shuffler.io/search"} style={{textDecoration: "none", color: "#f85a3e"}} target="_blank"> <a rel="noopener noreferrer" href={"https://shuffler.io/search"} style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">
Click here Click here
</a> to search ALL apps, not just your activated ones. </a> to search ALL apps, not just your activated ones.
</span> </span>
+2 -2
View File
@@ -304,7 +304,7 @@ const Docs = (props) => {
<div style={{flex: 3, display: "flex", vAlign: "center",}}> <div style={{flex: 3, display: "flex", vAlign: "center",}}>
{mobile ? null : {mobile ? null :
<Typography style={{display: "inline", marginTop: 6, }}> <Typography style={{display: "inline", marginTop: 6, }}>
<a rel="norefferer" target="_blank" href={selectedMeta.link} target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}> <a rel="noopener noreferrer" target="_blank" href={selectedMeta.link} target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>
<Button style={{}} variant="outlined"> <Button style={{}} variant="outlined">
<EditIcon /> &nbsp;&nbsp;Edit <EditIcon /> &nbsp;&nbsp;Edit
</Button> </Button>
@@ -323,7 +323,7 @@ const Docs = (props) => {
<div style={{margin: 10, height: "100%", display: "inline",}}> <div style={{margin: 10, height: "100%", display: "inline",}}>
{selectedMeta.contributors.slice(0,7).map((data, index) => { {selectedMeta.contributors.slice(0,7).map((data, index) => {
return ( return (
<a rel="norefferer" target="_blank" href={data.url} target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}> <a rel="noopener noreferrer" target="_blank" href={data.url} target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>
<Tooltip title={data.url} placement="bottom"> <Tooltip title={data.url} placement="bottom">
<img alt={data.url} src={data.image} style={{marginTop: 5, marginRight: 10, height: 40, borderRadius: 40, }} /> <img alt={data.url} src={data.image} style={{marginTop: 5, marginRight: 10, height: 40, borderRadius: 40, }} />
</Tooltip> </Tooltip>
+46 -235
View File
@@ -1,15 +1,15 @@
import React, { useEffect} from 'react'; import React, { useEffect} from 'react';
import { useInterval } from 'react-powerhooks';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import { useTheme } from '@material-ui/core/styles'; import { useTheme } from '@material-ui/core/styles';
import {Badge, Avatar, Grid, Paper, Tooltip, Divider, Button, TextField, FormControl, IconButton, Menu, MenuItem, FormControlLabel, Chip, Switch, Typography, Zoom, CircularProgress, Dialog, DialogTitle, DialogActions, DialogContent} from '@material-ui/core'; import {Badge, Avatar, Grid, Paper, Tooltip, Divider, Button, TextField, FormControl, IconButton, Menu, MenuItem, FormControlLabel, Chip, Switch, Typography, Zoom, CircularProgress, Dialog, DialogTitle, DialogActions, DialogContent} from '@material-ui/core';
import {GridOn as GridOnIcon, List as ListIcon, Close as CloseIcon, Compare as CompareIcon, Maximize as MaximizeIcon, Minimize as MinimizeIcon, AddCircle as AddCircleIcon, Toc as TocIcon, Send as SendIcon, Search as SearchIcon, FileCopy as FileCopyIcon, Delete as DeleteIcon, BubbleChart as BubbleChartIcon, Restore as RestoreIcon, Cached as CachedIcon, GetApp as GetAppIcon, Apps as AppsIcon, Edit as EditIcon, MoreVert as MoreVertIcon, PlayArrow as PlayArrowIcon, Add as AddIcon, Publish as PublishIcon, CloudUpload as CloudUploadIcon, CloudDownload as CloudDownloadIcon} from '@material-ui/icons'; import {GridOn as GridOnIcon, List as ListIcon, Close as CloseIcon, Compare as CompareIcon, Maximize as MaximizeIcon, Minimize as MinimizeIcon, AddCircle as AddCircleIcon, Toc as TocIcon, Send as SendIcon, Search as SearchIcon, FileCopy as FileCopyIcon, Delete as DeleteIcon, BubbleChart as BubbleChartIcon, Restore as RestoreIcon, Cached as CachedIcon, GetApp as GetAppIcon, Apps as AppsIcon, Edit as EditIcon, MoreVert as MoreVertIcon, PlayArrow as PlayArrowIcon, Add as AddIcon, Publish as PublishIcon, CloudUpload as CloudUploadIcon, CloudDownload as CloudDownloadIcon} from '@material-ui/icons';
import NestedMenuItem from "material-ui-nested-menu-item"; import NestedMenuItem from "material-ui-nested-menu-item";
//import {Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Done as DoneIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons'; //import {Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Done as DoneIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons';
//https://next.material-ui.com/components/material-icons/ //https://next.material-ui.com/components/material-icons/
import {DataGrid, GridToolbarContainer, GridDensitySelector, GridToolbar} from '@material-ui/data-grid'; import {DataGrid, GridToolbar} from '@material-ui/data-grid';
//import JSONPretty from 'react-json-pretty'; //import JSONPretty from 'react-json-pretty';
//import JSONPrettyMon from 'react-json-pretty/dist/monikai' //import JSONPrettyMon from 'react-json-pretty/dist/monikai'
@@ -19,33 +19,14 @@ import {Link} from 'react-router-dom';
import { useAlert } from "react-alert"; import { useAlert } from "react-alert";
import ChipInput from 'material-ui-chip-input' import ChipInput from 'material-ui-chip-input'
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import CytoscapeWrapper from '../components/RenderCytoscape'
//import mobileImage from '../assets/img/mobile.svg';
//import bagImage from '../assets/img/bag.svg';
//import bookImage from '../assets/img/book.svg';
const inputColor = "#383B40" const inputColor = "#383B40"
const surfaceColor = "#27292D" const surfaceColor = "#27292D"
const svgSize = 24 const svgSize = 24
const imagesize = 22 const imagesize = 22
const flexContainerStyle = {
display: "flex",
flexDirection: "row",
justifyContent: "left",
alignContent: "space-between",
}
const flexBoxStyle = {
height: 125,
borderRadius: 4,
boxSizing: "border-box",
letterSpacing: "0.4px",
color: "#D6791E",
margin: 10,
flex: 1,
}
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
datagrid: { datagrid: {
@@ -258,42 +239,22 @@ export const GetIconInfo = (action) => {
console.log(`MISSING PATH FOR ${selectedKey} (find in scope): `, selectedItem.originalIcon.type.type) console.log(`MISSING PATH FOR ${selectedKey} (find in scope): `, selectedItem.originalIcon.type.type)
} }
if (selectedItem.originalIcon === undefined || selectedItem.originalIcon === "" && (selectedItem.icon !== "" && selectedItem.icon !== undefined)) { if ((selectedItem.originalIcon === undefined || selectedItem.originalIcon === "") && (selectedItem.icon !== "" && selectedItem.icon !== undefined)) {
const svg_pin = <svg width={svgSize} height={svgSize} viewBox={`0 0 ${svgSize} ${svgSize}`} version="1.1" xmlns="http://www.w3.org/2000/svg"><path d={selectedItem.icon} fill={selectedItem.iconColor}></path></svg> const svg_pin = <svg width={svgSize} height={svgSize} viewBox={`0 0 ${svgSize} ${svgSize}`} version="1.1" xmlns="http://www.w3.org/2000/svg"><path d={selectedItem.icon} fill={selectedItem.iconColor}></path></svg>
//const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin)
selectedItem.originalIcon = svg_pin selectedItem.originalIcon = svg_pin
//parsedIcons[selectedKey].originalIcon = svg_pin
//console.log("ADDED ICON: ", svg_pin)
} }
return selectedItem return selectedItem
} }
//const activeWorkflowStyle = {backgroundColor: "#FFF5EE"}
//const notificationStyle = {backgroundColor: "#E5F9FF"}
//const activeWorkflowStyle = {backgroundColor: "#3d3f43"}
const availableWorkflowStyle = {backgroundColor: "#3d3f43"}
const notificationStyle = {backgroundColor: "#3d3f43"}
const activeWorkflowStyle = {backgroundColor: "#3d3f43"}
const fontSize_16 = {fontSize: "16px",}
const counterStyle = {fontSize: "36px",fontWeight:"bold"}
const blockRightStyle = {textAlign: "right",padding: "20px 20px 0px 0px",width:"100%"}
const chipStyle = { const chipStyle = {
backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white", backgroundColor: "#3d3f43", marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white",
} }
const flexContentStyle = {
display: "flex",
flexDirection: "row"
}
const iconStyle = {
width: "75px",
height: "75px",
padding: "20px"
}
export const validateJson = (showResult) => { export const validateJson = (showResult) => {
//showResult = showResult.split(" None").join(" \"None\"") //showResult = showResult.split(" None").join(" \"None\"")
@@ -302,15 +263,13 @@ export const validateJson = (showResult) => {
var jsonvalid = true var jsonvalid = true
try { try {
const tmp = String(JSON.parse(showResult))
if (!showResult.includes("{") && !showResult.includes("[")) { if (!showResult.includes("{") && !showResult.includes("[")) {
jsonvalid = false jsonvalid = false
} }
} catch (e) { } catch (e) {
showResult = showResult.split("\'").join("\"") showResult = showResult.split("'").join("\"")
try { try {
const tmp = String(JSON.parse(showResult))
if (!showResult.includes("{") && !showResult.includes("[")) { if (!showResult.includes("{") && !showResult.includes("[")) {
jsonvalid = false jsonvalid = false
} }
@@ -328,7 +287,7 @@ export const validateJson = (showResult) => {
} }
const Workflows = (props) => { const Workflows = (props) => {
const { globalUrl, isLoggedIn, isLoaded, removeCookie, cookies, userdata} = props; const { globalUrl, isLoggedIn, isLoaded, userdata} = props;
document.title = "Shuffle - Workflows" document.title = "Shuffle - Workflows"
const theme = useTheme(); const theme = useTheme();
const alert = useAlert() const alert = useAlert()
@@ -338,19 +297,14 @@ const Workflows = (props) => {
const referenceUrl = globalUrl+"/api/v1/hooks/" const referenceUrl = globalUrl+"/api/v1/hooks/"
var upload = "" var upload = ""
const [file, setFile] = React.useState("");
const [workflows, setWorkflows] = React.useState([]); const [workflows, setWorkflows] = React.useState([]);
const [filteredWorkflows, setFilteredWorkflows] = React.useState([]); const [filteredWorkflows, setFilteredWorkflows] = React.useState([]);
const [selectedWorkflow, setSelectedWorkflow] = React.useState({}); const [selectedWorkflow, setSelectedWorkflow] = React.useState({});
const [selectedExecution, setSelectedExecution] = React.useState({});
const [workflowExecutions, setWorkflowExecutions] = React.useState([]);
const [firstrequest, setFirstrequest] = React.useState(true) const [firstrequest, setFirstrequest] = React.useState(true)
const [workflowDone, setWorkflowDone] = React.useState(false) const [workflowDone, setWorkflowDone] = React.useState(false)
const [, setTrackingId] = React.useState("")
const [selectedWorkflowId, setSelectedWorkflowId] = React.useState("") const [selectedWorkflowId, setSelectedWorkflowId] = React.useState("")
const [collapseJson, setCollapseJson] = React.useState(false)
const [field1, setField1] = React.useState("") const [field1, setField1] = React.useState("")
const [field2, setField2] = React.useState("") const [field2, setField2] = React.useState("")
const [downloadUrl, setDownloadUrl] = React.useState("https://github.com/frikky/shuffle-workflows") const [downloadUrl, setDownloadUrl] = React.useState("https://github.com/frikky/shuffle-workflows")
@@ -364,14 +318,11 @@ const Workflows = (props) => {
const [newWorkflowDescription, setNewWorkflowDescription] = React.useState(""); const [newWorkflowDescription, setNewWorkflowDescription] = React.useState("");
const [newWorkflowTags, setNewWorkflowTags] = React.useState([]); const [newWorkflowTags, setNewWorkflowTags] = React.useState([]);
const [showExtraOptions, setShowExtraOptions] = React.useState(true);
const [defaultReturnValue, setDefaultReturnValue] = React.useState(""); const [defaultReturnValue, setDefaultReturnValue] = React.useState("");
const [update, setUpdate] = React.useState("test");
const [deleteModalOpen, setDeleteModalOpen] = React.useState(false); const [deleteModalOpen, setDeleteModalOpen] = React.useState(false);
const [publishModalOpen, setPublishModalOpen] = React.useState(false); const [publishModalOpen, setPublishModalOpen] = React.useState(false);
const [editingWorkflow, setEditingWorkflow] = React.useState({}) const [editingWorkflow, setEditingWorkflow] = React.useState({})
const [executionLoading, setExecutionLoading] = React.useState(false)
const [importLoading, setImportLoading] = React.useState(false) const [importLoading, setImportLoading] = React.useState(false)
const [isDropzone, setIsDropzone] = React.useState(false); const [isDropzone, setIsDropzone] = React.useState(false);
const [view, setView] = React.useState("grid") const [view, setView] = React.useState("grid")
@@ -422,8 +373,6 @@ const Workflows = (props) => {
}) })
} }
//console.log("FOUND: ", found)
//if (found) {
if (found.every(v => v === true)) { if (found.every(v => v === true)) {
newWorkflows.push(curWorkflow) newWorkflows.push(curWorkflow)
continue continue
@@ -463,9 +412,7 @@ const Workflows = (props) => {
} }
//console.log("Removing filter index", index)
newfilters.splice(index, 1) newfilters.splice(index, 1)
//console.log("FILTER LENGTH: ", filters.length)
if (newfilters.length === 0) { if (newfilters.length === 0) {
newfilters = [] newfilters = []
@@ -473,7 +420,6 @@ const Workflows = (props) => {
} else { } else {
setFilters(newfilters) setFilters(newfilters)
} }
//console.log("FILTERS: ", newfilters)
findWorkflow(newfilters) findWorkflow(newfilters)
} }
@@ -625,14 +571,14 @@ const Workflows = (props) => {
} }
// Initialize the workflow itself // Initialize the workflow itself
const ret = setNewWorkflow(data.name, data.description, data.tags, data.default_return_value, {}, false) setNewWorkflow(data.name, data.description, data.tags, data.default_return_value, {}, false)
.then((response) => { .then((response) => {
if (response !== undefined) { if (response !== undefined) {
// SET THE FULL THING // SET THE FULL THING
data.id = response.id data.id = response.id
// Actually create it // Actually create it
const ret = setNewWorkflow(data.name, data.description, data.tags, data.default_return_value, data, false) setNewWorkflow(data.name, data.description, data.tags, data.default_return_value, data, false)
.then((response) => { .then((response) => {
if (response !== undefined) { if (response !== undefined) {
alert.success(`Successfully imported ${data.name}`) alert.success(`Successfully imported ${data.name}`)
@@ -653,10 +599,10 @@ const Workflows = (props) => {
useEffect(() => { useEffect(() => {
if (isDropzone) { if (isDropzone) {
//redirectOpenApi();
setIsDropzone(false); setIsDropzone(false);
} }
}, [isDropzone]); }, [isDropzone]);
const getAvailableWorkflows = () => { const getAvailableWorkflows = () => {
fetch(globalUrl+"/api/v1/workflows", { fetch(globalUrl+"/api/v1/workflows", {
@@ -683,9 +629,6 @@ const Workflows = (props) => {
return response.json() return response.json()
}) })
.then((responseJson) => { .then((responseJson) => {
setSelectedExecution({})
setWorkflowExecutions([])
//console.log(responseJson)
if (responseJson !== undefined) { if (responseJson !== undefined) {
setWorkflows(responseJson) setWorkflows(responseJson)
@@ -720,16 +663,14 @@ const Workflows = (props) => {
return return
} }
if (responseJson.length > 0){
//setSelectedWorkflow(responseJson[0])
//getWorkflowExecution(responseJson[0].id)
}
}) })
.catch(error => { .catch(error => {
alert.error(error.toString()) alert.error(error.toString())
}); });
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => { useEffect(() => {
if (workflows.length <= 0 && firstrequest) { if (workflows.length <= 0 && firstrequest) {
const tmpView = localStorage.getItem('view'); const tmpView = localStorage.getItem('view');
@@ -740,7 +681,7 @@ const Workflows = (props) => {
setFirstrequest(false) setFirstrequest(false)
getAvailableWorkflows() getAvailableWorkflows()
} }
}) })
const viewStyle = { const viewStyle = {
color: "#ffffff", color: "#ffffff",
@@ -749,7 +690,6 @@ const Workflows = (props) => {
minWidth: 1024, minWidth: 1024,
maxWidth: 1024, maxWidth: 1024,
margin: "auto", margin: "auto",
/*maxHeight: "90vh",*/
} }
const emptyWorkflowStyle = { const emptyWorkflowStyle = {
@@ -769,14 +709,6 @@ const Workflows = (props) => {
} }
const scrollStyle = {
marginTop: "10px",
overflow: "scroll",
height: "90%",
overflowX: "hidden",
overflowY: "auto",
}
const paperAppContainer = { const paperAppContainer = {
display: "flex", display: "flex",
flexWrap: 'wrap', flexWrap: 'wrap',
@@ -813,38 +745,6 @@ const Workflows = (props) => {
justifyContent: "space-between", justifyContent: "space-between",
} }
const executeWorkflow = (id) => {
alert.show("Executing workflow "+id)
setTrackingId(id)
fetch(globalUrl+"/api/v1/workflows/"+id+"/execute", {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for WORKFLOW EXECUTION :O!")
}
return response.json()
})
.then((responseJson) => {
if (!responseJson.success) {
alert.error(responseJson.reason)
}
})
.catch(error => {
alert.error(error.toString())
});
}
function sleep (time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
const exportAllWorkflows = () => { const exportAllWorkflows = () => {
for (var key in workflows) { for (var key in workflows) {
exportWorkflow(workflows[key], false) exportWorkflow(workflows[key], false)
@@ -861,13 +761,12 @@ const Workflows = (props) => {
} }
} }
if (trigger.status == "running") { if (trigger.status === "running") {
trigger.status = "stopped" trigger.status = "stopped"
} }
const newId = uuidv4() const newId = uuidv4()
if (trigger.trigger_type === "WEBHOOK") { if (trigger.trigger_type === "WEBHOOK") {
const hookname = "webhook_"+newId
if (trigger.parameters !== undefined && trigger.parameters !== null && trigger.parameters.length === 2) { if (trigger.parameters !== undefined && trigger.parameters !== null && trigger.parameters.length === 2) {
trigger.parameters[0].value = referenceUrl+"webhook_"+trigger.id trigger.parameters[0].value = referenceUrl+"webhook_"+trigger.id
trigger.parameters[1].value = "webhook_"+trigger.id trigger.parameters[1].value = "webhook_"+trigger.id
@@ -897,7 +796,7 @@ const Workflows = (props) => {
} }
if (data.actions !== null && data.actions !== undefined) { if (data.actions !== null && data.actions !== undefined) {
for (var key in data.actions) { for (key in data.actions) {
data.actions[key].authentication_id = "" data.actions[key].authentication_id = ""
for (var subkey in data.actions[key].parameters) { for (var subkey in data.actions[key].parameters) {
@@ -914,7 +813,7 @@ const Workflows = (props) => {
} }
const newId = uuidv4() const newId = uuidv4()
for (var branchkey in data.branches) { for ( branchkey in data.branches) {
const branch = data.branches[branchkey] const branch = data.branches[branchkey]
if (branch.source_id === data.actions[key].id) { if (branch.source_id === data.actions[key].id) {
branch.source_id = newId branch.source_id = newId
@@ -929,14 +828,13 @@ const Workflows = (props) => {
data.start = newId data.start = newId
} }
//data.actions[key].environment = isCloud ? "cloud" : "Shuffle"
data.actions[key].environment = "" data.actions[key].environment = ""
data.actions[key].id = newId data.actions[key].id = newId
} }
} }
if (data.workflow_variables !== null && data.workflow_variables !== undefined) { if (data.workflow_variables !== null && data.workflow_variables !== undefined) {
for (var key in data.workflow_variables) { for (key in data.workflow_variables) {
const param = data.workflow_variables[key] const param = data.workflow_variables[key]
if (param.name.includes("key") || param.name.includes("user") || param.name.includes("pass") || param.name.includes("api") || param.name.includes("auth") || param.name.includes("secret") || param.name.includes("email")) { if (param.name.includes("key") || param.name.includes("user") || param.name.includes("pass") || param.name.includes("api") || param.name.includes("auth") || param.name.includes("secret") || param.name.includes("email")) {
param.value = "" param.value = ""
@@ -983,7 +881,6 @@ const Workflows = (props) => {
// } // }
//} //}
} }
//return
// Add correct ID's for triggers // Add correct ID's for triggers
// Add mag // Add mag
@@ -1042,8 +939,7 @@ const Workflows = (props) => {
data.id = "" data.id = ""
data.name = data.name+"_copy" data.name = data.name+"_copy"
data = deduplicateIds(data) data = deduplicateIds(data)
//console.log("COPIED DATA: ", data)
//return
fetch(globalUrl+"/api/v1/workflows", { fetch(globalUrl+"/api/v1/workflows", {
method: 'POST', method: 'POST',
@@ -1061,7 +957,7 @@ const Workflows = (props) => {
} }
return response.json() return response.json()
}) })
.then((responseJson) => { .then(() => {
setTimeout(() => { setTimeout(() => {
getAvailableWorkflows() getAvailableWorkflows()
}, 1000) }, 1000)
@@ -1091,7 +987,7 @@ const Workflows = (props) => {
return response.json() return response.json()
}) })
.then((responseJson) => { .then(() => {
setTimeout(() => { setTimeout(() => {
getAvailableWorkflows() getAvailableWorkflows()
}, 1000) }, 1000)
@@ -1107,7 +1003,7 @@ const Workflows = (props) => {
} }
const NewWorkflowPaper = (props) => { const NewWorkflowPaper = () => {
const [hover, setHover] = React.useState(false) const [hover, setHover] = React.useState(false)
const innerColor = "rgba(255,255,255,0.3)" const innerColor = "rgba(255,255,255,0.3)"
@@ -1143,10 +1039,7 @@ const Workflows = (props) => {
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const [anchorEl, setAnchorEl] = React.useState(null); const [anchorEl, setAnchorEl] = React.useState(null);
var boxWidth = "2px"
if (selectedWorkflow.id === data.id) {
boxWidth = "4px"
}
var boxColor = "#FECC00" var boxColor = "#FECC00"
if (data.is_valid) { if (data.is_valid) {
@@ -1168,8 +1061,7 @@ const Workflows = (props) => {
} }
const actions = data.actions !== null ? data.actions.length : 0 const actions = data.actions !== null ? data.actions.length : 0
const [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data) const [triggers, subflows] = getWorkflowMeta(data)
const workflowMenuButtons = <Menu const workflowMenuButtons = <Menu
id="long-menu" id="long-menu"
@@ -1182,7 +1074,6 @@ const Workflows = (props) => {
}} }}
> >
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => { <MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
//console.log("DATA:" ,data)
setModalOpen(true) setModalOpen(true)
setEditingWorkflow(data) setEditingWorkflow(data)
setNewWorkflowName(data.name) setNewWorkflowName(data.name)
@@ -1271,14 +1162,20 @@ const Workflows = (props) => {
const foundOrg = userdata.orgs.find(org => org.id === data["org_id"]) const foundOrg = userdata.orgs.find(org => org.id === data["org_id"])
if (foundOrg !== undefined && foundOrg !== null) { if (foundOrg !== undefined && foundOrg !== null) {
//position: "absolute", bottom: 5, right: -5, //position: "absolute", bottom: 5, right: -5,
const imageStyle = {width: imagesize, height: imagesize, pointerEvents: "none", marginRight: 10, marginLeft: data.creator_org !== undefined && data.creator_org.length > 0 ? 20 : 0, borderRadius: 10, border: foundOrg.id === userdata.active_org.id ? `3px solid ${boxColor}` : null, cursor: "pointer", marginRight: 10, } const imageStyle = {
width: imagesize,
height: imagesize,
pointerEvents: "none",
marginLeft: data.creator_org !== undefined && data.creator_org.length > 0 ? 20 : 0,
borderRadius: 10,
border: foundOrg.id === userdata.active_org.id ? `3px solid ${boxColor}` : null,
cursor: "pointer",
marginRight: 10 }
//<Tooltip title={`Org: ${foundOrg.name}`} placement="bottom">
image = foundOrg.image === "" ? image = foundOrg.image === "" ?
<img alt={foundOrg.name} src={theme.palette.defaultImage} style={imageStyle} /> <img alt={foundOrg.name} src={theme.palette.defaultImage} style={imageStyle} />
: :
<img alt={foundOrg.name} src={foundOrg.image} style={imageStyle} onClick={() => { <img alt={foundOrg.name} src={foundOrg.image} style={imageStyle} onClick={() => {
//setFilteredWorkflows(newWorkflows)
}}/> }}/>
orgName = foundOrg.name orgName = foundOrg.name
@@ -1295,8 +1192,6 @@ const Workflows = (props) => {
<Tooltip title={`Org "${orgName}"`} placement="bottom"> <Tooltip title={`Org "${orgName}"`} placement="bottom">
<div styl={{cursor: "pointer"}} onClick={() => { <div styl={{cursor: "pointer"}} onClick={() => {
addFilter(orgId) addFilter(orgId)
//setFilters(["Org "+orgName])
//setFilteredWorkflows(newWorkflows)
}}> }}>
{image} {image}
</div> </div>
@@ -1360,20 +1255,6 @@ const Workflows = (props) => {
</Typography> </Typography>
</span> </span>
</Tooltip> </Tooltip>
{/*
<Tooltip color="primary" title={`Actions: ${data.actions.length}`} placement="bottom">
<AppsIcon />
</Tooltip>
<Tooltip color="primary" title={`Webhooks: ${webhooks}`} placement="bottom">
<RestoreIcon />
</Tooltip>
: null}
{schedules > 0 ?
<Tooltip color="primary" title={`Schedules: ${schedules}`} placement="bottom">
<RestoreIcon />
</Tooltip>
: null}
*/}
</Grid> </Grid>
<Grid item style={{justifyContent: "left", overflow: "hidden", marginTop: 5,}}> <Grid item style={{justifyContent: "left", overflow: "hidden", marginTop: 5,}}>
{data.tags !== undefined ? {data.tags !== undefined ?
@@ -1404,23 +1285,13 @@ const Workflows = (props) => {
aria-label="more" aria-label="more"
aria-controls="long-menu" aria-controls="long-menu"
aria-haspopup="true" aria-haspopup="true"
style={{color: "white"}}
onClick={menuClick} onClick={menuClick}
style={{padding:"0px",color:"white", color:"#979797"}} style={{padding:"0px", color:"#979797"}}
> >
<MoreVertIcon /> <MoreVertIcon />
</IconButton> </IconButton>
{workflowMenuButtons} {workflowMenuButtons}
</Grid> </Grid>
{/*
<Grid>
<Link to={"/workflows/"+data.id}>
<Tooltip title="Edit workflow" placement="bottom">
<EditIcon style={{borderRadius: "4px", color: "#F85A3E", height: 20, width: 20, padding: 7, fontSize: "small"}} />
</Tooltip>
</Link>
</Grid>
*/}
</Grid> </Grid>
: null} : null}
</Paper> </Paper>
@@ -1430,29 +1301,6 @@ const Workflows = (props) => {
const dividerColor = "rgb(225, 228, 232)"
const resultPaperAppStyle = {
minHeight: "100px",
minWidth: "100%",
overflow: "hidden",
maxWidth: "100%",
marginTop: "5px",
color: "white",
backgroundColor: surfaceColor,
cursor: "pointer",
display: "flex",
}
function replaceAll(string, search, replace) {
return string.split(search).join(replace);
}
const resultsLength = Object.getOwnPropertyNames(selectedExecution).length > 0 && selectedExecution.results !== null ? selectedExecution.results.length : 0
// Can create and set workflows // Can create and set workflows
const setNewWorkflow = (name, description, tags, defaultReturnValue, editingWorkflow, redirect) => { const setNewWorkflow = (name, description, tags, defaultReturnValue, editingWorkflow, redirect) => {
@@ -1480,11 +1328,9 @@ const Workflows = (props) => {
if (defaultReturnValue !== undefined) { if (defaultReturnValue !== undefined) {
workflowdata["default_return_value"] = defaultReturnValue workflowdata["default_return_value"] = defaultReturnValue
//console.log("WORKFLOW: ", workflowdata)
} }
//console.log(workflowdata)
//return
return fetch(globalUrl+"/api/v1/workflows"+extraData, { return fetch(globalUrl+"/api/v1/workflows"+extraData, {
method: method, method: method,
@@ -1535,7 +1381,6 @@ const Workflows = (props) => {
console.log("Importing!") console.log("Importing!")
setImportLoading(true) setImportLoading(true)
const file = event.target.value
if (event.target.files.length > 0) { if (event.target.files.length > 0) {
for (var key in event.target.files) { for (var key in event.target.files) {
const file = event.target.files[key] const file = event.target.files[key]
@@ -1560,7 +1405,7 @@ const Workflows = (props) => {
} }
// Initialize the workflow itself // Initialize the workflow itself
const ret = setNewWorkflow(data.name, data.description, data.tags, data.default_return_value, {}, false) setNewWorkflow(data.name, data.description, data.tags, data.default_return_value, {}, false)
.then((response) => { .then((response) => {
if (response !== undefined) { if (response !== undefined) {
// SET THE FULL THING // SET THE FULL THING
@@ -1570,7 +1415,7 @@ const Workflows = (props) => {
data.is_valid = false data.is_valid = false
// Actually create it // Actually create it
const ret = setNewWorkflow(data.name, data.description, data.tags, data.default_return_value, data, false) setNewWorkflow(data.name, data.description, data.tags, data.default_return_value, data, false)
.then((response) => { .then((response) => {
if (response !== undefined) { if (response !== undefined) {
alert.success("Successfully imported "+data.name) alert.success("Successfully imported "+data.name)
@@ -1593,26 +1438,17 @@ const Workflows = (props) => {
const getWorkflowMeta = (data) => { const getWorkflowMeta = (data) => {
let triggers = 0 let triggers = 0
let schedules = 0
let webhooks = 0
let subflows = 0 let subflows = 0
if (data.triggers !== undefined && data.triggers !== null && data.triggers.length > 0) { if (data.triggers !== undefined && data.triggers !== null && data.triggers.length > 0) {
triggers = data.triggers.length triggers = data.triggers.length
for (let key in data.triggers) { for (let key in data.triggers) {
if (data.triggers[key].app_name === "Shuffle Workflow") {
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
} else if (data.triggers[key].app_name === "Shuffle Workflow") {
subflows += 1 subflows += 1
} }
} }
} }
return [triggers, schedules, webhooks, subflows] return [triggers, subflows]
} }
const WorkflowListView = () => { const WorkflowListView = () => {
@@ -1632,8 +1468,6 @@ const Workflows = (props) => {
} }
var image = "" var image = ""
var orgName = ""
var orgId = ""
if (userdata.orgs !== undefined) { if (userdata.orgs !== undefined) {
const foundOrg = userdata.orgs.find(org => org.id === data["org_id"]) const foundOrg = userdata.orgs.find(org => org.id === data["org_id"])
if (foundOrg !== undefined && foundOrg !== null) { if (foundOrg !== undefined && foundOrg !== null) {
@@ -1648,17 +1482,11 @@ const Workflows = (props) => {
//setFilteredWorkflows(newWorkflows) //setFilteredWorkflows(newWorkflows)
}}/> }}/>
orgName = foundOrg.name
orgId = foundOrg.id
} }
} }
return ( return (
<div styl={{cursor: "pointer"}} onClick={() => { <div styl={{cursor: "pointer"}} >
//addFilter(orgId)
//setFilters(["Org "+orgName])
//setFilteredWorkflows(newWorkflows)
}}>
{image} {image}
</div> </div>
) )
@@ -1676,21 +1504,13 @@ const Workflows = (props) => {
</Grid> </Grid>
) )
}}, }},
{/* field: 'category', headerName: 'category', width: 140, renderCell: (params) => {
const data = params.row.record
const category = data.category === undefined ? "not defined" : data.category
return (
<Typography>
{category}
</Typography>
)
} */},
{ field: 'options', headerName: 'Options', width: 200, sortable: false, { field: 'options', headerName: 'Options', width: 200, sortable: false,
disableClickEventBubbling: true, disableClickEventBubbling: true,
renderCell: (params) => { renderCell: (params) => {
const data = params.row.record; const data = params.row.record;
const actions = data.actions !== null ? data.actions.length : 0 const actions = data.actions !== null ? data.actions.length : 0
let [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data); let [triggers, subflows] = getWorkflowMeta(data);
return ( return (
<Grid item> <Grid item>
@@ -1889,10 +1709,9 @@ const Workflows = (props) => {
onDelete={(chip, index) => { onDelete={(chip, index) => {
newWorkflowTags.splice(index, 1) newWorkflowTags.splice(index, 1)
setNewWorkflowTags(newWorkflowTags) setNewWorkflowTags(newWorkflowTags)
setUpdate("delete "+chip)
}} }}
/> />
{showExtraOptions ?
<TextField <TextField
onBlur={(event) => setDefaultReturnValue(event.target.value)} onBlur={(event) => setDefaultReturnValue(event.target.value)}
InputProps={{ InputProps={{
@@ -1908,7 +1727,7 @@ const Workflows = (props) => {
margin="dense" margin="dense"
fullWidth fullWidth
/> />
: null}
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button style={{}} onClick={() => { <Button style={{}} onClick={() => {
@@ -1967,11 +1786,7 @@ const Workflows = (props) => {
const workflowButtons = const workflowButtons =
<span> <span>
{/*workflows.length > 0 ?
<Tooltip color="primary" title={"Create new workflow"} placement="top">
<Button color="primary" style={{}} variant="text" onClick={() => setModalOpen(true)}><AddIcon /></Button>
</Tooltip>
: null*/}
{view === "list" && ( {view === "list" && (
<Tooltip color="primary" title={"Grid View"} placement="top"> <Tooltip color="primary" title={"Grid View"} placement="top">
<Button color="primary" variant="text" onClick={() => { <Button color="primary" variant="text" onClick={() => {
@@ -2056,7 +1871,6 @@ const Workflows = (props) => {
<h2>Workflows</h2> <h2>Workflows</h2>
</div> </div>
</div> </div>
{/* {/*
<div style={flexContainerStyle}> <div style={flexContainerStyle}>
<div style={{...flexBoxStyle, ...activeWorkflowStyle}}> <div style={{...flexBoxStyle, ...activeWorkflowStyle}}>
@@ -2107,7 +1921,7 @@ const Workflows = (props) => {
<div style={{display: "flex", margin: "0px 0px 20px 0px"}}> <div style={{display: "flex", margin: "0px 0px 20px 0px"}}>
<div style={{flex: 1}}> <div style={{flex: 1}}>
<Typography style={{marginTop: 7, marginBottom: "auto"}}> <Typography style={{marginTop: 7, marginBottom: "auto"}}>
<a rel="norefferer" target="_blank" href="https://shuffler.io/docs/workflows" target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>Learn more about Workflows</a> <a rel="noopener noreferrer" href="https://shuffler.io/docs/workflows" target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>Learn more about Workflows</a>
</Typography> </Typography>
</div> </div>
<div style={{flex: 1, float: "right",}}> <div style={{flex: 1, float: "right",}}>
@@ -2116,7 +1930,6 @@ const Workflows = (props) => {
InputProps={{ InputProps={{
style:{ style:{
color: "white", color: "white",
//backgroundColor: inputColor,
}, },
}} }}
placeholder="Add Filter" placeholder="Add Filter"
@@ -2126,7 +1939,7 @@ const Workflows = (props) => {
onAdd={(chip) => { onAdd={(chip) => {
addFilter(chip) addFilter(chip)
}} }}
onDelete={(chip, index) => { onDelete={(_, index) => {
removeFilter(index) removeFilter(index)
}} }}
/> />
@@ -2202,7 +2015,6 @@ const Workflows = (props) => {
} }
alert.success("Getting specific workflows from your URL.") alert.success("Getting specific workflows from your URL.")
var cors = "cors"
fetch(globalUrl+"/api/v1/workflows/download_remote", { fetch(globalUrl+"/api/v1/workflows/download_remote", {
method: "POST", method: "POST",
mode: "cors", mode: "cors",
@@ -2223,7 +2035,6 @@ const Workflows = (props) => {
return response.json() return response.json()
}) })
.then((responseJson) => { .then((responseJson) => {
//console.log("DATA: ", responseJson)
if (!responseJson.success) { if (!responseJson.success) {
if (responseJson.reason !== undefined) { if (responseJson.reason !== undefined) {
alert.error("Failed loading: "+responseJson.reason) alert.error("Failed loading: "+responseJson.reason)
+1 -1
View File
@@ -11,7 +11,7 @@ RUN go get github.com/docker/docker/api/types && \
go get github.com/mackerelio/go-osstat/cpu && \ go get github.com/mackerelio/go-osstat/cpu && \
go get github.com/mackerelio/go-osstat/memory && \ go get github.com/mackerelio/go-osstat/memory && \
go get github.com/satori/go.uuid && \ go get github.com/satori/go.uuid && \
go get github.com/frikky/shuffle-shared go get github.com/shuffle/shuffle-shared
RUN go build RUN go build
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o orborus . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o orborus .
+1 -1
View File
@@ -1,5 +1,5 @@
NAME=shuffle-orborus NAME=shuffle-orborus
VERSION=0.9.28 VERSION=0.9.30
echo "Running docker build with $NAME:$VERSION" echo "Running docker build with $NAME:$VERSION"
#docker rmi frikky/shuffle:$NAME --force #docker rmi frikky/shuffle:$NAME --force
+2 -3
View File
@@ -4,10 +4,9 @@ go 1.15
require ( require (
github.com/containerd/containerd v1.5.7 // indirect github.com/containerd/containerd v1.5.7 // indirect
github.com/docker/docker v20.10.9+incompatible github.com/docker/docker v20.10.10+incompatible
github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-connections v0.4.0 // indirect
github.com/mackerelio/go-osstat v0.2.1 github.com/mackerelio/go-osstat v0.2.1
github.com/satori/go.uuid v1.2.0 github.com/satori/go.uuid v1.2.0
github.com/shuffle/shuffle-shared v0.1.19 github.com/shuffle/shuffle-shared v0.1.26
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
) )
File diff suppressed because it is too large Load Diff
+178 -79
View File
@@ -12,6 +12,7 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@@ -26,6 +27,7 @@ import (
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/mount"
//"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
//"github.com/docker/docker/api/types/filters" //"github.com/docker/docker/api/types/filters"
dockerclient "github.com/docker/docker/client" dockerclient "github.com/docker/docker/client"
@@ -106,11 +108,13 @@ func getThisContainerId() {
out, err := exec.Command("bash", "-c", cmd).Output() out, err := exec.Command("bash", "-c", cmd).Output()
if err == nil { if err == nil {
containerId = strings.TrimSpace(string(out)) containerId = strings.TrimSpace(string(out))
log.Printf("[DEBUG] Set containerId network to %s", containerId)
// cgroup error. Use fallback strategy below. // cgroup error. Use fallback strategy below.
// https://github.com/moby/moby/issues/7015 // https://github.com/moby/moby/issues/7015
//log.Printf("Checking if %s is in %s", ".scope", string(out)) //log.Printf("Checking if %s is in %s", ".scope", string(out))
if strings.Contains(string(out), ".scope") { if strings.Contains(string(out), ".scope") {
log.Printf("[DEBUG] ContainerId contains scope. setting to empty.")
containerId = "" containerId = ""
//docker-76c537e9a4b7c7233011f5d70e6b7f2d600b6413ac58a96519b8dca7a3f7117a.scope //docker-76c537e9a4b7c7233011f5d70e6b7f2d600b6413ac58a96519b8dca7a3f7117a.scope
} }
@@ -132,49 +136,64 @@ func getThisContainerId() {
log.Printf(`[INFO] Started with containerId "%s"`, containerId) log.Printf(`[INFO] Started with containerId "%s"`, containerId)
} }
// Deploys the internal worker whenever something happens func deployServiceWorkers(image string) {
// https://docs.docker.com/engine/api/sdk/examples/ log.Printf("[DEBUG] Validating deployment of workers as services IF swarmConfig = run (value: %#v)", swarmConfig)
func deployWorker(image string, identifier string, env []string, executionRequest shuffle.ExecutionRequest) {
// Binds is the actual "-v" volume.
// Max 20% CPU every second
//CPUQuota: 25000,
//CPUPeriod: 100000,
//CPUShares: 256,
hostConfig := &container.HostConfig{
LogConfig: container.LogConfig{
Type: "json-file",
Config: map[string]string{},
},
Resources: container.Resources{},
Binds: []string{
"/var/run/docker.sock:/var/run/docker.sock:rw",
},
NetworkMode: container.NetworkMode(fmt.Sprintf("container:%s", containerId)),
}
if cleanupEnv == "true" {
hostConfig.AutoRemove = true
}
config := &container.Config{
Image: image,
Env: env,
}
//var swarmConfig = os.Getenv("SHUFFLE_SWARM_CONFIG")
parsedUuid := uuid.NewV4()
if swarmConfig == "run" { if swarmConfig == "run" {
// frikky@debian:~/git/shuffle/functions/onprem/worker$ docker service create --replicas 5 --name shuffle-workers --env SHUFFLE_SWARM_CONFIG=run --publish published=33333,target=33333 ghcr.io/frikky/shuffle-worker:nightly // frikky@debian:~/git/shuffle/functions/onprem/worker$ docker service create --replicas 5 --name shuffle-workers --env SHUFFLE_SWARM_CONFIG=run --publish published=33333,target=33333 ghcr.io/frikky/shuffle-worker:nightly
networkName := "shuffle-executions"
ctx := context.Background()
//docker network create --driver=overlay workers
networkCreateOptions := types.NetworkCreate{
Driver: "overlay",
}
_, err := dockercli.NetworkCreate(
ctx,
networkName,
networkCreateOptions,
)
if err != nil {
if strings.Contains(fmt.Sprintf("%s", err), "already exists") {
} else {
log.Printf("[DEBUG] Failed to create network %s for workers: %s. This is not critical, and containers will still be added", networkName, err)
}
}
//serviceOptions := types.ServiceCreateOptions{}
//service, err := dockercli.ServiceCreate(
// context.Background(),
// serviceSpec,
// serviceOptions,
//)
log.Printf("[DEBUG] Deploying containers with swarm")
//containerName := fmt.Sprintf("shuffle-worker-%s", parsedUuid) //containerName := fmt.Sprintf("shuffle-worker-%s", parsedUuid)
containerName := fmt.Sprintf("shuffle-workers")
replicas := uint64(2)
scaleReplicas := os.Getenv("SHUFFLE_SCALE_REPLICAS")
if len(scaleReplicas) > 0 {
log.Printf("[DEBUG] SHUFFLE_SCALE_REPLICAS set to value %#v. Trying to overwrite default (2/node)", scaleReplicas)
tmpInt, err := strconv.Atoi(scaleReplicas)
if err != nil {
log.Printf("[ERROR] %s is not a valid number for replication", scaleReplicas)
} else {
replicas = uint64(tmpInt)
}
}
innerContainerName := fmt.Sprintf("shuffle-workers")
log.Printf("[DEBUG] Deploying %d containers for worker with swarm to each node. Service name: %s. Image: %s", replicas, innerContainerName, image)
serviceSpec := swarm.ServiceSpec{ serviceSpec := swarm.ServiceSpec{
Annotations: swarm.Annotations{ Annotations: swarm.Annotations{
Name: containerName, Name: innerContainerName,
Labels: map[string]string{}, Labels: map[string]string{},
}, },
Networks: []swarm.NetworkAttachmentConfig{
swarm.NetworkAttachmentConfig{
Target: networkName,
},
},
EndpointSpec: &swarm.EndpointSpec{ EndpointSpec: &swarm.EndpointSpec{
Ports: []swarm.PortConfig{ Ports: []swarm.PortConfig{
swarm.PortConfig{ swarm.PortConfig{
@@ -207,7 +226,7 @@ func deployWorker(image string, identifier string, env []string, executionReques
Condition: swarm.RestartPolicyConditionNone, Condition: swarm.RestartPolicyConditionNone,
}, },
Placement: &swarm.Placement{ Placement: &swarm.Placement{
MaxReplicas: 1, MaxReplicas: replicas,
}, },
}, },
} }
@@ -216,27 +235,80 @@ func deployWorker(image string, identifier string, env []string, executionReques
serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("DOCKER_API_VERSION=%s", dockerApiVersion)) serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("DOCKER_API_VERSION=%s", dockerApiVersion))
} }
if len(os.Getenv("SHUFFLE_SCALE_REPLICAS")) > 0 {
serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("DOCKER_API_VERSION=%s", os.Getenv("SHUFFLE_SCALE_REPLICAS")))
}
serviceOptions := types.ServiceCreateOptions{} serviceOptions := types.ServiceCreateOptions{}
service, err := dockercli.ServiceCreate( _, err = dockercli.ServiceCreate(
context.Background(), ctx,
serviceSpec, serviceSpec,
serviceOptions, serviceOptions,
) )
if err == nil { if err == nil {
log.Printf("[DEBUG] Waiting 10 seconds for workers to come awake") log.Printf("[DEBUG] Successfully deployed workers with %d replicas", replicas)
time.Sleep(time.Duration(10) * time.Second) //time.Sleep(time.Duration(10) * time.Second)
} //log.Printf("[DEBUG] Servicecreate request: %#v %#v", service, err)
log.Printf("Servicecreate request: %#v %#v", service, err)
err = sendWorkerRequest(executionRequest)
if err != nil {
log.Printf("[ERROR] Failed worker request: %s", err)
} else { } else {
log.Printf("[DEBUG] Started worker from request: %s - %#v - %s", containerName, service, err) if !strings.Contains(fmt.Sprintf("%s", err), "Already Exists") && !strings.Contains(fmt.Sprintf("%s", err), "is already in use by service") {
log.Printf("[ERROR] Failed making service: %s", err)
}
} }
return
}
}
// Deploys the internal worker whenever something happens
// https://docs.docker.com/engine/api/sdk/examples/
func deployWorker(image string, identifier string, env []string, executionRequest shuffle.ExecutionRequest) error {
// Binds is the actual "-v" volume.
// Max 20% CPU every second
//CPUQuota: 25000,
//CPUPeriod: 100000,
//CPUShares: 256,
hostConfig := &container.HostConfig{
LogConfig: container.LogConfig{
Type: "json-file",
Config: map[string]string{},
},
Resources: container.Resources{},
Binds: []string{
"/var/run/docker.sock:/var/run/docker.sock:rw",
},
}
hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId))
if cleanupEnv == "true" {
hostConfig.AutoRemove = true
}
config := &container.Config{
Image: image,
Env: env,
}
//var swarmConfig = os.Getenv("SHUFFLE_SWARM_CONFIG")
parsedUuid := uuid.NewV4()
if swarmConfig == "run" {
go func() {
err := sendWorkerRequest(executionRequest)
if err != nil {
log.Printf("[ERROR] Failed worker request for %s: %s", executionRequest.ExecutionId, err)
if strings.Contains(fmt.Sprintf("%s", err), "connection refused") || strings.Contains(fmt.Sprintf("%s", err), "EOF") {
workerImage := fmt.Sprintf("%s/%s/shuffle-worker:%s", baseimageregistry, baseimagename, workerVersion)
deployServiceWorkers(workerImage)
}
//return err
} else {
log.Printf("[DEBUG] Started worker from request with name: %s", executionRequest.ExecutionId)
}
}()
return nil
} }
//log.Printf("[INFO] Identifier: %s", identifier) //log.Printf("[INFO] Identifier: %s", identifier)
@@ -264,19 +336,40 @@ func deployWorker(image string, identifier string, env []string, executionReques
if err != nil { if err != nil {
log.Printf("[ERROR] Container create error(2): %s", err) log.Printf("[ERROR] Container create error(2): %s", err)
return return err
} }
} else { } else {
log.Printf("[ERROR] Container create error: %s", err) log.Printf("[ERROR] Container create error: %s", err)
return return err
} }
} }
containerStartOptions := types.ContainerStartOptions{} containerStartOptions := types.ContainerStartOptions{}
err = dockercli.ContainerStart(context.Background(), cont.ID, containerStartOptions) err = dockercli.ContainerStart(context.Background(), cont.ID, containerStartOptions)
if err != nil { if err != nil {
log.Printf("[ERROR] Failed to start container in environment %s: %s", environment, err) log.Printf("[DEBUG] Failed initial container start. Running WITHOUT custom network. Err: %s", err)
return // Trying to recreate and start WITHOUT network if it's possible. No extended checks. Old execution system (<0.9.30)
if strings.Contains(fmt.Sprintf("%s", err), "cannot join network") || strings.Contains(fmt.Sprintf("%s", err), "No such container") {
hostConfig.NetworkMode = ""
//container.NetworkMode(fmt.Sprintf("container:%s", containerId))
cont, err = dockercli.ContainerCreate(
context.Background(),
config,
hostConfig,
nil,
nil,
identifier+"-2",
)
err = dockercli.ContainerStart(context.Background(), cont.ID, containerStartOptions)
}
if err != nil {
log.Printf("[ERROR] Failed to start container in environment %s: %s", environment, err)
return err
} else {
log.Printf("[INFO] Container %s was created under environment %s", cont.ID, environment)
}
//stats, err := cli.ContainerInspect(context.Background(), containerName) //stats, err := cli.ContainerInspect(context.Background(), containerName)
//if err != nil { //if err != nil {
@@ -303,7 +396,7 @@ func deployWorker(image string, identifier string, env []string, executionReques
log.Printf("[INFO] Container %s was created under environment %s", cont.ID, environment) log.Printf("[INFO] Container %s was created under environment %s", cont.ID, environment)
} }
return return nil
} }
func stopWorker(containername string) error { func stopWorker(containername string) error {
@@ -336,6 +429,7 @@ func initializeImages() {
appSdkVersion = "0.8.97" appSdkVersion = "0.8.97"
log.Printf("[WARNING] SHUFFLE_APP_SDK_VERSION not defined. Defaulting to %s", appSdkVersion) log.Printf("[WARNING] SHUFFLE_APP_SDK_VERSION not defined. Defaulting to %s", appSdkVersion)
} }
if workerVersion == "" { if workerVersion == "" {
workerVersion = "nightly" workerVersion = "nightly"
log.Printf("[WARNING] SHUFFLE_WORKER_VERSION not defined. Defaulting to %s", workerVersion) log.Printf("[WARNING] SHUFFLE_WORKER_VERSION not defined. Defaulting to %s", workerVersion)
@@ -486,6 +580,8 @@ func main() {
//workerImage := fmt.Sprintf("%s/%s:worker%s", baseimageregistry, baseimagename, baseimagetagsuffix) //workerImage := fmt.Sprintf("%s/%s:worker%s", baseimageregistry, baseimagename, baseimagetagsuffix)
workerImage := fmt.Sprintf("%s/%s/shuffle-worker:%s", baseimageregistry, baseimagename, workerVersion) workerImage := fmt.Sprintf("%s/%s/shuffle-worker:%s", baseimageregistry, baseimagename, workerVersion)
go deployServiceWorkers(workerImage)
log.Printf("[INFO] Finished configuring docker environment") log.Printf("[INFO] Finished configuring docker environment")
// FIXME - time limit // FIXME - time limit
@@ -638,7 +734,6 @@ func main() {
continue continue
} else { } else {
//log.Printf("[INFO] Adding to be ran %s", execution.ExecutionId) //log.Printf("[INFO] Adding to be ran %s", execution.ExecutionId)
executionIds = append(executionIds, execution.ExecutionId)
} }
// Now, how do I execute this one? // Now, how do I execute this one?
@@ -665,11 +760,15 @@ func main() {
env = append(env, fmt.Sprintf("DOCKER_API_VERSION=%s", dockerApiVersion)) env = append(env, fmt.Sprintf("DOCKER_API_VERSION=%s", dockerApiVersion))
} }
go deployWorker(workerImage, containerName, env, execution) err = deployWorker(workerImage, containerName, env, execution)
log.Printf("[INFO] ExecutionID %s was deployed and to be removed from queue.", execution.ExecutionId)
zombiecounter += 1 zombiecounter += 1
toBeRemoved.Data = append(toBeRemoved.Data, execution) if err == nil {
log.Printf("[INFO] ExecutionID %s was deployed and to be removed from queue.", execution.ExecutionId)
toBeRemoved.Data = append(toBeRemoved.Data, execution)
executionIds = append(executionIds, execution.ExecutionId)
} else {
log.Printf("[WARNING] Execution ID %s failed to deploy: %s", execution.ExecutionId, err)
}
} }
// Removes handled workflows (worker is made) // Removes handled workflows (worker is made)
@@ -731,6 +830,7 @@ func main() {
// Is this ok to do with Docker? idk :) // Is this ok to do with Docker? idk :)
func getRunningWorkers(ctx context.Context, workerTimeout int) int { func getRunningWorkers(ctx context.Context, workerTimeout int) int {
//log.Printf("[DEBUG] Getting running workers with API version %s", dockerApiVersion)
containers, err := dockercli.ContainerList(ctx, types.ContainerListOptions{ containers, err := dockercli.ContainerList(ctx, types.ContainerListOptions{
All: true, All: true,
}) })
@@ -741,8 +841,8 @@ func getRunningWorkers(ctx context.Context, workerTimeout int) int {
newVersionSplit := strings.Split(fmt.Sprintf("%s", err), "version is") newVersionSplit := strings.Split(fmt.Sprintf("%s", err), "version is")
if len(newVersionSplit) > 1 { if len(newVersionSplit) > 1 {
dockerApiVersion = strings.TrimSpace(newVersionSplit[1]) //dockerApiVersion = strings.TrimSpace(newVersionSplit[1])
log.Printf("[INFO] Changed the API version to default to %s", dockerApiVersion) log.Printf("[DEBUG] WANT to change the API version to default to %s?", strings.TrimSpace(newVersionSplit[1]))
} }
return maxConcurrency return maxConcurrency
@@ -790,6 +890,11 @@ func getRunningWorkers(ctx context.Context, workerTimeout int) int {
// Should it check what happened to the execution? idk // Should it check what happened to the execution? idk
func zombiecheck(ctx context.Context, workerTimeout int) error { func zombiecheck(ctx context.Context, workerTimeout int) error {
executionIds = []string{} executionIds = []string{}
if swarmConfig == "run" {
//log.Printf("[DEBUG] Skipping Zombie check due to new execution model (swarm)")
return nil
}
log.Println("[INFO] Looking for old containers (zombies)") log.Println("[INFO] Looking for old containers (zombies)")
containers, err := dockercli.ContainerList(ctx, types.ContainerListOptions{ containers, err := dockercli.ContainerList(ctx, types.ContainerListOptions{
All: true, All: true,
@@ -876,20 +981,8 @@ func zombiecheck(ctx context.Context, workerTimeout int) error {
return nil return nil
} }
type ExecutionRequest struct {
ExecutionId string `json:"execution_id"`
Authorization string `json:"authorization"`
HTTPProxy string `json:"http_proxy"`
HTTPSProxy string `json:"https_proxy"`
BaseUrl string `json:"base_url"`
EnvironmentName string `json:"environment_name"`
Timezone string `json:"timezone"`
Cleanup string `json:"cleanup"`
ShufflePassProxyToApp string `json:"shuffle_pass_proxy_to_app"`
}
func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest) error { func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest) error {
parsedRequest := ExecutionRequest{ parsedRequest := shuffle.OrborusExecutionRequest{
ExecutionId: workflowExecution.ExecutionId, ExecutionId: workflowExecution.ExecutionId,
Authorization: workflowExecution.Authorization, Authorization: workflowExecution.Authorization,
BaseUrl: os.Getenv("BASE_URL"), BaseUrl: os.Getenv("BASE_URL"),
@@ -916,6 +1009,8 @@ func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest) error {
return err return err
} }
//log.Printf("[DEBUG] Data: %s", string(data))
streamUrl := fmt.Sprintf("%s:33333/api/v1/execute", parsedBaseurl) streamUrl := fmt.Sprintf("%s:33333/api/v1/execute", parsedBaseurl)
req, err := http.NewRequest( req, err := http.NewRequest(
"POST", "POST",
@@ -925,23 +1020,27 @@ func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest) error {
client := &http.Client{} client := &http.Client{}
if err != nil { if err != nil {
log.Printf("[ERROR] Failed creating finishing request: %s", err) log.Printf("[ERROR] Failed creating worker request: %s", err)
return err return err
} }
newresp, err := client.Do(req) newresp, err := client.Do(req)
if err != nil { if err != nil {
log.Printf("[ERROR] Error running finishing request: %s", err) log.Printf("[ERROR] Error running worker request: %s", err)
return err return err
} }
if newresp.StatusCode != 200 {
log.Printf("[ERROR] Error running request - status code is %d, not 200", newresp.StatusCode)
return errors.New(fmt.Sprintf("Bad statuscode: %d - expecting 200", newresp.StatusCode))
}
body, err := ioutil.ReadAll(newresp.Body) body, err := ioutil.ReadAll(newresp.Body)
if err != nil { if err != nil {
log.Printf("[ERROR] Failed reading body: %s", err) log.Printf("[ERROR] Failed reading body in worker request: %s", err)
return err return err
} else {
log.Printf("[INFO] NEWRESP (from backend): %s", string(body))
} }
log.Printf("[DEBUG] NEWRESP (from worker request %s): %s (Status: %d)", workflowExecution.ExecutionId, string(body), newresp.StatusCode)
return nil return nil
} }
+1 -1
View File
@@ -1,5 +1,5 @@
NAME=shuffle-worker NAME=shuffle-worker
VERSION=0.9.29 VERSION=0.9.30
echo "Running docker build with $NAME:$VERSION" echo "Running docker build with $NAME:$VERSION"
#CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker.bin . #CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker.bin .
+3 -1
View File
@@ -1,6 +1,8 @@
module worker module worker
go 1.15 go 1.15
//replace github.com/shuffle/shuffle-shared => ../../../../../git/shuffle-shared
require ( require (
github.com/containerd/containerd v1.5.7 // indirect github.com/containerd/containerd v1.5.7 // indirect
@@ -8,6 +10,6 @@ require (
github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-connections v0.4.0 // indirect
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/shuffle/shuffle-shared v0.1.20 github.com/shuffle/shuffle-shared v0.1.27
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
) )
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
#curl -XPOST "http://192.168.86.37:33335/api/v1/run" -H "Content-Type: application/json" -d '{"execution_id":"ec35241a-b713-45c5-bda2-823fa4ce21e6","authorization":"d044f927-ad19-4f0e-a01f-51365e46c4e9","http_proxy":"","https_proxy":"","base_url":"http:\/\/192.168.86.37:5001","url":"http:\/\/192.168.86.37:33333","environment_name":"Shuffle","timezone":"","cleanup":"false","shuffle_pass_proxy_to_app":"","action":{"app_name":"Shuffle Tools","app_version":"1.1.0","app_id":"080b539e-638e-4a2b-9202-07d6360c2ddf","errors":[],"id":"13d73f9c-82be-4104-8b1e-8651ef189412","is_valid":true,"isStartNode":true,"sharing":true,"label":"Shuffle Tools_1","public":true,"generated":false,"environment":"Shuffle","name":"repeat_back_to_me","parameters":[{"description":"The message to repeat","id":"572cfe8c-eddf-4b66-8e53-99aeecfbd838","name":"call","example":"REPEATING: Hello world","value":"hello world","multiline":true,"options":null,"action_field":"","variant":"STATIC_VALUE","required":true,"configuration":false,"tags":null,"schema":{"type":"string"},"skip_multicheck":false,"value_replace":null,"unique_toggled":false}],"execution_variable":{"description":"","id":"","name":"","value":""},"position":{"x":367.5,"y":398.25},"authentication_id":"","category":"Testing","reference_url":"","sub_action":false,"source_workflow":""},"workflow_execution":{"type":"workflow","status":"EXECUTING","start":"13d73f9c-82be-4104-8b1e-8651ef189412","execution_argument":"","execution_id":"ec35241a-b713-45c5-bda2-823fa4ce21e6","execution_org":"015c13d4-2e03-45fd-82a9-9b1f8095937a","started_at":1635630830,"completed_at":0,"workflow_id":"ca6d239b-6523-4b11-8c92-00ea4ab82b6d","last_node":"","authorization":"d044f927-ad19-4f0e-a01f-51365e46c4e9","result":"","project_id":"","locations":null,"workflow":{"actions":[{"app_name":"Shuffle Tools","app_version":"1.1.0","app_id":"080b539e-638e-4a2b-9202-07d6360c2ddf","errors":[],"id":"13d73f9c-82be-4104-8b1e-8651ef189412","is_valid":true,"isStartNode":true,"sharing":true,"label":"Shuffle Tools_1","public":true,"generated":false,"environm
curl -XPOST http://192.168.86.37:33335/api/v1/run -H "Content-Type: application/json" -d '{"execution_id":"ec35241a-b713-45c5-bda2-823fa4ce21e6","authorization":"d044f927-ad19-4f0e-a01f-51365e46c4e9","http_proxy":"","https_proxy":"","base_url":"http:\/\/192.168.86.37:5001","url":"http:\/\/192.168.86.37:33333","environment_name":"Shuffle","timezone":"","cleanup":"false","shuffle_pass_proxy_to_app":"","action":{"app_name":"Shuffle Tools","app_version":"1.1.0","app_id":"080b539e-638e-4a2b-9202-07d6360c2ddf","errors":[],"id":"13d73f9c-82be-4104-8b1e-8651ef189412","is_valid":true,"isStartNode":true,"sharing":true,"label":"Shuffle Tools_1","public":true,"generated":false,"environment":"Shuffle","name":"repeat_back_to_me","parameters":[{"description":"The message to repeat","id":"572cfe8c-eddf-4b66-8e53-99aeecfbd838","name":"call","example":"REPEATING: Hello world","value":"hello world","multiline":true,"options":null,"action_field":"","variant":"STATIC_VALUE","required":true,"configuration":false,"tags":null,"schema":{"type":"string"},"skip_multicheck":false,"value_replace":null,"unique_toggled":false}],"execution_variable":{"description":"","id":"","name":"","value":""},"position":{"x":367.5,"y":398.25},"authentication_id":"","category":"Testing","reference_url":"","sub_action":false,"source_workflow":""},"workflow_execution":{"type":"workflow","status":"EXECUTING","start":"13d73f9c-82be-4104-8b1e-8651ef189412","execution_argument":"","execution_id":"ec35241a-b713-45c5-bda2-823fa4ce21e6","execution_org":"015c13d4-2e03-45fd-82a9-9b1f8095937a","started_at":1635630830,"completed_at":0,"workflow_id":"ca6d239b-6523-4b11-8c92-00ea4ab82b6d","last_node":"","authorization":"d044f927-ad19-4f0e-a01f-51365e46c4e9","result":"","project_id":"","locations":null,"workflow":{"actions":[{"app_name":"Shuffle Tools","app_version":"1.1.0","app_id":"080b539e-638e-4a2b-9202-07d6360c2ddf","errors":[],"id":"13d73f9c-82be-4104-8b1e-8651ef189412","is_valid":true,"isStartNode":true,"sharing":true,"label":"Shuffle Tools_1","public":true,"generated":false,"environment":"Shuffle","name":"repeat_back_to_me","parameters":[{"description":"The message to repeat","id":"572cfe8c-eddf-4b66-8e53-99aeecfbd838","name":"call","example":"REPEATING: Hello world","value":"hello world","multiline":true,"options":null,"action_field":"","variant":"STATIC_VALUE","required":true,"configuration":false,"tags":null,"schema":{"type":"string"},"skip_multicheck":false,"value_replace":null,"unique_toggled":false}],"execution_variable":{"description":"","id":"","name":"","value":""},"position":{"x":367.5,"y":398.25},"authentication_id":"","category":"Testing","reference_url":"","sub_action":false,"source_workflow":""}],"branches":[],"visual_branches":[],"triggers":[],"schedules":[],"configuration":{"exit_on_error":false,"start_from_top":false,"skip_notifications":false},"created":1635611089,"edited":1635611106,"last_runtime":0,"id":"ca6d239b-6523-4b11-8c92-00ea4ab82b6d","is_valid":true,"name":"Tools testing","description":"","start":"13d73f9c-82be-4104-8b1e-8651ef189412","owner":"2b3a37f3-fdd2-4965-b78d-f5d2fe9a2572","sharing":"private","org":[{"name":"","id":"015c13d4-2e03-45fd-82a9-9b1f8095937a","users":null,"role":"","creator_org":"","image":""}],"execution_org":{"name":"","id":"015c13d4-2e03-45fd-82a9-9b1f8095937a","users":null,"role":"","creator_org":"","image":""},"org_id":"015c13d4-2e03-45fd-82a9-9b1f8095937a","workflow_variables":null,"execution_environment":"","previously_saved":true,"categories":{"siem":{"name":"","description":"","count":0},"communication":{"name":"","description":"","count":0},"assets":{"name":"","description":"","count":0},"cases":{"name":"","description":"","count":0},"network":{"name":"","description":"","count":0},"intel":{"name":"","description":"","count":1},"edr":{"name":"","description":"","count":0},"other":{"name":"","description":"","count":0}},"example_argument":"","public":false,"default_return_value":"","contact_info":{"name":"","url":""},"published_id":""},"results":[],"org_id":"","sub_execution_count":0,"execution_source":"default","execution_parent":"","execution_source_node":"","execution_source_auth":""}}'
+471 -60
View File
@@ -23,6 +23,7 @@ import (
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
//"github.com/docker/docker/api/types/filters" //"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/swarm"
dockerclient "github.com/docker/docker/client" dockerclient "github.com/docker/docker/client"
//"github.com/go-git/go-billy/v5/memfs" //"github.com/go-git/go-billy/v5/memfs"
@@ -32,6 +33,10 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/patrickmn/go-cache" "github.com/patrickmn/go-cache"
"github.com/satori/go.uuid" "github.com/satori/go.uuid"
// No necessary outside shared
"cloud.google.com/go/datastore"
"cloud.google.com/go/storage"
) )
// This is getting out of hand :) // This is getting out of hand :)
@@ -39,6 +44,7 @@ var environment = os.Getenv("ENVIRONMENT_NAME")
var baseUrl = os.Getenv("BASE_URL") var baseUrl = os.Getenv("BASE_URL")
var appCallbackUrl = os.Getenv("BASE_URL") var appCallbackUrl = os.Getenv("BASE_URL")
var cleanupEnv = strings.ToLower(os.Getenv("CLEANUP")) var cleanupEnv = strings.ToLower(os.Getenv("CLEANUP"))
var dockerApiVersion = strings.ToLower(os.Getenv("DOCKER_API_VERSION"))
var timezone = os.Getenv("TZ") var timezone = os.Getenv("TZ")
var baseimagename = "frikky/shuffle" var baseimagename = "frikky/shuffle"
var registryName = "registry.hub.docker.com" var registryName = "registry.hub.docker.com"
@@ -48,20 +54,26 @@ var topClient *http.Client
var data string var data string
var requestsSent = 0 var requestsSent = 0
/*
var environments []string var environments []string
var parents map[string][]string var parents map[string][]string
var children map[string][]string var children map[string][]string
var visited []string var visited []string
var executed []string var executed []string
var nextActions []string var nextActions []string
var containerIds []string
var extra int var extra int
var startAction string var startAction string
*/
var results []shuffle.ActionResult var results []shuffle.ActionResult
var allLogs map[string]string var allLogs map[string]string
var containerIds []string
var executionRunning bool var executionRunning bool
// New Worker mappings
var portMappings map[string]int
var baseport = 33333
// removes every container except itself (worker) // removes every container except itself (worker)
func shutdown(workflowExecution shuffle.WorkflowExecution, nodeId string, reason string, handleResultSend bool) { func shutdown(workflowExecution shuffle.WorkflowExecution, nodeId string, reason string, handleResultSend bool) {
log.Printf("[INFO] Shutdown (%s) started with reason %#v. Result amount: %d. ResultsSent: %d, Send result: %#v", workflowExecution.Status, reason, len(workflowExecution.Results), requestsSent, handleResultSend) log.Printf("[INFO] Shutdown (%s) started with reason %#v. Result amount: %d. ResultsSent: %d, Send result: %#v", workflowExecution.Status, reason, len(workflowExecution.Results), requestsSent, handleResultSend)
@@ -131,11 +143,15 @@ func shutdown(workflowExecution shuffle.WorkflowExecution, nodeId string, reason
} }
// FIXME: Add an API call to the backend // FIXME: Add an API call to the backend
authorization := os.Getenv("AUTHORIZATION") if os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" {
if len(authorization) > 0 { authorization := os.Getenv("AUTHORIZATION")
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", authorization)) if len(authorization) > 0 {
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", authorization))
} else {
log.Printf("[ERROR] No authorization specified for abort")
}
} else { } else {
log.Printf("[ERROR] No authorization specified for abort") req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", workflowExecution.Authorization))
} }
req.Header.Add("Content-Type", "application/json") req.Header.Add("Content-Type", "application/json")
@@ -175,28 +191,58 @@ func shutdown(workflowExecution shuffle.WorkflowExecution, nodeId string, reason
time.Sleep(time.Duration(sleepDuration) * time.Second) time.Sleep(time.Duration(sleepDuration) * time.Second)
os.Exit(3) os.Exit(3)
} else { } else {
log.Printf("[DEBUG] Sending result and resetting values (K8s & Swarm).") log.Printf("\n\n[DEBUG] Sending result and resetting values (K8s & Swarm).\n\n")
environments = []string{} //UpdateExecutionVariables(ctx, workflowExecution.ExecutionId, startAction, children, parents, visited, executed, nextActions, environments, extra)
parents = map[string][]string{}
children = map[string][]string{} /*
visited = []string{} environments = []string{}
executed = []string{} parents = map[string][]string{}
nextActions = []string{} children = map[string][]string{}
containerIds = []string{} visited = []string{}
extra = 0 executed = []string{}
startAction = "" nextActions = []string{}
results = []shuffle.ActionResult{} containerIds = []string{}
allLogs = map[string]string{} extra = 0
startAction = ""
results = []shuffle.ActionResult{}
allLogs = map[string]string{}
*/
requestsSent = 0
executionRunning = false executionRunning = false
} }
//cacheKey := fmt.Sprintf("workflowexecution-%s", workflowExecution.ExecutionId) //cacheKey := fmt.Sprintf("workflowexecution-%s", workflowExecution.ExecutionId)
} }
// Deploys the internal worker whenever something happens // Deploys the internal worker whenever something happens
func deployApp(cli *dockerclient.Client, image string, identifier string, env []string, workflowExecution shuffle.WorkflowExecution, actionId string) error { func deployApp(cli *dockerclient.Client, image string, identifier string, env []string, workflowExecution shuffle.WorkflowExecution, action shuffle.Action) error {
// form basic hostConfig // form basic hostConfig
ctx := context.Background() ctx := context.Background()
if os.Getenv("SHUFFLE_SWARM_CONFIG") == "run" {
//identifier := fmt.Sprintf("%s_%s_%s_%s", appname, appversion, action.ID, workflowExecution.ExecutionId)
appName := strings.Replace(identifier, fmt.Sprintf("_%s", action.ID), "", -1)
appName = strings.Replace(appName, fmt.Sprintf("_%s", workflowExecution.ExecutionId), "", -1)
appName = strings.ToLower(appName)
log.Printf("[INFO] New appname: %s, image: %s", appName, image)
exposedPort, err := findAppInfo(image, appName)
if err != nil {
log.Printf("[ERROR] Failed finding and creating port for %s: %s", appName, err)
return err
}
log.Printf("[DEBUG] Should run towards port %d for app %s", exposedPort, appName)
err = sendAppRequest(baseUrl, exposedPort, action, workflowExecution)
if err != nil {
log.Printf("[ERROR] Failed sending request to app %s on port %d: %s", appName, exposedPort, err)
return err
}
log.Printf("[DEBUG] Successfully ran request towards port %d for app %s", exposedPort, appName)
return nil
}
// Max 10% CPU every second // Max 10% CPU every second
//CPUShares: 128, //CPUShares: 128,
//CPUQuota: 10000, //CPUQuota: 10000,
@@ -211,7 +257,7 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env []
if os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" { if os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" {
hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:worker-%s", workflowExecution.ExecutionId)) hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:worker-%s", workflowExecution.ExecutionId))
log.Printf("Environments: %#v", env) //log.Printf("Environments: %#v", env)
} }
// Removing because log extraction should happen first // Removing because log extraction should happen first
@@ -245,7 +291,7 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env []
}) })
} }
} else { } else {
log.Printf("[WARNING] No mounted folders") log.Printf("[WARNING] Not mounting folders")
} }
config := &container.Config{ config := &container.Config{
@@ -305,7 +351,7 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env []
stats, err := cli.ContainerInspect(ctx, cont.ID) stats, err := cli.ContainerInspect(ctx, cont.ID)
if err != nil { if err != nil {
log.Printf("[ERROR] Failed getting container stats") log.Printf("[ERROR] Failed getting container stats for container %s: %s", cont.ID, err)
} else { } else {
//log.Printf("[INFO] Info for container: %#v", stats) //log.Printf("[INFO] Info for container: %#v", stats)
//log.Printf("%#v", stats.Config) //log.Printf("%#v", stats.Config)
@@ -328,7 +374,7 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env []
//allLogs[actionId] = logs //allLogs[actionId] = logs
if stats.ContainerJSONBase.State.Status == "exited" && !strings.Contains(logs, "Normal execution.") { if stats.ContainerJSONBase.State.Status == "exited" && !strings.Contains(logs, "Normal execution.") {
log.Printf("[WARNING] BAD Execution Logs for %s: %s", actionId, logs) log.Printf("[WARNING] BAD Execution Logs for %s: %s", action.ID, logs)
exit = true exit = true
} }
} }
@@ -530,7 +576,11 @@ func removeIndex(s []string, i int) []string {
} }
func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) { func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
ctx := context.Background()
startAction, extra, children, parents, visited, executed, nextActions, environments := shuffle.GetExecutionVariables(ctx, workflowExecution.ExecutionId)
log.Printf("[INFO] Inside execution results with %d / %d results", len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)+extra) log.Printf("[INFO] Inside execution results with %d / %d results", len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)+extra)
if len(startAction) == 0 { if len(startAction) == 0 {
startAction = workflowExecution.Start startAction = workflowExecution.Start
if len(startAction) == 0 { if len(startAction) == 0 {
@@ -540,11 +590,11 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
} }
//log.Printf("NEXTACTIONS: %s", nextActions) //log.Printf("NEXTACTIONS: %s", nextActions)
queueNodes := []string{}
//if len(nextActions) == 0 { //if len(nextActions) == 0 {
// nextActions = append(nextActions, startAction) // nextActions = append(nextActions, startAction)
//} //}
queueNodes := []string{}
if len(workflowExecution.Results) == 0 { if len(workflowExecution.Results) == 0 {
nextActions = []string{startAction} nextActions = []string{startAction}
} else { } else {
@@ -602,7 +652,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
} }
if len(appendActions) > 0 { if len(appendActions) > 0 {
log.Printf("APPENDED NODES: %#v", appendActions) //log.Printf("APPENDED NODES: %#v", appendActions)
nextActions = append(nextActions, appendActions...) nextActions = append(nextActions, appendActions...)
} }
} }
@@ -1013,7 +1063,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
// If cleanup is set, it should run for efficiency // If cleanup is set, it should run for efficiency
pullOptions := types.ImagePullOptions{} pullOptions := types.ImagePullOptions{}
if cleanupEnv == "true" { if cleanupEnv == "true" {
err = deployApp(dockercli, images[0], identifier, env, workflowExecution, action.ID) err = deployApp(dockercli, images[0], identifier, env, workflowExecution, action)
if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") { if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") {
if strings.Contains(err.Error(), "exited prematurely") { if strings.Contains(err.Error(), "exited prematurely") {
log.Printf("[DEBUG] Shutting down (2)") log.Printf("[DEBUG] Shutting down (2)")
@@ -1025,8 +1075,8 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
executed := false executed := false
if err == nil { if err == nil {
log.Printf("[DEBUG] Downloaded image %s from backend (CLEANUP)", image) log.Printf("[DEBUG] Downloaded image %s from backend (CLEANUP)", image)
//err = deployApp(dockercli, image, identifier, env, workflow, action.ID) //err = deployApp(dockercli, image, identifier, env, workflow, action)
err = deployApp(dockercli, image, identifier, env, workflowExecution, action.ID) err = deployApp(dockercli, image, identifier, env, workflowExecution, action)
if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") { if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") {
if strings.Contains(err.Error(), "exited prematurely") { if strings.Contains(err.Error(), "exited prematurely") {
log.Printf("[DEBUG] Shutting down (41)") log.Printf("[DEBUG] Shutting down (41)")
@@ -1040,7 +1090,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
if !executed { if !executed {
image = images[2] image = images[2]
err = deployApp(dockercli, image, identifier, env, workflowExecution, action.ID) err = deployApp(dockercli, image, identifier, env, workflowExecution, action)
if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") { if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") {
if strings.Contains(err.Error(), "exited prematurely") { if strings.Contains(err.Error(), "exited prematurely") {
log.Printf("[DEBUG] Shutting down (3)") log.Printf("[DEBUG] Shutting down (3)")
@@ -1078,7 +1128,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
log.Printf("[INFO] Successfully downloaded %s", image) log.Printf("[INFO] Successfully downloaded %s", image)
} }
err = deployApp(dockercli, image, identifier, env, workflowExecution, action.ID) err = deployApp(dockercli, image, identifier, env, workflowExecution, action)
if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") { if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") {
log.Printf("[ERROR] Failed deploying image for the FOURTH time. Aborting if the image doesn't exist") log.Printf("[ERROR] Failed deploying image for the FOURTH time. Aborting if the image doesn't exist")
@@ -1101,7 +1151,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
} }
} else { } else {
err = deployApp(dockercli, images[0], identifier, env, workflowExecution, action.ID) err = deployApp(dockercli, images[0], identifier, env, workflowExecution, action)
if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") { if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") {
if strings.Contains(err.Error(), "exited prematurely") { if strings.Contains(err.Error(), "exited prematurely") {
log.Printf("[DEBUG] Shutting down (9)") log.Printf("[DEBUG] Shutting down (9)")
@@ -1112,7 +1162,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
// Trying to replace with lowercase to deploy again. This seems to work with Dockerhub well. // Trying to replace with lowercase to deploy again. This seems to work with Dockerhub well.
// FIXME: Should try to remotely download directly if this persists. // FIXME: Should try to remotely download directly if this persists.
image = images[1] image = images[1]
err = deployApp(dockercli, image, identifier, env, workflowExecution, action.ID) err = deployApp(dockercli, image, identifier, env, workflowExecution, action)
if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") { if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") {
if strings.Contains(err.Error(), "exited prematurely") { if strings.Contains(err.Error(), "exited prematurely") {
log.Printf("[DEBUG] Shutting down (10)") log.Printf("[DEBUG] Shutting down (10)")
@@ -1125,8 +1175,8 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
executed := false executed := false
if err == nil { if err == nil {
log.Printf("[DEBUG] Downloaded image %s from backend (CLEANUP)", image) log.Printf("[DEBUG] Downloaded image %s from backend (CLEANUP)", image)
//err = deployApp(dockercli, image, identifier, env, workflow, action.ID) //err = deployApp(dockercli, image, identifier, env, workflow, action)
err = deployApp(dockercli, image, identifier, env, workflowExecution, action.ID) err = deployApp(dockercli, image, identifier, env, workflowExecution, action)
if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") { if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") {
if strings.Contains(err.Error(), "exited prematurely") { if strings.Contains(err.Error(), "exited prematurely") {
log.Printf("[DEBUG] Shutting down (40)") log.Printf("[DEBUG] Shutting down (40)")
@@ -1140,7 +1190,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
if !executed { if !executed {
image = images[2] image = images[2]
err = deployApp(dockercli, image, identifier, env, workflowExecution, action.ID) err = deployApp(dockercli, image, identifier, env, workflowExecution, action)
if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") { if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") {
if strings.Contains(err.Error(), "exited prematurely") { if strings.Contains(err.Error(), "exited prematurely") {
log.Printf("[DEBUG] Shutting down (11)") log.Printf("[DEBUG] Shutting down (11)")
@@ -1177,7 +1227,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
} }
} }
err = deployApp(dockercli, image, identifier, env, workflowExecution, action.ID) err = deployApp(dockercli, image, identifier, env, workflowExecution, action)
if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") { if err != nil && !strings.Contains(err.Error(), "Conflict. The container name") {
log.Printf("[ERROR] Failed deploying image for the FOURTH time. Aborting if the image doesn't exist") log.Printf("[ERROR] Failed deploying image for the FOURTH time. Aborting if the image doesn't exist")
if strings.Contains(err.Error(), "exited prematurely") { if strings.Contains(err.Error(), "exited prematurely") {
@@ -1242,12 +1292,14 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
} }
func executionInit(workflowExecution shuffle.WorkflowExecution) error { func executionInit(workflowExecution shuffle.WorkflowExecution) error {
parents = map[string][]string{} parents := map[string][]string{}
children = map[string][]string{} children := map[string][]string{}
nextActions := []string{}
extra := 0
results = workflowExecution.Results results = workflowExecution.Results
startAction = workflowExecution.Start startAction := workflowExecution.Start
log.Printf("[INFO] STARTACTION: %s", startAction) log.Printf("[INFO] STARTACTION: %s", startAction)
if len(startAction) == 0 { if len(startAction) == 0 {
log.Printf("[INFO] Didn't find execution start action. Setting it to workflow start action.") log.Printf("[INFO] Didn't find execution start action. Setting it to workflow start action.")
@@ -1342,7 +1394,7 @@ func executionInit(workflowExecution shuffle.WorkflowExecution) error {
pullOptions := types.ImagePullOptions{} pullOptions := types.ImagePullOptions{}
_ = pullOptions _ = pullOptions
for _, image := range onpremApps { for _, image := range onpremApps {
log.Printf("[INFO] Image: %s", image) //log.Printf("[INFO] Image: %s", image)
// Kind of gambling that the image exists. // Kind of gambling that the image exists.
if strings.Contains(image, " ") { if strings.Contains(image, " ") {
image = strings.ReplaceAll(image, " ", "-") image = strings.ReplaceAll(image, " ", "-")
@@ -1361,12 +1413,41 @@ func executionInit(workflowExecution shuffle.WorkflowExecution) error {
//log.Printf("Successfully downloaded and built %s", image) //log.Printf("Successfully downloaded and built %s", image)
} }
ctx := context.Background()
visited := []string{}
executed := []string{}
environments := []string{}
for _, action := range workflowExecution.Workflow.Actions {
found := false
for _, environment := range environments {
if action.Environment == environment {
found = true
break
}
}
if !found {
environments = append(environments, action.Environment)
}
}
//var visited []string
//var executed []string
err := shuffle.UpdateExecutionVariables(ctx, workflowExecution.ExecutionId, startAction, children, parents, visited, executed, nextActions, environments, extra)
if err != nil {
log.Printf("\n\n[ERROR] Failed to update exec variables for execution %s: %s\n\n", workflowExecution.ExecutionId, err)
}
return nil return nil
} }
func handleDefaultExecution(client *http.Client, req *http.Request, workflowExecution shuffle.WorkflowExecution) error { func handleDefaultExecution(client *http.Client, req *http.Request, workflowExecution shuffle.WorkflowExecution) error {
// if no onprem runs (shouldn't happen, but extra check), exit // if no onprem runs (shouldn't happen, but extra check), exit
// if there are some, load the images ASAP for the app // if there are some, load the images ASAP for the app
ctx := context.Background()
//startAction, extra, children, parents, visited, executed, nextActions, environments := shuffle.GetExecutionVariables(ctx, workflowExecution.ExecutionId)
startAction, extra, _, _, _, _, _, _ := shuffle.GetExecutionVariables(ctx, workflowExecution.ExecutionId)
err := executionInit(workflowExecution) err := executionInit(workflowExecution)
if err != nil { if err != nil {
@@ -1377,7 +1458,6 @@ func handleDefaultExecution(client *http.Client, req *http.Request, workflowExec
log.Printf("[DEBUG] DEFAULT EXECUTION Startaction: %s", startAction) log.Printf("[DEBUG] DEFAULT EXECUTION Startaction: %s", startAction)
ctx := context.Background()
setWorkflowExecution(ctx, workflowExecution, false) setWorkflowExecution(ctx, workflowExecution, false)
streamResultUrl := fmt.Sprintf("%s/api/v1/streams/results", baseUrl) streamResultUrl := fmt.Sprintf("%s/api/v1/streams/results", baseUrl)
@@ -1616,6 +1696,7 @@ func runTestExecution(client *http.Client, workflowId, apikey string) (string, s
} }
func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) { func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
//log.Printf("[DEBUG] Got stream workflow queue")
body, err := ioutil.ReadAll(request.Body) body, err := ioutil.ReadAll(request.Body)
if err != nil { if err != nil {
log.Println("(3) Failed reading body for workflowqueue") log.Println("(3) Failed reading body for workflowqueue")
@@ -1659,7 +1740,7 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
if workflowExecution.Status == "FINISHED" { if workflowExecution.Status == "FINISHED" {
log.Printf("[DEBUG] Workflowexecution is already FINISHED. No further action can be taken") log.Printf("[DEBUG] Workflowexecution is already FINISHED. No further action can be taken")
resp.WriteHeader(401) resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is already finished because it has status %s"}`, workflowExecution.LastNode, workflowExecution.Status))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is already finished because it has status %s. Lastnode: %s"}`, workflowExecution.Status, workflowExecution.LastNode)))
return return
} }
@@ -1829,6 +1910,10 @@ func sendResult(workflowExecution shuffle.WorkflowExecution, data []byte) {
} }
func validateFinished(workflowExecution shuffle.WorkflowExecution) { func validateFinished(workflowExecution shuffle.WorkflowExecution) {
ctx := context.Background()
//startAction, extra, children, parents, visited, executed, nextActions, environments := shuffle.GetExecutionVariables(ctx, workflowExecution.ExecutionId)
_, extra, _, _, _, _, _, environments := shuffle.GetExecutionVariables(ctx, workflowExecution.ExecutionId)
log.Printf("[INFO] VALIDATION. Status: %s, shuffle.Actions: %d, Extra: %d, Results: %d\n", workflowExecution.Status, len(workflowExecution.Workflow.Actions), extra, len(workflowExecution.Results)) log.Printf("[INFO] VALIDATION. Status: %s, shuffle.Actions: %d, Extra: %d, Results: %d\n", workflowExecution.Status, len(workflowExecution.Workflow.Actions), extra, len(workflowExecution.Results))
//if len(workflowExecution.Results) == len(workflowExecution.Workflow.Actions)+extra { //if len(workflowExecution.Results) == len(workflowExecution.Workflow.Actions)+extra {
@@ -1849,6 +1934,7 @@ func validateFinished(workflowExecution shuffle.WorkflowExecution) {
} }
func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) { func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) {
//log.Printf("[DEBUG] Got stream result")
body, err := ioutil.ReadAll(request.Body) body, err := ioutil.ReadAll(request.Body)
if err != nil { if err != nil {
log.Println("Failed reading body for stream result queue") log.Println("Failed reading body for stream result queue")
@@ -1963,13 +2049,13 @@ func webserverSetup(workflowExecution shuffle.WorkflowExecution) net.Listener {
return listener return listener
} }
log.Printf("OLD HOSTNAME: %s", appCallbackUrl) log.Printf("[DEBUG] OLD HOSTNAME: %s", appCallbackUrl)
if os.Getenv("SHUFFLE_SWARM_CONFIG") == "run" { if os.Getenv("SHUFFLE_SWARM_CONFIG") == "run" {
log.Printf("\n\nStarting webserver on port 33333 with hostname: %s\n\n", hostname) log.Printf("\n\nStarting webserver on port %d with hostname: %s\n\n", baseport, hostname)
appCallbackUrl = fmt.Sprintf("http://%s:33333", hostname) appCallbackUrl = fmt.Sprintf("http://%s:%d", hostname, baseport)
listener, err = net.Listen("tcp", ":33333") listener, err = net.Listen("tcp", fmt.Sprintf(":%d", baseport))
if err != nil { if err != nil {
log.Printf("[ERROR] Failed to assign port to 33333") log.Printf("[ERROR] Failed to assign port to %d: %s", baseport, err)
return nil return nil
} }
@@ -1996,9 +2082,8 @@ func runWebserver(listener net.Listener) {
r.HandleFunc("/api/v1/execute", handleRunExecution).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/execute", handleRunExecution).Methods("POST", "OPTIONS")
} }
http.Handle("/", r)
//log.Fatal(http.ListenAndServe(port, nil)) //log.Fatal(http.ListenAndServe(port, nil))
http.Handle("/", r)
log.Fatal(http.Serve(listener, nil)) log.Fatal(http.Serve(listener, nil))
} }
@@ -2057,7 +2142,7 @@ func downloadDockerImageBackend(client *http.Client, imageName string) error {
imageLoadResponse, err := dockercli.ImageLoad(context.Background(), tar, true) imageLoadResponse, err := dockercli.ImageLoad(context.Background(), tar, true)
if err != nil { if err != nil {
log.Printf("[ERROR] Error loading: %s", err) log.Printf("[ERROR] Error loading images: %s", err)
return err return err
} }
@@ -2077,11 +2162,316 @@ func downloadDockerImageBackend(client *http.Client, imageName string) error {
return nil return nil
} }
func deploySwarmService(dockercli *dockerclient.Client, name, image string, deployport int) error {
log.Printf("[DEBUG] Deploying service for %s to swarm on port %d", name, deployport)
//containerName := fmt.Sprintf("shuffle-worker-%s", parsedUuid)
if len(baseimagename) == 0 {
baseimagename = "frikky/shuffle"
//var baseimagename = "frikky/shuffle"
//var registryName = "registry.hub.docker.com"
}
//image := fmt.Sprintf("%s:%s", baseimagename, name)
log.Printf("[DEBUG] Deploying app with name %s with image %s", name, image)
containerName := fmt.Sprintf(strings.Replace(name, ".", "-", -1))
serviceSpec := swarm.ServiceSpec{
Annotations: swarm.Annotations{
Name: containerName,
Labels: map[string]string{},
},
Networks: []swarm.NetworkAttachmentConfig{
swarm.NetworkAttachmentConfig{
Target: "shuffle-executions",
},
},
EndpointSpec: &swarm.EndpointSpec{
Ports: []swarm.PortConfig{
swarm.PortConfig{
Protocol: swarm.PortConfigProtocolTCP,
PublishMode: swarm.PortConfigPublishModeIngress,
Name: "app-port",
PublishedPort: uint32(deployport),
TargetPort: uint32(deployport),
},
},
},
TaskTemplate: swarm.TaskSpec{
Resources: &swarm.ResourceRequirements{
Reservations: &swarm.Resources{},
},
ContainerSpec: &swarm.ContainerSpec{
Image: image,
Env: []string{
fmt.Sprintf("SHUFFLE_SWARM_CONFIG=%s", os.Getenv("SHUFFLE_SWARM_CONFIG")),
fmt.Sprintf("SHUFFLE_APP_EXPOSED_PORT=%d", deployport),
},
},
RestartPolicy: &swarm.RestartPolicy{
Condition: swarm.RestartPolicyConditionNone,
},
Placement: &swarm.Placement{
MaxReplicas: 5,
},
},
}
/*
Mounts: []mount.Mount{
mount.Mount{
Source: "/var/run/docker.sock",
Target: "/var/run/docker.sock",
Type: mount.TypeBind,
},
},
*/
if dockerApiVersion != "" {
serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("DOCKER_API_VERSION=%s", dockerApiVersion))
}
serviceOptions := types.ServiceCreateOptions{}
service, err := dockercli.ServiceCreate(
context.Background(),
serviceSpec,
serviceOptions,
)
_ = service
if err != nil {
log.Printf("[DEBUG] Failed deploying %s with image %s: %s", name, image, err)
return err
}
log.Printf("[DEBUG] Successfully deployed service %s with image %s on port %d", name, image, deployport)
return nil
}
// Runs data discovery
func findAppInfo(image, name string) (int, error) {
dockercli, err := dockerclient.NewEnvClient()
if err != nil {
log.Printf("[ERROR] Unable to create docker client (2): %s", err)
return -1, err
}
highest := baseport
exposedPort := -1
// Exists as a "cache" layer
if portMappings != nil {
for key, value := range portMappings {
if value > highest {
highest = value
}
if key == name {
exposedPort = value
break
}
}
} else {
portMappings = make(map[string]int)
}
//Filters:
if exposedPort == -1 {
serviceListOptions := types.ServiceListOptions{}
services, err := dockercli.ServiceList(
context.Background(),
serviceListOptions,
)
// Basic self-correction
if err != nil {
log.Printf("[ERROR] Unable to list services: %s (may continue anyway?)", err)
if strings.Contains(fmt.Sprintf("%s", err), "is too new") {
// Static for some reason
defaultVersion := "1.40"
dockerApiVersion = defaultVersion
os.Setenv("DOCKER_API_VERSION", defaultVersion)
log.Printf("[DEBUG] Setting Docker API to %s default and retrying listing requests", defaultVersion)
} else {
return -1, err
}
services, err = dockercli.ServiceList(
context.Background(),
serviceListOptions,
)
if err != nil {
log.Printf("[ERROR] Unable to list services (2): %s", err)
return -1, err
}
}
for _, service := range services {
//log.Printf("[INFO] Service: %#v", service.Spec.Annotations.Name)
for _, endpoint := range service.Spec.EndpointSpec.Ports {
if strings.Contains(endpoint.Name, "port") {
portMappings[service.Spec.Annotations.Name] = int(endpoint.PublishedPort)
if int(endpoint.PublishedPort) > highest {
highest = int(endpoint.PublishedPort)
}
if service.Spec.Annotations.Name == name || service.Spec.Annotations.Name == strings.Replace(name, ".", "-", -1) {
exposedPort = int(endpoint.PublishedPort)
//break
}
}
}
//log.Printf("%s - %s", service.Spec.Annotations.Name, strings.Replace(name, ".", "-", -1))
if service.Spec.Annotations.Name != name && service.Spec.Annotations.Name != strings.Replace(name, ".", "-", -1) {
continue
}
// Break if it's the correct port, as it's the right service
if exposedPort >= 0 {
break
}
}
}
//log.Printf("[DEBUG] Portmappings: %#v", portMappings)
if exposedPort >= 0 {
log.Printf("[INFO] Found service %s on port %d - no need to deploy another", name, exposedPort)
} else {
// Increment by 1 for highest port
if highest <= baseport {
highest = baseport
}
highest += 1
err = deploySwarmService(dockercli, name, image, highest)
if err != nil {
log.Printf("[WARNING] NOT Found service: %s. error: %s", name, err)
return highest, err
} else {
log.Printf("[INFO] Deployed app with name %s", name)
}
exposedPort = highest
log.Printf("[DEBUG] Waiting 10 seconds before moving on to let app start")
time.Sleep(time.Duration(10) * time.Second)
}
return exposedPort, nil
}
func sendAppRequest(incomingUrl string, port int, action shuffle.Action, workflowExecution shuffle.WorkflowExecution) error {
parsedRequest := shuffle.OrborusExecutionRequest{
ExecutionId: workflowExecution.ExecutionId,
Authorization: workflowExecution.Authorization,
EnvironmentName: os.Getenv("ENVIRONMENT_NAME"),
Timezone: os.Getenv("TZ"),
Cleanup: os.Getenv("CLEANUP"),
HTTPProxy: os.Getenv("HTTP_PROXY"),
HTTPSProxy: os.Getenv("HTTPS_PROXY"),
ShufflePassProxyToApp: os.Getenv("SHUFFLE_PASS_APP_PROXY"),
BaseUrl: baseUrl,
Action: action,
FullExecution: workflowExecution,
}
//var baseUrl = os.Getenv("BASE_URL")
//var appCallbackUrl = os.Getenv("BASE_URL")
parsedBaseurl := incomingUrl
if strings.Count(baseUrl, ":") >= 2 {
baseUrlSplit := strings.Split(baseUrl, ":")
if len(baseUrlSplit) >= 3 {
parsedBaseurl = strings.Join(baseUrlSplit[0:2], ":")
//parsedRequest.BaseUrl = fmt.Sprintf("%s:33333", parsedBaseurl)
}
}
if len(parsedRequest.Url) == 0 {
// Fixed callback url to the worker itself
if strings.Count(parsedBaseurl, ":") >= 2 {
parsedRequest.Url = parsedBaseurl
} else {
// Callback to worker
parsedRequest.Url = fmt.Sprintf("%s:%d", parsedBaseurl, baseport)
}
log.Printf("[DEBUG] Should add a baseurl for the app to get back to: %s", parsedRequest.Url)
}
// FIXME: Swapping because this was confusing during dev
tmp := parsedRequest.Url
parsedRequest.Url = parsedRequest.BaseUrl
parsedRequest.BaseUrl = tmp
log.Printf("[DEBUG] Worker URL: %s, Backend URL: %s", parsedRequest.BaseUrl, parsedRequest.Url)
data, err := json.Marshal(parsedRequest)
if err != nil {
log.Printf("[ERROR] Failed marshalling worker request: %s", err)
return err
}
streamUrl := fmt.Sprintf("%s:%d/api/v1/run", parsedBaseurl, port)
req, err := http.NewRequest(
"POST",
streamUrl,
bytes.NewBuffer([]byte(data)),
)
client := &http.Client{}
if err != nil {
log.Printf("[ERROR] Failed creating finishing request: %s", err)
return err
}
newresp, err := client.Do(req)
if err != nil {
log.Printf("[ERROR] Error running finishing request: %s", err)
return err
}
body, err := ioutil.ReadAll(newresp.Body)
if err != nil {
log.Printf("[ERROR] Failed reading body: %s", err)
return err
} else {
log.Printf("[INFO] NEWRESP (from app): %s", string(body))
}
return nil
}
// Initial loop etc // Initial loop etc
func main() { func main() {
/*
appName := "shuffle-tools_1.1.0"
image := "frikky/shuffle:shuffle-tools_1.1.0"
exposedPort, err := findAppInfo(image, appName)
if err != nil {
log.Printf("[ERROR] Failed finding and creating port for %s: %s", appName, err)
os.Exit(3)
}
log.Printf("[DEBUG] Should run towards port %d for app %s", exposedPort, appName)
err = sendAppRequest(appCallbackUrl, exposedPort, shuffle.Action{}, shuffle.WorkflowExecution{})
if err != nil {
log.Printf("[ERROR] Failed sending request to app %s on port %d: %s", appName, exposedPort, err)
os.Exit(3)
}
*/
_, err := shuffle.RunInit(datastore.Client{}, storage.Client{}, "", "", false, "")
if err != nil {
log.Printf("[ERROR] Failed to run worker init: %s", err)
} else {
log.Printf("[DEBUG] Ran init for worker to set up cache system. Docker version: %s", dockerApiVersion)
}
log.Printf("[INFO] Setting up worker environment") log.Printf("[INFO] Setting up worker environment")
sleepTime := 5 sleepTime := 5
client := &http.Client{ client := &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
Proxy: nil, Proxy: nil,
@@ -2105,7 +2495,7 @@ func main() {
timezone = "Europe/Amsterdam" timezone = "Europe/Amsterdam"
} }
log.Printf("[INFO] Running with timezone %s", timezone) log.Printf("[INFO] Running with timezone %s and swarm config %#v", timezone, os.Getenv("SHUFFLE_SWARM_CONFIG"))
if os.Getenv("SHUFFLE_SWARM_CONFIG") == "run" { if os.Getenv("SHUFFLE_SWARM_CONFIG") == "run" {
workflowExecution := shuffle.WorkflowExecution{} workflowExecution := shuffle.WorkflowExecution{}
listener := webserverSetup(workflowExecution) listener := webserverSetup(workflowExecution)
@@ -2121,6 +2511,8 @@ func main() {
//}() //}()
runWebserver(listener) runWebserver(listener)
log.Printf("[ERROR] Stopped listener - exiting.")
os.Exit(3)
} }
//imageName := fmt.Sprintf("%s/%s:shuffle_openapi_1.0.0", registryName, baseimagename) //imageName := fmt.Sprintf("%s/%s:shuffle_openapi_1.0.0", registryName, baseimagename)
@@ -2174,6 +2566,7 @@ func main() {
topClient = client topClient = client
firstRequest := true firstRequest := true
environments := []string{}
for { for {
// Because of this, it always has updated data. // Because of this, it always has updated data.
// Removed request requirement from app_sdk // Removed request requirement from app_sdk
@@ -2319,12 +2712,14 @@ func main() {
} }
func handleRunExecution(resp http.ResponseWriter, request *http.Request) { func handleRunExecution(resp http.ResponseWriter, request *http.Request) {
if executionRunning { /*
log.Println("[WARNING] An execution is already running on this worker") if executionRunning {
resp.WriteHeader(500) log.Println("[WARNING] An execution is already running on this worker")
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "An execution is already running"}`))) resp.WriteHeader(500)
return resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "An execution is already running"}`)))
} return
}
*/
executionRunning = true executionRunning = true
body, err := ioutil.ReadAll(request.Body) body, err := ioutil.ReadAll(request.Body)
@@ -2359,6 +2754,7 @@ func handleRunExecution(resp http.ResponseWriter, request *http.Request) {
} }
//if strings.ToLower(os.Getenv("SHUFFLE_PASS_APP_PROXY")) == "true" { //if strings.ToLower(os.Getenv("SHUFFLE_PASS_APP_PROXY")) == "true" {
// Is it ok if these are standard? Should they be update-able after launch? Hmm
if len(execRequest.HTTPProxy) > 0 { if len(execRequest.HTTPProxy) > 0 {
log.Printf("[DEBUG] Sending proxy info to child process") log.Printf("[DEBUG] Sending proxy info to child process")
os.Setenv("SHUFFLE_PASS_APP_PROXY", execRequest.ShufflePassProxyToApp) os.Setenv("SHUFFLE_PASS_APP_PROXY", execRequest.ShufflePassProxyToApp)
@@ -2444,11 +2840,23 @@ func handleRunExecution(resp http.ResponseWriter, request *http.Request) {
executionRunning = false executionRunning = false
log.Printf("[INFO] Workflow %s is finished. Exiting worker.", workflowExecution.ExecutionId) log.Printf("[INFO] Workflow %s is finished. Exiting worker.", workflowExecution.ExecutionId)
log.Printf("[DEBUG] Shutting down (20)") log.Printf("[DEBUG] Shutting down (20)")
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad status %s"}`, workflowExecution.Status))) resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad status for execution - already %s. Returning with 200 OK"}`, workflowExecution.Status)))
return return
} }
//ctx := context.Background()
//startAction, extra, children, parents, visited, executed, nextActions, environments := shuffle.GetExecutionVariables(ctx, workflowExecution.ExecutionId)
extra := 0
for _, trigger := range workflowExecution.Workflow.Triggers {
//log.Printf("Appname trigger (0): %s", trigger.AppName)
if trigger.AppName == "User Input" || trigger.AppName == "Shuffle Workflow" {
extra += 1
}
}
log.Printf("[INFO] Status: %s, Results: %d, actions: %d", workflowExecution.Status, len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)+extra) log.Printf("[INFO] Status: %s, Results: %d, actions: %d", workflowExecution.Status, len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)+extra)
if workflowExecution.Status != "EXECUTING" { if workflowExecution.Status != "EXECUTING" {
executionRunning = false executionRunning = false
@@ -2470,7 +2878,10 @@ func handleRunExecution(resp http.ResponseWriter, request *http.Request) {
if err != nil { if err != nil {
log.Printf("[INFO] Workflow setup failed: %s", workflowExecution.ExecutionId, err) log.Printf("[INFO] Workflow setup failed: %s", workflowExecution.ExecutionId, err)
log.Printf("[DEBUG] Shutting down (30)") log.Printf("[DEBUG] Shutting down (30)")
shutdown(workflowExecution, "", "", true) resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Error in execution init: %s"}`, err)))
return
//shutdown(workflowExecution, "", "", true)
} }
handleExecutionResult(workflowExecution) handleExecutionResult(workflowExecution)