Merge branch 'launch' into launch
This commit is contained in:
+19
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,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
|
||||||
|
|||||||
@@ -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
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}}>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
- <a href="https://github.com/frikky/security-openapis" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">Security API's</a>
|
- <a rel="noopener noreferrer" href="https://github.com/frikky/security-openapis" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">Security API's</a>
|
||||||
- <a href="https://github.com/APIs-guru/openapi-directory/tree/main/APIs" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI directory</a>
|
- <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>
|
||||||
- <a href="https://editor.swagger.io/" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI Validator</a>
|
- <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>
|
||||||
|
|||||||
@@ -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 /> Edit
|
<EditIcon /> 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>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,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
|
||||||
|
|||||||
@@ -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
@@ -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,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 .
|
||||||
|
|||||||
@@ -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
@@ -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":""}}'
|
||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user